curobo.examples.getting_started.humanoid_retargeting module

SOMA adapter for retargeting BVH motion capture to Unitree G1 using cuRobo IK and MPC.

This guide walks through setting up cuRobo for retargeting human motion capture data to a humanoid robot. We use the Unitree G1 29-DOF robot as an example, but the same approach applies to any humanoid with a URDF.

This script is a SOMA adapter that bridges soma_retargeter I/O with the cuRobo MotionRetargeter API. All solver logic (IK, MPC, warm-starting) lives in MotionRetargeter; this file handles only BVH loading, effector conversion, CSV output, and visualization.

What is Motion Retargeting?

Motion retargeting transfers a motion from one character (the source) to another (the target) that may have a different body shape, limb proportions, or degrees of freedom. In humanoid robotics, the source is typically a human performer captured via optical motion capture, and the target is a robot. Copying joint angles directly does not work as the source and target have different kinematic structures, limb lengths, joint limits, and degrees of freedom.

Retargeting breaks down into three sub-problems:

  1. Link mapping: which human joint corresponds to which robot link?

  2. Proportion rescaling: rescale and offset the human mocap poses to match the robot’s limb lengths, body proportions, and joint frame conventions.

  3. Solving for joint angles: find robot joint angles at every frame that best reproduce the rescaled target poses while respecting kinematic constraints.

SOMA Retargeter handles all three: it loads a BVH file, applies a per-robot link mapping and per-joint scale factors/pose offsets, and includes its own IK solver for step 3. This guide shows how to replace the IK solver with cuRobo, leveraging GPU-accelerated collision-free trajectory optimization.

How cuRobo Retargets Motion

cuRobo sets up an optimization problem to find robot joint angles that best reproduce the target poses across all N tracked links, subject to joint limits and (optionally) self-collision avoidance. Each tracked robot link is a tool_frame that receives an independent pose target. Links can be weighted differently: feet and pelvis get high position weight to maintain balance, while shoulder rotations get low weight since the elbow and wrist targets already constrain the arm.

Two-phase solving. Retargeting a motion clip proceeds frame by frame using two phases:

  1. Global IK (frame 0): The first frame has no prior solution, so the solver runs a broad search using many random seeds (default 64) with no velocity limit. This explores the full configuration space to find a good initial pose.

  2. Local solver (frames 1 … N): Each subsequent frame uses the previous frame’s solution as a warm start. This can be either:

    • Warm-started IK (use_mpc=False): a single-seed, velocity-limited IK solve that tracks the motion frame by frame.

    • MPC (use_mpc=True): model predictive control that optimizes a trajectory over a planning horizon, producing smoother results with acceleration and jerk costs.

digraph { rankdir=LR; edge [color="#2B4162"; fontsize=10]; node [shape="box", style="rounded, filled", fontsize=12, color="#cccccc"]; target [label="Per-Frame\nTool Pose Targets", color="#708090", fontcolor="white"]; global [label="Global IK\n64 seeds, no v-limit", color="#76b900", fontcolor="white"]; local [label="Local IK or MPC\n1 seed, v-limited", color="#558c8c", fontcolor="white"]; output [label="Joint Trajectory\n(num_envs, num_frames, num_dof)", color="#708090", fontcolor="white"]; target -> global [label="frame 0"]; global -> local [label="warm start"]; local -> local [label="warm start\nframes 1…N"]; local -> output; }

Warm-starting is what connects isolated per-frame IK solutions into a temporally coherent motion: the solver starts from a configuration that is already close to the answer, and the velocity limit prevents it from jumping to a distant solution even if one has lower cost.

The MotionRetargeter API encapsulates this two-phase loop. You provide target poses; it manages solver construction, warm-starting, and mode switching internally.

Step 2: Define Tool Frames

Each human skeleton joint maps to a robot link, becoming a tool frame in cuRobo’s IK solver. Each tool frame carries an independent position and rotation weight: feet and hips get high weight to maintain balance, while mid-chain links like shoulders get low weight since the elbow and wrist targets already constrain the arm.

