nx_featmatch.nx source
↩ module page · 130 lines · 4878 B
1// nx_featmatch.nx -- THE FEATURE-MATCH WIRE: two images in, CORRESPONDENCES out, with nothing hand-supplied.
2// This is the last classical link the cadtwin census lists as missing on P1. Every reconstruction rung built
3// so far (essential matrix, pose refinement, dense sweep, fusion) has been fed correspondences that the GATE
4// constructed from ground truth -- which quietly assumes away the hardest part of a real capture.
5// Uses the EXISTING nx_features Harris + non-max-suppression rather than a second corner detector; the only
6// new work is bridging the flat i64 intensity buffers this reconstruction chain uses into the *Image struct
7// nx_features expects, plus the matcher itself. license_tier: ORIGINAL
8import "nx_planesweep.nx"
9import "nx_features.nx"
10
11const FM_RATIO: i64 = 80 // winner must beat the runner-up by 20% or the match is AMBIGUOUS -> dropped
12const FM_EXCL: i64 = 3 // runner-up must be this far from the winner (see fm_match_one)
13
14// bridge: flat i64 intensities -> the 1-channel *Image that nx_features consumes
15func fm_load(src: *i64, w: i64, h: i64) -> *Image {
16 let img: *Image = nx_image_alloc(w, h, 1)
17 var i: i64 = 0
18 while i < w * h {
19 var v: i64 = src[i]
20 if v < 0 { v = 0 }
21 if v > 255 { v = 255 }
22 img.pixels[i] = v as u8
23 i = i + 1
24 }
25 return img
26}
27// detect corners via the existing Harris + NMS; writes (x,y) pairs into out. Returns the count.
28func fm_corners(img: *Image, thresh: i64, out: *i64, maxn: i64) -> i64 {
29 let resp: *ImageS64 = nx_feat_harris_response(img)
30 let mask: *Image = nx_feat_corners_nms(resp, thresh)
31 let w: i64 = mask.width
32 let h: i64 = mask.height
33 var n: i64 = 0
34 var y: i64 = 0
35 while y < h {
36 var x: i64 = 0
37 while x < w {
38 if mask.pixels[y * w + x] != (0 as u8) {
39 if n < maxn {
40 out[n * 2 + 0] = x
41 out[n * 2 + 1] = y
42 n = n + 1
43 }
44 }
45 x = x + 1
46 }
47 y = y + 1
48 }
49 return n
50}
51// Match one corner of A into B by scanning a window, with a RATIO TEST.
52// ⚠the runner-up is taken only from OUTSIDE a small exclusion radius around the winner. SAD varies smoothly,
53// so the second-best score is otherwise always the pixel NEXT TO the winner -- the ratio is then ~1 and the
54// test throws away every correct match. The competitor that matters is a DIFFERENT candidate location.
55func fm_match_one(ia: *i64, ib: *i64, w: i64, h: i64, ax: i64, ay: i64, rad: i64, out2: *i64) -> i64 {
56 var best: i64 = PS_BAD
57 var bx: i64 = 0 - 1
58 var by: i64 = 0 - 1
59 var dy: i64 = 0 - rad
60 while dy <= rad {
61 var dx: i64 = 0 - rad
62 while dx <= rad {
63 let sc: i64 = ps_sad(ia, ib, w, h, ax, ay, ax + dx, ay + dy)
64 if sc >= 0 {
65 if sc < best {
66 best = sc
67 bx = ax + dx
68 by = ay + dy
69 }
70 }
71 dx = dx + 1
72 }
73 dy = dy + 1
74 }
75 if bx < 0 { return 0 - 1 }
76 var second: i64 = PS_BAD
77 dy = 0 - rad
78 while dy <= rad {
79 var dx2: i64 = 0 - rad
80 while dx2 <= rad {
81 let cx: i64 = ax + dx2
82 let cy: i64 = ay + dy
83 var far: i64 = 0
84 var ddx: i64 = cx - bx
85 var ddy: i64 = cy - by
86 if ddx < 0 { ddx = 0 - ddx }
87 if ddy < 0 { ddy = 0 - ddy }
88 if ddx > FM_EXCL { far = 1 }
89 if ddy > FM_EXCL { far = 1 }
90 if far == 1 {
91 let sc2: i64 = ps_sad(ia, ib, w, h, ax, ay, cx, cy)
92 if sc2 >= 0 {
93 if sc2 < second { second = sc2 }
94 }
95 }
96 dx2 = dx2 + 1
97 }
98 dy = dy + 1
99 }
100 if second < PS_BAD {
101 if best * 100 > second * FM_RATIO { return 0 - 1 }
102 }
103 out2[0] = bx
104 out2[1] = by
105 return 1
106}
107// Full wire: detect in A, match into B, emit surviving correspondences as (ax,ay,bx,by). Returns the count.
108func fm_wire(ia: *i64, ib: *i64, w: i64, h: i64, thresh: i64, rad: i64, pairs: *i64, maxn: i64) -> i64 {
109 let imga: *Image = fm_load(ia, w, h)
110 let corners: *i64 = sys_mmap(maxn * 16 + 64) as *i64
111 let nc: i64 = fm_corners(imga, thresh, corners, maxn)
112 let o2: *i64 = sys_mmap(32) as *i64
113 var n: i64 = 0
114 var i: i64 = 0
115 while i < nc {
116 let ax: i64 = corners[i * 2 + 0]
117 let ay: i64 = corners[i * 2 + 1]
118 if fm_match_one(ia, ib, w, h, ax, ay, rad, o2) == 1 {
119 if n < maxn {
120 pairs[n * 4 + 0] = ax
121 pairs[n * 4 + 1] = ay
122 pairs[n * 4 + 2] = o2[0]
123 pairs[n * 4 + 3] = o2[1]
124 n = n + 1
125 }
126 }
127 i = i + 1
128 }
129 return n
130}