Turn any video or live webcam feed into full-body animation β body, fingers, head, eye gaze, and facial expression β and export it as BVH (Blender / Unity) plus a per-frame facial-morph sidecar.
No game engine. No plugins. No GPU required.
Left: source footage Β· Right: Mocapy's live pose + hand + face-mesh solve
π₯ video / webcam
β
βΌ
π§ MediaPipe pose (33) + hands (21Γ2) + face mesh (478) + 52 blendshapes
β
βΌ
𦴠solver bone quaternions + facial morphs
β
βΌ
π¦ export BVH Β· .morphs.json β any humanoid VRM rig
Works on any VRM (0.x or 1.x) with standard humanoid bone names. Or skip the rig
entirely with - and use the bundled default skeleton.
| 𦴠Full-body solve | Torso, arms, legs, head/neck, 24 finger bones + thumbs per hand |
| π Face & gaze | Iris-driven eye gaze + 52 ARKit blendshapes β facial morph weights + 12 VRM presets |
| π― Two detection backends | cv2 + MediaPipe Tasks, or a WASM tasks-vision bridge in headless Chromium for higher-fidelity legs |
| π» Holistic (CPU-only) mode | One combined pose+face+hands model that runs with no GPU β works on low-end machines |
| π¦ Live skeleton preview | Realtime overlay window (pose + hands + face mesh + bounding boxes) |
| π‘οΈ Robustness layers | Leg-collision gating, finger depth-recovery + smoothing, plant-adaptive foot grounding, torso de-wobble |
| π€ Exports | BVH + a per-frame .morphs.json facial sidecar |
| π Detect once, retarget many | A shared frames.json lets you re-solve one capture onto many rigs without re-detecting |
# 1. install (editable)
pip install -e .
# 2. download the MediaPipe models into models/ (see Installation below)
# 3a. detect: video β frames.json (add --preview for the live overlay window)
mocapy-detect fixtures/test.mp4 frames.json
# 3b. solve: frames.json β BVH ( '-' = bundled default skeleton, no VRM needed )
mocapy-solve frames.json assets/AliciaSolid.vrm out.bvh
mocapy-solve frames.json - out.bvhDrop out.bvh into Blender (File β Import β Motion Capture (.bvh)) and hit play.
| Requirement | Why |
|---|---|
| Python 3.10 β 3.12 (3.11 recommended) | mediapipe 0.10.x ships wheels for these versions |
| Miniconda / Anaconda (recommended) | Isolates mediapipe's native deps from system Python |
| Webcam (optional) | Only for live capture |
| ~150 MB free disk | Source + models + sample fixtures |
πͺππ§ Windows Β· macOS Β· Linux all work identically. Shell snippets use Unix-style paths; on Windows PowerShell, swap
/β\.
conda env create -f environment.yml
conda activate mocapyPrefer plain venv / pip instead of conda?
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "numpy>=1.26,<2.0" "opencv-python>=4.8" "mediapipe>=0.10.21"
β οΈ numpy must be<2.0. numpy 2.x crashes inside the VRM skeleton loader on some builds. Bothenvironment.ymlandpyproject.tomlpin this for you.
pip install -e .This registers three CLI entrypoints: mocapy-detect, mocapy-solve, and
mocapy-webcam.
The .task model files (~50 MB total) are not committed. Download them from
MediaPipe's official model repository into a models/ folder at the repo root:
models/
βββ pose_landmarker_full.task
βββ pose_landmarker_heavy.task # optional β only for --pose-model heavy / --strong
βββ hand_landmarker.task
βββ face_landmarker.task
βββ holistic_landmarker.task # optional β only for --holistic
The higher-fidelity --backend bridge path runs the browser-grade tasks-vision
models in headless Chromium. It needs Node.js + ffprobe (from FFmpeg) on
your PATH, plus a one-time install:
cd node_bridge
npm install # pulls puppeteer's headless Chromium + @mediapipe/tasks-visionThe default
cv2backend needs none of this β bridge is purely opt-in.
Mocapy runs in two stages around a shared frames.json. This split lets you
detect once and re-solve onto many avatars.
mocapy-detect <video> [frames.json] [options]| Flag | Description |
|---|---|
--backend {cv2,bridge} |
Detector. cv2 (default) or bridge (WASM in Chromium β best legs) |
--pose-model {full,heavy} |
heavy is steadier on legs, ~3Γ slower |
--hand-conf FLOAT |
Hand-detection confidence (bridge). Lower (e.g. 0.3) catches faster hands |
--strong |
Preset = --pose-model heavy --hand-conf 0.3 (best limbs + fingers) |
--holistic |
One combined CPU-only model (bridge) β for machines with no GPU |
--preview |
Open a live overlay window (pose + hands + face mesh + boxes) |
--max-frames N |
Stop after N frames |
--no-leg-stabilize |
Disable leg-collision gating |
--no-hand-stabilize |
Disable finger depth-recovery + smoothing |
# fast, no extra deps
mocapy-detect fixtures/test.mp4 frames.json
# highest-fidelity legs, with a live preview window
mocapy-detect fixtures/test.mp4 frames.json --backend bridge --strong --preview
# CPU-only, low-end machine
mocapy-detect fixtures/test.mp4 frames.json --backend bridge --holisticWrites the same frames.json, so Stage B is unchanged.
mocapy-webcam --device 0 --duration 30 --output frames.json| Flag | Default | Description |
|---|---|---|
--device INT |
0 |
Webcam index (try 1/2 for multiple cams) |
--duration SEC |
10 |
Max recording length |
--output PATH |
frames.json |
Where to write the capture |
--width / --height |
1280 / 720 |
Capture resolution |
--target-fps FPS |
30 |
Throttle rate (60 fps cams drop every other frame) |
--mirror / --no-mirror |
on | Mirror-image capture (selfie convention) |
--no-preview |
β | Headless (no window) β for scripts/servers |
--pose-model {full,heavy} |
full |
heavy is more accurate, ~3Γ slower |
Stop a webcam capture with
qorESC(not Ctrl-C) soframes.jsonis flushed.
mocapy-solve <frames.json> <vrm|-> <out.bvh> [options]Pass a VRM path to retarget onto that rig, or - to use the bundled default
skeleton (no VRM file needed). Writes the BVH plus a <out>.bvh.morphs.json sidecar.
| Flag | Description |
|---|---|
--fov FLOAT |
Camera field of view for depth back-projection (tune root Z motion) |
--mirror |
Left/right-mirror the solved output (retarget to opposite handedness) |
--ground |
Enable foot-grounding (drives hips-Y from the planted foot) |
--no-foot-stabilize |
Disable plant-adaptive foot jitter hold |
mocapy-solve frames.json assets/AliciaSolid.vrm out.bvh # onto a rig
mocapy-solve frames.json - out.bvh # bundled skeleton
mocapy-solve frames.json assets/cj.vrm out.bvh --mirror --ground # mirrored + grounded| File | Plays in | Contains |
|---|---|---|
.bvh |
Blender, Unity, Maya, MotionBuilder | Full bone hierarchy + per-frame rotations + root translation |
.bvh.morphs.json |
(sidecar) | Per-frame facial morph weights to pair with the BVH |
The first BVH frame is a clean rest pose (identity rotations), so the rig holds its A-pose until you press play β convenient for Mixamo-style retargeting.
mocapy/ the library
βββ pipeline.py orchestration: solve_frame β solve_sequence β BVH
βββ detect/ MediaPipe Tasks + OneEuro filters + WASM bridge backend
βββ solve/ torso Β· arms Β· legs Β· head Β· wrist Β· fingers Β· eyes Β· morphs Β· blend
βββ vrm/skeleton.py VRM / glTF rest-pose parser
βββ export/ bvh.py Β· morphs.py
node_bridge/ tasks-vision (WASM) detection bridge + realtime preview
tools/ webcam capture Β· Blender add-on Β· demo-GIF generator
models/ MediaPipe .task files (download separately, ~50 MB)
assets/ example VRM avatars
fixtures/ sample video + reference captures
media/ README demo GIF
Set MOCAPY_ROOT to relocate fixtures/ / assets/ / models/.
| Symptom | Fix |
|---|---|
No module named mediapipe |
Activate the env: conda activate mocapy |
No module named 'mocapy' |
Run pip install -e . from the repo root |
Crash inside load_skeleton (float scalar) |
numpy is 2.x β pip install "numpy>=1.26,<2.0" |
| Webcam returns no frames | Wrong --device β try 1 or 2 |
| Body "marches in place" | Capture was un-mirrored β re-record (webcam mirror is on by default) |
| Hands don't curl | Hands were out of frame / too far / too dark β get closer, brighten the room |
UnicodeEncodeError printing ζζ© |
Windows: set PYTHONIOENCODING=utf-8 |
--backend bridge fails to start |
Need Node.js + ffprobe on PATH and npm install in node_bridge/ |
See LICENSE. Built on Google's MediaPipe
for landmark detection.
Made with 𦴠and a lot of quaternions.
