Brows: traced ring, quantised raise
A brow at 320x200 is fourteen pixels wide and three tall. Its shape carries almost nothing at that size; its height above the eye carries the expression, and a brow raise is the most legible beat on a face. So the ring is traced and the height is quantised - the split the eyes already got, where the lid is a traced feature and the iris a quantised primitive. The decomposition is the point. The traced ring already contains the real height, so adding a quantised raise on top would move the brow twice. The height is measured OUT of the ring, quantised, and put back, so the shape that renders is his at a height that snaps between a few levels and holds. Measured at both ends rather than as one number, because raise and tilt are different expressions out of one mechanism: both ends up is surprise, inner up alone is worry, inner down is anger. They share a dwell - the gaze quantiser, renamed quantizeSnap now that it has two callers - so the brow hits its pose in one frame instead of crawling into it with one end arriving before the other. Measured against the eye's corner midpoint, never its lid. Same trap the gaze origin has and worth avoiding twice: brows and lids move together constantly, so a brow that jumped on every blink would read as a tic. Rest pose from the take median rather than the neutral frame, for the reason gaze learned the hard way - that frame is picked by minimum mouth aperture and says nothing about the brows. Two correspondences resolved from geometry, not declared: which ring is which brow, and which end is the outer one. The second matters more - backwards, the tilt mirrors and worry renders as its own opposite, which reads as a directed performance choice rather than a bug and would never be questioned. Which EDGE is upper is deliberately left unresolved: it traverses the same ring the other way, an even-odd fill has no winding, and both ends still land on fixed slots. Also fixes a bug from the exposure work: the live render applied exposure to the plate and the mouth but not to the eyes, so on 2s the preview and the export disagreed. A preview that disagrees with the export is the one bug this tool cannot afford. perfIndex now exists as a named thing so the two paths cannot drift apart again. 91 -> 105 assertions. Ground truth on all four synthetic brow poses, tilt separating worry from anger by sign, a blink not faking a raise, and a shared dwell never emitting a half-raised brow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
2e4406d579
commit
03df81fa0b
8 changed files with 419 additions and 26 deletions
14
index.html
14
index.html
|
|
@ -102,7 +102,8 @@
|
|||
<h2>source + landmarks</h2>
|
||||
<canvas id="cv-source"></canvas>
|
||||
<div class="legend">outer lip <b style="color:#4ade80">—</b> · inner lip <b style="color:#f87171">—</b> ·
|
||||
lids <b style="color:#60a5fa">—</b> · iris <b style="color:#fbbf24">—</b></div>
|
||||
lids <b style="color:#60a5fa">—</b> · iris <b style="color:#fbbf24">—</b> ·
|
||||
brows <b style="color:#c084fc">—</b></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>stabilised (head-local)</h2>
|
||||
|
|
@ -161,6 +162,11 @@
|
|||
<label class="ctl"><span>gaze dwell</span><input type="range" id="gazeDwell" min="0" max="6" value="2"><output id="gazeDwellv"></output></label>
|
||||
<label class="ctl"><span>iris size</span><input type="range" id="irisSize" min="20" max="70" value="42"><output id="irisSizev"></output></label>
|
||||
<label class="ctl"><span>pupil</span><input type="range" id="pupilPx" min="0" max="7" value="3"><output id="pupilPxv"></output></label>
|
||||
<label class="ctl"><span>brow vertices</span><input type="range" id="browVerts" min="4" max="10" step="2" value="6"><output id="browVertsv"></output></label>
|
||||
<label class="ctl"><span>brow weight</span><input type="range" id="browWeight" min="0" max="3" value="1"><output id="browWeightv"></output></label>
|
||||
<label class="ctl"><span>brow raise gain</span><input type="range" id="browGain" min="50" max="300" value="100"><output id="browGainv"></output></label>
|
||||
<label class="ctl"><span>brow step</span><input type="range" id="browStep" min="0" max="6" value="2"><output id="browStepv"></output></label>
|
||||
<label class="ctl"><span>brow dwell</span><input type="range" id="browDwell" min="0" max="6" value="2"><output id="browDwellv"></output></label>
|
||||
<label class="ctl"><span>suggest tolerance</span><input type="range" id="tol" min="2" max="60" value="14"><output id="tolv"></output></label>
|
||||
<div class="legend">
|
||||
<b>mouth lead</b> shifts the performance tracks earlier (positive) against
|
||||
|
|
@ -183,6 +189,12 @@
|
|||
value carries across takes. <b>blink hold</b> is the minimum length of a
|
||||
blink: a real blink is one frame at 12fps and a single frame of closed
|
||||
eye reads as a dropout, so it is extended to a beat.
|
||||
<b>brow step</b> and <b>brow dwell</b> quantise the brow's HEIGHT above
|
||||
the eye, not its shape — the traced ring is his, the height snaps between
|
||||
a few levels and holds. Both ends move independently, so raise and tilt
|
||||
come out of one control: both up is surprise, inner up is worry, inner
|
||||
down is anger. <b>brow weight</b> thickens the ring, which it needs at
|
||||
three pixels tall.<br>
|
||||
<b>pupil</b> is a square, in whole pixels, 0 to turn it off: at this size
|
||||
a circle of radius 1.5 is a plus sign with the corners gnawed off and it
|
||||
changes shape as it moves, where a square stays the mark you drew.<br>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue