code wiki / (root) / nx_presence.nx

nx_presence.nx source

↩ module page · 121 lines · 4638 B

1// nx_presence.nx -- person-presence detection (skin-chrominance + blob 2// concentration). R2 of the MEDIA-STUDIO arc, and the TRUE dead-air signal: 3// motion alone is not enough -- a moving curtain or a TV showing scenery is 4// still "dead air" because no PERSON is onscreen. A person presents a 5// coherent region of skin tone; empty rooms, moving non-person objects, and 6// scattered skin-coloured noise do not. 7// 8// Two cheap, sovereign signals (pure integer, no learned weights): 9// skin per-mille : fraction of pixels inside the skin-chrominance gamut. 10// concentration : skin pixels / area of their bounding box, per-mille. 11// A solid blob fills its own box (high); scattered skin- 12// coloured speckle spans a huge box but fills little (low). 13// PRESENT iff skin >= min_skin AND concentration >= min_conc. 14// 15// The skin gamut is the Kovac et al. 2003 uniform-daylight rule (a fixed 16// colour-science test, documented below). The DECISION thresholds 17// (min_skin, min_conc) are CALLER-supplied -- those are the business knobs. 18// 19// genealogy_id: kovac_2003_skin_segmentation + connected_region_density 20// (attribution UNVERIFIED -- record-hint, do not cite as sourced) 21// lineage_id: skin_chrominance_rule + bounding_box_density + threshold 22// nx_safety_envelope: 23// intended_use: "Flag whether a person is onscreen, for dead-air trimming 24// and as the front-end of body/aesthetic analysis." 25// verdict: NOT_YET_EVALUATED 26// license_tier: ORIGINAL 27 28import "syscalls.nx" 29import "nx_image.nx" 30 31const NX_PRES_PRESENT: i64 = 1 32const NX_PRES_ABSENT: i64 = 0 33 34// A subject filling >= this fraction (permille) of the frame with skin counts 35// as present even without a tight blob -- the frame-filling person, which is 36// the common cam/portrait case and which a pure concentration gate wrongly 37// rejects. Data-driven knob; calibrate on real footage. (Proper fix for 38// localization is a connected-component largest-blob, queued as R2-refine.) 39const NX_PRES_MIN_FILL: i64 = 300 40 41// Skin-pixel test, Kovac et al. 2003 (uniform daylight). Pure integer. 42// R>95, G>40, B>20, max-min>15, |R-G|>15, R>G, R>B. 43func nx_skin_is_pixel(r: i64, g: i64, b: i64) -> i64 { 44 if r <= 95 { return 0 } 45 if g <= 40 { return 0 } 46 if b <= 20 { return 0 } 47 var mx: i64 = r 48 if g > mx { mx = g } 49 if b > mx { mx = b } 50 var mn: i64 = r 51 if g < mn { mn = g } 52 if b < mn { mn = b } 53 if mx - mn <= 15 { return 0 } 54 var d: i64 = r - g 55 if d < 0 { d = 0 - d } 56 if d <= 15 { return 0 } 57 if r <= g { return 0 } 58 if r <= b { return 0 } 59 return 1 60} 61 62// Scan an RGB image. Writes: 63// out[0] = skin per-mille (skin pixels / total, *1000) 64// out[1] = concentration per-mille (skin pixels / skin bounding-box area) 65// out[2] = raw skin pixel count 66func nx_presence_scan(img: *Image, out: *i64) -> i64 { 67 let w: i64 = img.width 68 let h: i64 = img.height 69 let total: i64 = w * h 70 var cnt: i64 = 0 71 var minx: i64 = w 72 var maxx: i64 = 0 - 1 73 var miny: i64 = h 74 var maxy: i64 = 0 - 1 75 var y: i64 = 0 76 while y < h { 77 var x: i64 = 0 78 while x < w { 79 let r: i64 = nx_image_get(img, x, y, 0) 80 let g: i64 = nx_image_get(img, x, y, 1) 81 let b: i64 = nx_image_get(img, x, y, 2) 82 if nx_skin_is_pixel(r, g, b) == 1 { 83 cnt = cnt + 1 84 if x < minx { minx = x } 85 if x > maxx { maxx = x } 86 if y < miny { miny = y } 87 if y > maxy { maxy = y } 88 } 89 x = x + 1 90 } 91 y = y + 1 92 } 93 out[2] = cnt 94 out[0] = 0 95 out[1] = 0 96 out[3] = 0 - 1 // bbox minx,miny,maxx,maxy (-1 = none) 97 out[4] = 0 - 1 98 out[5] = 0 - 1 99 out[6] = 0 - 1 100 if total <= 0 { return 0 } 101 out[0] = cnt * 1000 / total 102 if cnt <= 0 { return 0 } 103 out[3] = minx 104 out[4] = miny 105 out[5] = maxx 106 out[6] = maxy 107 let bw: i64 = maxx - minx + 1 108 let bh: i64 = maxy - miny + 1 109 let area: i64 = bw * bh 110 if area <= 0 { return 0 } 111 out[1] = cnt * 1000 / area 112 return 0 113} 114 115// PRESENT iff enough skin AND that skin is concentrated (a blob, not speckle). 116func nx_presence_classify(skin_permille: i64, conc_permille: i64, min_skin: i64, min_conc: i64) -> i64 { 117 if skin_permille < min_skin { return NX_PRES_ABSENT } 118 if conc_permille >= min_conc { return NX_PRES_PRESENT } // compact skin blob 119 if skin_permille >= NX_PRES_MIN_FILL { return NX_PRES_PRESENT } // frame-filling subject 120 return NX_PRES_ABSENT 121}