code wiki / (root) / nx_cam.nx

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}