anymove-capture
Turn a two-camera AI clip into a game-ready animation with anymove - read every joint with the creature's trained reader, check that the two views agree, fit the rig in both views at once, and export
SKILL.mdskills/anymove-capture/SKILL.md
GitHubanymove-capture: clip in, animation out
tools/capture.sh <c> runs/<c>/videos/roar/s1/h3max.mp4 roar
Writes runs/<c>/clips/roar/: det.npz (every joint in both views, every frame), overlay.mp4 (the joints drawn on
the clip), motion.json (the fit: root path and rotation, Euler angles per bone, joints in metres),
render/animation.glb (the rig with the animation, for any engine), render/persp and render/side frames, and
showcase.mp4 (the AI clip with joints, the rig in 3/4, and clip vs rig through the same side camera).
What happens
- Read. The clip is aligned to the reference frame (video models resize or crop the canvas: an affine fit by ECC; "ECC did not converge" means a plain stretch was used, so check the overlay), split into its two views, and the reader finds every joint in each (about 10 s for 5 s of video).
- Check the views agree. A joint has the same height in both halves. Per foot (creature.json
"feet"), the correlation of its height between the views over the clip is the paw agreement. If the halves run out of step in time, the front view is time-warped onto the side view (only when that cuts the height gap by 15% or more). - Fit. Blender's forward kinematics in torch, Euler angles bounded by the joint limits, fitted to both views at once. Heights come mostly from the side view (the front view foreshortens leg lifts), left-right from the front view. It starts from an analytic IK pose aimed at the triangulated joints and anneals a robust loss from 40 px to 6 px, so big poses (sit, rear, slam) are found. Under a minute for 5 s (about 90 s for the dragon's roar on a CPU).
- Export. Blender keys the rig, writes the GLB and renders the views.
Is it good? Read the solve output
The dragon's roar, for example:
paw height agreement between views: {'fpaw_L': 1.0, 'fpaw_R': 1.0, 'hpaw_L': 0.78, 'hpaw_R': None, 'score': 0.93}
sync as generated: mean height gap 4.4 px, worst 8.6 px, best constant lag 0 frames
reprojection error px: {'front': 5.92, 'front_x_only': 2.55, 'side': 7.99}
- Paw agreement score: 0.8 or more, the views tell one story: keep it. 0.5-0.8: look at
showcase.mp4, often one limb is off. Under 0.5: the video model animated the views differently; reroll the clip (anymove-film).Nonefor a foot means it barely moved (planted feet in a roar); judge that clip by the reprojection error instead. - Reprojection error (layout pixels, how far the fitted rig's joints land from the readings): front_x_only of 1-3 px and side of 2-8 px were typical on good clips. Scale by the creature's size in the frame.
- Results so far: dragon take-off 1.00, roar 0.93, swipe 0.85, walk 0.81; spider rear-up 0.99, bite 0.96, pounce 0.89, walk 0.36-0.49 (fails); demon wing beat 0.99, slam 0.84, roar 0.73 with feet planted and 3 px reprojection; wolf trot 0.94 with H3 Max.
When a fit is wrong
- Legs crossed or a limb flipped side to side in the side view: the painted colours make labels reliable, so swap
fixing is off.
python -m mocap.solve ... --fix-swapsswaps near/far legs where heights say the reader mixed them up; it "fixed" correct labels on one clip, so only use it after looking. Its pairs come from creature.json'spaintgroups. If the video dropped the paint and the front view swaps feet, set"fix_front_swaps": truein creature.json. - The fit stays standing while the clip sits or rears: check
overlay.mp4; if the readings are right, the limits in rig.json are too tight for the pose. - Feet slide or sink: contact joints come from creature.json
contacts, or default to every painted-limb joint near the ground at rest. Check the solve output'splanted contactsline. 1 to 2 cm a step is as good as it gets for now. - A low-confidence wrong reading the fit rightly ignored still counts in
reproj_px.reproj_confident_pxleaves those out, so judge by it. Limbs behind the body read badly from the side, sotools/score_takes.pyalso reportsside_near, the near side alone. - One joint chain won't fit, such as a tail:
--joint-weightand--joint-sigmaweight it by name prefix, though that didn't rescue the T-rex's tail swipe. - Run
--no-alignto skip the time warp when the views were already in step and it hurt.
Debug views: python tools/fit_check.py VIDEO runs/<c>/clips/<name> out.jpg 10 40 80 (readings as rings, fitted
joints as dots, per frame) and python tools/plot_heights.py runs/<c>/clips/<name>/det.npz out.png (joint heights
per view over time: lags and swaps show up as curves that don't overlap). Both need MOCAP_CREATURE set.
Showcase renders (3/4 view on a shadow-catcher floor)
MOCAP_CREATURE=assets/<c> blender -b -P blender/apply_motion.py -- assets/<c>/rigged/rigged.blend \
assets/<c>/rigged/rig_info.json runs/<c>/clips/roar/motion.json runs/<c>/clips/roar/tile \
--views persp --samples 16 --res 1152x648 --cam -5.4,-6.0,1.4 --target 0,-0.3,1.5 --lens 32
tools/capture.sh uses creature.json "camera" for its 3/4 renders, and without one frames the take itself
(tools/frame_camera.py). So the takes share one camera, save one that frames them all:
python tools/frame_camera.py --write assets/<c>/creature.json runs/<c>/clips/*/motion.json. Options take a space, never
=. Frame the creature to its whole clip, not its rest pose: read the joint heights from the render's joints.json (the
demon flew to 5.3 m), then check every frame's alpha bounding box. Cameras that
worked: dragon --cam -5.4,-6.0,1.4 --target 0,-0.3,1.5, spider -3.3,-3.65,1.3 / 0,-0.1,0.78, demon
-6.4,-7.1,1.8 / 0,-0.1,2.1 (flying: -7.4,-8.3,2.1 / 0,-0.1,2.6). The shadow catcher leaves a faint denoiser haze
in the alpha; when compositing, drop alpha under 10/255. --springs assets/wolf/fur.json adds damped spring fur on
the wolf (bones from blender/add_fur_bones.py).