# GVHMR: recovering human motion in a world frame from one camera

> GVHMR turns monocular video into human meshes that stay anchored in a gravity-aligned world frame instead of drifting with the camera. Around that core sits a research codebase with pinned cu121 wheels, Hydra-driven benchmarks and a demo guide that contradicts its own news entries.

**zju3dv/GVHMR** — Code for "GVHMR: World-Grounded Human Motion Recovery via Gravity-View Coordinates", Siggraph Asia 2024, TPAMI 2026

- Repository: https://github.com/zju3dv/GVHMR
- Website: https://zju3dv.github.io/gvhmr
- Stars: 2,195 · Forks: 257
- Language: Jupyter Notebook
- License: NOASSERTION
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/zju3dv-gvhmr

## Recovering a human mesh in world coordinates from one camera

Monocular human recovery usually hands back a mesh that is correct only relative to the frame it was detected in: move the camera and the body walks with it. GVHMR targets the other problem. Recovery happens in gravity-view coordinates, which the title states outright, and the paper behind it ran at SIGGRAPH Asia 2024 with a TPAMI version listed since.

The payoff is a trajectory in a fixed world frame. A person crossing a room stays on the ground plane instead of sliding toward the viewer, and what comes out is a time-stamped pose sequence that animation, physics or scene assembly can consume without re-solving camera motion afterwards.

Layout follows that pipeline rather than a reusable library shape. Top level entries include docs/, hmr4d/, tools/, third-party/, requirements.txt, setup.py, pyproject.toml and pyrightconfig.json, with model code under hmr4d/ and entry points under tools/. A pyrightconfig.json at the root tells you that static type checking is part of the intended workflow, something rarely configured in research code that ships only shell entry points.

The project's declared primary language is Jupyter Notebook, even though what the root ships is a Python package plus shell entry points. For a user that mismatch is cosmetic: you clone the repository and run scripts, not notebooks.

## The demo entry points and what the -s flag actually skips

Two hosted demos exist if you want to look before cloning: a Google Colab notebook and a HuggingFace Space. Locally the entry points live under tools/demo, and two scripts cover the two input shapes, a single file or a whole folder.

For one clip, with the camera known to be static:

```bash
python tools/demo/demo.py --video=docs/example_video/tennis.mp4 -s
```

For a directory, -f names the input folder and -d the output folder:

```bash
python tools/demo/demo_folder.py -f inputs/demo/folder_in -d outputs/demo/folder_out -s
```

That `-s` flag deserves thought. Visual odometry is skipped when the camera is static, and run otherwise, so on a tripod capture the flag removes an entire estimation stage along with the error it introduces. Handheld phone footage is the opposite case: dropping -s is what separates a trajectory anchored in the world from one that follows the operator's hands.

## Two news entries dated 2025-03-08 quietly redefine the defaults

Two things in the README deserve a second look before you build a pipeline around its flags.

First, both news entries carry the same date, 2025-03-08, and the first states that DPVO is no longer used by default, replaced by a SimpleVO implementation the authors describe as more efficient and compatible with GVHMR. The demo section further down the same file still says the camera will be estimated by DPVO when -s is omitted. Both statements sit in the current text, so the documented default path contradicts the entry announcing its replacement, and nothing in the file anchors those news entries to a version.

Second, both entries are dated 2025-03-08 while the last push to the repository was on 2026-09-15. A year and a half of commits has landed since that news line without a new entry, so treat that section as a snapshot rather than a current index. If your inputs are moving-camera footage, confirm which estimator actually runs before drawing conclusions from a test run.

The other entry on that same date is more actionable. Option f_mm sets the focal length of a full-frame camera in millimetres, a physical property of the capture rig that a reconstruction can otherwise only guess from image evidence. Supplying it from your own lens specification removes one assumption that would otherwise be folded into recovered body scale.

## Benchmarks live in Hydra config keys, not in a results table

Benchmark numbers are not printed in the repository. The Reproduce section hands you commands instead, with datasets addressed through Hydra overrides on global/task and the experiment configuration passed separately as exp.

To evaluate 3DPW, RICH and EMDB in one run against the provided checkpoint:

```bash
python tools/train.py global/task=gvhmr/test_3dpw_emdb_rich exp=gvhmr/mixed/mixed ckpt_path=inputs/checkpoints/gvhmr/gvhmr_siga24_release.ckpt
```

Single datasets use the same line with a narrower task key, one of gvhmr/test_3dpw, gvhmr/test_rich or gvhmr/test_emdb. Note that ckpt_path is expected to point at a file you have already placed under inputs/checkpoints/, so nothing in that command fetches weights for you.

Routing evaluation through tools/train.py has a side effect worth knowing. You inherit the training entry point's configuration system, its device handling and its Hydra tree, which is convenient if you already speak that dialect and mildly awkward if you do not. Logging rides on lightning and tensorboardX from the pin list, so runs write through the standard Lightning path rather than a bespoke one.

## Training skips the post-processing that testing applies

Training strips one override:

```bash
python tools/train.py exp=gvhmr/mixed/mixed
```

A README note states that no post-processing of the kind applied in the test script runs during training, so global metrics from a training run differ from the evaluation path. The same note calls the training-side numbers still useful for comparison against baseline methods, which is a weaker promise than reproduction and worth keeping the distinction in mind.

Hardware gets named in a comment above the training command: the checkpoint was trained with 2x4090 for 420 epochs, and that comment warns that different GPU settings lead to different results. The warning is the most useful sentence in the whole Reproduce section. It means numbers computed on your own cards are not expected to match the published ones, and that batch size and precision choices are not incidental details.

So if you are reproducing a figure, go through the evaluation path with post-processing intact. If you are training from scratch, read the resulting metrics as a training signal.

