fix(solver): ellipse diagnostic used wrong H direction
residualForEllipse computed C = H^T·E·H, but H maps image → output, so
the correct output-space conic is C = H^{-T}·E·H^{-1}. The forward form
represents a geometrically meaningless conic and produced nonsensical
numbers in the per-datum table (a 210mm circle was reported as 2046mm).
The deskewed image itself was correct — only the diagnostic was wrong,
because these residuals are display-only and don't feed back into the
solver. Now invert H once and compute the conic in output space.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da5be3851d
commit
e07ee9d204
@ -94,6 +94,36 @@ function relativeMaxDiff(a: Mat3, b: Mat3): number {
|
|||||||
return scale > 0 ? diff / scale : diff
|
return scale > 0 ? diff / scale : diff
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Inverse of a 3×3 homography (row-major). Returns null if singular. */
|
||||||
|
function invertMat3(h: Mat3): Mat3 | null {
|
||||||
|
const a = h[0]
|
||||||
|
const b = h[1]
|
||||||
|
const c = h[2]
|
||||||
|
const d = h[3]
|
||||||
|
const e = h[4]
|
||||||
|
const f = h[5]
|
||||||
|
const g = h[6]
|
||||||
|
const hh = h[7]
|
||||||
|
const i = h[8]
|
||||||
|
const det =
|
||||||
|
a * (e * i - f * hh) -
|
||||||
|
b * (d * i - f * g) +
|
||||||
|
c * (d * hh - e * g)
|
||||||
|
if (Math.abs(det) < 1e-20) return null
|
||||||
|
const invDet = 1 / det
|
||||||
|
return [
|
||||||
|
(e * i - f * hh) * invDet,
|
||||||
|
(c * hh - b * i) * invDet,
|
||||||
|
(b * f - c * e) * invDet,
|
||||||
|
(f * g - d * i) * invDet,
|
||||||
|
(a * i - c * g) * invDet,
|
||||||
|
(c * d - a * f) * invDet,
|
||||||
|
(d * hh - e * g) * invDet,
|
||||||
|
(b * g - a * hh) * invDet,
|
||||||
|
(a * e - b * d) * invDet,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Geometric helpers ──────────────────────────────────────────────────────
|
// ─── Geometric helpers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
function dist(a: Point, b: Point): number {
|
function dist(a: Point, b: Point): number {
|
||||||
@ -647,19 +677,33 @@ function residualForEllipse(
|
|||||||
scale: number,
|
scale: number,
|
||||||
isPrimary: boolean,
|
isPrimary: boolean,
|
||||||
): RawReport {
|
): RawReport {
|
||||||
// Pull the image ellipse back to world space via C = H^T E H
|
// E is the image-space ellipse conic; H maps image → output. The
|
||||||
|
// output-space conic we want to check for circularity is therefore
|
||||||
|
// C = H^{-T} · E · H^{-1}
|
||||||
|
// (points q on C iff H^{-1}·q lands on E). Using H^T·E·H computes the
|
||||||
|
// wrong conic and produces meaningless diagnostic numbers.
|
||||||
const E = ellipseMatrix(ellipse.center, ellipse.axisEndA, ellipse.axisEndB)
|
const E = ellipseMatrix(ellipse.center, ellipse.axisEndA, ellipse.axisEndB)
|
||||||
const HT = [
|
const Hi = invertMat3(H)
|
||||||
[H[0], H[3], H[6]],
|
const zeroReport: RawReport = {
|
||||||
[H[1], H[4], H[7]],
|
label: ellipse.label,
|
||||||
[H[2], H[5], H[8]],
|
type: "ellipse",
|
||||||
|
expectedMm: ellipse.diameterMm,
|
||||||
|
measuredMm: 0,
|
||||||
|
residuals: [0, 0, 0],
|
||||||
|
details: "(H singular, cannot compute)",
|
||||||
|
}
|
||||||
|
if (!Hi) return zeroReport
|
||||||
|
const HiT = [
|
||||||
|
[Hi[0], Hi[3], Hi[6]],
|
||||||
|
[Hi[1], Hi[4], Hi[7]],
|
||||||
|
[Hi[2], Hi[5], Hi[8]],
|
||||||
]
|
]
|
||||||
const Hm = [
|
const Hm = [
|
||||||
[H[0], H[1], H[2]],
|
[Hi[0], Hi[1], Hi[2]],
|
||||||
[H[3], H[4], H[5]],
|
[Hi[3], Hi[4], Hi[5]],
|
||||||
[H[6], H[7], H[8]],
|
[Hi[6], Hi[7], Hi[8]],
|
||||||
]
|
]
|
||||||
// C = HT · E · Hm
|
// C = HiT · E · Hm (= H^{-T} E H^{-1})
|
||||||
const EH: number[][] = [
|
const EH: number[][] = [
|
||||||
[0, 0, 0],
|
[0, 0, 0],
|
||||||
[0, 0, 0],
|
[0, 0, 0],
|
||||||
@ -683,7 +727,7 @@ function residualForEllipse(
|
|||||||
for (let j = 0; j < 3; j++) {
|
for (let j = 0; j < 3; j++) {
|
||||||
let s = 0
|
let s = 0
|
||||||
for (let k = 0; k < 3; k++) {
|
for (let k = 0; k < 3; k++) {
|
||||||
s += (HT[i]?.[k] ?? 0) * (EH[k]?.[j] ?? 0)
|
s += (HiT[i]?.[k] ?? 0) * (EH[k]?.[j] ?? 0)
|
||||||
}
|
}
|
||||||
;(C[i] as number[])[j] = s
|
;(C[i] as number[])[j] = s
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user