code wiki / (root) / nx_facefind_lib.nx

nx_facefind_lib.nx source

↩ module page · 203 lines · 9914 B

1// nx_facefind_lib.nx -- FACE AND EYE BOXES from a gray plane (aesthetictwin AT30, 2026-09-16): the loaded cascades 2// (nx_haar_xml) run by the sovereign evaluator (nx_haar_cascade) over integral tables (nx_integral). The face is the 3// best-voted group over the whole plane at window sizes from min_face_permil of the shorter side; the eyes are the 4// best-voted groups on each half of the face's eye band. Every count the verdict rests on travels in the result (raw 5// detections, groups, the cap flag) so a miss can be read, never guessed. The trained data is a beta-only bootstrap arm; 6// this composite is written against the flat cascade layout, so a cascade the estate trains itself drops in unchanged. 7// license_tier: ORIGINAL No hw writes (Rule 26). 8import "nx_syscalls.nx" 9import "nx_integral.nx" 10import "nx_haar_cascade.nx" 11import "nx_haar_xml.nx" 12 13const FF_FACE_XML: *u8 = "knowledge/fetched/cmp_aesthetictwin_haar_frontalface_default.xml" 14const FF_FACE_XML_PARENT: *u8 = "../knowledge/fetched/cmp_aesthetictwin_haar_frontalface_default.xml" 15const FF_EYE_XML: *u8 = "knowledge/fetched/cmp_aesthetictwin_haar_eye.xml" 16const FF_EYE_XML_PARENT: *u8 = "../knowledge/fetched/cmp_aesthetictwin_haar_eye.xml" 17const FF_MIN_FACE_PERMIL: i64 = 100 18const FF_SCALE_PERMIL: i64 = 1100 19const FF_DET_CAP: i64 = 4096 20const FF_GRP_CAP: i64 = 512 21const FF_MIN_NEIGHBORS: i64 = 3 22const FF_GROUP_EPS_PERMIL: i64 = 200 23const FF_EYE_TOP_PERMIL: i64 = 150 24const FF_EYE_BOT_PERMIL: i64 = 600 25const FF_EYE_MIN_DIV: i64 = 10 26const FF_EYE_MAX_DIV: i64 = 3 27const FF_EYE_MIN_NEIGHBORS: i64 = 2 28const FF_PERMIL: i64 = 1000 29const FF_I64: i64 = 8 30const FF_R_FACE: i64 = 0 31const FF_R_FX: i64 = 1 32const FF_R_FY: i64 = 2 33const FF_R_FW: i64 = 3 34const FF_R_FH: i64 = 4 35const FF_R_FVOTES: i64 = 5 36const FF_R_EYES: i64 = 6 37const FF_R_RX: i64 = 7 38const FF_R_RY: i64 = 8 39const FF_R_RW: i64 = 9 40const FF_R_RH: i64 = 10 41const FF_R_LX: i64 = 11 42const FF_R_LY: i64 = 12 43const FF_R_LW: i64 = 13 44const FF_R_LH: i64 = 14 45const FF_R_GROUPS: i64 = 15 46const FF_R_RAW: i64 = 16 47const FF_R_CAPPED: i64 = 17 48const FF_R_EYE_RAW: i64 = 18 49const FF_R_EYE_GROUPS: i64 = 19 50const FF_R_FACE_RANK: i64 = 20 51const FF_R_RVOTES: i64 = 21 52const FF_R_LVOTES: i64 = 22 53const FF_R_N: i64 = 23 54const FF_FACE_CANDIDATES: i64 = 4 55const FF_JOINT_MIN_PERMIL: i64 = 500 56const FF_E_RAW: i64 = 0 57const FF_E_GROUPS: i64 = 1 58const FF_E_RX: i64 = 2 59const FF_E_RY: i64 = 3 60const FF_E_RW: i64 = 4 61const FF_E_RH: i64 = 5 62const FF_E_RV: i64 = 6 63const FF_E_LX: i64 = 7 64const FF_E_LY: i64 = 8 65const FF_E_LW: i64 = 9 66const FF_E_LH: i64 = 10 67const FF_E_LV: i64 = 11 68const FF_E_VOTE_OFF: i64 = 4 69const FF_EYE_OUT_N: i64 = 12 70const FF_OK: i64 = 0 71const FF_E_NOFACE: i64 = 0 - 1 72const FF_E_NOEYES: i64 = 0 - 2 73 74func ff_min(a: i64, b: i64) -> i64 { if a < b { return a } return b } 75func ff_load_face(rc: *i64) -> *HaarCascade { var c: *HaarCascade = hx_load(FF_FACE_XML, rc); if (c as i64) == 0 { c = hx_load(FF_FACE_XML_PARENT, rc) } return c } 76func ff_load_eye(rc: *i64) -> *HaarCascade { var c: *HaarCascade = hx_load(FF_EYE_XML, rc); if (c as i64) == 0 { c = hx_load(FF_EYE_XML_PARENT, rc) } return c } 77// find the face and both eyes on a gray plane; res[FF_R_*]; returns FF_OK, FF_E_NOFACE or FF_E_NOEYES (the face box is still 78// reported when the eyes are missing) 79func ff_find(face_c: *HaarCascade, eye_c: *HaarCascade, gray: *u8, w: i64, h: i64, stride: i64, min_face_permil: i64, res: *i64) -> i64 { 80 var k: i64 = 0 81 while k < FF_R_N { res[k] = 0; k = k + 1 } 82 let ii: *IntegralImage = nx_integral_from_gray(gray, w, h, stride) 83 let sq: *IntegralImage = nx_integral_sq_from_gray(gray, w, h, stride) 84 var min_face: i64 = ff_min(w, h) * min_face_permil / FF_PERMIL 85 if min_face < face_c.hdr[HC_H_WINW] { min_face = face_c.hdr[HC_H_WINW] } 86 let dets: *i64 = sys_mmap(FF_DET_CAP * HC_DET_FIELDS * FF_I64) as *i64 87 let capped: *i64 = sys_mmap(FF_I64) as *i64 88 let nd: i64 = hc_detect(face_c, ii, sq, 0, 0, w, h, min_face, ff_min(w, h), FF_SCALE_PERMIL, dets, FF_DET_CAP, capped) 89 res[FF_R_RAW] = nd 90 res[FF_R_CAPPED] = capped[0] 91 let groups: *i64 = sys_mmap(FF_GRP_CAP * HC_GRP_FIELDS * FF_I64) as *i64 92 let ng: i64 = hc_group(dets, nd, FF_MIN_NEIGHBORS, FF_GROUP_EPS_PERMIL, groups, FF_GRP_CAP) 93 res[FF_R_GROUPS] = ng 94 if ng <= 0 { return FF_E_NOFACE } 95 // JOINT SELECTION: a face is a face with eyes. The best-voted group alone picked a chest on two stills (a 13-vote false 96 // face with no eye evidence); so the top candidates are tried in vote order and the first that carries both eyes wins; 97 // when none does, the top candidate stands and the verdict says NOEYES 98 let taken: *u8 = sys_mmap(ng + 1) 99 let eyes: *i64 = sys_mmap(FF_EYE_OUT_N * FF_I64) as *i64 100 var cand: i64 = 0 101 var chosen: i64 = 0 - 1 102 var found_eyes: i64 = 0 103 var top_votes: i64 = 0 104 while cand < FF_FACE_CANDIDATES { 105 var bi: i64 = 0 - 1 106 var bv: i64 = 0 107 var g: i64 = 0 108 while g < ng { if taken[g] == (0 as u8) { if groups[g * HC_GRP_FIELDS + HC_G_VOTES] > bv { bv = groups[g * HC_GRP_FIELDS + HC_G_VOTES]; bi = g } } g = g + 1 } 109 if bi < 0 { cand = FF_FACE_CANDIDATES } else { 110 taken[bi] = 1 as u8 111 if chosen < 0 { chosen = bi; top_votes = bv } 112 // a lower candidate may displace the top one only with at least half its votes: a 10-vote false face with 113 // false eyes must not beat a 28-vote true face whose eyes the eye cascade missed 114 if bv * FF_PERMIL < top_votes * FF_JOINT_MIN_PERMIL { cand = FF_FACE_CANDIDATES } else { 115 let bo: i64 = bi * HC_GRP_FIELDS 116 let ne: i64 = ff_eyes(eye_c, ii, sq, groups[bo + HC_G_X], groups[bo + HC_G_Y], groups[bo + HC_G_W], groups[bo + HC_G_H], eyes) 117 if ne == 2 { chosen = bi; found_eyes = 1; res[FF_R_FACE_RANK] = cand; cand = FF_FACE_CANDIDATES } else { cand = cand + 1 } 118 } 119 } 120 } 121 let co: i64 = chosen * HC_GRP_FIELDS 122 res[FF_R_FACE] = 1 123 res[FF_R_FX] = groups[co + HC_G_X] 124 res[FF_R_FY] = groups[co + HC_G_Y] 125 res[FF_R_FW] = groups[co + HC_G_W] 126 res[FF_R_FH] = groups[co + HC_G_H] 127 res[FF_R_FVOTES] = groups[co + HC_G_VOTES] 128 if found_eyes == 0 { ff_eyes(eye_c, ii, sq, res[FF_R_FX], res[FF_R_FY], res[FF_R_FW], res[FF_R_FH], eyes) } 129 res[FF_R_EYE_RAW] = eyes[FF_E_RAW] 130 res[FF_R_EYE_GROUPS] = eyes[FF_E_GROUPS] 131 if found_eyes == 0 { return FF_E_NOEYES } 132 res[FF_R_EYES] = 2 133 res[FF_R_RX] = eyes[FF_E_RX] 134 res[FF_R_RY] = eyes[FF_E_RY] 135 res[FF_R_RW] = eyes[FF_E_RW] 136 res[FF_R_RH] = eyes[FF_E_RH] 137 res[FF_R_LX] = eyes[FF_E_LX] 138 res[FF_R_LY] = eyes[FF_E_LY] 139 res[FF_R_LW] = eyes[FF_E_LW] 140 res[FF_R_LH] = eyes[FF_E_LH] 141 res[FF_R_RVOTES] = eyes[FF_E_RV] 142 res[FF_R_LVOTES] = eyes[FF_E_LV] 143 return FF_OK 144} 145// the eyes of one face box: the eye cascade over the face's eye band, then ONE box per half as the vote-weighted mean of 146// every group on that half (a single best group sat 3 to 5 px off the iris on two stills; the weighted mean centres it). 147// out[FF_E_*]; returns the number of eyes found (0, 1 or 2) 148func ff_eyes(eye_c: *HaarCascade, ii: *IntegralImage, sq: *IntegralImage, fx: i64, fy: i64, fw: i64, fh: i64, out: *i64) -> i64 { 149 var k: i64 = 0 150 while k < FF_EYE_OUT_N { out[k] = 0; k = k + 1 } 151 let ey0: i64 = fy + fh * FF_EYE_TOP_PERMIL / FF_PERMIL 152 let ey1: i64 = fy + fh * FF_EYE_BOT_PERMIL / FF_PERMIL 153 var emin: i64 = fw / FF_EYE_MIN_DIV 154 if emin < eye_c.hdr[HC_H_WINW] { emin = eye_c.hdr[HC_H_WINW] } 155 let emax: i64 = fw / FF_EYE_MAX_DIV 156 let edets: *i64 = sys_mmap(FF_DET_CAP * HC_DET_FIELDS * FF_I64) as *i64 157 let ecapped: *i64 = sys_mmap(FF_I64) as *i64 158 let ned: i64 = hc_detect(eye_c, ii, sq, fx, ey0, fx + fw, ey1, emin, emax, FF_SCALE_PERMIL, edets, FF_DET_CAP, ecapped) 159 out[FF_E_RAW] = ned 160 let egroups: *i64 = sys_mmap(FF_GRP_CAP * HC_GRP_FIELDS * FF_I64) as *i64 161 // every group (a single detection is a one-vote group); a half is read at the corroborated floor first and falls back to 162 // its single detections, announced through the votes it carries, so a thin eye is measured with its evidence beside it 163 let neg: i64 = hc_group(edets, ned, 1, FF_GROUP_EPS_PERMIL, egroups, FF_GRP_CAP) 164 out[FF_E_GROUPS] = neg 165 let mid: i64 = fx + fw / 2 166 var n: i64 = 0 167 var floor: i64 = FF_EYE_MIN_NEIGHBORS 168 while floor >= 1 { 169 if ff_half_mean(egroups, neg, mid, 0, floor, out, FF_E_RX) == 1 { n = n + 1 } 170 if ff_half_mean(egroups, neg, mid, 1, floor, out, FF_E_LX) == 1 { n = n + 1 } 171 floor = floor - 1 172 } 173 return n 174} 175// the vote-weighted mean box of the groups on one half (side 0 = image left, 1 = image right) with at least `floor` votes; 176// written once, the first time a half yields, so the corroborated floor wins over the fallback; returns 1 when it wrote 177func ff_half_mean(egroups: *i64, neg: i64, mid: i64, side: i64, floor: i64, out: *i64, slot: i64) -> i64 { 178 if out[slot + FF_E_VOTE_OFF] > 0 { return 0 } 179 var sx: i64 = 0 180 var sy: i64 = 0 181 var sw: i64 = 0 182 var sh: i64 = 0 183 var sv: i64 = 0 184 var g: i64 = 0 185 while g < neg { 186 let go: i64 = g * HC_GRP_FIELDS 187 let cx: i64 = egroups[go + HC_G_X] + egroups[go + HC_G_W] / 2 188 var on_side: i64 = 0 189 if side == 0 { if cx < mid { on_side = 1 } } else { if cx >= mid { on_side = 1 } } 190 let v: i64 = egroups[go + HC_G_VOTES] 191 if on_side == 1 { if v >= floor { 192 sx = sx + egroups[go + HC_G_X] * v; sy = sy + egroups[go + HC_G_Y] * v; sw = sw + egroups[go + HC_G_W] * v; sh = sh + egroups[go + HC_G_H] * v; sv = sv + v 193 } } 194 g = g + 1 195 } 196 if sv <= 0 { return 0 } 197 out[slot] = sx / sv 198 out[slot + 1] = sy / sv 199 out[slot + 2] = sw / sv 200 out[slot + 3] = sh / sv 201 out[slot + FF_E_VOTE_OFF] = sv 202 return 1 203}