nx_cam.nx source
↩ module page · 202 lines · 7131 B
1// nx_cam.nx -- camera + homography primitives (V9).
2//
3// Pinhole camera model (Hartley+Zisserman 2004 chapter 6) and 3x3
4// planar homography arithmetic. Pure i64 with Q14 homography
5// coefficients. Closes the geometry stack for 3D reasoning.
6//
7// What this unlocks:
8// + perspective correction (warp tilted photo of a book to rectangular)
9// + image stitching (homography between overlapping photos)
10// + augmented reality (project 3D world points onto image plane)
11// + planar object recognition (rectify then match)
12// + camera calibration scaffolding
13//
14// Homography is a 9-element i64 array in Q14 row-major:
15// [H0 H1 H2]
16// [H3 H4 H5]
17// [H6 H7 H8]
18//
19// genealogy_id: hartley_zisserman_2004 + faugeras_1993_3d_vision
20// lineage_id: pinhole_projection + planar_homography
21
22// nx_safety_envelope:
23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
24// sil_target: SIL1
25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
26// verdict: NOT_YET_EVALUATED
27
28import "syscalls.nx"
29import "nx_image.nx"
30const NX_MAGIC_1024: i64 = 1024
31
32const NX_CAM_Q: i64 = 16384 // Q14
33
34// ===== Pinhole projection ==============================================
35//
36// (X, Y, Z) world -> (u, v) image:
37// u = fx * X / Z + cx
38// v = fy * Y / Z + cy
39//
40// Returns 0 on success, -1 if Z == 0 (behind camera / on focal plane).
41
42func nx_cam_pinhole_project(X: i64, Y: i64, Z: i64,
43 fx: i64, fy: i64,
44 cx: i64, cy: i64,
45 out_u: *i64, out_v: *i64) -> i64 {
46 if Z == 0 { return -1 }
47 out_u[0] = fx * X / Z + cx
48 out_v[0] = fy * Y / Z + cy
49 return 0
50}
51
52// ===== Homography constructors =========================================
53
54func nx_cam_homography_identity(H: *i64) -> i64 {
55 H[0] = NX_CAM_Q; H[1] = 0; H[2] = 0
56 H[3] = 0; H[4] = NX_CAM_Q; H[5] = 0
57 H[6] = 0; H[7] = 0; H[8] = NX_CAM_Q
58 return 0
59}
60
61// Translation: (u, v) -> (u + tx, v + ty)
62// In Q14: tx and ty given as plain integers, scaled by Q internally.
63
64func nx_cam_homography_translate(H: *i64, tx: i64, ty: i64) -> i64 {
65 H[0] = NX_CAM_Q; H[1] = 0; H[2] = tx * NX_CAM_Q
66 H[3] = 0; H[4] = NX_CAM_Q; H[5] = ty * NX_CAM_Q
67 H[6] = 0; H[7] = 0; H[8] = NX_CAM_Q
68 return 0
69}
70
71// Uniform scale: (u, v) -> (sx_q*u/Q, sy_q*v/Q)
72// sx_q, sy_q given in Q14 (so passing 2*Q14=32768 means 2x scale).
73
74func nx_cam_homography_scale(H: *i64, sx_q: i64, sy_q: i64) -> i64 {
75 H[0] = sx_q; H[1] = 0; H[2] = 0
76 H[3] = 0; H[4] = sy_q; H[5] = 0
77 H[6] = 0; H[7] = 0; H[8] = NX_CAM_Q
78 return 0
79}
80
81// Rotation around origin by theta degrees (using same trig table as
82// nx_geom, but we re-embed cos/sin via a small table look-up of 36
83// 5-degree entries).
84
85func nx_cam_homography_rotate(H: *i64, theta_deg: i64) -> i64 {
86 let cos_t: *i64 = (sys_mmap(36 * 8)) as *i64
87 let sin_t: *i64 = (sys_mmap(36 * 8)) as *i64
88 // Q10 cos/sin values (same as nx_geom).
89 cos_t[0]=NX_MAGIC_1024; cos_t[1]=1020; cos_t[2]=1008; cos_t[3]=989
90 cos_t[4]=962; cos_t[5]=928; cos_t[6]=887; cos_t[7]=839
91 cos_t[8]=784; cos_t[9]=724; cos_t[10]=658; cos_t[11]=587
92 cos_t[12]=512; cos_t[13]=433; cos_t[14]=350; cos_t[15]=265
93 cos_t[16]=178; cos_t[17]=89; cos_t[18]=0; cos_t[19]=-89
94 cos_t[20]=-178;cos_t[21]=-265;cos_t[22]=-350;cos_t[23]=-433
95 cos_t[24]=-512;cos_t[25]=-587;cos_t[26]=-658;cos_t[27]=-724
96 cos_t[28]=-784;cos_t[29]=-839;cos_t[30]=-887;cos_t[31]=-928
97 cos_t[32]=-962;cos_t[33]=-989;cos_t[34]=-1008;cos_t[35]=-1020
98 sin_t[0]=0; sin_t[1]=89; sin_t[2]=178; sin_t[3]=265
99 sin_t[4]=350; sin_t[5]=433; sin_t[6]=512; sin_t[7]=587
100 sin_t[8]=658; sin_t[9]=724; sin_t[10]=784; sin_t[11]=839
101 sin_t[12]=887; sin_t[13]=928; sin_t[14]=962; sin_t[15]=989
102 sin_t[16]=1008;sin_t[17]=1020;sin_t[18]=NX_MAGIC_1024;sin_t[19]=1020
103 sin_t[20]=1008;sin_t[21]=989; sin_t[22]=962; sin_t[23]=928
104 sin_t[24]=887; sin_t[25]=839; sin_t[26]=784; sin_t[27]=724
105 sin_t[28]=658; sin_t[29]=587; sin_t[30]=512; sin_t[31]=433
106 sin_t[32]=350; sin_t[33]=265; sin_t[34]=178; sin_t[35]=89
107
108 var t: i64 = theta_deg / 5
109 while t < 0 { t = t + 72 }
110 while t >= 72 { t = t - 72 }
111 var c: i64 = 0
112 var s: i64 = 0
113 if t < 36 {
114 c = cos_t[t]
115 s = sin_t[t]
116 }
117 if t >= 36 {
118 c = -cos_t[t - 36]
119 s = -sin_t[t - 36]
120 }
121 // Convert Q10 to Q14 by * 16.
122 H[0] = c * 16; H[1] = -s * 16; H[2] = 0
123 H[3] = s * 16; H[4] = c * 16; H[5] = 0
124 H[6] = 0; H[7] = 0; H[8] = NX_CAM_Q
125 return 0
126}
127
128// ===== Homography apply =================================================
129//
130// Apply 3x3 homography to a 2D point. Returns 0 on success, -1 if
131// the denominator collapses to zero (degenerate).
132
133func nx_cam_homography_apply(H: *i64, u: i64, v: i64,
134 out_u: *i64, out_v: *i64) -> i64 {
135 let denom: i64 = H[6] * u + H[7] * v + H[8]
136 if denom == 0 { return -1 }
137 let num_u: i64 = H[0] * u + H[1] * v + H[2]
138 let num_v: i64 = H[3] * u + H[4] * v + H[5]
139 out_u[0] = num_u / denom
140 out_v[0] = num_v / denom
141 return 0
142}
143
144// ===== Image warp via inverse homography ===============================
145//
146// For each destination pixel (u', v'), apply H_inv to find the source
147// location (u, v) and sample. Out-of-bounds becomes 0.
148// Nearest-neighbor sampling (bilinear would be a refinement).
149
150func nx_cam_warp_image(src: *Image, H_inv: *i64) -> *Image {
151 let w: i64 = src.width
152 let h: i64 = src.height
153 let dst: *Image = nx_image_alloc(w, h, 1)
154 let su: *i64 = (sys_mmap(8)) as *i64
155 let sv: *i64 = (sys_mmap(8)) as *i64
156 var y: i64 = 0
157 while y < h {
158 var x: i64 = 0
159 while x < w {
160 if nx_cam_homography_apply(H_inv, x, y, su, sv) == 0 {
161 let usrc: i64 = su[0]
162 let vsrc: i64 = sv[0]
163 if usrc >= 0 {
164 if usrc < w {
165 if vsrc >= 0 {
166 if vsrc < h {
167 nx_image_set(dst, x, y, 0,
168 nx_image_get(src, usrc, vsrc, 0))
169 }
170 }
171 }
172 }
173 }
174 x = x + 1
175 }
176 y = y + 1
177 }
178 return dst
179}
180
181// ===== Homography compose ===============================================
182//
183// C = A * B (3x3 matrix multiply with Q14 normalization).
184
185func nx_cam_homography_compose(A: *i64, B: *i64, C: *i64) -> i64 {
186 var i: i64 = 0
187 while i < 3 {
188 var j: i64 = 0
189 while j < 3 {
190 var sum: i64 = 0
191 var k: i64 = 0
192 while k < 3 {
193 sum = sum + A[i * 3 + k] * B[k * 3 + j]
194 k = k + 1
195 }
196 C[i * 3 + j] = sum / NX_CAM_Q
197 j = j + 1
198 }
199 i = i + 1
200 }
201 return 0
202}