code wiki / _hdl_build / nx_essential3d_gate.nx
nx_essential3d_gate.nx source
↩ module page · 141 lines · 6165 B
1// nx_essential3d_gate.nx -- benchmark the essential-matrix 8-point estimation (cadtwin P1 pose front). Two
2// cameras view a non-coplanar 3D cloud; fit E from 12 correspondences via the SVD-free power-iteration null
3// space; the HELD-OUT epipolar residual (6 points NOT used to fit) proves E is the true two-view geometry,
4// not overfit. Neg-control (wrong correspondences) -> large residual. This recovers relative pose geometry
5// from images with UNKNOWN poses = the front of two-view SfM. expect_exit: 0 license_tier: ORIGINAL
6import "nx_essential3d.nx"
7
8func hg_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
9func hg_putn(v: i64) -> i64 {
10 let bb: *u8 = sys_mmap(28)
11 var m: i64 = v
12 if m < 0 { hg_puts("-" as *u8); m = 0 - m }
13 let t: *u8 = sys_mmap(28)
14 var k: i64 = 0
15 if m == 0 { t[0] = 48 as u8; k = 1 }
16 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
17 var i: i64 = 0
18 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
19 sys_write(1, bb, k)
20 return 0
21}
22func hg_tooth(name: *u8, pass: i64, fails: *i64) -> i64 {
23 hg_puts(" " as *u8); hg_puts(name); hg_puts(" -> " as *u8)
24 if pass == 1 { hg_puts("PASS\n" as *u8); return 0 }
25 hg_puts("FAIL\n" as *u8)
26 fails[0] = fails[0] + 1
27 return 0
28}
29
30func main() -> i64 {
31 hg_puts("=== nx_essential3d_gate -- essential matrix (8-point), held-out epipolar benchmark ===\n" as *u8)
32 let fails: *i64 = sys_mmap(8) as *i64
33 fails[0] = 0
34
35 // non-coplanar 3D cloud (18 points spread in a volume, deterministic scatter)
36 let npt: i64 = 18
37 let X: *i64 = sys_mmap(1024) as *i64
38 var i: i64 = 0
39 while i < npt {
40 X[i * 3] = ((i * 7) % 9 - 4) * 220
41 X[i * 3 + 1] = ((i * 5) % 7 - 3) * 220
42 X[i * 3 + 2] = ((i * 11) % 5 - 2) * 260
43 i = i + 1
44 }
45
46 // two cameras (different positions = baseline), both looking at origin
47 let b0: *i64 = sys_mmap(128) as *i64
48 let b1: *i64 = sys_mmap(128) as *i64
49 r3_lookat(4000, 800, 5000, 0, 0, 0, b0)
50 r3_lookat(0 - 3500, 1200, 5200, 0, 0, 0, b1)
51
52 // correspondences: normalized coords in each camera
53 let c0: *i64 = sys_mmap(1024) as *i64 // (nx,ny) per point in cam0
54 let c1: *i64 = sys_mmap(1024) as *i64 // in cam1
55 let uv: *i64 = sys_mmap(16) as *i64
56 i = 0
57 while i < npt {
58 e3_projn(4000, 800, 5000, b0, X[i * 3], X[i * 3 + 1], X[i * 3 + 2], uv)
59 c0[i * 2] = uv[0]; c0[i * 2 + 1] = uv[1]
60 e3_projn(0 - 3500, 1200, 5200, b1, X[i * 3], X[i * 3 + 1], X[i * 3 + 2], uv)
61 c1[i * 2] = uv[0]; c1[i * 2 + 1] = uv[1]
62 i = i + 1
63 }
64
65 // fit E from the first 12 correspondences (Hartley-normalized 8-point)
66 let nfit: i64 = 12
67 let E: *i64 = sys_mmap(9 * 8) as *i64
68 e3_estimate_norm(c0, c1, nfit, E)
69 hg_puts(" E (Q14, x1000-scaled view): " as *u8)
70 i = 0
71 while i < 9 { hg_putn(E[i]); hg_puts(" " as *u8); i = i + 1 }
72 hg_puts("\n" as *u8)
73
74 // training residual (mean over the 12 fit points)
75 var tr: i64 = 0
76 i = 0
77 while i < nfit { tr = tr + e3_residual(E, c0[i * 2], c0[i * 2 + 1], c1[i * 2], c1[i * 2 + 1]); i = i + 1 }
78 tr = tr / nfit
79 // HELD-OUT residual (points 12..17, NOT used to fit)
80 var ho: i64 = 0
81 var hn: i64 = 0
82 i = nfit
83 while i < npt { ho = ho + e3_residual(E, c0[i * 2], c0[i * 2 + 1], c1[i * 2], c1[i * 2 + 1]); hn = hn + 1; i = i + 1 }
84 ho = ho / hn
85 hg_puts(" TRAIN mean epipolar residual=" as *u8); hg_putn(tr)
86 hg_puts(" HELD-OUT mean residual=" as *u8); hg_putn(ho); hg_puts(" (both expect ~0)\n" as *u8)
87
88 // E non-degenerate (not all ~0)
89 var enorm: i64 = 0
90 i = 0
91 while i < 9 { let a: i64 = E[i]; if a < 0 { enorm = enorm - a } else { enorm = enorm + a } i = i + 1 }
92 var t1: i64 = 0
93 if enorm > 1000 { t1 = 1 }
94 let ig1: i64 = hg_tooth("T1 E non-degenerate (recovered a real matrix)" as *u8, t1, fails)
95
96 var t2: i64 = 0
97 if tr < 60 { t2 = 1 }
98 let ig2: i64 = hg_tooth("T2 TRAINING epipolar residual ~0 (< 60)" as *u8, t2, fails)
99
100 var t3: i64 = 0
101 if ho < 60 { t3 = 1 }
102 let ig3: i64 = hg_tooth("T3 HELD-OUT epipolar residual ~0 (< 60) = TRUE geometry, not overfit" as *u8, t3, fails)
103
104 // neg-control: fit E from WRONG correspondences (shuffle cam1)
105 let Aw: *i64 = sys_mmap(2048) as *i64
106 i = 0
107 while i < nfit {
108 let jj: i64 = (i + 5) % nfit
109 e3_row(c0[i * 2], c0[i * 2 + 1], c1[jj * 2], c1[jj * 2 + 1], row)
110 var j: i64 = 0
111 while j < 9 { Aw[i * 9 + j] = row[j]; j = j + 1 }
112 i = i + 1
113 }
114 let Ew: *i64 = sys_mmap(9 * 8) as *i64
115 e3_estimate(Aw, nfit, Ew)
116 // its held-out residual on the CORRECT correspondences (should be large -- wrong geometry)
117 var nres: i64 = 0
118 i = nfit
119 while i < npt { nres = nres + e3_residual(Ew, c0[i * 2], c0[i * 2 + 1], c1[i * 2], c1[i * 2 + 1]); i = i + 1 }
120 nres = nres / hn
121 hg_puts(" neg-control (wrong-corr E) held-out residual=" as *u8); hg_putn(nres); hg_puts("\n" as *u8)
122 var t4: i64 = 0
123 if nres > ho * 4 { if nres > 100 { t4 = 1 } }
124 let ig4: i64 = hg_tooth("T4 NEG-CONTROL wrong correspondences -> large residual (metric works)" as *u8, t4, fails)
125
126 // determinism
127 let E2: *i64 = sys_mmap(9 * 8) as *i64
128 e3_estimate_norm(c0, c1, nfit, E2)
129 var same: i64 = 1
130 i = 0
131 while i < 9 { if E2[i] != E[i] { same = 0 } i = i + 1 }
132 let ig5: i64 = hg_tooth("T5 deterministic" as *u8, same, fails)
133
134 hg_puts("\nSOTA CONTEXT: the essential matrix = relative pose from images with UNKNOWN poses (Hartley-Zisserman/\n" as *u8)
135 hg_puts("COLMAP front-end). SVD-FREE via power-iteration null space (same tool as nx_register3d). Composes:\n" as *u8)
136 hg_puts("features(nx_features) -> match -> E (this) -> R,t decomposition (next) -> triangulate(nx_recon3d) -> register.\n" as *u8)
137 hg_puts("\nfails=" as *u8); hg_putn(fails[0]); hg_puts("\n" as *u8)
138 if fails[0] == 0 { hg_puts("GREEN -- essential matrix 8-point 5/5, held-out epipolar-benchmarked (pose front of SfM)\n" as *u8); return 0 }
139 hg_puts("RED\n" as *u8)
140 return 1
141}