Timsty's picture
Add files using upload-large-folder tool
a89a522 verified
|
Raw
History Blame Contribute Delete
8.3 kB
# Motion Representation
## Overview
MotionBricks represents motion as a normalized feature vector per frame. The representation separates **root motion** (global position and heading of the robot's pelvis) from **body motion** (joint rotations, positions, velocities, and foot contacts). This separation lets the root model and the pose/tokenizer module operate on different subsets of the same representation.
Throughout the paper and codebase, two interchangeable subsets are used:
- **Global** (`GlobalRootGlobalJoints`, 414 dims) — **root model** mostly operates with this representation for precise global root control. This is also what the data loader returns directly.
- **Local** (`LocalRootGlobalJoints`, 413 dims) — used by the **pose/tokenizer module**.
The two subsets share the same 409-dim body features and differ only in how the root is parameterized (5 global vs 4 local dims). They convert losslessly to each other via `dual_rep.global_to_local` / `dual_rep.local_to_global`. In the training loop, batches come out of the loader in the global representation and are converted to local on the fly before being passed to the pose/tokenizer module. Concretely, the `"motion"` tensor in every batch dict is always the **global** motion — per-sample conversion to local happens inside the training step.
The current configuration uses the **DualRootGlobalJoints** representation on the **G1Skeleton34** skeleton (Unitree G1 with 34 joints). The full feature vector is 418-dimensional per frame, composed of the 414-dim global subset and the 413-dim local subset that share the 409-dim body features.
See the MotionBricks paper for the full derivation of the representation.
## Feature Breakdown
All body features are defined in the **global (world) frame**. The `local_` prefix on `local_vel` is a naming holdover — in `DualRootGlobalJoints` (`removing_heading=False`), the velocity is NOT heading-rotated, and its normalization statistics are computed from the same world-frame values.
### Body Features (409 dimensions)
Shared by both global and local root representations.
| Feature | Dimensions | Description |
|---------|-----------|-------------|
| `ric_data` | 99 | Global joint positions with the **projected (XZ) root position** subtracted per frame, for 33 non-root joints. Not heading-canonicalized. |
| `global_rot_data` | 204 | **Global (world-frame)** 6D continuous rotations for all 34 joints.|
| `local_vel` | 102 | **Global-frame** per-joint velocity, computed as finite differences of world positions. |
| `foot_contacts` | 4 | Binary contact states for left ankle, left toe, right ankle, right toe. |
### Global Root Features (5 dimensions)
Used by the global representation subset (consumed by the root model).
| Feature | Dimensions | Description |
|---------|-----------|-------------|
| `global_root_pos` | 3 | XYZ position in world frame (during training and inference, first frame's root XZ is placed at origin). |
| `global_root_heading` | 2 | Root heading as (cos, sin) of the Y-axis rotation angle. |
### Local Root Features (4 dimensions)
Used by the local representation subset (consumed by the pose/tokenizer module). Derived from the global root during the `global_to_local` conversion.
| Feature | Dimensions | Description |
|---------|-----------|-------------|
| `local_root_rot_vel` | 1 | Angular velocity around the Y-axis. |
| `local_root_vel` | 2 | Root translational velocity in the XZ plane, expressed in the root's heading-aligned frame. |
| `global_root_y` | 1 | Root height (Y-axis position in world frame). |
### Combined Dimensions
| Representation | Formula | Total |
|---------------|---------|-------|
| Global subset (`GlobalRootGlobalJoints`) | 5 (global root) + 409 (body) | **414** |
| Local subset (`LocalRootGlobalJoints`) | 4 (local root) + 409 (body) | **413** |
| Full dual (`DualRootGlobalJoints`) | 5 + 4 + 409 | **418** |
The root model uses the **global subset** (414 dims). The pose/tokenizer module uses the **local subset** (413 dims).
## Skeleton: G1Skeleton34
The skeleton defines the kinematic tree. G1Skeleton34 has 34 joints: 32 active joints from the Unitree G1 robot plus 2 dummy toe joints for foot contact detection.
```
pelvis (root)
|-- left_hip_pitch -- left_hip_roll -- left_hip_yaw -- left_knee
| \-- left_ankle_pitch -- left_ankle_roll -- left_toe_base*
|-- right_hip_pitch -- right_hip_roll -- right_hip_yaw -- right_knee
| \-- right_ankle_pitch -- right_ankle_roll -- right_toe_base*
|-- waist_yaw -- waist_roll -- waist_pitch
|-- left_shoulder_pitch -- left_shoulder_roll -- left_shoulder_yaw -- left_elbow
| \-- left_wrist_roll -- left_wrist_pitch -- left_wrist_yaw -- left_hand_roll
|-- right_shoulder_pitch -- right_shoulder_roll -- right_shoulder_yaw -- right_elbow
\-- right_wrist_roll -- right_wrist_pitch -- right_wrist_yaw -- right_hand_roll
```
*Dummy toe joints (not actuated on the real robot).
### MuJoCo Joint Mapping
The MuJoCo model has 29 hinge joints (excluding the free-floating root and toe joints). The output qpos vector is 36-dimensional:
| Indices | Content |
|---------|---------|
| 0-2 | Root translation (x, y, z) |
| 3-6 | Root quaternion (w, x, y, z) |
| 7-35 | 29 joint angles (1 DOF per hinge joint) |
The `mujoco_qpos_converter` class handles the mapping between the 34-joint motion representation and the 29-DOF MuJoCo model, including coordinate system transformation (motion space: Y-up, Z-forward; MuJoCo space: Z-up, X-forward).
## Coordinate Systems
| Space | Up | Forward | Handedness |
|-------|-----|---------|------------|
| Motion | Y | Z | Right-handed |
| MuJoCo | Z | X | Right-handed |
The coordinate transformation between the two:
- Motion X = MuJoCo Y
- Motion Y = MuJoCo Z
- Motion Z = MuJoCo X
## Normalization
All features are z-score normalized before being fed to models:
```
normalized = (feature - mean) / sqrt(std^2 + eps)
```
where `eps = 1e-5` for numerical stability. The `mean.npy` and `std.npy` files are computed per-dimension over the training dataset and stored alongside each model checkpoint in the `stats/motion/` directory.
## Feature Computation Pipeline
MotionBricks does **not** apply a fixed heading canonicalization to its features. Instead, each motion segment is placed at the origin with a heading that is *randomly rotated* at training time and *explicitly chosen by the caller* at inference time. This way the model sees motion in all orientations, so there is nothing to gain from pre-canonicalizing to a fixed frame.
The pipeline, mirroring the order in `compute_motion_features` in `motionlib/core/motion_reps/tools/motion_features.py`, is:
```
Raw motion (local / global joint rotations + root translation)
│
â–¼
Compute ROOT features (compute_heading_info + compute_heading_features):
• Global root: (root XYZ, heading cos/sin)
• Local root : (XZ linear velocity, Y angular velocity, root height)
│
â–¼
Compute BODY features in the WORLD frame (compute_position_features):
• ric_data (world joint positions − per-frame root XZ)
• local_vel (world-frame finite-difference velocity)
• foot_contacts (from position/velocity thresholds)
• global_rot_data (world-frame 6D rotations)
│
â–¼
Concatenate per frame → [T, 418]; obtain normalization stats (z-score)
│
â–¼
The data loader returns the NORMALIZED GLOBAL rep [T, 414].
│
â–¼
At training / inference time, for each motion segment:
1. Call `change_first_heading(..., first_heading_angle)`
- TRAINING: first_heading_angle ~ Uniform(0, 2π) → random heading
- INFERENCE: first_heading_angle = 0 → deterministic
Effect: rotates every frame so that the first frame faces the target
heading, AND places the first frame's root XZ at the origin
(Y / root height is preserved).
2. If feeding the pose / tokenizer module: convert to LOCAL via
`dual_rep.global_to_local(...)` (lossless; invertible via `local_to_global`).
```
The inverse pipeline (used at inference time) converts features back to joint positions and rotations, which are then mapped to MuJoCo qpos via the `mujoco_qpos_converter`.