## Pinned wheels tie the install to cu121 and one architecture

requirements.txt is the only installation artefact you can inspect at the root, since the README defers the actual procedure to docs/INSTALL.md and does not reproduce it. A representative slice, copied as written:

```bash
--extra-index-url https://download.pytorch.org/whl/cu121
torch==2.3.0+cu121
torchvision==0.18.0+cu121
timm==0.9.12
lightning==2.3.0
hydra-core==1.3
numpy==1.23.5
```

The extra index URL and the +cu121 local version specifiers pick the cu121 wheel channel. An inline comment on the timm pin says it exists for HMR2.0a feature extraction, a direct pointer at a backbone the model draws on rather than learns from scratch.

The most restrictive line is a bare URL rather than a version:

```bash
pytorch3d @ https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu121_pyt230/pytorch3d-0.7.6-cp310-cp310-linux_x86_64.whl
```

Its filename encodes cp310, the cu121 build, the PyTorch 2.3.0 ABI and linux_x86_64 in a single string. Read that as a constraint rather than a preference: a different interpreter version, GPU architecture or PyTorch build has no matching file at that address, so you either build the wheel locally or edit the pin. The neighbouring entries show how much of the 3D stack is assumed present, with trimesh, smplx, chumpy, pycolmap and wis3d listed as plain packages and ultralytics==8.2.42 pinned for 2D pose. A diffusion group, diffusers[torch]==0.19.3 and transformers==4.31.0, is present but commented out, as is open3d==0.17.0, so image handling runs through av==13.0.0 and imageio==2.34.1 with an inline comment crediting imageio[pyav] with improved performance over imageio[ffmpeg].

## A package with a version number and no build-system table

setup.py is short and unremarkable. It registers the name gvhmr at version 1.0.0, collects packages through find_packages(), and points its url field back at the GitHub repository. No dependencies are declared there, consistent with requirements.txt being the only place constraints live.

pyproject.toml is shorter still, and shorter than most projects manage. Its entire content is Black configuration, a line length of 120 and an include pattern for Python sources, plus the customary exclude list for build and cache directories. There is no build-system table and no project metadata.

The practical consequence is small but worth naming. With no build-system table, the build backend is whatever an installer's default provides rather than something the project pins, and nothing reads that plain requirements.txt automatically, so the constraints that define a working environment have to be copied by hand.

One item has to be read directly: GitHub could not classify the license into a standard category, so the LICENSE file at the root holds the actual terms.

## WHAM and 4D-Humans are the base, not the comparison

The README credits WHAM, 4D-Humans and ViTPose-Pytorch explicitly, saying the project would not be possible without them, and the timm pin for HMR2.0a feature extraction points into the same family of work. At the root, a .gitmodules file and a third-party/ directory indicate that part of this stack arrives as submodules rather than being vendored inside hmr4d/.

That has two operational consequences. Setting up the environment means resolving submodules in addition to the pin list, a step covered only by the pointer to docs/INSTALL.md. And when output looks wrong, the defect can sit in code you did not write yourself.

For anyone weighing options, the honest framing is that GVHMR extends that lineage rather than replacing it. The timm pin, the ultralytics pin for 2D pose and the WHAM and 4D-Humans credits all point at reuse. World-grounded gravity-view output is the addition, and that is the part you would actually be adopting.

## Conclusion

Treat GVHMR as a paper repository rather than a library, because its own documentation is mid-migration: the demo section still names an estimator the news entries say was replaced. It earns its place when world-grounded trajectories replace camera-relative meshes for animation or scene work, and you are willing to pin the cu121 environment, resolve submodules, and place a checkpoint under inputs/checkpoints/ yourself. For everything else, open the Colab or HuggingFace demo first and judge the output there before touching requirements.txt.

## FAQ

### Do GVHMR demo scripts require a static camera?

No. The -s flag skips visual odometry for cameras you already know are static, and without it the camera is estimated. Note that the demo section names DPVO for that estimate while the 2025-03-08 news entry says DPVO is no longer the default and that SimpleVO replaced it.

### How do I run the 3DPW, RICH and EMDB benchmarks in GVHMR?

Pass the combined task key gvhmr/test_3dpw_emdb_rich together with the experiment gvhmr/mixed/mixed and a ckpt_path pointing at a checkpoint under inputs/checkpoints/. For one dataset at a time, use gvhmr/test_3dpw, gvhmr/test_rich or gvhmr/test_emdb instead of the combined key.

### What does the f_mm option do in GVHMR?

It specifies the focal length of a full-frame camera in millimetres, and it is listed as a new option in the 2025-03-08 news entry. Giving the real value from your lens removes one quantity the pipeline would otherwise estimate from image evidence.

### Can I install GVHMR on macOS or with a newer PyTorch?

The pinned pytorch3d wheel is built for cp310, cu121, the PyTorch 2.3.0 ABI and linux_x86_64, while torch itself is pinned at 2.3.0+cu121 through an extra index URL for cu121. A different platform, interpreter version or PyTorch build means building that wheel locally or editing requirements.txt, and the README sends you to docs/INSTALL.md for the procedure it does not print.

### Does GVHMR ship pretrained weights or a versioned download?

The evaluation command expects a checkpoint already placed under inputs/checkpoints/, and the repository has no GitHub release to fetch one from. setup.py declares version 1.0.0, but that string is package metadata rather than a downloadable artefact.

## Sources

- [Issues](https://github.com/zju3dv/GVHMR/issues)
- [Project website](https://zju3dv.github.io/gvhmr)
- [README](https://github.com/zju3dv/GVHMR/blob/main/README.md)
- [zju3dv/GVHMR on GitHub](https://github.com/zju3dv/GVHMR)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zju3dv-gvhmr
