Project Info
Inspiration
We wanted to teach a Unitree G1 humanoid to throw a boxing jab without writing a single keyframe by hand. Motion capture suits are expensive and most reference motions for humanoids come from clean lab data, which does not look like how a real person actually moves. Our bet was simple: a phone camera and one sweaty person in leopard tights should be enough to drive a 29 degree of freedom robot. If we could go from a normal video to a trained policy, anyone could turn human movement into robot behavior.
What it does
The project is a full pipeline that takes ordinary monocular phone videos of a person jabbing and produces robot reference motions plus a trained control policy for the Unitree G1. The flow is: Record short clips of a jab on a phone. Run markerless motion capture to recover the 3D human body per frame. Retarget that human motion onto the G1 skeleton (29 joints). Export each clip as a reference CSV (root position, root rotation, joint angles). Validate every CSV, then render the robot and an overlay so we can eyeball quality. Train a multilayer perceptron (MLP) tracking policy on the reference motions and export it as an ONNX policy. The output is a batch of clean, validated jab references and a policy that makes the robot reproduce the motion.
How we built it
The core chain is GVHMR for markerless capture, GMR for retargeting, and an Isaac style tracking trainer for the policy. GVHMR turns each video into world grounded SMPL-X parameters. It runs YOLO for person detection, ViTPose for 2D keypoints, HMR2 for body shape, and the GVHMR network for world grounded recovery. GMR takes the SMPL-X motion and solves inverse kinematics to fit the G1, giving us joint angles per frame. A small exporter converts the retargeted pickle into the headerless CSV format the trainer expects, and a validator checks units, NaNs, foot contact, and frame count. We wrote a headless MuJoCo renderer that draws the robot only, with a camera that re-centers every frame, so the output video shows the motion clearly instead of the robot drifting off screen. Everything is wrapped in an idempotent batch script that processes around 122 clips, skips work that is already done, and stages every artifact (CSV, pickle, overlay video, robot video, side by side) into one output folder. Training ran on rented GPUs, producing checkpoints, a policy ONNX, and verification videos. All of this ran on a Windows laptop through WSL2 with a 8 GB RTX 4060, which forced us to be careful about memory the whole way through.
Challenges we ran into
8 GB of VRAM broke the obvious plan.** The naive approach was to load GVHMR, YOLO, ViTPose, and HMR2 and run a clip straight through. Four models plus SMPL-X parameters does not fit on a 4060. We rewrote the flow to load one model at a time, run it to completion, dump its output to disk, free the GPU, and only then load the next stage. That turned a single function call into a staged pipeline with intermediate files between every step. It doubled the disk traffic, but it was the only way the clips ran at all. Numbers passed validation and the motion was still wrong. Our validator checks units, NaNs, foot contact, and frame count, and a clip can clear all four while the robot does something that looks nothing like a jab. A retarget that puts the wrist in the right place with the elbow folded backward is valid by every metric we wrote and useless on the robot. We only caught these by watching the render, so we stopped trusting the CSV and started trusting the video. The MuJoCo render itself fought us. The robot's root translates across the floor during a jab, so a fixed camera lets it walk out of frame within a second and the clip is unwatchable. We wrote a camera that recomputes its target from the robot's root every frame so the body stays centered while the motion still reads. Getting that re-centering to track the body without also cancelling the motion we wanted to see took more iterations than the capture code did. The overlay never lined up on the first try. GVHMR recovers a generic SMPL-X body, and that body is not the specific person in leopard tights, so projecting the recovered mesh back onto the original footage drifts at the shoulders and hips. The capture is correct in 3D and still looks wrong composited on 2D video, which sent us chasing a bug that was not a bug before we added an explicit alignment step for the overlay. Running the batch dozens of times surfaced its own problem. With around 122 clips and a laptop that needed sleep, any crash on clip 80 used to mean restarting from clip 1. We made the batch idempotent so it skips any clip whose artifacts already exist and stages every output, the CSV, pickle, overlay, robot render, and side by side, into one folder per clip. That script was not clever, and it saved us more time than anything clever did. Training had to leave the laptop, and finding a GPU to rent took some hunting. Nebius set us up with free credits, which was a real help going in. The catch was timing: the whole hackathon was reaching for GPUs at once, so the instances with enough VRAM to hold the policy and the reference batch kept coming back as unavailable whenever we tried to grab one. Rather than wait it out against the deadline, we moved the training setup over to RunPod and got a machine there. By the time checkpoints, the ONNX policy, and the verification videos came back, lining up the compute had been as much work as writing the code that ran on it.
What we learned
Validation catches format errors, not semantic ones. We added visual checks (overlay, global view, robot render side by side) because numbers alone lied to us. Off the shelf monocular capture is good but not perfect. The 3D body shape never fits one specific person exactly, so the overlay needs an alignment step if you want it to look right on top of the original footage. Small hardware changes the engineering. Most of our design decisions, from static camera mode to one model at a time scheduling, came from living inside 8 GB of VRAM. A boring, idempotent batch pipeline that puts everything in one folder is worth more than any single clever script, especially when you are re running it dozens of times under a deadline. What is next More motion types beyond the jab, better automatic quality scoring so bad clips get filtered without a human looking, and pushing the trained policy onto real hardware. The longer-term goal is medical: adapting the pipeline for surgery by focusing on precise wrist sensing and signaling, so a surgeon's fine hand motion can be captured from video and faithfully mimicked by a robot.
ROBO JAB
Human jab → Unitree G1 jab policy
AI Hackathon 2026 UC Berkeley / Ultimate Bots Physical-AI hack. The pipeline takes a phone video of a person throwing a jab and produces a trained, deployable motion-tracking policy for a real 29-DoF Unitree G1. Capture is markerless, training runs in sim, and the output is an ONNX policy the robot can run.

