code wiki / (root) / nx_photogram_hbench.nx

nx_photogram_hbench.nx source

↩ module page · 230 lines · 7990 B

1// nx_photogram_hbench.nx -- run the Nishi front end on a REAL photograph pair and emit its matches. 2// 3// This organ deliberately does NOT score itself. It reads two images, detects/describes/matches, and 4// prints the matched coordinate pairs. The ground-truth homography is never opened here and the 5// correctness rule is never implemented here. A single external scorer grades this organ, OpenCV SIFT 6// and OpenCV ORB from identical output formats, so all three are judged by exactly the same code 7// against exactly the same ground truth. Scoring yourself with your own scorer is the oldest way to 8// win a benchmark that means nothing. 9// 10// Usage: nx_photogram_hbench <img1.pgm|ppm> <img2.pgm|ppm> [maxkp] 11// Output: one "M <x1> <y1> <x2> <y2>" line per match, then a "SUMMARY ..." line. 12// license_tier: ORIGINAL expect_exit: 0 13import "nx_photogram_front.nx" 14const HB_MAGIC_262144: i64 = 262144 15 16const HB_MAXBYTES: i64 = 8388608 17const HB_DEFAULT_KP: i64 = 500 18 19func hw(s: *u8) -> i64 { 20 var n: i64 = 0 21 while s[n] != (0 as u8) { n = n + 1 } 22 sys_write(1, s, n) 23 return 0 24} 25 26func hn(v: i64) -> i64 { 27 let bb: *u8 = sys_mmap(32) 28 let t: *u8 = sys_mmap(32) 29 var m: i64 = v 30 var neg: i64 = 0 31 if m < 0 { neg = 1; m = 0 - m } 32 var k: i64 = 0 33 if m == 0 { t[0] = 48 as u8; k = 1 } 34 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 35 var i: i64 = 0 36 if neg == 1 { bb[0] = 45 as u8; i = 1 } 37 var j: i64 = 0 38 while j < k { bb[i + j] = t[k - 1 - j]; j = j + 1 } 39 sys_write(1, bb, i + k) 40 return 0 41} 42 43func hb_isdigit(c: i64) -> i64 { 44 if c < 48 { return 0 } 45 if c > 57 { return 0 } 46 return 1 47} 48func hb_isspace(c: i64) -> i64 { 49 if c == 32 { return 1 } 50 if c == 10 { return 1 } 51 if c == 13 { return 1 } 52 if c == 9 { return 1 } 53 return 0 54} 55 56// Netpbm header fields are whitespace-separated with '#' comments running to end of line. pos is a 57// one-element cursor so the caller can advance through magic/width/height/maxval in sequence. 58// ⚠Every loop here exits by a FLAG, never by assigning the cursor past the end. The obvious 59// pseudo-break `p = n + 1` does leave the loop, but it also destroys p -- and p is precisely what the 60// next field depends on. Written that way, width parsed correctly and then height and maxval both read 61// 0 because the cursor had been slammed to the end of the buffer, so every image failed to load with 62// no indication of where. The flag form preserves the cursor. 63func hb_next_int(buf: *u8, n: i64, pos: *i64) -> i64 { 64 var p: i64 = pos[0] 65 var scanning: i64 = 1 66 while scanning == 1 { 67 if p >= n { scanning = 0 } else { 68 let c: i64 = buf[p] as i64 69 if hb_isspace(c) == 1 { p = p + 1 } else { 70 if c == 35 { 71 var incomment: i64 = 1 72 while incomment == 1 { 73 if p >= n { incomment = 0 } else { 74 if (buf[p] as i64) == 10 { incomment = 0 } else { p = p + 1 } 75 } 76 } 77 } else { scanning = 0 } 78 } 79 } 80 } 81 var v: i64 = 0 82 var indigits: i64 = 1 83 while indigits == 1 { 84 if p >= n { indigits = 0 } else { 85 let d: i64 = buf[p] as i64 86 if hb_isdigit(d) == 1 { 87 v = v * 10 + (d - 48) 88 p = p + 1 89 } else { indigits = 0 } 90 } 91 } 92 pos[0] = p 93 return v 94} 95 96// Read a binary Netpbm image (P5 grayscale or P6 RGB) into a grayscale byte buffer. 97// Returns 1 on success; wh[0]=width wh[1]=height, gray = caller-supplied buffer. 98func hb_load_pnm(path: *u8, gray: *u8, wh: *i64) -> i64 { 99 let fd: i64 = sys_openat_rd(path) 100 if fd < 0 { return 0 } 101 let buf: *u8 = sys_mmap(HB_MAXBYTES) 102 var total: i64 = 0 103 var reading: i64 = 1 104 while reading == 1 { 105 let got: i64 = sys_read(fd, buf + total, HB_MAGIC_262144) 106 if got <= 0 { reading = 0 } else { 107 total = total + got 108 if total >= HB_MAXBYTES - HB_MAGIC_262144 { reading = 0 } 109 } 110 } 111 sys_close(fd) 112 if total < 16 { return 0 } 113 if (buf[0] as i64) != 80 { return 0 } 114 let kind: i64 = buf[1] as i64 115 let pos: *i64 = sys_mmap(64) as *i64 116 pos[0] = 2 117 let w: i64 = hb_next_int(buf, total, pos) 118 let h: i64 = hb_next_int(buf, total, pos) 119 let mx: i64 = hb_next_int(buf, total, pos) 120 if w <= 0 { return 0 } 121 if h <= 0 { return 0 } 122 if mx <= 0 { return 0 } 123 // exactly one whitespace byte separates the header from the raster 124 var dstart: i64 = pos[0] + 1 125 var i: i64 = 0 126 if kind == 53 { 127 while i < w * h { 128 gray[i] = buf[dstart + i] 129 i = i + 1 130 } 131 } else { 132 if kind == 54 { 133 while i < w * h { 134 let o: i64 = dstart + i * 3 135 let r: i64 = buf[o] as i64 136 let g: i64 = buf[o + 1] as i64 137 let b: i64 = buf[o + 2] as i64 138 // Rec.601 luma, integer: the same conversion OpenCV's COLOR_BGR2GRAY applies, so the 139 // baselines and this organ see the same pixels rather than differently-greyed ones. 140 gray[i] = ((r * 299 + g * 587 + b * 114) / 1000) as u8 141 i = i + 1 142 } 143 } else { return 0 } 144 } 145 wh[0] = w 146 wh[1] = h 147 return 1 148} 149 150func main(argc: i64, argv: *i64) -> i64 { 151 if argc < 3 { 152 hw("usage: nx_photogram_hbench <img1> <img2> [maxkp]\n" as *u8) 153 return 2 154 } 155 let p1v: i64 = argv[1] 156 let p2v: i64 = argv[2] 157 let p1: *u8 = p1v as *u8 158 let p2: *u8 = p2v as *u8 159 var maxkp: i64 = HB_DEFAULT_KP 160 if argc >= 4 { 161 let p3v: i64 = argv[3] 162 let p3: *u8 = p3v as *u8 163 let pos: *i64 = sys_mmap(64) as *i64 164 pos[0] = 0 165 let m: i64 = hb_next_int(p3, 12, pos) 166 if m > 0 { maxkp = m } 167 } 168 pf_init() 169 let g1: *u8 = sys_mmap(HB_MAXBYTES) 170 let g2: *u8 = sys_mmap(HB_MAXBYTES) 171 let wh1: *i64 = sys_mmap(64) as *i64 172 let wh2: *i64 = sys_mmap(64) as *i64 173 if hb_load_pnm(p1, g1, wh1) == 0 { hw("ERROR cannot load image 1\n" as *u8); return 3 } 174 if hb_load_pnm(p2, g2, wh2) == 0 { hw("ERROR cannot load image 2\n" as *u8); return 3 } 175 let w1: i64 = wh1[0] 176 let h1: i64 = wh1[1] 177 let w2: i64 = wh2[0] 178 let h2: i64 = wh2[1] 179 180 let kx1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 181 let ky1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 182 let kl1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 183 let kx2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 184 let ky2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 185 let kl2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 186 let d1: *i64 = sys_mmap(maxkp * PF_NWORD * 8 + 64) as *i64 187 let d2: *i64 = sys_mmap(maxkp * PF_NWORD * 8 + 64) as *i64 188 let a1: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 189 let a2: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 190 // pyramid detect+describe: the baselines (SIFT, ORB) are both multi-scale, so a single-scale run 191 // here would not be a like-for-like comparison 192 let n1: i64 = pf_pyramid(g1, w1, h1, maxkp, kx1, ky1, kl1, d1, a1) 193 let n2: i64 = pf_pyramid(g2, w2, h2, maxkp, kx2, ky2, kl2, d2, a2) 194 195 let ma: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 196 let mb: *i64 = sys_mmap(maxkp * 8 + 64) as *i64 197 let nm: i64 = pf_match(d1, n1, d2, n2, ma, mb, maxkp) 198 199 var i: i64 = 0 200 while i < nm { 201 let ia: i64 = ma[i] 202 let ib: i64 = mb[i] 203 hw("M " as *u8) 204 hn(kx1[ia]) 205 hw(" " as *u8) 206 hn(ky1[ia]) 207 hw(" " as *u8) 208 hn(kx2[ib]) 209 hw(" " as *u8) 210 hn(ky2[ib]) 211 hw("\n" as *u8) 212 i = i + 1 213 } 214 hw("SUMMARY kp1=" as *u8) 215 hn(n1) 216 hw(" kp2=" as *u8) 217 hn(n2) 218 hw(" matches=" as *u8) 219 hn(nm) 220 hw(" w1=" as *u8) 221 hn(w1) 222 hw(" h1=" as *u8) 223 hn(h1) 224 hw(" w2=" as *u8) 225 hn(w2) 226 hw(" h2=" as *u8) 227 hn(h2) 228 hw("\n" as *u8) 229 return 0 230}