Pose Estimation¶
Pose estimators recover human motion from monocular video as an AMASS-style SMPL-X clip that the retargeting
extensions consume. An estimator is a PoseEstimatorBase subclass configured by PoseEstimatorOptions; the backend
that ships is GVHMR, installed with the pose extra (pip install 'eden-world[pose]', or
uv sync --inexact --extra pose in a checkout) and imported lazily. The
pose estimation guide covers eden pose gvhmr.
from eden.extensions.pose_estimation.base import PoseEstimatorBase
from eden.options.extensions.pose_estimation import PoseEstimatorOptions
Base class and options¶
Base class for pose-estimation backends (monocular video → human motion).
Classes¶
PoseEstimatorBase
¶
PoseEstimatorBase(options: T)
Bases: ConfigurableMixin[PoseEstimatorOptions]
Abstract base class for recovering human motion from video.
Takes no env, unlike the retargeting and grasp-sampling bases: estimation is offline
preprocessing whose output is a motion clip, and the robot only enters at retargeting.
Backends expose the estimate at two levels — :meth:estimate keeps the backend's own
frames and layout (so camera-space work and provenance survive), :meth:estimate_clip
packs it into the AMASS-style Z-up SMPL clip the retargeting extensions consume.
No registry, as in retargeting: backends are chosen by CLI subcommand, never resolved
by name from a config.
Source code in eden/utils/common.py
Attributes¶
Methods:¶
estimate
abstractmethod
¶
Recover the motion in video, in the backend's own frames and layout.
estimate_clip
abstractmethod
¶
Recover video as an AMASS-style, Z-up SMPL clip ready for np.savez.
close
¶
Release whatever the backend holds (a loaded model, GPU memory); no-op if nothing.
Always safe to call, so callers can use try/finally regardless of backend.
Configuration options for pose-estimation extensions (video → human motion).
:class:PoseEstimatorOptions mirrors
:class:~eden.extensions.pose_estimation.base.PoseEstimatorBase's attributes, so the shared
fields are type-checked at configure() time. Backend-specific knobs stay as class
attributes on the backend and arrive as extras — e.g. GVHMR's static_cam /
use_dpvo / f_mm.
Classes¶
PoseEstimatorOptions
¶
Bases: DynamicConfigurableOptions
Options shared by every pose-estimation backend.
| PARAMETER | DESCRIPTION |
|---|---|
checkpoint_root
|
Directory of externally downloaded weights. Empty keeps the backend's own resolution
(GVHMR:
TYPE:
|
ckpt_path
|
Model checkpoint file. Empty derives it from
TYPE:
|
output_root
|
Where the backend writes — and reuses, which is what makes a re-run cheap — its per-video intermediates. Empty keeps the backend default, usually relative to cwd.
TYPE:
|
motion_fps
|
Frame rate stamped on the clip: the rate the output is expressed in, not necessarily the source video's. GVHMR relabels every input to 30 fps without resampling frames and integrates root motion at that rate, so 30 is right there even for a 60 fps source.
TYPE:
|
verbose
|
Save the backend's intermediate preprocessing overlays.
TYPE:
|
Source code in eden/options/options.py
Attributes¶
GVHMR¶
Eden wrapper around GVHMR (world-grounded human motion recovery from monocular video).
GVHMR (zju3dv/GVHMR <https://github.com/zju3dv/GVHMR>, SIGGRAPH Asia 2024) is consumed
through the packaged fork Kashu7100/eden_gvhmr <https://github.com/Kashu7100/eden_gvhmr>
(import name hmr4d). :meth:GvhmrEstimator.estimate_clip writes what
eden retarget soma --body-model smplx reads, so video → clip → robot qpos is two steps.
See docs/guides/pose-estimation.md for setup.
In-process. The fork installs beside Eden: it drops upstream's pytorch3d requirement for
inference, relaxes a stale numpy<1.24 cap, does not compose Hydra configs (so it does not fight
a host that owns Hydra's global state), and carries no VCS dependency, so it ships on PyPI.
pip install 'eden-world[pose]' resolves additively into Eden's environment and this class
simply imports hmr4d.
One consequence worth knowing: GVHMR runs on Eden's video decoder and person detector
rather than the versions its numerics were tuned against. Measured one change at a time on the
bundled tennis clip, av 13 → 17 moves body joints by 3.6° max 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 all bit-identical. The estimate stays valid — it is just
not bit-comparable with the published checkpoint's reference stack.
Classes¶
GvhmrEstimator
¶
GvhmrEstimator(options: PoseEstimatorOptions)
Bases: PoseEstimatorBase
Monocular video → world-grounded SMPL-X motion.
The shared options (checkpoint_root, ckpt_path, output_root, motion_fps,
verbose) are documented on
:class:~eden.options.extensions.pose_estimation.PoseEstimatorOptions; the fields below
are GVHMR's own and reach the options as extras.
The model is loaded once, on the first :meth:estimate call, and reused across videos.
GVHMR is CUDA-only; select a GPU with CUDA_VISIBLE_DEVICES.
No overlay rendering. GVHMR can draw in-camera / world overlay videos, but that path
needs two dependencies inference does not — pytorch3d (built against your torch/CUDA) and
chumpy from git — neither of which can be declared, so the pose extra does not install
them. Rather than offer a flag that fails after the whole pipeline has run, Eden does not
expose rendering at all: the product here is the .npz. To eyeball an estimate, install
those two and use GVHMR's own CLI (python -m hmr4d.demo). verbose still works — its
bbox / keypoint overlays are drawn with cv2 and need nothing extra.
| PARAMETER | DESCRIPTION |
|---|---|
static_cam
|
Skip visual odometry (assume the camera does not move). Much faster, and the right choice for tripod footage; wrong for handheld video, where camera motion would be absorbed into the body motion.
TYPE:
|
use_dpvo
|
Use DPVO instead of the default SimpleVO for camera motion. Requires GVHMR's
TYPE:
|
f_mm
|
Full-frame-equivalent focal length in mm.
TYPE:
|
Source code in eden/extensions/pose_estimation/gvhmr/estimator.py
Attributes¶
Methods:¶
close
¶
Drop the loaded model and free its GPU memory (idempotent).
Source code in eden/extensions/pose_estimation/gvhmr/estimator.py
estimate
¶
Recover the motion in video, verbatim in GVHMR's own frames.
**overrides take any of :data:_RECOVER_KEYS for this call only.
| RETURNS | DESCRIPTION |
|---|---|
dict
|
|
Source code in eden/extensions/pose_estimation/gvhmr/estimator.py
estimate_clip
¶
Recover video as an AMASS-style, Z-up SMPL-X clip.
Ready for :func:~eden.extensions.pose_estimation.gvhmr.convert.save_amass_npz;
:func:~eden.extensions.pose_estimation.gvhmr.convert.to_amass_smplx_clip lists the fields.
Source code in eden/extensions/pose_estimation/gvhmr/estimator.py
Functions:¶
Conversion to an AMASS-style clip¶
GVHMR's world-frame output is Y-up; these helpers rotate it to Eden's Z-up and write an AMASS-style SMPL-X .npz.
GVHMR's world-frame SMPL-X output → an AMASS-style, Z-up SMPL-X clip.
Two things change. Up axis: GVHMR predicts in a gravity-aligned frame whose +Y is up
(its "ay" frame — hmr4d.utils.geo.hmr_global's "az"->"ay" is -90° about X), while
Eden/Genesis/AMASS are Z-up. Pose layout: GVHMR returns global_orient (T, 3) +
body_pose (T, 63); AMASS packs one poses (T, 165), whose jaw/eyes/hands tail stays zero
because GVHMR does not estimate it.
Does not import GVHMR, so the conversion is testable without the backend installed.
Attributes¶
Functions:¶
y_up_to_z_up_quat
¶
The wxyz quaternion of :data:Y_UP_TO_Z_UP_AXIS_ANGLE (maps +Y to +Z).
y_up_to_z_up_root
¶
y_up_to_z_up_root(global_orient: ndarray, transl: ndarray, pelvis_j0: ndarray) -> tuple[ndarray, ndarray]
Rotate an SMPL-X root (global_orient, transl) pair from Y-up into Z-up.
body_pose is unaffected — SMPL joint rotations are parent-relative, so only the root
sees a change of world frame.
The translation is not simply R @ transl: SMPL-X rotates the body about the
rest-pose pelvis J0(betas) (BodyModelSMPLX.get_skeleton(betas)[..., 0, :]), which
puts the pelvis at J0 + transl in the world, so rotating about the origin needs::
transl' = R @ (transl + J0) - J0
global_orient / transl are (T, 3), pelvis_j0 (T, 3) or (3,);
returns the Z-up pair as float32.
Source code in eden/extensions/pose_estimation/gvhmr/convert.py
to_amass_smplx_clip
¶
to_amass_smplx_clip(result: dict, *, motion_fps: float, source_video: str | Path | None = None) -> dict[str, ndarray]
Pack one :meth:GvhmrEstimator.estimate result into AMASS-style SMPL-X npz fields.
Emits the AMASS fields Eden's retargeting path reads (poses, trans, betas,
gender, mocap_frame_rate, surface_model_type) plus gvhmr_* copies of the
raw Y-up estimate, so a saved clip stays traceable to what the model predicted.
betas is averaged over the clip — AMASS carries one shape vector per sequence, and
GVHMR's per-frame betas are already a clip-average repeated per frame.
Source code in eden/extensions/pose_estimation/gvhmr/convert.py
save_amass_npz
¶
Write a :func:to_amass_smplx_clip payload to path with an .npz suffix (parents created).