Reading the strip left → right: the raw phone video of a human jab; the markerless mocap (GVHMR) that extracts the 3D motion; that motion retargeted onto the G1, which becomes the CSV the model trains on; and the trained G1 policy executing the jab in sim. One of about 120 clips in the dataset.
Architecture
Three independent stages, capture → train → deploy, connected by one portable artifact: a CSV of G1 joint angles. Each stage runs on different hardware and is swappable on its own.
CAPTURE (laptop GPU / WSL)
────────────────────────────────────────────────────────────────────────
phone video
│ YOLOv8 → ViTPose-H → HMR2.0 → GVHMR (world-grounded SMPL, run -s)
│ detect 17 2D kpts 3D mesh markerless mocap
▼ GMR : inverse-kinematics retarget (human SMPL → robot joints)
29-DoF G1 joint angles
│
▼
CSV root_pos[3] + root_rot_xyzw[4] + 29 joints @ 30 fps (headerless)
~120 clips · format-verified against the trainer and the G1 URDF
│
TRAIN (RunPod H100)
────────────────────────────────────────────────────────────────────────
csv_to_npz → npz (+ body vel/accel via forward kinematics)
│ ~99 clips concatenated → one reference, sampled per episode
▼ unitree_rl_mjlab · Unitree-G1-Tracking-No-State-Estimation (PPO)
MuJoCo-Warp, 4096 envs, domain randomization, ~50 Hz
obs: joint encoders + IMU → MLP → 29 joint-position targets
│ auto-export
▼
policy.onnx (obs → actions)
│
DEPLOY (Jetson Orin NX)
────────────────────────────────────────────────────────────────────────
policy.onnx → unitree_sdk2 LowCmd PD (q_des + Kp/Kd)
joint-order map → G1JointIndex · sim-to-sim (MuJoCo) gate first
▼
real 29-DoF Unitree G1 throws the jab
Capture (laptop GPU)
A phone video runs through four vision models and then a retargeter. YOLOv8 detects
the person, ViTPose-H finds 17 2D keypoints, HMR2.0 (4D-Humans) lifts that to a 3D
body mesh, and GVHMR grounds it in world space as SMPL motion (run with -s, no SLAM,
for static-camera clips). GMR then retargets the human SMPL motion onto the G1 by
inverse kinematics, solving which 29 G1 joint angles reproduce the motion within the
robot's joint limits. A human body and a robot body differ, so you cannot copy angles
directly.
The output is one CSV per clip: headerless, root_pos[3] + root_rot(xyzw)[4] + 29 joints, at 30 fps. That CSV is the contract between capture and training, format-
verified against the trainer and the robot URDF.
Train (H100)
The mocap gives a kinematic reference that is not dynamically feasible. A real G1
holding those exact angles would topple. The fix is a PPO policy that learns to track
the reference while staying balanced, under domain randomization, in
unitree_rl_mjlab (MuJoCo-Warp, GPU-parallel, 4096 envs). csv_to_npz adds body
velocities and accelerations via forward kinematics. The
Unitree-G1-Tracking-No-State-Estimation task is deployable by design: the policy
observes only what the real robot can measure (joint encoders and IMU), with no
privileged sim state.
The task tracks one motion at a time, so to cover every jab we concatenate all ~99 clips into one long reference and let the env sample random start points across it. That gives one policy for all jab variations. The policy itself is a small MLP that maps an observation to 29 joint-position targets at about 50 Hz.
Deploy (Jetson Orin)
Training auto-exports policy.onnx with an obs input and an actions output. On
the real 29-DoF G1 it runs on the onboard Jetson Orin NX and commands joints via
unitree_sdk2 LowCmd (PD control: target position plus Kp/Kd, about 50 Hz). A deploy
config maps the policy's joint order to the SDK's G1JointIndex and sets the gains.
It is validated in MuJoCo sim-to-sim before hardware.
Why this design
One portable artifact, the CSV, decouples capture from training, so the H100 side
never needs the mocap stack and either trainer (unitree_rl_mjlab or
Isaac/BeyondMimic) reads the same data. Deployability is built in from the start
instead of retrofitted: the observation space, joint order, control rate, and ONNX
export all match the real robot. Capture stays markerless, so a phone is the only
capture hardware, with no mocap suit or marker rig.
Status
| Stage | State |
|---|---|
| Capture (video → GVHMR → GMR → CSV) | done and verified; ~120 clean CSVs in data/ |
| Data ↔ trainer format match | verified against csv_to_npz (xyzw, 29-DoF, joint order) |
| Data ↔ hardware (29-DoF G1) | confirmed with Ultimate Bots |
| Training (RunPod H100, unitree_rl_mjlab) | done; multi-motion ran 10k iters, converged ~0.68 rad |
Deployable artifact (policy.onnx) | exported and validated (obs → actions); in runpod_out/final/ |
| Deploy config (29-DoF G1) | generated and self-verified; drop-in package in deploy_config/ |
| On-robot deploy | deployed on G1 (build deploy stack, sim-to-sim, hardware) |
Repo layout
| Path | What it is |
|---|---|
TRAINING_RUNPOD.md | The real, reproducible training run (RunPod H100, unitree_rl_mjlab): version pins, exact commands, results. Start here for training. |
DEPLOY.md | Pre-flight package for the real G1: the deploy contract (obs 154-dim, action 29, 50 Hz, gains, joint map) extracted from the saved config, plus the checklist. |
deploy_config/ | Drop-in deploy package for unitree_rl_mjlab's deploy/robots/g1 (29-DoF): generated deploy.yaml, policy.onnx, jab.npz, FSM snippet, plus the variant research. |
CAPTURE_GUIDE.md | How to film the jab (camera angle, framing). |
data/README.md | The CSV → npz → train data spec with format guarantees. |
G1_PLAN.md | Approach and key decisions. |
NEBIUS_TRAINING.md, AGENT_TRAIN_RUNBOOK.md | The Isaac-Lab/BeyondMimic alternative we planned but did not run. Banner-flagged. |
data/ | The ~120 validated G1-motion CSVs. |
runpod_out/ | Training checkpoints, progress renders, the policy.onnx. |
scripts/ | The capture and processing tooling. |
Capture scripts (scripts/, local, WSL/Linux)
| Script | Does |
|---|---|
setup_capture.sh | install GMR and GVHMR |
09_gvhmr.sh → 10_retarget.sh gvhmr → 11_to_csv.sh → 20_validate_motion.py | the per-clip chain |
process_jab.sh <video> | one-shot: video → validated CSV, auto-copies to data/ |
batch_jabs.sh <dir>, monitor_batch.sh | batch many clips with live progress |
make_filmstrip.py | build the filmstrip GIF above (make_sidebyside.py is the 2-panel variant) |
extract_jabs.py, extract_body_models.py, analyze_csvs.py | helpers |
Key facts (verified, reproducible)
GVHMR runs with -s (SLAM off), which suits static-camera in-place jabs and skips the
DPVO build. The CSV format is headerless with 36 columns, root_pos[3] + root_rot_xyzw[4] + 29 dof, in G1-29dof joint order, and it matches both
unitree_rl_mjlab and whole_body_tracking. The trainer needs pinned versions because
the repo leaves them unpinned and the latest releases break: mujoco==3.5.0,
warp-lang==1.12.0, plus scipy, with rendering through the EGL libs and
MUJOCO_GL=egl. The target hardware is a 29-DoF G1 with a Jetson Orin NX, driven over
unitree_sdk2 LowCmd PD at about 50 Hz.
Sim-to-sim (the trained policy in MuJoCo)