This mapping is read automatically from SOMA’s retargeter config via pipeline_utils.get_retargeter_config(source_type, target_type). To customize the mapping or weights for a different robot, edit the corresponding SOMA retargeter config (ik_map section).

Step 3: Build the Robot Configuration

Use the build_robot_model script to build the cuRobo config with collision spheres and the self-collision matrix:

python -m curobo.examples.getting_started.build_robot_model \
  --urdf curobo/content/assets/robot/g1/g1_29dof_rev_1_0.urdf \
  --asset-path curobo/content/assets/robot/g1 \
  --tool-frames \
    pelvis torso_link \
    left_shoulder_roll_link left_elbow_link left_wrist_yaw_link \
    right_shoulder_roll_link right_elbow_link right_wrist_yaw_link \
    left_hip_roll_link left_knee_link left_ankle_roll_link \
    right_hip_roll_link right_knee_link right_ankle_roll_link \
  --output curobo/content/configs/robot/unitree_g1_29dof_retarget.yml \
  --visualize

The --visualize flag opens a Viser web viewer (default http://localhost:8080) where you can inspect the fitted collision spheres.

A pre-built config is included at curobo/content/configs/robot/unitree_g1_29dof_retarget.yml.

Step 4: Three Levels of Retargeting

MotionRetargeter is the high-level API that implements the two-phase loop described above. You configure it once, then call solve_sequence (offline) or solve_frame (streaming); it manages solver construction, warm-starting, and mode switching internally.

Both IK solvers use ik/lbfgs_retarget_ik.yml, a specialized LBFGS optimizer config tuned for retargeting. The MPC solver uses mpc/lbfgs_retarget_mpc.yml.

Per-link weighting: each tool frame receives independent position and rotation weights via ToolPoseCriteria, passed as a dict in MotionRetargeterCfg.tool_pose_criteria.

The same two-phase solver runs at three fidelity levels. Start with Level 1 to get results quickly, then upgrade to the higher levels as needed.

Level

Mode

Flag

Trade-off

1

IK, no self-collision

self_collision_check=False

Fastest. Robot links may interpenetrate on constrained poses.

2

IK, with self-collision

self_collision_check=True

~10–20 % slower; prevents robot links interpenetration.

3

MPC

use_mpc=True

2–4× slower; adds trajectory smoothness (acceleration/jerk costs).

Level 1: IK without Self-Collision

IK without Self-Collision

The fastest option. Collision spheres are not loaded, so initialization and per-frame solve time are both minimized. Use this level when speed matters more than physical plausibility, or as a first pass to verify the link mapping.

from curobo.motion_retargeter import (
    MotionRetargeter,
    MotionRetargeterCfg,
    SequenceGoalToolPose,
)
from curobo.types import ToolPoseCriteria

cfg = MotionRetargeterCfg.create(
    robot="unitree_g1_29dof_retarget.yml",
    tool_pose_criteria={
        "pelvis": ToolPoseCriteria.track_position_and_orientation(
            xyz=[1.0, 1.0, 1.0], rpy=[0.067, 0.067, 0.067],
        ),
        "left_ankle_roll_link": ToolPoseCriteria.track_position_and_orientation(
            xyz=[1.0, 1.0, 1.0], rpy=[0.067, 0.067, 0.067],
        ),
        # ... one entry per tool frame
    },
    num_envs=1,
    self_collision_check=False,   # no self-collision
)
retargeter = MotionRetargeter(cfg)

# positions: (num_frames, num_envs, num_links, num_goalset, 3) torch tensor
# quaternions: (num_frames, num_envs, num_links, num_goalset, 4) torch tensor, wxyz convention
seq = SequenceGoalToolPose(
    tool_frames=cfg.tool_frames,
    position=positions,
    quaternion=quaternions,
)

result = retargeter.solve_sequence(seq)
# result.joint_state.position: (num_envs, num_frames, num_dof)

CLI equivalent:

python -m curobo.examples.getting_started.humanoid_retargeting \
    --input motion.bvh --output motion.csv \
    --no-self-collision

Level 2: IK with Self-Collision

IK with Self-Collision Avoidance

Enables cuRobo’s self-collision avoidance. The solver penalizes configurations where collision spheres overlap, preventing interpenetrating limbs. This is the recommended default for generating training data or deployment motions.

cfg = MotionRetargeterCfg.create(
    robot="unitree_g1_29dof_retarget.yml",
    tool_pose_criteria={...},
    num_envs=1,
    self_collision_check=True,    # self-collision avoidance enabled
)
retargeter = MotionRetargeter(cfg)
result = retargeter.solve_sequence(seq)

CLI equivalent:

python -m curobo.examples.getting_started.humanoid_retargeting \
    --input motion.bvh --output motion.csv

Level 3: MPC (Trajectory-Based)

MPC with Self-Collision Avoidance

Switches the local solver from frame-by-frame IK to Model Predictive Control. Instead of solving IK independently per frame, MPC optimizes a trajectory over a planning horizon, producing smoother results with acceleration and jerk costs.

cfg = MotionRetargeterCfg.create(
    robot="unitree_g1_29dof_retarget.yml",
    tool_pose_criteria={...},
    num_envs=1,
    self_collision_check=True,
    use_mpc=True,                 # MPC local solver
    steps_per_target=4,
)
retargeter = MotionRetargeter(cfg)
result = retargeter.solve_sequence(seq)

# result.joint_state: one frame per input target (num_envs, num_frames, num_dof)
# result.trajectory: smooth intermediate frames (MPC only)

CLI equivalent:

python -m curobo.examples.getting_started.humanoid_retargeting \
    --input motion.bvh --output motion.csv \
    --mpc --steps-per-target 4

Key points (all levels):

  • Quaternion convention: cuRobo uses (w, x, y, z) order. Warp and many mocap formats use (x, y, z, w); convert before constructing SequenceGoalToolPose.

  • Output DOFs: The solution contains 35 DOFs: 6 virtual base joints [x, y, z, roll, pitch, yaw] followed by 29 body joints.

  • Streaming mode: call retargeter.solve_frame(tool_pose) one frame at a time for online/teleoperation use cases.

For a full description of all configuration parameters and their tuning guidance, see MotionRetargeterCfg.

Step 5: Running the SOMA Example

Requires soma-retargeter to be installed. See the README.

Single file:

python -m curobo.examples.getting_started.humanoid_retargeting \
    --input /path/to/motion.bvh \
    --output /path/to/output.csv

Batch (folder of BVH files):

python -m curobo.examples.getting_started.humanoid_retargeting \
    --input /path/to/bvh_folder/ \
    --output /path/to/csv_folder/

With visualization (opens a web viewer at http://localhost:8080):

python -m curobo.examples.getting_started.humanoid_retargeting \
    --input motion.bvh --output motion.csv --visualize

The viewer provides play/pause, loop, speed, frame slider, target sphere visibility, and a motion dropdown (when multiple clips are loaded).

Additional flags:

  • --robot-config: cuRobo robot YAML config (default: unitree_g1_29dof_retarget.yml).

  • --no-self-collision: disable self-collision checking (Level 1).

  • --mpc: enable MPC mode (Level 3).

  • --steps-per-target N: MPC steps per input frame (default: 4).

  • --max-frames N: limit retargeting to the first N frames.

  • --max-batch N: max clips to retarget in parallel (default: 100).

Additional Details

  • The virtual base joints (base_link_x/y/z, base_link_xtheta/ytheta/ztheta) give the pelvis 6-DOF freedom. Their limits should be wide enough for the expected motion range.

  • The SOMA example outputs joint data in CSVAnimationBuffer format. The first 7 values per frame are the root transform [x, y, z, qx, qy, qz, qw] (converted from the 6 virtual base DOFs via Pose.from_euler_xyz_intrinsic), followed by the 29 body joint angles reordered to SOMA CSV convention.

visualize_motion(
robot_config,
joint_solutions_list,
joint_names,
sample_rate,
port=8080,
target_positions_list=None,
tool_frames=None,
motion_names=None,
default_frame_skip=1,
)

Play back retargeted motions in a viser web viewer.

Parameters:
  • robot_config (str) – cuRobo robot YAML config name or path.

  • joint_solutions_list (List[ndarray]) – List of arrays, each (num_frames, num_dof).

  • joint_names (List[str]) – Joint names matching the solution DOF order.

  • sample_rate (float) – Playback FPS.

  • port (int) – Viser server port.

  • target_positions_list (List[ndarray]) – Optional list of (num_frames, num_links, 3) target positions, one per motion.

  • tool_frames (List[str]) – Optional link names for target labeling.

  • motion_names (List[str]) – Optional display names for the dropdown. Defaults to ["Motion 0", "Motion 1", ...].

  • default_frame_skip (int) – Initial frame skip value. For MPC trajectories, set to steps_per_target * interpolation_steps to play back at the original input frame rate.

export_motion_to_usd(
robot_config,
joint_solutions_list,
joint_names,
traj_dt,
output_paths,
frame_skip=1,
target_positions_list=None,
tool_frames=None,
target_radius=0.025,
flatten_usd=False,
)

Export retargeted motions to USD animation files.

Uses UsdWriter.write_trajectory_animation to produce animated USD files with robot link meshes and optional target spheres showing the IK effector goals.

Parameters:
  • robot_config (str) – cuRobo robot YAML config name or path.

  • joint_solutions_list (List[ndarray]) – List of arrays, each (num_frames, num_dof).

  • joint_names (List[str]) – Joint names matching the solution DOF order.

  • traj_dt (float) – Time between consecutive trajectory frames in seconds. For non-MPC this is 1 / sample_rate. For MPC this is 1 / (sample_rate * steps_per_target * interpolation_steps).

  • output_paths (List[str]) – List of output .usd file paths, one per motion.

  • frame_skip (int) – Write every Nth frame (default: 1 = all frames).

  • target_positions_list (List[ndarray]) – Optional list of (num_frames, num_links, 3) target positions to visualize as animated spheres.

  • tool_frames (List[str]) – Optional link names for target sphere labeling.

  • target_radius (float) – Radius for target spheres (meters).

  • flatten_usd (bool) – Flatten USD stage (single file, no references).

retarget_bvh_files(
input_path,
output_path,
robot_config='unitree_g1_29dof_retarget.yml',
do_visualize=False,
viz_port=8080,
use_mpc=False,
steps_per_target=4,
max_frames=None,
self_collision_check=True,
max_num_envs=4,
velocity_regularization_weight=None,
acceleration_regularization_weight=None,
usd_output=None,
)

Load BVH files, retarget via MotionRetargeter, save as SOMA CSV.

Parameters:
  • input_path (Path) – BVH file or directory of BVH files.

  • output_path (Path) – Output CSV file or directory.

  • robot_config (str) – cuRobo robot YAML config name.

  • do_visualize (bool) – Open viser web viewer after retargeting.

  • viz_port (int) – Viser server port.

  • use_mpc (bool) – Use MPC instead of frame-by-frame IK.

  • steps_per_target (int) – MPC steps per target frame (only with use_mpc).

  • max_frames (int) – Limit retargeting to the first N frames.

  • self_collision_check (bool) – Enable self-collision checking.

  • max_num_envs (int) – Maximum batch size per retargeting pass. Clips are processed in chunks of this size to avoid GPU OOM.

  • usd_output (str) – Path for USD animation export (None to skip).

  • velocity_regularization_weight (float)

  • acceleration_regularization_weight (float)

main()