Pose Estimation from Video¶
eden pose recovers human motion from ordinary monocular video, so a phone clip can become a robot trajectory without
a motion-capture studio. It writes an AMASS-style SMPL-X clip, which is exactly what
eden retarget soma reads. This page covers installing the
estimator, running it, the frame and timing conventions of its output, and the Python API.
eden pose gvhmr --src clip.mp4 --out motions/clip.npz --static-cam
eden retarget soma --src motions/clip.npz --out qpos/ --body-model smplx \
--body-model-path /data/body_models/smplx/SMPLX_NEUTRAL.npz --snap-to-ground
The backend is GVHMR (world-grounded human motion recovery, SIGGRAPH Asia 2024),
installed from the eden-gvhmr package (import name hmr4d). It is the only eden pose subcommand.
Install¶
GVHMR installs into the same environment as Eden, through the pose extra:
Then download the model weights and provide the body models:
gvhmr-download-checkpoints # GVHMR, HMR2, ViTPose and YOLO weights, from eden-gvhmr
export GVHMR_CHECKPOINT_ROOT=/data/gvhmr/checkpoints # or pass --checkpoint-root on every run
The checkpoint root holds gvhmr/, hmr2/, vitpose/, yolo/ and body_models/. The SMPL and SMPL-X body models
in body_models/ are licence-gated and are not downloaded for you: register at
smpl.is.tue.mpg.de and smpl-x.is.tue.mpg.de and
place them there as GVHMR's own install instructions describe. Without --checkpoint-root or the environment
variable, GVHMR looks in ./inputs/checkpoints.
GVHMR runs on CUDA only; pick a GPU with CUDA_VISIBLE_DEVICES. hmr4d is imported lazily, so import eden never
needs it, and a missing install reports the extra to install instead of a bare ModuleNotFoundError.
Running eden pose gvhmr¶
| flag | default | effect |
|---|---|---|
--src |
required | one video, or a directory searched recursively |
--out |
required | output directory (the source tree is mirrored), or a .npz file for a single video |
--pattern |
*.mp4 |
glob for a directory --src |
--checkpoint-root |
GVHMR's own resolution | weights directory (see Install) |
--ckpt |
<root>/gvhmr/gvhmr_siga24_release.ckpt |
GVHMR checkpoint file |
--static-cam |
off | assume the camera does not move, skipping visual odometry |
--use-dpvo |
off | use DPVO instead of SimpleVO for camera motion (needs GVHMR's DPVO install and checkpoint) |
--f-mm |
estimated | full-frame-equivalent focal length in mm, when you know the lens |
--output-root |
GVHMR's outputs/demo |
where GVHMR writes, and reuses, its per-video intermediates |
--skip-existing |
off | skip a video whose output exists |
--verbose |
off | also save GVHMR's bounding-box and keypoint preprocessing overlays |
One model load serves the whole batch. GVHMR keeps each video's intermediates (tracking, ViTPose features, the raw
hmr4d_results.pt) under --output-root and reuses them, so a re-run of the same video is nearly free.
--static-cam only for a fixed camera
For tripod footage --static-cam is correct and much faster. For handheld video it is wrong: the camera's own
motion would be absorbed into the body motion.
The output¶
Each video becomes one .npz:
| key | shape | content |
|---|---|---|
poses |
(T, 165) |
SMPL-X axis-angle pose, AMASS layout: root (3), body (63), then jaw, eyes and hands, which are zero because GVHMR does not estimate them |
trans |
(T, 3) |
root translation, Z-up |
betas |
(10,) |
body shape, averaged over the clip |
gender |
"neutral" (GVHMR predicts with the neutral SMPL-X model) |
|
mocap_frame_rate |
always 30 |
|
surface_model_type |
"smplx" |
|
gvhmr_* |
the raw Y-up estimate (gvhmr_global_orient, gvhmr_transl, gvhmr_betas, gvhmr_pelvis_j0, gvhmr_K_fullimg, gvhmr_video_fps), for provenance |
|
source_video |
the input path |
Three properties of the output matter downstream.
Up axis. GVHMR predicts in a gravity-aligned frame whose +Y is up; Eden, Genesis and AMASS are Z-up. The clip is
rotated into Z-up before it is written. Only the root changes (SMPL joint rotations are parent-relative), and its
translation is corrected for SMPL-X's rest-pose pelvis offset J0, because the body rotates about J0 rather than
the world origin:
Rotating trans alone, as GVHMR's own get_tgtcoord_rootparam does, offsets the whole clip. J0 comes from GVHMR's
own SMPL-X model.
Frame rate. GVHMR re-encodes every input to 30 fps without resampling frames and integrates root motion at that
rate, so the clip is always stamped 30 fps. There is deliberately no --fps flag: any other stamp would relabel a
clip whose motion was already integrated at 30 fps. A 60 fps video therefore gives a time-warped trajectory; the
command warns before running the pipeline, and the fix is to re-encode the video to 30 fps first.
Height. GVHMR's world translation is only defined up to a vertical offset, so the clip may float or sink by a few
centimetres. Retarget with eden retarget soma --snap-to-ground, which applies one constant vertical shift so the
robot's lowest collision point touches z = 0.
No overlay videos¶
Eden does not expose GVHMR's in-camera and world overlay rendering. That path needs pytorch3d (built against your
torch and CUDA) and chumpy from git, neither of which can be declared as a dependency, so the pose extra does not
install them; a flag that failed after the whole pipeline had run would be worse than none. To look at an estimate,
install those two and use GVHMR's own CLI, python -m hmr4d.demo --video clip.mp4 -s. --verbose works from Eden:
its overlays are drawn with OpenCV.
Reproducibility¶
In Eden's environment GVHMR runs on Eden's video decoder (av) and person detector (ultralytics), not the versions
its checkpoint was tuned with. The estimator's module docstring records the effect, measured one change at a time on
GVHMR's bundled tennis clip: av 13 → 17 moves body joints by up to 3.6°, and ultralytics 8.2.42 → 8.4.113 moves
the detected boxes by 8.5 px and body joints by 3.4°; numpy (including the 1.x → 2.x jump), timm and imageio are
bit-identical. The estimate stays valid, but it is not bit-comparable with the published reference stack. To
reproduce published numbers, install GVHMR in its own environment with its pinned versions and run it there.
Python API¶
No Eden scene or entity is involved: estimation is offline preprocessing, and the robot only enters at retargeting.
from eden.extensions.pose_estimation.gvhmr import GvhmrEstimator, save_amass_npz
estimator = GvhmrEstimator(
options=GvhmrEstimator.configure(
# checkpoint_root is optional: $GVHMR_CHECKPOINT_ROOT is used when unset.
static_cam=True,
)
)
try:
clip = estimator.estimate_clip("tennis.mp4") # AMASS-style, Z-up
save_amass_npz(clip, "motions/tennis.npz")
raw = estimator.estimate("tennis.mp4") # GVHMR's own output: Y-up world and camera frame
finally:
estimator.close()
- The shared options (
PoseEstimatorOptions):checkpoint_root,ckpt_path,output_root,motion_fps(must stay 30; any other value raises),verbose. GVHMR's own:static_cam,use_dpvo,f_mm. - The model loads on the first
estimatecall and is reused;close()frees it and its GPU memory. estimate(video, **overrides)returnsglobalandincamSMPL-X parameters,pelvis_j0,K_fullimg,output_dirandvideo_fps. It accepts per-call overrides ofstatic_cam,use_dpvo,f_mm,output_rootandverbose; anything else raises.estimate_clipconverts that result withto_amass_smplx_clip(ineden.extensions.pose_estimation.gvhmr).
A runnable script: examples/recipes/pose_estimation/gvhmr/video_to_motion.py.
Licensing¶
GVHMR is released for non-commercial research use (see the licence in its repository), and the SMPL and SMPL-X body models carry their own licences. Eden ships no GVHMR code or weights, only this wrapper; check those terms before using recovered motion in a product.
See also¶
- Retargeting:
eden retarget somaturns the clip into robot qpos. - Motion datasets: loading motion for training.
- API: pose estimation.