The trained policy running in MuJoCo against the jab reference. The solid robot is the
policy; the ghost is the target motion it tracks. The same policy was loaded and run in
MuJoCo locally on the laptop 4060 (scripts/wsl_local_mjlab_setup.sh then
scripts/wsl_local_sim.sh) to confirm it executes end to end off the training box; this
clip is the converged-policy render, where GL was hardware-accelerated. This is the gate
before hardware (DEPLOY.md §6): it must track the jab and stay upright in sim first.
Results
About 120 jab clips captured and validated. One policy trained on all of them
(multi-motion, 10k iterations on an H100) converged to about 0.68 rad joint error,
which reads as a recognizable jab. The deployable policy.onnx, the final checkpoint,
the 21 training checkpoints, the obs/action config (params/), and the converged
render are in runpod_out/final/. Per-cycle progress renders are in
runpod_out/progress/.
Analysis
View
Metric
- 25
- 1
- 1
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- PythonIn code
- Hugging FaceClaimed
- PyTorchClaimed
1 of 3 appear in the indexed code. 2 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
No AI coding agent signals were found in this repository.
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
212 KB
Source files
47
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
abtonmoy/ai_hackathon_calhacks
341 files · 276.4 MB · @ bd8a0a6
Structure
Application logic
227 files · 67%Domain rules, services and shared utilities.
+5 more
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- Markdown34%
- YAML28%
- Python27%
- Shell12%
Share of indexed source by file size. Binary and vendored files are excluded.
Feature verification
CSV validator (units, NaN, foot contact, frame count checks)Verified
Validate every CSV for units, NaNs, foot contact, and frame count before rendering/training
Claimed on Devposthigh confidencescripts/20_validate_motion.py:8— Docstring and checks explicitly cover NaN/inf, DoF count, root height, base drift (foot-planted check), and radians-vs-degrees
Deploy config generation for real G1 (joint order, gains, self-verified against template)Verified
A deploy config is generated and self-verified for the 29-DoF G1 (gains, joint map, action scales) as a drop-in package
Claimed on readmehigh confidencescripts/gen_deploy_config.py:12— Docstring states it self-verifies stiffness/damping/scale arrays against known-good template arrays to prove joint ordering; generates deploy.yamldeploy_config/jab/params/deploy.yaml— Generated output file actually present in repo
Export reference CSV (root pos/rot + 29 joint angles, headerless, 30fps)Verified
Export each clip as a reference CSV with root position, root rotation, and joint angles
Claimed on readmehigh confidencescripts/11_to_csv.sh— Calls GMR's batch_gmr_pkl_to_csv.py to convert pkl to headerless CSVdata/README.md:14— Documents exact 36-column format: root_pos[3], root_rot_xyzw[4], 29 DoF joints, 30fpsdata/IMG_3327.csv— 122 CSV files actually present in data/ matching the documented format
Exported ONNX policy (obs to actions)Verified
Export the trained policy as an ONNX file (policy.onnx) mapping observations to actions
Claimed on readmehigh confidencerunpod_out/final/policy.onnx— ONNX policy file present in the training output directorydeploy_config/jab/exported/policy.onnx— Same policy also staged into the generated deploy package
GMR/GVHMR install and setup automationVerified
setup_capture.sh installs GMR and GVHMR
Claimed on readmehigh confidencescripts/setup_capture.sh— Setup script present matching the README's stated purpose (install GMR and GVHMR)
Idempotent batch script processing ~120 clips into one output folder per clipVerified
An idempotent batch script processes around 122 clips, skips completed work, and stages every artifact into one output folder
Claimed on Devposthigh confidencescripts/process_jab.sh:34— Comment 'require the .pt. Skip entirely if it already exists (idempotent re-runs)' with a guard at line 37 printing 'GVHMR output exists, skipping mocap'scripts/batch_jabs.sh— Loops over all videos in a directory, calls process_jab.sh per clip, continues past failures (set -uo pipefail, not -e), writes pass/fail summarydata/IMG_3327.csv— 122 CSVs present in data/, matching the 'around 122 clips' claim
PPO tracking policy trained in unitree_rl_mjlab (MuJoCo-Warp, 4096 envs, domain randomization)Verified
Train a PPO/MLP tracking policy on the reference motions using unitree_rl_mjlab with domain randomization, 4096 parallel envs, on an H100
Claimed on readmehigh confidencerunpod_out/final/train_multimotion.log— Present training log file from the actual RunPod runrunpod_out/final/checkpoints/model_9999.pt— 21 checkpoints (model_0.pt through model_9999.pt) present, consistent with '10k iterations' claimrunpod_out/final/params/env.yaml— Saved training env config, referenced as source of truth throughout DEPLOY.md
Side-by-side / filmstrip comparison video generationVerified
Stage artifacts including overlay video, robot video, and side-by-side comparison into one output folder
Claimed on readmehigh confidencescripts/make_sidebyside.py:1— Builds a labelled side-by-side GIF from raw phone video and G1 renderscripts/make_filmstrip.py:1— Builds a horizontal filmstrip GIF from multiple labelled videos, used for the README pipeline GIF
CSV to NPZ conversion for training (with body vel/accel via forward kinematics)Code-supported
csv_to_npz adds body velocities and accelerations via forward kinematics ahead of training
Claimed on readmemedium confidencedata/csv_to_npz.py:1— An Isaac-Lab-based csv_to_npz script exists in this repo (replays a CSV motion and outputs NPZ), but README states the actually-executed training used unitree_rl_mjlab's own csv_to_npz, which lives in an external cloned repo not present hereTRAINING_RUNPOD.md:45— Documents running unitree_rl_mjlab's scripts/csv_to_npz.py on the RunPod box, external to this clone
Headless MuJoCo renderer with self-recentering camera (robot-only render)Code-supported
Wrote a headless MuJoCo renderer that draws the robot only, with a camera that re-centers every frame
Claimed on Devpostlow confidencescripts/render_ref.sh— Script invokes an external GMR repo's vis_robot_motion.py (outside this clone, path /home/abtonmoy/repos/GMR) with MUJOCO_GL=egl to render a pkl to video; the custom re-centering camera logic itself is not present anywhere in this repository
Markerless motion capture pipeline (GVHMR: YOLO, ViTPose, HMR2, world-grounded SMPL-X)Code-supported
Run markerless motion capture (GVHMR, using YOLO detection, ViTPose 2D keypoints, HMR2 body shape) to recover the 3D human body per frame
Claimed on readmemedium confidencescripts/09_gvhmr.sh— Script clones the external zju3dv/GVHMR repo and invokes its demo.py with -s (SLAM off) to run GVHMR on a video; GVHMR itself (which bundles YOLO/ViTPose/HMR2) is an external dependency, not vendored in this repo
Retargeting human motion to G1 skeleton (GMR inverse kinematics, 29 joints)Code-supported
Retarget captured human motion onto the G1 skeleton (29 joints) via GMR inverse kinematics
Claimed on readmemedium confidencescripts/10_retarget.sh— Script invokes GMR's gvhmr_to_robot.py with --robot g1 to retarget GVHMR output to the robot; GMR itself is an external cloned dependency, not present in this repo
Sim-to-sim validation gate (trained policy replayed in MuJoCo before hardware)Code-supported
The trained policy was loaded and run in MuJoCo locally to confirm it executes end to end, as the gate before hardware
Claimed on readmemedium confidencescripts/wsl_local_sim.sh— Script referenced by README for running the trained policy in MuJoCo locally on the laptop; sim-to-sim setup scripts (wsl_local_mjlab_setup.sh, wsl_local_sim.sh) exist in scripts/, but the actual sim video (assets/sim_to_sim.gif) cannot be independently confirmed to be genuine output from code inspection alone
On-robot deployment to a real Unitree G1 via unitree_sdk2 LowCmd PD controlClaimed only
Deployed straight to a real 29-DoF Unitree G1; policy commands joints via unitree_sdk2 LowCmd PD at ~50Hz on hardware
Claimed on Devpostmedium confidenceOverlay video with explicit alignment step (SMPL-X mesh composited on original footage)Claimed only
An explicit alignment step corrects overlay drift when projecting the recovered SMPL-X mesh back onto the original footage
Claimed on Devpostmedium confidence
An AI agent derived these features from the project’s Devpost page and readme, then searched the code for each one. Verified features are backed by cited code; claimed-only features had no supporting code, which is not by itself proof a feature is missing.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.