Analysis half of the pipeline in docs/roto-puppet.md. Stabilises a face out of a clip via a similarity fit on rigid landmarks, reduces the lip contour to a fixed vertex budget, selects sparse keys on velocity minima, and previews the result as flat indexed fills so timing can be judged without an Animator Pro render. - landmarks.js ordered lip/oval rings; slot position is vertex identity - mathutil.js closed-form 2D similarity, Procrustes mean, transform smoothing - pipeline.js stabilise -> subsample -> key-select - raster.js indexed scanline fill, no antialiasing - take.js take-file writer - selftest.js 29 assertions, incl. ring simplicity at every vertex budget Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
2.3 KiB
JavaScript
54 lines
2.3 KiB
JavaScript
// MediaPipe FaceLandmarker index tables.
|
|
// Ring arrays are ORDERED traversals, not raw connection sets: vertex position
|
|
// within a ring is the vertex's identity, and every downstream stage depends on
|
|
// that ordering being stable. See docs/roto-puppet.md, "Fixed topology".
|
|
|
|
// Rigid landmarks for the similarity fit. Eye corners, nose bridge, nose tip.
|
|
// Nothing here may be a feature that moves under performance: including the
|
|
// mouth or brows bleeds performance into the stabilization.
|
|
export const RIGID = [33, 133, 362, 263, 168, 6, 1];
|
|
|
|
// Outer lip ring, clockwise from the right corner over the top.
|
|
// index 0 = right corner, 5 = top centre, 10 = left corner, 15 = bottom centre.
|
|
export const LIPS_OUTER = [
|
|
61, 185, 40, 39, 37, 0, 267, 269, 270, 409,
|
|
291, 375, 321, 405, 314, 17, 84, 181, 91, 146,
|
|
];
|
|
|
|
// Inner lip ring, same orientation and the same four cardinal positions.
|
|
export const LIPS_INNER = [
|
|
78, 191, 80, 81, 82, 13, 312, 311, 310, 415,
|
|
308, 324, 318, 402, 317, 14, 87, 178, 88, 95,
|
|
];
|
|
|
|
// Inner upper / lower lip centres. Their separation is the aperture signal that
|
|
// decides whether the mouth interior is present at all.
|
|
export const APERTURE = [13, 14];
|
|
|
|
// Face oval, used only to derive the placeholder plate in v1.
|
|
export const FACE_OVAL = [
|
|
10, 338, 297, 332, 284, 251, 389, 356, 454, 323, 361, 288,
|
|
397, 365, 379, 378, 400, 377, 152, 148, 176, 149, 150, 136,
|
|
172, 58, 132, 93, 234, 127, 162, 21, 54, 103, 67, 109,
|
|
];
|
|
|
|
// Eye corners, for the calibration box and for reporting fit residual.
|
|
export const EYE_INNER = [133, 362];
|
|
|
|
// Pick `n` slots from a ring of `len` by even spacing. Returns RING POSITIONS,
|
|
// not landmark ids: positions are the vertex identity downstream, and mapping ids
|
|
// back to positions with indexOf would silently pick the wrong slot if a table
|
|
// ever repeated an id.
|
|
//
|
|
// For even n this naturally lands on the cardinal positions (corners and lip
|
|
// centres) of a 20-point ring. Fixed indices, never adaptive decimation: the
|
|
// vertex at slot k means the same thing on every frame of the shot.
|
|
export function subsampleSlots(len, n) {
|
|
const out = [];
|
|
for (let k = 0; k < n; k++) out.push(Math.round((k * len) / n) % len);
|
|
return out;
|
|
}
|
|
|
|
export function subsampleRing(ring, n) {
|
|
return subsampleSlots(ring.length, n).map((s) => ring[s]);
|
|
}
|