Part of epic #869. Do this slice first — it de-risks everything downstream. No app code changes; deliverables are offline scripts, docs, and go/no-go decisions.
Goal
Prove the three model conversions numerically and settle the licensing questions, so Slices C/E are wiring jobs instead of research.
Task 1 — MediaPipe Face Landmarker → ONNX, with parity proof
The face_landmarker_v2_with_blendshapes.task bundle (download from Google's MediaPipe model page, Apache-2.0) is a zip containing three TFLite models:
- Face detector (BlazeFace short-range),
- Face landmark model (478 3D landmarks + presence/tracking scores),
- Blendshape model (MLP-Mixer; input = a subset of ~146 2D landmarks, output = 52 blendshape scores).
Write scripts/export-facecap-onnx.py (one-time offline dev tool, NOT shipped — follow the header comment style of scripts/export-rmib-onnx.py):
- Unzip the
.task, extract the three .tflite files.
- Convert each with
tf2onnx (python -m tf2onnx.convert --tflite model.tflite --output model.onnx --opset 18). If tf2onnx chokes on custom ops, fall back to tflite2onnx or onnx2tf round-trip — document which worked.
- Parity test built into the script: run the Python
mediapipe FaceLandmarker on 5–10 test images (include: neutral face, open mouth, wink, smile, head turned ~30°), then run the exported ONNX graphs with onnxruntime implementing the same pre/post-processing, and assert blendshape scores match within ~1e-2 and landmarks within ~1px. Print the exact pre/post-processing contract discovered (input sizes, normalization ranges, letterbox behavior, which 146 landmark indices feed the blendshape model, score/sigmoid handling, detector anchor decode) — this contract is the spec Slice C implements in C++, so write it into the doc, not just the console.
- Also extract the canonical face landmark positions MediaPipe uses for its facial transformation matrix (Procrustes reference) — needed for head pose in Slice C. If pulling MediaPipe's own geometry pipeline data is awkward, document the alternative: Umeyama rigid fit of a fixed stable-landmark subset (nose bridge, eye corners, ears region) against their positions in a reference neutral frame.
Task 2 — MediaPipe Pose Landmarker → ONNX (the body fallback)
Same recipe for pose_landmarker_full.task (detector + landmark model, 33 landmarks with world coordinates). Add to the same script (--pose flag). Parity: world-landmark positions within ~1cm on test images. Document the landmark index → body-part table (0=nose, 11/12=shoulders, 13/14=elbows, 15/16=wrists, 23/24=hips, 25/26=knees, 27/28=ankles, …) in the doc.
Task 3 — SAM 3D Body: license due-diligence + export feasibility
- Read the SAM License (from the facebook/sam-3d-body-dinov3 HF repo and Meta's release). Answer specifically: (a) may we redistribute converted ONNX weights on our HF models repo (
fernandotonon/QtMeshEditor-models)? (b) may end users use outputs commercially? (c) any acceptable-use constraints that matter for a general-purpose editor? Record the verdict + reasoning in THIRD_PARTY_AI_MODELS.md (follow the PBRify/UniRig/SkinTokens entry format). MHR (the rig definition) is Apache-2.0 — record that separately; we need MHR's joint list + rest pose regardless.
- If the license passes: export to ONNX. Study SAM3DBody-cpp first — it already runs the model via ONNX Runtime, so its export recipe/graph layout is the shortcut. Write
scripts/export-bodycap-onnx.py. Measure: model size on disk, single-frame latency on M-series CPU (target: ≤200 ms/frame is fine for offline video; live body capture can run at reduced rate). Document the I/O contract: input image size/normalization, output layout (127 MHR joint rotations — which format: 6D rotation? quaternion? Euler? — plus root translation and shape betas we'll ignore).
- Extract the MHR skeleton definition (127 joint names, parent hierarchy, rest/bind pose world orientations) from the Apache-2.0 MHR repo into a JSON we can embed or host — Slice E's retarget needs each joint's rest world orientation for the same
W · clip · W⁻¹ conjugation applyMotionClip does with cmuRestWorld.
- If the license FAILS the bar: record the rejection in
THIRD_PARTY_AI_MODELS.md (like RigNet/SF3D/Hunyuan3D) and note that Slice E ships Pose-IK only.
Task 4 — Write the spike doc
docs/MOCAP_SPIKE.md (follow docs/IMAGE_TO_3D_SPIKE_764.md): conversion recipes + versions pinned, parity numbers, pre/post-processing contracts for all models, MHR skeleton notes, license verdicts, model sizes, measured latencies, and the go/no-go recommendation for the SAM backend.
Acceptance criteria
Part of epic #869. Do this slice first — it de-risks everything downstream. No app code changes; deliverables are offline scripts, docs, and go/no-go decisions.
Goal
Prove the three model conversions numerically and settle the licensing questions, so Slices C/E are wiring jobs instead of research.
Task 1 — MediaPipe Face Landmarker → ONNX, with parity proof
The
face_landmarker_v2_with_blendshapes.taskbundle (download from Google's MediaPipe model page, Apache-2.0) is a zip containing three TFLite models:Write
scripts/export-facecap-onnx.py(one-time offline dev tool, NOT shipped — follow the header comment style ofscripts/export-rmib-onnx.py):.task, extract the three.tflitefiles.tf2onnx(python -m tf2onnx.convert --tflite model.tflite --output model.onnx --opset 18). If tf2onnx chokes on custom ops, fall back totflite2onnxor onnx2tf round-trip — document which worked.mediapipeFaceLandmarker on 5–10 test images (include: neutral face, open mouth, wink, smile, head turned ~30°), then run the exported ONNX graphs withonnxruntimeimplementing the same pre/post-processing, and assert blendshape scores match within ~1e-2 and landmarks within ~1px. Print the exact pre/post-processing contract discovered (input sizes, normalization ranges, letterbox behavior, which 146 landmark indices feed the blendshape model, score/sigmoid handling, detector anchor decode) — this contract is the spec Slice C implements in C++, so write it into the doc, not just the console.Task 2 — MediaPipe Pose Landmarker → ONNX (the body fallback)
Same recipe for
pose_landmarker_full.task(detector + landmark model, 33 landmarks with world coordinates). Add to the same script (--poseflag). Parity: world-landmark positions within ~1cm on test images. Document the landmark index → body-part table (0=nose, 11/12=shoulders, 13/14=elbows, 15/16=wrists, 23/24=hips, 25/26=knees, 27/28=ankles, …) in the doc.Task 3 — SAM 3D Body: license due-diligence + export feasibility
fernandotonon/QtMeshEditor-models)? (b) may end users use outputs commercially? (c) any acceptable-use constraints that matter for a general-purpose editor? Record the verdict + reasoning inTHIRD_PARTY_AI_MODELS.md(follow the PBRify/UniRig/SkinTokens entry format). MHR (the rig definition) is Apache-2.0 — record that separately; we need MHR's joint list + rest pose regardless.scripts/export-bodycap-onnx.py. Measure: model size on disk, single-frame latency on M-series CPU (target: ≤200 ms/frame is fine for offline video; live body capture can run at reduced rate). Document the I/O contract: input image size/normalization, output layout (127 MHR joint rotations — which format: 6D rotation? quaternion? Euler? — plus root translation and shape betas we'll ignore).W · clip · W⁻¹conjugationapplyMotionClipdoes withcmuRestWorld.THIRD_PARTY_AI_MODELS.md(like RigNet/SF3D/Hunyuan3D) and note that Slice E ships Pose-IK only.Task 4 — Write the spike doc
docs/MOCAP_SPIKE.md(followdocs/IMAGE_TO_3D_SPIKE_764.md): conversion recipes + versions pinned, parity numbers, pre/post-processing contracts for all models, MHR skeleton notes, license verdicts, model sizes, measured latencies, and the go/no-go recommendation for the SAM backend.Acceptance criteria
scripts/export-facecap-onnx.pyproduces detector/landmarks/blendshapes ONNX with in-script parity vs Python mediapipe (tolerances above) on the test images.THIRD_PARTY_AI_MODELS.md; if PASS,scripts/export-bodycap-onnx.py+ measured size/latency; if FAIL, rejection recorded and epic scope adjusted.docs/MOCAP_SPIKE.mdwritten with the full I/O + preprocessing contracts (the implementable spec for Slices C/E).