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}