Ch16 | 多模态动作获取:文生动作与视频学习
本章定位:Part IV(单形态实战)第四章。Ch15 建立了 motion tracking 的完整工程能力——给定参考动作,策略可以忠实复现。但参考动作从哪来?本章回答这个问题:通过文本描述("walk forward slowly")、视频(YouTube 上的舞蹈)、或单 RGB 相机实时遥操作获取参考动作。这是从"人工提供参考"到"自动获取参考"的关键跨越。
参考:✅ ProtoMotions(CALM/MaskedMimic)· ✅ TextOp(arXiv 2602.07439 预印本)· ✅ HumanPlus(CoRL'24)· ✅ HDMI(CMU)· ✅ WholeBodyVLA(ICLR'26 Poster)/ LeVERB(投稿 ICLR'26,审稿中)
机器人:G1/H1 · 累积项目:C(续)
前置自测
📋 答不出 \(\ge\) 3 题 → 先回前置章节复习
本章直接依赖 Ch15 的 motion tracking 经验。如果你还没有在 Ch15 中跑通 BeyondMimic tracking task 和 ProtoMotions AMP,强烈建议先完成。
- [Ch15] BeyondMimic 的 body_position_tracking reward 为什么要在 base frame 下计算,而不是 world frame?
- [Ch15] AMP 判别器与直接跟踪(Mimic)的核心区别是什么?AMP 的 gradient penalty 的作用是什么?
- [Ch15] ProtoMotions 中从 AMP 切换到 CALM 需要修改哪些配置?约几行差异?
- [Ch14] G1 的 per-joint action scale 公式是什么?为什么不能统一设置?
- [NLP] CLIP 模型的 text encoder 输出什么格式的向量?维度通常是多少?
本章目标
学完本章后,你应该能够:
- 用 CALM 实现文本条件控制:在 ProtoMotions 中配置 CALM,用自然语言 prompt 控制 G1 的动作风格
- 理解 MaskedMimic 的 motion inpainting 思想:解释为什么部分约束(文本/关键帧/目标位置)可以补全完整动作
- 用 TextOp 实现实时文生动作管线:理解 RobotMDAR + Tracker 的两层架构,从文本到真机全流程
- 精读 HumanPlus 的 Shadowing Transformer:理解如何从单 RGB 相机实时控制人形机器人
- 精读 HDMI 的视频 → loco-manipulation 管线:理解如何从单目视频中同时提取人体和物体轨迹
- 在三种动作来源(MoCap/视频/文本)之间做出工程选型
Ch15 的 BeyondMimic 和 AMP 解决了"如何跟踪参考动作"的问题。但到目前为止,参考动作的来源要么是高质量 MoCap 数据(AMASS),要么是手动准备的 CSV 文件。本章探索三种更灵活的来源:文本描述、视频和实时遥操作。
16.1 算法回顾:从 MoCap 到文本/视频 ⭐⭐
这一节解决什么问题:建立动作获取的技术谱系——MoCap、文本生成、视频估计的优劣对比和技术基础。
动作来源的三个时代
| 时代 | 动作来源 | 代表方法 | 精度 | 成本 | 灵活性 |
|---|---|---|---|---|---|
| MoCap 时代 | 光学/惯性 MoCap | AMASS, Vicon | 极高 (<1mm) | 极高(设备 + 场地) | 低(需要演员穿戴) |
| 视频时代 | 单目/多目 RGB | 4DHumans, WHAM, HybrIK | 中等 (~5cm) | 低(只需相机) | 高(任何视频) |
| 文本时代 | 自然语言描述 | MDM, T2M-GPT, CALM | 低 (~10cm+) | 极低(只需打字) | 极高(想象 = 输入) |
跨领域类比:这三个时代就像绘画的三种方式。MoCap 是"照相写实"——精确但昂贵且受限于现场。视频是"写生"——看着真实场景画,精度中等但便宜灵活。文本是"命题创作"——只给一个题目("画一个人在雨中跑步"),画出来的内容取决于画家(模型)的能力,最灵活但最不可控。
Motion Diffusion Model (MDM) 核心思想
MDM(Tevet et al., ICLR 2023)是文生动作的里程碑。它把动作序列看作一种"图像"——在时间维度和关节维度上都有结构——然后用扩散模型从噪声中去噪生成:
其中 \(x_0\) 是一段动作序列(\(T\) 帧 \(\times\) \(J\) 关节),\(c\) 是 CLIP text embedding。
工程含义:MDM 生成的是运动学轨迹(关节角度随时间变化),不是控制信号。这个轨迹不保证物理可行——可能包含脚在地面以下、质心超出支撑多边形等问题。必须经过 Ch15 的 tracking policy 转化为物理可行的控制信号。
这就是为什么 Ch15 是 Ch16 的前置——Ch15 的 tracker 是 Ch16 所有方法的物理执行层。
CLIP Text Embedding → Motion Latent 映射
文生动作的核心是把文本语义映射到运动空间。CLIP(Radford et al., 2021)提供了一个预训练的 text encoder,把自然语言描述映射到 512 维向量:
import clip
model, preprocess = clip.load("ViT-B/32")
text = clip.tokenize(["a person walks forward slowly"])
text_embedding = model.encode_text(text) # (1, 512)
这个 512 维向量包含了文本的语义信息——"walks" 对应运动类型,"forward" 对应方向,"slowly" 对应速度。模型需要学习从这个语义空间到运动空间的映射。
反事实推理:如果不用 CLIP 而是训练一个专用的 text encoder 会怎样? CLIP 的优势是在约 4 亿(400M)图文对上预训练,对各种自然语言描述有泛化能力。专用 text encoder 只在动作-文本配对数据上训练——数据量有限(BABEL 为约 43 小时 AMASS 提供动作标注,含 28k+ 序列级标签和 63k+ 帧级标签),泛化能力差。对于"a person moonwalks"这种训练集中没有的描述,CLIP 可能还有一定理解能力,专用 encoder 几乎肯定失败。
视频姿态估计技术栈
从视频中获取动作需要"人体姿态估计"——从 RGB 像素中恢复人体的 3D 关节位置和朝向。主要工具:
| 工具 | 输入 | 输出 | 精度 | 速度 | 特点 |
|---|---|---|---|---|---|
| 4D-Humans | 单目 RGB 视频 | SMPL body mesh + 跟踪 | ~5cm | ~15 FPS | 全身 SMPL body(不含手部) |
| WHAM | 单目 RGB 视频 | SMPL body + global translation | ~4cm | ~20 FPS | 全局平移更准 |
| HybrIK | 单帧 RGB 图像 | SMPL pose/shape(混合解析-神经 IK) | ~5cm | ~30 FPS | 由 3D 关节解算 SMPL 旋转 |
| HaMeR | 单帧 RGB 图像 | MANO hand | ~2cm | ~25 FPS | 专门做手部 |
HumanPlus 的选择:WHAM(body)+ HaMeR(hands)→ SMPL-X → retarget 到 H1。这个组合在全身 + 手部的精度和速度之间取得了好的平衡。
工程陷阱:所有视频姿态估计都有遮挡问题——当人体被物体或自身部位遮挡时,估计精度急剧下降。KungfuBot(Ch15.5)的 physics filter 就是为了过滤这些不准确的帧。
VQ-VAE Motion Tokenization
VQ-VAE(Vector Quantized Variational Autoencoder)把连续的运动序列离散化为 token 序列——类似于 NLP 中把句子切分为 token。
VQ-VAE 的三个组件:
# VQ-VAE motion tokenizer(概念性实现)
class MotionVQVAE(nn.Module):
def __init__(self, motion_dim=263, latent_dim=512, codebook_size=512):
super().__init__()
# 1. Encoder: 连续动作 → 连续 latent
self.encoder = nn.Sequential(
nn.Conv1d(motion_dim, 512, kernel_size=4, stride=2, padding=1),
nn.ReLU(),
nn.Conv1d(512, latent_dim, kernel_size=4, stride=2, padding=1),
)
# 2. Codebook: K 个离散向量
self.codebook = nn.Embedding(codebook_size, latent_dim)
# 3. Decoder: 离散 token → 重建动作
self.decoder = nn.Sequential(
nn.ConvTranspose1d(latent_dim, 512, kernel_size=4, stride=2, padding=1),
nn.ReLU(),
nn.ConvTranspose1d(512, motion_dim, kernel_size=4, stride=2, padding=1),
)
def quantize(self, z_e):
"""找到 codebook 中最近的向量"""
# z_e: (B, latent_dim, T')
# codebook: (K, latent_dim)
distances = torch.cdist(
z_e.permute(0, 2, 1), # (B, T', latent_dim)
self.codebook.weight.unsqueeze(0) # (1, K, latent_dim)
)
indices = distances.argmin(dim=-1) # (B, T') — token indices
z_q = self.codebook(indices).permute(0, 2, 1) # (B, latent_dim, T')
return z_q, indices
def forward(self, motion):
"""
motion: (B, motion_dim, T) — 原始动作序列
"""
z_e = self.encoder(motion) # (B, latent_dim, T')
z_q, indices = self.quantize(z_e) # 量化到离散 token
motion_recon = self.decoder(z_q) # 重建
# Loss: 重建 + codebook alignment + commitment
recon_loss = F.mse_loss(motion_recon, motion)
codebook_loss = F.mse_loss(z_q.detach(), z_e) + 0.25 * F.mse_loss(z_q, z_e.detach())
return motion_recon, indices, recon_loss + codebook_loss
工程含义:token 化后可以用 NLP 的自回归模型(GPT 风格)来生成动作——输入是 text token 序列,输出是 motion token 序列,然后通过 VQ-VAE decoder 恢复为连续动作。T2M-GPT 使用了这种"离散 motion token + GPT"的范式。(注意:TextOp 的高层生成器用的是自回归 motion diffusion 模型,属于另一类范式,详见 16.4。)
codebook_size 的选择:太小(如 64)→ 动作种类被过度压缩,重建质量差。太大(如 4096)→ 很多 token 使用率极低,浪费容量。512-1024 是文献中的常见选择。
从 MDM 到机器人控制的完整链路
文本描述: "walk forward slowly"
↓ CLIP text encoder
文本向量: (512,)
↓ MDM / T2M-GPT / TextOp RobotMDAR
运动学轨迹: (T, J) — 关节角度序列
↓ ★ 关键步骤: 物理可行性检查 (Ch15.5 physics filter)
过滤后轨迹: (T', J)
↓ ★ 关键步骤: tracking policy (Ch15.1 BeyondMimic)
关节控制信号: 50Hz joint position targets
↓ PD 控制器
关节力矩: → 机器人执行
两个 ★ 步骤是 Ch15 和 Ch16 的连接点。没有 Ch15 的 tracker,文生动作只是一段好看的动画——无法在物理世界中执行。这就是为什么本章是 Ch15 的后续而不是独立章节。
视频姿态估计的工程管线
以 WHAM 为例,从安装到输出的完整流程:
# 安装 WHAM
git clone https://github.com/yohanshin/WHAM.git
cd WHAM
pip install -e .
# 下载预训练模型
bash fetch_demo_data.sh
# 运行姿态估计
python demo.py \
--video_path input_video.mp4 \
--output_dir results/ \
--save_pkl \
--render # 可选:渲染叠加可视化
# 输出文件结构
# results/
# ├── wham_output.pkl # SMPL 参数
# ├── rendered_video.mp4 # 叠加可视化
# └── poses/ # 逐帧 3D 关节位置
WHAM 输出的 pkl 结构:
import pickle
with open('results/wham_output.pkl', 'rb') as f:
data = pickle.load(f)
# data 包含:
# 'body_pose': (T, 69) — 23 个 SMPL body joint × 3 axis-angle
# 'global_orient': (T, 3) — root 朝向 (axis-angle)
# 'transl': (T, 3) — root 全局平移
# 'betas': (10,) — 体形参数
# 'pred_cam': (T, 3) — 预测的相机参数
WHAM vs 4DHumans 的选择:WHAM 的 transl 字段提供了全局平移估计——这对机器人控制至关重要(机器人需要知道"走了多远",不只是"关节怎么弯")。4DHumans 的全局平移估计较弱。如果你的任务需要全局位移(如行走、跑步),用 WHAM。如果只需要局部姿态(如站立手势),4DHumans 和 WHAM 都可以。
⚠️ 常见陷阱
⚠️ 编程陷阱:CLIP text embedding 维度不匹配。错误做法:假设所有 CLIP 模型输出 512 维。实际上 ViT-B/32 输出 512 维,ViT-L/14 输出 768 维。模型和维度必须配对,否则 downstream 网络的输入维度不匹配。正确做法:在配置中明确指定 CLIP model name 和对应的 embedding dim。
💡 概念误区:文生动作生成的动作直接可用。MDM/T2M-GPT 生成的是运动学轨迹,不保证物理可行。必须经过 tracking policy(Ch15)转化为物理控制信号。直接把运动学轨迹发送给关节 PD 控制器——如果轨迹要求脚在空中悬停(物理不可行),PD 控制器会产生巨大力矩,机器人猛烈振荡。
🧠 思维陷阱:视频估计精度 ~5cm 够用了。5cm 的平均误差看起来不大,但某些关键帧(如接触瞬间、转向起始)的误差可能远大于平均值。这些关键帧的误差会导致 tracking policy 在物理上不可行——Ch15.5 的 physics filter 就是为了解决这个问题。
SMPL 表示基础
几乎所有视频姿态估计和动作生成方法都使用 SMPL(Skinned Multi-Person Linear model)作为中间表示。理解 SMPL 的参数结构对于后续所有 retarget 工作至关重要。
SMPL 参数(详见附录 G):
| 参数 | 维度 | 含义 | 用途 |
|---|---|---|---|
betas |
(10,) | 体形参数 | 控制身高、胖瘦,retarget 时用于骨骼缩放 |
global_orient |
(3,) | root 朝向 | axis-angle 表示,需要转换到机器人坐标系 |
body_pose |
(69,) | 23 关节 \(\times\) 3 axis-angle | 核心运动参数,需要 IK 转为机器人关节角 |
transl |
(3,) | root 位置 | 全局平移,需要身高比缩放 |
关键工程概念:SMPL 的每个关节是 3-DoF ball joint(用 axis-angle 表示),但 G1 的每个关节是 1-DoF hinge joint(用单个角度表示)。这意味着 SMPL 的 \(23\times3=69\) 维关节空间不能直接映射到 G1 的 29 维——需要通过 IK(逆运动学) 求解。TextOp 的一个关键创新就是绕过了 SMPL,直接在 G1 的 29 维空间中训练(Robot-Skeleton 表示),避免了 retarget 误差。
WHAM 输出处理示例
从 WHAM 输出到可用的关节数据,需要以下处理:
# WHAM 输出处理(实际可运行代码片段)
import numpy as np
import pickle
# 加载 WHAM 输出
with open('results/wham_output.pkl', 'rb') as f:
wham_data = pickle.load(f)
print("WHAM 输出结构:")
for key, val in wham_data.items():
if isinstance(val, np.ndarray):
print(f" {key}: shape={val.shape}, dtype={val.dtype}")
# 典型输出:
# body_pose: shape=(300, 69), dtype=float32 — 10s × 30fps = 300 帧
# global_orient: shape=(300, 3), dtype=float32
# transl: shape=(300, 3), dtype=float32
# betas: shape=(10,), dtype=float32
# 基本信息提取
T = wham_data['body_pose'].shape[0]
fps = 30 # WHAM 输出帧率 = 视频帧率
duration = T / fps
print(f"动作长度: {duration:.1f}s, 帧数: {T}, 帧率: {fps}Hz")
# 检查全局平移范围
transl = wham_data['transl']
print(f"移动范围: x=[{transl[:,0].min():.2f}, {transl[:,0].max():.2f}], "
f"y=[{transl[:,1].min():.2f}, {transl[:,1].max():.2f}], "
f"z=[{transl[:,2].min():.2f}, {transl[:,2].max():.2f}]")
本质洞察:本章所有方法——无论是文本生成(CALM/TextOp)、视频估计(WHAM/HDMI)还是实时遥操(HumanPlus)——都可以看作"参考动作来源"的不同实例。它们的输出最终都要经过 Ch15 的 tracking policy 转化为物理可行的关节控制信号。区别只在于获取参考动作的方式和精度——MoCap 最精确但最昂贵,文本最灵活但最不精确,视频居中。理解了这个统一视角,你就能在任何新方法出现时迅速定位它在管线中的位置。
练习
- [概念题] 解释 MoCap、视频估计和文本生成三种动作来源的精度-成本 trade-off。在什么场景下你会选择每一种?
- [计算题] CLIP ViT-B/32 的 text embedding 是 512 维。如果你要把它映射到一个 150 帧 \(\times\) 29 关节的动作序列,中间需要什么结构?估算参数量。
- [跨章综合题,Ch15+Ch16] Ch15 的 BeyondMimic tracking policy 的 obs 中有 ~60 维的 motion reference terms。如果参考动作来自 MDM(而不是 MoCap),这些 reference terms 的质量会如何变化?对 tracking 效果有什么影响?
Ch15.2 已经介绍了 ProtoMotions 中 AMP → ASE → CALM 的配置切换。本节深入 CALM 的工程实现——如何把文本描述注入判别器,让策略生成符合文字描述的动作。
16.2 CALM 文本条件控制 ⭐⭐⭐
这一节解决什么问题:精读 CALM 的工程实现——text embedding 如何注入判别器,以及如何在 ProtoMotions 中完成从 AMP 到 CALM 的切换。
CALM 的核心思想
回顾 Ch15.2 的 AMP:判别器区分"策略动作"和"参考动作",但不关心哪种参考动作。AMP 学到的是"像参考数据集中的某个动作"——但你无法指定"像走路"还是"像跑步"。
CALM(Conditional Adversarial Latent Models,Tessler et al., SIGGRAPH 2023)在 AMP 的基础上加入条件——判别器不仅区分真/假,还检查动作是否匹配文字描述:
CALM 继承 AMP 的 LSGAN(最小二乘)判别器,\(D_{CALM}\) 输出回归值(参考且匹配 \(c\) 时回归到 \(+1\),否则 \(-1\)),风格 reward 沿用 AMP 的 LSGAN 形式:
从 AMP 到 CALM 的工程差异
Ch15.2 已经给出了配置差异的概览。本节深入实现细节:
AMP 的判别器(无条件):
class AMPDiscriminator(nn.Module):
def __init__(self, obs_dim, hidden_dims=(1024, 512)):
super().__init__()
input_dim = obs_dim * 2 # [s_t, s_{t+1}] 拼接
self.net = build_mlp(input_dim, hidden_dims, output_dim=1)
def forward(self, obs, next_obs):
x = torch.cat([obs, next_obs], dim=-1)
return self.net(x)
CALM 的判别器(条件——text embedding 注入):
class ConditionalAMPDiscriminator(nn.Module):
def __init__(self, obs_dim, condition_dim=512, hidden_dims=(1024, 512)):
super().__init__()
# 输入多了 condition_dim 维
input_dim = obs_dim * 2 + condition_dim
self.net = build_mlp(input_dim, hidden_dims, output_dim=1)
def forward(self, obs, next_obs, text_embedding):
# 关键:text_embedding 与状态拼接
x = torch.cat([obs, next_obs, text_embedding], dim=-1)
return self.net(x)
差异只在 input_dim 多了 condition_dim,以及 forward 多了 text_embedding 参数。
Text embedding 的来源:
class CLIPTextEncoder(nn.Module):
def __init__(self, model_name="ViT-B/32"):
super().__init__()
self.model, _ = clip.load(model_name, device="cuda")
self.model.eval()
for param in self.model.parameters():
param.requires_grad = False # 冻结 CLIP
def forward(self, text_list):
tokens = clip.tokenize(text_list).to("cuda")
with torch.no_grad():
embedding = self.model.encode_text(tokens) # (B, 512)
return embedding.float()
CLIP encoder 是冻结的——不在 CALM 训练过程中更新。这保证了 text embedding 的语义一致性。如果 CLIP 被 fine-tune,文本语义可能在训练过程中漂移,导致判别器学到的条件映射失效。
Gradient Penalty 的实现
Ch15 已经提到 gradient penalty 是 AMP 训练稳定的关键。在 CALM 中,gradient penalty 同样重要——条件判别器如果过度自信会导致 reward 二值化:
# CALM gradient penalty 实现
def compute_gradient_penalty(discriminator, real_obs, real_next_obs, text_emb):
"""计算 WGAN-GP 风格的 gradient penalty"""
real_obs.requires_grad_(True)
real_next_obs.requires_grad_(True)
# 判别器对真实数据的输出
d_real = discriminator(real_obs, real_next_obs, text_emb)
# 对输入计算梯度
grad_obs = torch.autograd.grad(
outputs=d_real,
inputs=[real_obs, real_next_obs],
grad_outputs=torch.ones_like(d_real),
create_graph=True,
retain_graph=True,
)
# 梯度范数
grad_norm = sum(g.reshape(g.size(0), -1).norm(dim=1) for g in grad_obs)
# 惩罚偏离 1 的梯度
penalty = ((grad_norm - 1.0) ** 2).mean()
return penalty
gradient_penalty_weight = 5.0 的含义:判别器的总 loss = GAN loss + 5.0 \(\times\) gradient penalty。较大的 penalty weight 让判别器更"保守"——它的输出更平滑,策略获得的 reward 信号更连续。如果 penalty weight 太小(如 0.1),判别器容易过拟合到训练数据,对策略新生成的动作给出极端的判断。
CALM 的完整训练 Loss
# CALM 训练的 loss 组成
def calm_training_step(agent, batch):
"""CALM 的一步训练"""
obs = batch['obs']
next_obs = batch['next_obs']
actions = batch['actions']
text_labels = batch['text_labels']
# === 1. PPO Loss(策略优化) ===
# 与 Ch15 的 PPO 完全相同
ppo_loss = compute_ppo_loss(agent.actor, agent.critic, batch)
# === 2. 判别器 Loss ===
# 2a. 对真实数据(AMASS motions + 对应 text)
text_emb = agent.text_encoder(text_labels)
real_obs, real_next_obs = sample_real_transitions(batch['motion_data'])
d_real = agent.discriminator(real_obs, real_next_obs, text_emb)
# 2b. 对策略生成的数据
d_fake = agent.discriminator(obs, next_obs, text_emb)
# 2c. LSGAN(最小二乘)判别器 loss——AMP/CALM 用回归目标:参考→+1,策略→-1
disc_loss = 0.5 * torch.mean((d_real - 1.0) ** 2) \
+ 0.5 * torch.mean((d_fake + 1.0) ** 2)
# 2d. Gradient penalty
gp = compute_gradient_penalty(agent.discriminator, real_obs, real_next_obs, text_emb)
disc_total = disc_loss + 5.0 * gp
# === 3. AMP/CALM 风格 Reward(LSGAN 形式,给 PPO 使用的额外 reward) ===
with torch.no_grad():
amp_reward = torch.clamp(1.0 - 0.25 * (d_fake - 1.0) ** 2, min=0.0)
# === 4. Encoder Loss(ASE/CALM 特有) ===
if hasattr(agent, 'encoder'):
latent = agent.encoder(obs, text_emb)
encoder_loss = compute_encoder_loss(latent, batch)
return ppo_loss, disc_total, amp_reward
CALM 训练监控
| Tensorboard Panel | 健康趋势 | 异常信号 | 对应动作 |
|---|---|---|---|
disc/loss |
稳定在 0.5-1.5 | 降到 ~0 | 判别器过度自信 → 增大 gp_weight |
disc/accuracy |
稳定在 50-70% | >90% | 判别器过拟合 → 增大 gp_weight |
amp_reward |
缓慢上升 | 不涨或下降 | 策略没学到匹配风格 |
policy/entropy |
缓慢下降 | 快速降到 0 | entropy 坍塌 → 加 entropy bonus |
text_diversity |
多样的 prompt | 集中在少数 | 训练数据标注不均衡 |
关键监控:disc/accuracy 应该在 50-70%。如果 >90%,判别器太强了——策略获得的 reward 信号太弱。如果 <50%,判别器太弱——策略没有受到有效的风格约束。
ProtoMotions 中的 CALM 配置
# examples/experiments/calm/mlp.yaml
agent:
_target_: protomotions.agents.calm.CALM
# 条件判别器
discriminator:
_target_: protomotions.modules.ConditionalAMPDiscriminator
hidden_dims: [1024, 512]
gradient_penalty_weight: 5.0
discriminator_weight: 1.0
condition_dim: 512 # CLIP ViT-B/32
# 条件 skill encoder
encoder:
_target_: protomotions.modules.ConditionalSkillEncoder
latent_dim: 64
hidden_dims: [512, 256]
condition_dim: 512
encoder_weight: 1.0
# CLIP text encoder
text_encoder:
_target_: protomotions.modules.CLIPTextEncoder
model_name: "ViT-B/32"
learning_rate: 5.0e-4
gamma: 0.99
训练数据准备:Motion + Text 配对
CALM 需要动作和文字描述的配对数据。AMASS 本身没有文字标注——需要额外的标注数据集:
| 数据集 | 动作数 | 文字标注 | 适用 |
|---|---|---|---|
| BABEL | ~43h AMASS(28k+ 序列标签 / 63k+ 帧标签) | 自然语言动作描述 | 文本-动作标注 |
| TEACH | ~3K 标注 | 时间对齐的文字-动作对 | 高质量对齐 |
| HumanML3D | ~14K | 每个动作 3 段文字描述 | MDM/T2M-GPT 训练 |
| KIT-ML | ~3K | 英文动作描述 | 辅助数据 |
Motion YAML manifest 中的 text 字段(Ch15.2 已提到):
motions:
- file: data/amass/walking_slow.npy
fps: 30
text: "A person walks forward slowly with relaxed arms"
- file: data/amass/running_fast.npy
fps: 30
text: "A person runs forward quickly with swinging arms"
- file: data/amass/jumping.npy
fps: 30
text: "A person jumps in place"
AMP 忽略 text 字段,CALM 使用它。这种设计让同一个 manifest 文件可以被所有算法复用。
CALM 训练流程
# Step 1: 准备带文字标注的 motion 数据
# 使用 BABEL/TEACH 标注的 AMASS 子集
python scripts/prepare_calm_data.py \
--amass_dir data/amass/ \
--babel_dir data/babel/ \
--output data/calm_dataset.yaml
# Step 2: CALM 训练
python protomotions/train_agent.py \
+exp=calm_mlp \
+robot=g1 \
+simulator=isaacgym \
motion_file=data/calm_dataset.yaml \
num_envs=4096 \
max_iterations=15000 \
experiment_name=g1_calm
# Step 3: 文本条件评估
python protomotions/eval_agent.py \
+robot=g1 +simulator=isaacgym \
checkpoint=results/g1_calm/last.ckpt \
text_prompt="walk forward slowly"
可控性实验:同一 prompt 不同 seed
CALM 的一个关键特性是同一 prompt 可以生成多样化的动作——因为 latent space 的不同采样会产生风格变化:
# 同一 prompt,不同 seed
for seed in 1 2 3 4 5; do
python protomotions/eval_agent.py \
+robot=g1 +simulator=isaacgym \
checkpoint=results/g1_calm/last.ckpt \
text_prompt="walk forward slowly" \
seed=$seed \
--record_video
done
# 比较 5 个视频的差异
# 预期:都在走路,但步幅、摆臂幅度、节奏有差异
本质洞察:CALM 的条件控制不是"精确指定动作"——它是约束动作的风格分布。"walk forward slowly"约束了速度(慢)和方向(前),但没有约束步幅(大步慢走 vs 小步慢走)或摆臂(自然摆 vs 手插口袋)。这种"约束但不完全指定"的特性既是优势(灵活性)也是局限(不可控的维度可能产生不自然的行为)。
⚠️ 常见陷阱
⚠️ 编程陷阱:text_embedding 在训练中被错误更新。错误做法:CLIP encoder 的 requires_grad=True,导致 CALM 的 discriminator loss 反向传播到 CLIP 中。现象:训练初期正常但后期 text embedding 语义漂移,相似文本产生完全不同的动作。正确做法:冻结 CLIP encoder,for param in clip_model.parameters(): param.requires_grad = False。
💡 概念误区:CALM 可以响应任意文本。CALM 的文本理解能力受限于训练数据的标注覆盖。如果 BABEL/TEACH 中没有"moonwalk"的标注,CALM 对"do a moonwalk"的响应是不可预测的——可能生成一个和"walk"类似的动作(CLIP 空间中"moonwalk"和"walk"接近),也可能完全不对。
🧠 思维陷阱:CALM 的 latent_dim 越大越好。latent_dim=64 意味着 skill space 是 64 维的。太大(如 256)会导致 encoder 难以学到紧凑的表示——很多维度是冗余的,采样时容易产生 out-of-distribution 的 latent。太小(如 8)会导致表达能力不足——所有动作被压缩到 8 维空间,不同风格无法区分。
练习
- [实验题] 在 ProtoMotions 中训练 CALM,使用 5 个不同的 text prompt(走、跑、跳、转身、蹲下)。记录每个 prompt 下策略生成的动作质量。哪些 prompt 的效果好?为什么?
- [配置题] 写出从 ASE 切换到 CALM 需要修改的具体配置字段。标注哪些是"替换",哪些是"新增"。
- [设计题] 如果你要让 CALM 支持中文文本(如"慢慢走"),需要做哪些修改?CLIP 支持中文吗?如果不支持,有什么替代方案?
CALM 用文本描述控制动作风格,但只能给出"整体约束"(如"慢走")。MaskedMimic 更进一步——它可以从部分约束(一个关键帧、一个目标位置、一段文字)补全整个动作序列。
16.3 MaskedMimic 的 Motion Inpainting ⭐⭐⭐
这一节解决什么问题:理解 MaskedMimic 如何用"部分信息 → 补全完整动作"的思想统一多种控制模态。
MaskedMimic 的核心思想
MaskedMimic(Tessler et al., SIGGRAPH Asia 2024)的关键洞察是:
Key insight: 所有控制指令都可以看作部分的运动描述。文本描述指定了风格但不指定关节角度;关键帧指定了某些时刻的姿态但不指定中间过渡;目标位置指定了末端位置但不指定到达路径。一个统一的 "motion inpainting" 模型可以从任意部分描述中补全完整动作。
这个思想类似于 NLP 中的 masked language model(BERT):给定一个句子中的部分词,预测被遮挡的词。MaskedMimic 给定一个动作的部分信息(mask),预测被遮挡的部分。
两阶段训练
MaskedMimic 在 ProtoMotions 中的训练分为两个阶段:
Phase 1: 全约束专家(PPO)
这一阶段和 Ch15 的 motion tracking 完全相同——训练一个能精确跟踪参考动作的 expert policy。
# Phase 1: 全约束 expert
python protomotions/train_agent.py \
+exp=mimic_mlp +robot=g1 +simulator=isaacgym \
motion_file=data/amass_full.yaml \
max_iterations=50000 \
experiment_name=g1_expert
Expert 的 obs 包含完整的参考运动信息(~160 维,和 Ch15 的 tracking task 一致)。
Phase 2: Masked 蒸馏(在线 teacher-student distillation)
Phase 2 把 expert 的全信息策略蒸馏到一个能处理部分信息的 transformer。注意这是在线蒸馏:训练中机器人被初始化到随机动作的随机帧,expert(Phase 1 冻结策略)观察未遮挡的未来动作并实时给出 action,student 观察随机遮挡版本并预测 expert 的 action——监督信号是 expert 的在线输出,而不是预录的离线数据集。它不是 PPO(不重新探索),但也不是从固定数据集做离线 BC:
# MaskedMimic Phase 2 蒸馏(概念性实现)
class MaskedMimicTrainer:
def __init__(self, expert_policy, student_model, mask_ratio=0.7):
self.expert = expert_policy
self.expert.eval() # 冻结 expert
self.student = student_model # transformer
self.mask_ratio = mask_ratio
self.optimizer = torch.optim.Adam(self.student.parameters(), lr=1e-4)
def train_step(self, obs_full, motion_description):
"""
obs_full: 完整的 observation(包含全部参考运动信息)
motion_description: 结构化的运动描述(关键帧 + 文本 + 目标位置等)
"""
# 1. 用 expert 生成 ground truth action
with torch.no_grad():
expert_action = self.expert(obs_full)
# 2. 随机 mask motion_description
mask = self.generate_random_mask(motion_description)
masked_description = motion_description * mask
# 3. Student 从 masked description 预测 action
student_action = self.student(obs_full[:, :PROPRIO_DIM], masked_description)
# 注意:student 的 obs 只有 proprio 部分 + masked description
# 不包含完整的 motion reference(那是 expert 专属的)
# 4. BC loss: 模仿 expert 的 action
loss = F.mse_loss(student_action, expert_action)
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
return loss.item()
def generate_random_mask(self, description):
"""随机遮挡部分运动描述"""
mask = torch.ones_like(description)
# 随机选择遮挡类型
mask_types = ['keyframes', 'text', 'target_pos', 'body_parts']
selected = random.sample(mask_types, k=random.randint(1, 3))
for mt in selected:
if mt == 'keyframes':
# 遮挡某些时间帧的姿态
frame_mask = torch.rand(description.shape[1]) > self.mask_ratio
mask[:, frame_mask, :KEYFRAME_DIM] = 0
elif mt == 'text':
# 遮挡文本 embedding
if random.random() < self.mask_ratio:
mask[:, :, TEXT_START:TEXT_END] = 0
elif mt == 'target_pos':
# 遮挡目标位置
if random.random() < self.mask_ratio:
mask[:, :, TARGET_START:TARGET_END] = 0
elif mt == 'body_parts':
# 遮挡某些身体部位
parts_to_mask = random.sample(range(NUM_BODY_PARTS),
k=int(self.mask_ratio * NUM_BODY_PARTS))
for part in parts_to_mask:
mask[:, :, BODY_STARTS[part]:BODY_ENDS[part]] = 0
return mask
Phase 2 的关键设计: - Student 模型是 transformer(不是 MLP)——因为 mask 后的输入长度和内容是变化的,transformer 通过 attention 机制自然处理变长输入 - 监督式蒸馏 loss(MSE 到 expert action)而不是 PPO——在线查询 expert 直接学习比重新探索更稳定(属于在线 teacher-student distillation,不是离线数据集 BC) - 每个 batch 的 mask 组合不同——模型在训练中看到所有可能的 mask 模式
推理时的多模态控制
训练完成后,MaskedMimic 支持多种控制方式——只需要提供不同的 mask:
| 控制方式 | 提供的信息 | 遮挡的信息 | 应用场景 |
|---|---|---|---|
| 文本控制 | text embedding | 关键帧、目标位置 | "walk slowly" |
| 关键帧控制 | 起始和终止姿态 | 文本、中间帧 | 动画插值 |
| 目标导航 | 目标 (x, y, yaw) | 文本、关键帧 | 走到指定位置 |
| 混合控制 | 文本 + 起始姿态 | 目标位置、中间帧 | "从当前姿态开始跑步" |
| 全约束 | 全部信息 | 无 | 等同于 expert |
反事实推理:如果不用 mask 而是为每种控制方式训练一个独立模型会怎样? 需要 5 个独立模型(每种控制方式一个),训练和维护成本是 MaskedMimic 的 5 倍。更关键的是,独立模型之间不共享知识——文本控制模型不知道关键帧控制模型学到的运动规律。MaskedMimic 通过共享 transformer 参数,让不同控制模式之间隐式传递知识。
与 HOVER 的对比
| 维度 | HOVER(Ch14.6) | MaskedMimic |
|---|---|---|
| Mask 对象 | 身体部位(root/手臂/头...) | 信息类型(关键帧/文本/位置...) |
| Phase 1 | 全身跟踪 teacher | 全约束 tracking expert |
| Phase 2 | DAgger 蒸馏 | 在线 teacher-student 蒸馏 |
| 输出 | 关节位置目标 | 关节位置目标 |
| 用途 | 多控制模态(导航/操作/遥操) | 多信息源(文本/关键帧/目标) |
两者的思想高度相似——都是"训练一个全信息的 expert,然后蒸馏到能处理部分信息的 student"。区别在于 mask 的维度不同——HOVER 遮挡身体部位,MaskedMimic 遮挡信息类型。
ProtoMotions 中的 MaskedMimic 训练
# Phase 1: 训练全约束 expert
python protomotions/train_agent.py \
+exp=mimic_mlp +robot=g1 +simulator=isaacgym \
motion_file=data/amass_full.yaml \
max_iterations=50000 \
experiment_name=g1_expert
# Phase 2: 在线 teacher-student 蒸馏
python protomotions/train_masked_mimic.py \
+robot=g1 \
expert_checkpoint=results/g1_expert/last.ckpt \
motion_file=data/amass_full.yaml \
mask_ratio=0.7 \
max_iterations=100000 \
experiment_name=g1_masked_mimic
注意:MaskedMimic 的 Phase 2 是在线 teacher-student 蒸馏而不是 PPO。它仍然 rollout 与环境交互(student 用自己的动作推进),但学习信号来自实时查询冻结 expert 的 action(监督式蒸馏),而不是 PPO 的 reward 探索;它也不是从一个固定离线数据集做 BC。
⚠️ 常见陷阱
⚠️ 编程陷阱:MaskedMimic 的 Phase 2 是监督式蒸馏而非 RL 探索。错误做法:用 PPO 训练 Phase 2。现象:训练不稳定,因为 mask 后的输入信息不足以让 PPO 有效探索。正确做法:用在线 teacher-student 蒸馏,从 Phase 1 expert 实时取 action 作为监督。
💡 概念误区:MaskedMimic 可以从任意少的信息恢复完整动作。如果 mask 掉 99% 的信息(只给一个词"walk"),恢复的动作质量会很差。mask_ratio 有一个实用范围(~0.3-0.8),太高会导致补全质量下降。
练习
- [架构分析题] MaskedMimic 和 BERT 都使用了 mask 机制。列出 3 个相同点和 2 个不同点。
- [设计题] 如果你要用 MaskedMimic 做"给定起始姿态和目标位置,补全中间过渡动作",mask 应该怎么设置?哪些信息保留、哪些遮挡?
- [跨章综合题,Ch14+Ch16] MaskedMimic 的 Phase 2 蒸馏和 Ch14 HOVER 的 DAgger 蒸馏都是从 expert → student 的知识传递。对比两者在以下维度的差异:(a) expert 的训练方式,(b) student 的架构(MLP vs transformer),(c) mask 的含义,(d) 蒸馏 loss 的类型。
MaskedMimic 和 HOVER 的统一视角
回顾 Ch14.6 的 HOVER 和本节的 MaskedMimic,它们实际上是同一个思想的两种实例——"训练全信息 expert → 蒸馏到能处理部分信息的 student":
| 组件 | HOVER (Ch14.6) | MaskedMimic (Ch16.3) |
|---|---|---|
| Expert 训练 | PPO,全身 tracking reward | PPO,全约束 motion tracking |
| Expert 的 obs | 全身参考运动 + privileged | 完整参考运动 + 环境信息 |
| Student 架构 | MLP | Transformer |
| Mask 维度 | 身体部位(root/手臂/头/腿) | 信息类型(文本/关键帧/目标/场景) |
| 蒸馏方法 | DAgger(在线,rollout 中收集数据) | 在线 teacher-student 蒸馏(rollout 中实时查询 expert action) |
| Student 的 obs | 部分身体的参考 + proprio | 部分信息(masked description)+ proprio |
| 推理时输入 | 特定身体部位的控制信号 | 文本/关键帧/目标位置等组合 |
HOVER 与 MaskedMimic 的蒸馏都在线进行。 HOVER 用 DAgger——student 在自己的 rollout 中向 expert 查询标签。MaskedMimic 也是在线 teacher-student 蒸馏——student 用自己的动作 rollout,同时实时查询冻结 expert 在未遮挡观察下的 action 作为监督目标;两者都在 rollout 分布上学习,因此都能缓解纯离线 BC 的 distribution shift 问题。区别更多在 mask 的维度(身体部位 vs 信息类型)而非"在线/离线"。
工程启示:如果你要构建一个支持多种控制模态的统一控制器,有两条路线:
路线 A(HOVER 模式):
按身体部位分 mask → 训练 → 部署时选择控制哪些部位
适用:遥操作、导航+操作解耦
路线 B(MaskedMimic 模式):
按信息类型分 mask → 训练 → 部署时选择提供什么信息
适用:文本控制、关键帧插值、目标导航
两条路线可以组合——理论上可以同时在身体部位和信息类型两个维度上做 mask。但这会导致训练样本需求指数增长(mask 组合数太多),实际中通常选一条路线。
CALM 和 MaskedMimic 是学术方法——它们展示了概念的可行性。TextOp 把这些概念工程化为一个可部署的实时系统——从键盘输入文本到 G1 执行动作,端到端闭环。
16.4 TextOp 实时文生动作管线 ⭐⭐⭐
这一节解决什么问题:精读 TextOp 的两层架构和工程实现——如何实现"打字 → G1 立即动"的实时系统。
TextOp 是什么
TextOp(arXiv 2602.07439 预印本,项目页 text-op.github.io,代码 TeleHuman/TextOp)是一个实时交互式文生动作系统。它允许用户在 G1 运行过程中随时修改文本指令——机器人平滑地从当前动作过渡到新指令描述的动作。
两层架构
用户文本输入 (streaming)
↓
┌──────────────────────────────────┐
│ High-Level: RobotMDAR │
│ (Robot Motion Diffusion │
│ AutoRegressive Model) │
│ │
│ 输入: text embedding + 最近动作 │
│ 输出: 短时域运动学轨迹 (~1s) │
│ 推理: ~20ms per chunk │
└──────────────────────────────────┘
↓ 运动学轨迹
┌──────────────────────────────────┐
│ Low-Level: BeyondMimic Tracker │
│ (Ch15 的 tracking policy) │
│ │
│ 输入: proprio + reference motion │
│ 输出: joint position target │
│ 频率: 50 Hz │
└──────────────────────────────────┘
↓ 关节命令
Robot (G1)
关键设计:RobotMDAR 是自回归的——它不是一次生成整段动作(像 MDM 那样),而是每次生成 ~1 秒的短片段,基于当前文本和最近的动作上下文。这意味着用户可以在运行中修改文本,RobotMDAR 在下一个 chunk 中就会响应新指令。
RobotMDAR 的实现
TextOp 的 RobotMDAR 基于 DART(Diffusion-based AutoRegressive Transformer)的重构:
# RobotMDAR 的推理循环(概念性)
class RobotMDAR:
def __init__(self, model, clip_encoder, chunk_length=50):
self.model = model # 扩散 + 自回归 transformer
self.clip_encoder = clip_encoder
self.chunk_length = chunk_length # 50 帧 = 1 秒 @ 50Hz
self.motion_context = [] # 最近的动作上下文
def generate_chunk(self, text_prompt, current_state):
"""生成下一个 ~1 秒的运动学轨迹"""
# 文本编码
text_emb = self.clip_encoder(text_prompt) # (512,)
# 拼接动作上下文
if len(self.motion_context) > 0:
context = torch.cat(self.motion_context[-3:], dim=0)
else:
context = current_state.unsqueeze(0)
# 扩散去噪生成
noise = torch.randn(self.chunk_length, 29) # 29-DoF G1
trajectory = self.model.denoise(noise, text_emb, context)
# trajectory: (chunk_length, 29) — 关节角度序列
# 更新上下文
self.motion_context.append(trajectory)
if len(self.motion_context) > 5:
self.motion_context.pop(0)
return trajectory
TextOp 的工程亮点
1. Robot-Skeleton Motion Representation
TextOp 不使用 SMPL ball-and-socket 表示——它使用机器人骨骼的单自由度关节表示。SMPL 的每个关节是 3-DoF ball joint(用 axis-angle 或 quaternion 表示),但 G1 的每个关节是 1-DoF hinge joint。
SMPL 表示: 24 joints × 3 axis-angle = 72 维
G1 表示: 29 joints × 1 angle = 29 维
直接用 SMPL 表示训练 → 生成的动作需要从 72 维转换到 29 维 → 会引入转换误差。TextOp 从一开始就在 G1 的 29 维空间中训练,避免了这个问题。
2. Generator-Augmented Training Data
Tracking policy 在 MoCap 数据上训练,但部署时要跟踪 RobotMDAR 生成的动作——两者的分布不同。TextOp 把 RobotMDAR 生成的动作也加入 tracker 的训练数据中,缩小分布差距:
Tracker 训练数据 = MoCap 数据 (AMASS) + RobotMDAR 生成数据
3. Streaming 指令修改
TextOp 支持在运行中修改文本指令——不需要停下来重新开始。这通过 RobotMDAR 的自回归特性实现:每个 chunk 独立依赖当前文本,文本变化时下一个 chunk 自动响应。
TextOp 代码结构
TeleHuman/TextOp/
├── TextOpRobotMDAR/ # 高层文生动作模型
│ ├── model/ # DART-based diffusion autoregressive
│ ├── data/ # 数据处理
│ └── train.py # 训练脚本
├── TextOpTracker/ # 低层 tracking policy
│ ├── (built on BeyondMimic)
│ └── train.py
├── TextOpDeploy/ # 部署
│ ├── sim2sim/ # MuJoCo sim-to-sim
│ ├── sim2real/ # G1 真机
│ └── keyboard_joystick.py # 键盘输入文本
├── dataset/ # 数据预处理脚本
│ ├── retarget_smpl_to_g1.py
│ └── prepare_babel_annotations.py
└── deps/ # 第三方依赖
训练和部署流程
TextOp 的训练分为三个独立阶段——每个阶段可以独立跑通再进入下一阶段:
阶段 1:数据预处理
# Step 1a: AMASS → G1 29-DoF retarget
python dataset/retarget_smpl_to_g1.py \
--amass_dir data/amass/ \
--output_dir data/g1_motions/ \
--fps 50 # 目标帧率
# Step 1b: BABEL 文本标注对齐
# BABEL 为 AMASS 中的部分动作提供了自然语言标注
python dataset/prepare_babel_annotations.py \
--babel_dir data/babel/ \
--motion_dir data/g1_motions/ \
--output data/g1_motions_with_text.yaml
# 输出的 YAML manifest 格式:
# motions:
# - file: data/g1_motions/walking_01.npy
# fps: 50
# text: "A person walks forward slowly with relaxed arms"
# duration: 3.2
# - ...
数据量参考:TextOp 论文使用 AMASS 的 BABEL-TEACH 标注子集(~6K 条有文字标注的动作)+ LAFAN1 数据集。总训练数据约 15 小时的运动学轨迹。
阶段 2:训练 Tracker
# Step 2: 训练 Tracker(复用 BeyondMimic 架构)
cd TextOpTracker
python train.py --config configs/g1_tracker.yaml \
--motion_file ../data/g1_motions_with_text.yaml \
--num_envs 4096 \
--max_iterations 30000
# Tracker 的特殊之处:训练数据包含 RobotMDAR 生成的动作
# 初次训练时还没有 RobotMDAR → 先用纯 MoCap 训练
# RobotMDAR 训练好后 → 生成额外数据 → 重新训练 tracker
Generator-Augmented Training 的工程实现:
# TextOp tracker 的混合训练数据
def build_tracker_training_data(mocap_dir, generated_dir=None, gen_ratio=0.3):
"""构建 tracker 训练数据(混合 MoCap + 生成数据)"""
motions = []
# MoCap 数据(主体)
for f in glob(f"{mocap_dir}/*.npy"):
motions.append({"file": f, "source": "mocap", "weight": 1.0})
# RobotMDAR 生成数据(辅助)
if generated_dir:
for f in glob(f"{generated_dir}/*.npy"):
motions.append({"file": f, "source": "generated", "weight": gen_ratio})
print(f"Tracker data: {len(motions)} motions "
f"({sum(1 for m in motions if m['source']=='mocap')} mocap + "
f"{sum(1 for m in motions if m['source']=='generated')} generated)")
return motions
为什么要加入生成数据? RobotMDAR 生成的运动学轨迹和 MoCap 的分布不同——生成轨迹可能更"平滑"但关节范围更小。如果 tracker 只在 MoCap 数据上训练,部署时跟踪 RobotMDAR 的输出会有 distribution shift。加入 30% 的生成数据可以缩小这个 gap。
阶段 3:训练 RobotMDAR
# Step 3: 训练高层文生动作模型
cd TextOpRobotMDAR
python train.py --config configs/g1_mdar.yaml \
--motion_file ../data/g1_motions_with_text.yaml \
--text_encoder clip_vit_b_32 \
--chunk_length 50 \
--num_diffusion_steps 100 \
--batch_size 256 \
--max_epochs 500
# RobotMDAR 训练不需要物理引擎
# 它只学习 text → 运动学轨迹 的映射
# 训练在 CPU/GPU 上进行,不需要 Isaac Lab 或 MuJoCo
阶段 4:部署
# Step 4: sim-to-sim 部署测试(MuJoCo)
cd TextOpDeploy
python sim2sim/run.py \
--tracker_ckpt ../TextOpTracker/checkpoints/last.pt \
--mdar_ckpt ../TextOpRobotMDAR/checkpoints/last.pt \
--keyboard # 键盘输入文本
# 键盘操作:
# 输入文本后回车 → G1 开始执行对应动作
# 新文本覆盖旧文本 → G1 平滑过渡到新动作
# Ctrl+C 退出
# Step 5: sim-to-real 部署(真机 G1)
python sim2real/deploy.py \
--tracker_ckpt ../TextOpTracker/checkpoints/last.pt \
--mdar_ckpt ../TextOpRobotMDAR/checkpoints/last.pt \
--robot_ip 192.168.123.xxx \
--text "walk forward slowly"
TextOp 训练的资源需求
| 阶段 | GPU | 时间 | 说明 |
|---|---|---|---|
| 数据预处理 | CPU | ~2h | AMASS retarget + BABEL 对齐 |
| Tracker 训练 | 1 \(\times\) 4090 | ~8h | PPO, 4096 envs, 30K iter |
| RobotMDAR 训练 | 1 \(\times\) 4090 | ~12h | Diffusion, 500 epochs |
| Tracker 重训(含生成数据) | 1 \(\times\) 4090 | ~6h | 加入 MDAR 生成数据 |
| 总计 | ~28h | 单 4090 |
⚠️ 常见陷阱
⚠️ 编程陷阱:Tracker 和 RobotMDAR 的动作表示不一致。错误做法:RobotMDAR 在 SMPL 空间训练,Tracker 在 G1 关节空间训练——中间需要 retarget 转换。现象:实时部署时转换引入延迟和误差。正确做法:TextOp 的设计就是为了避免这个问题——两者都在 G1 29-DoF 空间中工作。
💡 概念误区:TextOp 的响应延迟是 chunk 长度。chunk_length=50 帧 = 1 秒 ≠ 响应延迟 1 秒。RobotMDAR 的推理时间约 20ms——用户修改文本后 ~20ms 内就开始生成新 chunk。感知到的延迟是 RobotMDAR 推理时间 + 动作过渡时间,通常 < 200ms。
🧠 思维陷阱:TextOp 可以完全替代 MoCap。TextOp 生成的动作自然度和精度不如 MoCap + BeyondMimic。对于需要精确复现特定动作的场景(如舞蹈编排),MoCap 仍然更好。TextOp 的优势是交互性和灵活性——不需要预录 MoCap,用户实时输入文本就能控制。
练习
- [实验题] 使用 TextOp 的 sim-to-sim 部署,依次输入 "walk forward", "turn left", "jump"。观察 G1 的过渡是否平滑。
- [设计题] TextOp 的 RobotMDAR 每次生成 1 秒 (50 帧) 的 chunk。如果改为 0.5 秒 (25 帧),响应更快但质量可能下降。如果改为 2 秒 (100 帧),质量更好但响应更慢。你会怎么选择?为什么?
- [跨章综合题,Ch15+Ch16] TextOp 的 Tracker 复用了 Ch15 的 BeyondMimic。列出 Tracker 需要的修改:(a) 训练数据(加入 RobotMDAR 生成数据),(b) observation(是否需要额外信息),(c) reward(是否需要调整 σ)。
16.5 LeVERB 端到端 VLA ⭐⭐
这一节解决什么问题:理解 VLA(Vision-Language-Action)的端到端架构与 TextOp 分层架构的差异。
从分层到端到端
TextOp 是分层架构:高层(文本→运动学轨迹)和低层(运动学轨迹→关节控制)明确分离。LeVERB(Xue, Huang et al., arXiv 2506.13751)代表另一条路线——端到端 VLA:
分层架构(TextOp):
文本 → [RobotMDAR] → 运动学轨迹 → [Tracker] → 关节控制
端到端架构(LeVERB):
文本 + 视觉 → [VLM] → latent action → [WBC] → 关节控制
核心区别:TextOp 的中间表示是运动学轨迹(人类可理解);LeVERB 的中间表示是latent action(模型学到的、人类不可理解的压缩表示)。
LeVERB 的双层系统
LeVERB 使用 Kahneman 的"System 1/System 2"类比:
| 层级 | 角色 | 输入 | 输出 | 训练 |
|---|---|---|---|---|
| System 2(高层 VLM) | "慢思考" | 视觉 + 语言指令 | latent action (CVAE) | 合成渲染的运动学 demo |
| System 1(低层 WBC) | "快反应" | proprio + latent action | 关节位置目标 | RL (PPO) in Isaac Lab |
CVAE(Conditional VAE)latent action space:高层 VLM 不直接输出关节角度——它输出一个 latent vector,低层 WBC 解码这个 latent 为关节控制。
# LeVERB CVAE latent action space(概念性)
class LatentActionCVAE(nn.Module):
def __init__(self, obs_dim, action_dim, latent_dim=32):
super().__init__()
# Encoder: obs + action → latent (训练时)
self.encoder = nn.Sequential(
nn.Linear(obs_dim + action_dim, 256),
nn.ReLU(),
nn.Linear(256, latent_dim * 2), # mean + logvar
)
# Decoder: obs + latent → action (推理时)
self.decoder = nn.Sequential(
nn.Linear(obs_dim + latent_dim, 256),
nn.ReLU(),
nn.Linear(256, action_dim),
)
def encode(self, obs, action):
h = self.encoder(torch.cat([obs, action], dim=-1))
mean, logvar = h.chunk(2, dim=-1)
return mean, logvar
def decode(self, obs, latent):
return self.decoder(torch.cat([obs, latent], dim=-1))
def reparameterize(self, mean, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mean + eps * std
CVAE 的好处是 latent space 是连续且紧凑的(~32 维),高层 VLM 只需要在这个低维空间中做决策——远比直接预测 29 维关节角度容易。
LeVERB 的关键数字
- 评估基准:150+ tasks from 10 categories
- Zero-shot 简单导航:80% 成功率
- 总体成功率:58.5%
- 对比 naive hierarchical VLA:7.8× 提升
- 评估平台:Unitree G1 in Isaac Lab
与 TextOp 的详细选型对比
| 维度 | TextOp(分层) | LeVERB(端到端) |
|---|---|---|
| 中间表示 | 运动学轨迹(可解释) | latent action(不可解释) |
| 视觉输入 | 无 | 有(RGB) |
| 训练数据 | AMASS + BABEL 文字标注 | 合成渲染的视觉 demo |
| 部署复杂度 | 中(两个独立模型) | 高(VLM + WBC) |
| 响应速度 | ~200ms | ~500ms(VLM 推理更慢) |
| 灵活性 | 高(文本可实时修改) | 更高(文本 + 视觉) |
| 可调试性 | 高(可检查中间轨迹) | 低(latent 不可解释) |
| 适用场景 | 纯文本交互控制 | 需要视觉理解的复杂任务 |
| 成熟度 | 较高(有预训练模型) | 研究前沿(代码部分开源) |
| GPU 需求 | 1 × 4090(推理) | 多 GPU(VLM 推理更重) |
跨领域类比:TextOp 和 LeVERB 的区别就像导航中的"文字路线指引"和"带地图的语音导航"。文字路线指引(TextOp)简单直接——"直走100米,左转"——你不需要看到路况也能执行。语音导航+地图(LeVERB)更强大——它能看到路况、避开拥堵、适应变化——但系统更复杂、需要更多计算资源。对于简单的路线(直走到目的地),文字就够了;对于复杂的路线(需要看路况决策),需要地图+视觉。
本质洞察:分层架构(TextOp)和端到端架构(LeVERB)的根本 trade-off 是可解释性 vs 信息整合。分层架构的中间表示(运动学轨迹)是人类可理解的——你可以检查、可视化、手动修改它。端到端架构的中间表示(latent action)是模型学到的——它可能包含比运动学轨迹更丰富的信息(如对环境的理解),但你无法直接检查它的含义。在工程实践中,分层架构因为可调试性更受青睐;在研究前沿,端到端架构展示了更强的任务泛化能力。
⚠️ 常见陷阱
💡 概念误区:端到端一定比分层好。端到端避免了信息瓶颈(中间表示的信息损失),但引入了可调试性问题——当系统失败时,你不知道是高层理解错了还是低层执行错了。TextOp 失败时你可以查看中间轨迹:如果轨迹对但执行错 → tracker 问题;如果轨迹不对 → RobotMDAR 问题。
🧠 思维陷阱:VLA 只需要一个大模型就够了。LeVERB 的"端到端"并不是说一个模型从 RGB → 关节控制。它仍然有两层——高层 VLM 和低层 WBC——只是中间表示从运动学轨迹变成了 latent action。低层 WBC 仍然需要单独的 RL 训练。
LeVERB 的 WBC 低层训练
LeVERB 的 System 1(低层 WBC)本质上是一个 conditioned-on-latent 的 tracking policy:
# LeVERB WBC actor(概念性实现)
class WBCActor(nn.Module):
def __init__(self, proprio_dim, latent_dim=32, action_dim=29):
super().__init__()
self.net = nn.Sequential(
nn.Linear(proprio_dim + latent_dim, 512),
nn.ELU(),
nn.Linear(512, 256),
nn.ELU(),
nn.Linear(256, action_dim),
)
def forward(self, proprio, latent_action):
"""
proprio: (N, proprio_dim) — robot joint pos/vel/gravity
latent_action: (N, latent_dim) — from high-level VLM
"""
x = torch.cat([proprio, latent_action], dim=-1)
return self.net(x)
WBC 的训练使用 PPO,reward 包含 tracking + task 组件。训练时 latent_action 由 CVAE encoder 从 ground truth action 编码得到(不需要 VLM 参与),部署时 latent_action 由 VLM 生成。
与 Ch15 tracker 的关系:LeVERB 的 WBC 和 Ch15 的 BeyondMimic tracker 在结构上高度相似——都是"接收某种形式的参考信号 + proprio → 关节控制"。区别在于参考信号的形式:BeyondMimic 接收运动学轨迹(~60 维),LeVERB 接收 latent action(~32 维)。
LeVERB 的训练数据构造
LeVERB 的高层 VLM 训练数据来自合成渲染的运动学 demo——不需要真机数据:
训练数据构造流程:
1. 在 Isaac Lab 中创建多种任务场景
2. 用 expert policy 执行任务,记录:
- 每步的 egocentric RGB 渲染
- 每步的语言描述(自动标注或手写)
- 每步的 action(关节控制信号)
3. 通过 CVAE encoder 把 action → latent_action
4. 训练数据 = (RGB, text, latent_action) 三元组
5. 高层 VLM 学习: (RGB, text) → latent_action
这种合成数据方法避免了真机数据收集的高成本——但代价是 sim-to-real gap 可能更大(渲染的 RGB 和真实 RGB 有视觉差异)。
练习
- [对比题] TextOp 和 LeVERB 分别在什么场景下更适合?列出 3 个场景及推荐方案。
- [设计题] 如果你要设计一个"看到桌上的杯子 → 走过去拿起来"的系统,用 TextOp 需要什么额外模块?用 LeVERB 呢?哪个更合适?为什么?
- [架构分析题] LeVERB 的 CVAE latent_dim=32。如果改为 4 或 128,分别有什么 trade-off?提示:从信息瓶颈和 VLM 预测难度两个角度分析。
16.6 视频 → 机器人完整管线 ⭐⭐
这一节解决什么问题:建立从 YouTube 视频到 G1 执行动作的完整数据管线。
端到端管线
Video (MP4)
↓ WHAM / 4DHumans
SMPL 参数 (body_pose, global_orient, transl)
↓ retarget_smpl_to_g1.py
G1 关节角度 + root 轨迹
↓ physics_filter.py (Ch15.5)
过滤后的物理可行动作
↓ csv_to_npz.py (Ch15.1)
G1 motion.npz
↓ BeyondMimic tracking (Ch15.1)
Tracking Policy
↓ ONNX export + RoboJuDo
G1 真机执行
每一步的工具和注意点
Step 1: 视频 → SMPL
# 使用 WHAM(全局平移更准),命令以官方 demo.py 为准
python demo.py \
--video input.mp4 \
--output_pth results/ \
--save_pkl
# 输出: results/<视频名>.pkl
# 该 pkl 按 subject id 组织为 dict,每个 subject 含:
# pose (T, 72), trans (T, 3), pose_world (T, 72), trans_world (T, 3), betas (10,), verts, frame_ids
# 其中 pose[:, :3] 是 root orient,pose[:, 3:] 是 body pose;world 版为世界坐标系结果
Step 2: SMPL → G1 retarget
# retarget 的关键步骤(概念性)
import numpy as np
def retarget_smpl_to_g1(smpl_pkl_path, output_csv_path, person_id=0):
"""SMPL → G1 29-DoF retarget(WHAM 输出按 subject id 组织,先选人再拆字段)"""
import pickle
with open(smpl_pkl_path, 'rb') as f:
data = pickle.load(f)
subj = data[person_id] # WHAM pkl 顶层是 {subject_id: {...}}
pose = subj['pose_world'] # (T, 72):前 3 维 root orient,其余 body pose
global_orient = pose[:, :3] # (T, 3)
body_pose = pose[:, 3:] # (T, 69) = 23 joints × 3 axis-angle
transl = subj['trans_world'] # (T, 3) 世界坐标系平移
T = len(body_pose)
g1_joints = np.zeros((T, 29))
g1_root_pos = np.zeros((T, 3))
g1_root_quat = np.zeros((T, 4))
for t in range(T):
# SMPL FK → 24 个 joint 的 3D 位置
smpl_joints_3d = smpl_forward_kinematics(
body_pose[t], global_orient[t], transl[t]
)
# IK 求解 G1 关节角度
g1_joints[t] = inverse_kinematics_g1(
smpl_joints_3d,
joint_limits=G1_JOINT_LIMITS
)
# Root 位置和朝向
g1_root_pos[t] = transl[t] * (1.32 / 1.70) # 身高比例缩放
g1_root_quat[t] = axis_angle_to_quat(global_orient[t])
# 保存为 CSV
save_csv(output_csv_path, g1_root_pos, g1_root_quat, g1_joints)
Step 3: Physics filter(复用 Ch15.5 的 KungfuBot filter)
Step 4: CSV → NPZ(复用 Ch15.1 的 csv_to_npz.py)
Step 5: BeyondMimic tracking(复用 Ch15.1 的训练流程)
质量评估:retarget 后的 MuJoCo 回放
在训练 tracking policy 之前,先在 MuJoCo 中回放 retarget 后的动作,目视检查质量:
# MuJoCo 回放
python scripts/playback_motion.py \
--mjcf g1_29dof.xml \
--motion data/g1_walking.npz \
--viewer viser
检查要点: - 脚是否在地面以上?(脚底穿地 → retarget 的 root 高度不对) - 关节角度是否在限位内?(某个关节卡在限位 → IK 求解失败) - 动作是否连续?(跳变 → 帧间插值问题) - 自碰撞?(手臂穿过身体 → IK 没有考虑碰撞约束)
Retarget 后的自动化质量评估
除了目视检查,应该用脚本自动检测常见问题:
# 自动化质量评估脚本
def assess_retarget_quality(motion_npz_path, robot_model_path):
"""评估 retarget 后动作的物理可行性"""
import mujoco
import numpy as np
motion = np.load(motion_npz_path)
root_pos = motion['root_pos'] # (T, 3)
root_quat = motion['root_quat'] # (T, 4)
joint_pos = motion['joint_pos'] # (T, 29)
m = mujoco.MjModel.from_xml_path(robot_model_path)
issues = {"ground_penetration": 0, "joint_limit": 0,
"velocity_spike": 0, "total_frames": len(root_pos)}
for t in range(len(root_pos)):
# 检查 1: 脚底是否穿地
# (简化: 检查 root 高度是否太低)
if root_pos[t, 2] < 0.3: # G1 root 最低约 0.4m
issues["ground_penetration"] += 1
# 检查 2: 关节角度在限位内
for j in range(m.njnt):
if m.jnt_limited[j]:
lo, hi = m.jnt_range[j]
if j < len(joint_pos[t]):
if joint_pos[t, j] < lo - 0.05 or joint_pos[t, j] > hi + 0.05:
issues["joint_limit"] += 1
# 检查 3: 相邻帧速度突变
if t > 0:
dt = 0.02 # 50 Hz
joint_vel = np.abs(joint_pos[t] - joint_pos[t-1]) / dt
if np.max(joint_vel) > 20.0: # rad/s
issues["velocity_spike"] += 1
# 输出质量报告
T = issues["total_frames"]
report = {
"ground_penetration_rate": issues["ground_penetration"] / T,
"joint_limit_violation_rate": issues["joint_limit"] / (T * 29),
"velocity_spike_rate": issues["velocity_spike"] / T,
"overall_pass": (issues["ground_penetration"] / T < 0.05 and
issues["velocity_spike"] / T < 0.02),
}
print(f"Quality Report for {motion_npz_path}:")
print(f" Ground penetration: {report['ground_penetration_rate']:.1%}")
print(f" Joint limit violations: {report['joint_limit_violation_rate']:.1%}")
print(f" Velocity spikes: {report['velocity_spike_rate']:.1%}")
print(f" Overall: {'✅ PASS' if report['overall_pass'] else '❌ FAIL'}")
return report
Post-Processing: 修复常见问题
当自动化检查发现问题时,以下 post-processing 步骤可以修复大部分问题:
def postprocess_retarget(motion, robot_model):
"""修复 retarget 后动作的常见问题"""
# 1. 脚底穿地修复: 整体抬高 root
min_foot_height = compute_min_foot_height(motion, robot_model)
if min_foot_height < 0.0:
motion['root_pos'][:, 2] -= min_foot_height + 0.01 # 抬到地面以上
print(f"Lifted root by {-min_foot_height + 0.01:.3f}m")
# 2. 关节角度 clamp 到限位
for j in range(robot_model.njnt):
if robot_model.jnt_limited[j]:
lo, hi = robot_model.jnt_range[j]
motion['joint_pos'][:, j] = np.clip(
motion['joint_pos'][:, j], lo + 0.01, hi - 0.01
)
# 3. 速度突变平滑: 低通滤波
from scipy.signal import savgol_filter
for j in range(29):
motion['joint_pos'][:, j] = savgol_filter(
motion['joint_pos'][:, j],
window_length=11, # 11 帧窗口 @ 50Hz = 0.22s
polyorder=3,
)
# 4. 坐标系转换 (Y-up → Z-up)
# SMPL: Y-up → MuJoCo: Z-up
if is_y_up(motion):
R = np.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]]) # Y→Z 旋转
motion['root_pos'] = motion['root_pos'] @ R.T
motion['root_quat'] = rotate_quaternions(motion['root_quat'], R)
return motion
Savgol 滤波器的窗口选择:window_length=11(0.22 秒)是经验值。太小(如 5)保留了大部分噪声;太大(如 21)会把快速动作(如挥拳)也平滑掉。对于高动态动作(功夫、跑酷),建议用 window_length=7。
完整的视频→训练管线脚本
#!/bin/bash
# 完整的 YouTube 视频 → G1 tracking 管线
set -e
VIDEO_URL="$1"
OUTPUT_DIR="output/$(date +%Y%m%d_%H%M%S)"
mkdir -p $OUTPUT_DIR
echo "Step 1: 下载视频"
yt-dlp -f 'best[height<=720]' -o "$OUTPUT_DIR/input.mp4" "$VIDEO_URL"
echo "Step 2: WHAM 姿态估计"
cd WHAM
python demo.py --video "$OUTPUT_DIR/input.mp4" \
--output_pth "$OUTPUT_DIR/wham/" --save_pkl
cd ..
echo "Step 3: SMPL → G1 retarget"
python retarget_smpl_to_g1.py \
--input "$OUTPUT_DIR/wham/input.pkl" \
--output "$OUTPUT_DIR/g1_raw.csv"
echo "Step 4: CSV → NPZ (帧率对齐)"
python csv_to_npz.py \
--input-csv "$OUTPUT_DIR/g1_raw.csv" \
--output-npz "$OUTPUT_DIR/g1_motion.npz" \
--input-fps 30 --output-fps 50
echo "Step 5: Post-processing + 质量评估"
python postprocess_and_assess.py \
--motion "$OUTPUT_DIR/g1_motion.npz" \
--robot g1_29dof.xml \
--output "$OUTPUT_DIR/g1_motion_clean.npz"
echo "Step 6: Physics filter"
python physics_filter.py \
--motion "$OUTPUT_DIR/g1_motion_clean.npz" \
--robot g1_29dof.xml \
--output "$OUTPUT_DIR/g1_motion_filtered.npz"
echo "Step 7: MuJoCo 回放检查"
python playback_motion.py \
--mjcf g1_29dof.xml \
--motion "$OUTPUT_DIR/g1_motion_filtered.npz" \
--viewer viser --record "$OUTPUT_DIR/playback.mp4"
echo "管线完成!检查 $OUTPUT_DIR/playback.mp4"
echo "如果回放质量好,继续训练 tracking policy:"
echo " uv run train Mjlab-Tracking-Flat-Unitree-G1 \\"
echo " --env.commands.motion.motion-file $OUTPUT_DIR/g1_motion_filtered.npz"
⚠️ 常见陷阱
⚠️ 编程陷阱:SMPL 和 G1 的坐标系不同。SMPL 使用 Y-up 坐标系,MuJoCo 使用 Z-up。不转换会导致 G1 "躺在地上"。正确做法:在 retarget 中做 Y-up → Z-up 旋转。
⚠️ 编程陷阱:视频帧率和动作帧率不匹配。视频通常是 24/30 FPS,WHAM 输出与视频帧率相同。G1 tracking 需要 50 Hz。必须用插值对齐帧率。
练习
- [实践题] 选择一段 YouTube 舞蹈视频(10 秒以内),用 WHAM 提取 SMPL 参数,retarget 到 G1,在 MuJoCo 中回放。记录遇到的问题和解决方案。
- [设计题] 如果视频中有多个人,WHAM 的输出是什么?如何选择要跟踪的那个人?
16.7 精读:HumanPlus Shadowing Transformer ⭐⭐⭐
这一节解决什么问题:理解 HumanPlus 如何用单 RGB 相机实现实时全身遥操作——从姿态估计到 HST 到 HIT 的完整链路。
HumanPlus 系统概览
HumanPlus(Fu, Zhao, Wu, Wetzstein, Finn, CoRL 2024, MarkFzp/humanplus)是 Stanford 的全栈人形遥操作系统。它的核心贡献是:
用一个 RGB 相机就能实时控制人形机器人做复杂任务——穿鞋、叠衣服、打乒乓球。
两阶段架构
Stage 1: Humanoid Shadowing Transformer (HST) — 低层控制器
训练: AMASS (40h MoCap) + PPO in IsaacGym
输入: robot proprio + retargeted target pose
输出: 19-D joint position target (H1)
硬件: 单 RTX 4090
特点: zero-shot sim-to-real
Stage 2: Humanoid Imitation Transformer (HIT) — 高层技能策略
训练: 真机遥操作数据 (40 demos/task)
输入: egocentric RGB + robot proprio
输出: target pose for HST
硬件: 真机 H1 + 两个头部 RGB 相机
HST 的实现
HST 是一个 decoder-only transformer,在 AMASS 数据集上用 PPO 训练:
# HST 架构(概念性简化)
class HumanoidShadowingTransformer(nn.Module):
def __init__(self, obs_dim, action_dim=19, hidden_dim=256, n_heads=4, n_layers=4):
super().__init__()
self.embed = nn.Linear(obs_dim, hidden_dim)
self.transformer = nn.TransformerDecoder(
nn.TransformerDecoderLayer(hidden_dim, n_heads, dim_feedforward=512),
num_layers=n_layers
)
self.action_head = nn.Linear(hidden_dim, action_dim)
def forward(self, obs_sequence):
"""
obs_sequence: (batch, seq_len, obs_dim)
包含: robot joint pos, joint vel, target pose from retarget
"""
x = self.embed(obs_sequence)
# Causal mask (decoder-only: 只看历史)
mask = nn.Transformer.generate_square_subsequent_mask(x.size(1))
x = self.transformer(x, x, tgt_mask=mask)
action = self.action_head(x[:, -1, :]) # 只取最后一步的输出
return action
为什么用 transformer 而不是 MLP? AMASS 包含极其多样的动作——从慢走到快跑到跳跃。MLP 处理固定长度输入,缺少对时间上下文的建模能力。Transformer 通过 attention 机制可以关注历史中的关键帧——比如"上一步是左脚着地"这个信息对决定"这一步应该抬右脚"很重要。
HST 的 obs 结构:
# HST 每步的 obs 组成
obs = torch.cat([
robot_joint_pos, # (19,) 当前关节角度
robot_joint_vel, # (19,) 当前关节速度
base_angular_velocity, # (3,) IMU 角速度
projected_gravity, # (3,) 重力方向投影
target_joint_pos, # (19,) retarget 的目标关节角度
target_joint_vel, # (19,) retarget 的目标关节速度
], dim=-1) # 总共 ~82 维
HST 接收窗口化的 obs 序列(如最近 10 步),而不是单帧 obs。这让 transformer 的 attention 能够关注时间上下文。
HST 的训练 Reward
# HumanPlus HST reward(论文 Table 1 的 reward terms)
rewards = {
# 指令跟踪(论文用指数核匹配目标速度)
"target_xy_velocity": "exp 匹配 target forward/lateral 速度",
"target_yaw_velocity": "exp 匹配 target yaw 速度",
# 姿态跟踪(L2 惩罚)
"target_joint_positions": "-||q - q_tg||^2",
"target_roll_pitch": "-||(roll,pitch) - (roll,pitch)_tg||^2",
# 正则与稳定项
"energy": "-||τ ⊙ dq||^2 (torque × velocity)",
"feet_contact": "feet contact 与 feet slipping 惩罚",
"alive": "存活 bonus",
}
注意:上表对应 HumanPlus 论文 Table 1 的实际项(target xy/yaw velocities、target joint positions、target roll & pitch、energy、feet contact/slipping、alive),与 Ch15 通用 motion tracking reward 不完全相同——HumanPlus 跟踪的是目标速度与目标关节角,而非完整 root 位姿轨迹。HST 的优势在于它在整个 AMASS(约 40 小时、11,000+ motions)上训练,是一个通用低层控制器。
HST 的训练配置
| 参数 | 值 | 说明 |
|---|---|---|
| 训练数据 | AMASS (40+ 小时) | 11,000+ motions |
| 机器人 | Unitree H1 (33-DoF) | 含 6-DoF Inspire 手 |
| 策略输出 | 19 维 | 身体关节 setpoints(手部不经 HST,直接给 PD) |
| 并行环境 | IsaacGym | 数千并行(具体 env 数/训练时长以官方实现为准) |
| HST 控制频率 | 50 Hz | 策略推理频率 |
| 身体姿态估计 | 25 fps | WHAM body retarget(RTX 4090) |
| 手部姿态估计 | 10 fps | HaMeR hand retarget(RTX 4090) |
| Sim-to-Real | Zero-shot | 无 fine-tuning |
实时遥操作的数据流
外部 RGB 相机
├─ WHAM: 身体姿态估计 (25 fps) → SMPL body_pose + global_orient + transl
│ ↓ retarget → H1 body target(19 个身体关节角)
│ ↓ 频率提升 (25 → 50 Hz, 线性插值)
│ ↓ HST 输入: robot proprio + body target
│ ↓ HST 推理 (~1ms on 4090) → 19 维身体 joint position commands (50 Hz)
│ ↓ PD 控制器 → 身体关节力矩
└─ HaMeR: 手部姿态估计 (10 fps) → MANO hand_pose
↓ retarget → Inspire 手 target joint angles
↓ 直接送入 PD 控制器(不经 HST)→ 手部关节力矩
频率对齐是工程关键:身体姿态 25 fps、手部姿态 10 fps,而 HST 策略推理 50 Hz。身体 target 在两步策略推理之间插值一次即可(25 Hz 下相邻帧姿态变化小);手部 target 角直接进 PD,不经过 HST。
HIT 的数据收集和训练
HST 解决了"如何从 target pose 控制机器人"。HIT 解决了"如何从视觉观察生成 target pose"——让机器人从遥操作 demo 中学会自主执行任务。
数据收集流程:
1. 人站在 H1 旁边做动作(穿鞋、叠衣服...)
2. 外部 RGB 相机捕捉人的动作
3. WHAM + HaMeR → SMPL-X → retarget → target pose
4. target pose → HST → H1 实时执行(shadowing)
5. H1 的 egocentric RGB + joint state 记录为 demo
每个任务: 约 25-40 个 demos;单条任务时长不一(论文中 5-50 秒不等,
如 Greeting ~5s、Rearrange ~10s、Warehouse/Fold Clothes ~20s、Wear a Shoe ~50s)
自主技能训练:
6. 训练 HIT (decoder-only transformer)
输入: egocentric RGB (ResNet-18 features) + robot proprio
输出: target pose for HST
7. HIT 部署: 头部 RGB → HIT → target pose → HST → 自主执行
40 个 demos 的工程意义:传统 imitation learning 需要数千个 demo。HumanPlus 用 40 个就够,关键在于 HST 提供了一个强大的低层控制器——HIT 只需要学"做什么"(高层策略),不需要学"怎么做"(低层控制)。这种分层设计极大降低了 demo 需求。
HumanPlus HIT 自主任务成功率(论文 Table 5,whole-task success):
| 任务 | demos 数 | 成功率 | 难度 |
|---|---|---|---|
| 叠衣服 (Fold Clothes) | 40 | 100% | 中——灵巧操作 |
| 整理物体 (Rearrange Objects) | 30 | 90% | 中——全身协调 |
| 打字 "AI" (Type AI) | 30 | 80% | 高——精细操作 |
| 双机器人打招呼 (Two-Robot Greeting) | 30 | 90% | 低 |
| 仓库搬运 (Warehouse) | 25 | 90% | 中——全身协调 |
| 穿鞋并行走 (Wear a Shoe and Walk) | 40 | 60% | 高——接触丰富 |
注意:打乒乓球(table tennis)在论文中是 shadowing(实时遥操作) 的展示任务,不是 HIT 自主策略的 40-demo 任务,不应列入上面的自主成功率表。
⚠️ 常见陷阱
⚠️ 编程陷阱:HumanPlus 基于 IsaacGym Preview 4(非 Manager-Based)。代码结构与 mjlab/Isaac Lab 不同,不能直接复用。如果要在 mjlab 中实现类似功能,需要重新实现 HST 的训练循环。
💡 概念误区:HST 就是 Ch15 的 tracking policy 加了 transformer。HST 确实是一种 tracking policy,但它的训练数据覆盖范围远大于 Ch15 的单条/少量动作——HST 在整个 AMASS(40 小时)上训练,是一个通用 motion tracker。Ch15 的 BeyondMimic 通常在特定任务的数据子集上训练。
⚠️ 编程陷阱:HumanPlus 的 retarget 包含手部映射。H1 使用 Inspire RH56DFX 6-DoF 手,SMPL-X 手部有 15 个关节 per hand。retarget 需要同时处理身体(WHAM → H1 body)和手部(HaMeR → Inspire hand)。如果只做身体 retarget 忽略手部,穿鞋/叠衣服等精细任务无法执行。
HumanPlus 的 DR 配置
HST 的 zero-shot sim-to-real 依赖于训练时的 Domain Randomization:
# HumanPlus DR 配置(基于论文 Table 2)
dr_config = {
# 论文 Table 2 的随机化项与范围
"base_payload": [-3.0, 3.0], # kg, 基座负载
"end_effector_payload":[0.0, 0.5], # kg, 末端负载
"center_of_base_mass": [-0.1, 0.1], # m, 基座质心偏移(每个轴)
"motor_strength": [0.8, 1.1], # 电机力矩缩放
"friction": [0.3, 0.9], # 摩擦系数
"control_delay": [0.02, 0.04], # s, 控制延迟
}
上表对应 HumanPlus 论文 Table 2 的实际项(base payload、end-effector payload、center of base mass、motor strength、friction、control delay)。若要加入 restitution、电机零位、观测噪声、随机推力等其它 DR 项,应作为"可选扩展",不属于论文 Table 2。
为何 DR 范围相对保守:HST 需要在极其多样的动作(约 40 小时 AMASS)上工作——如果 DR 太强,策略在困难动作(如单脚站立)上就难以收敛。
反事实推理:如果 HST 训练时完全不加 DR 会怎样? 仿真中 HST 的 tracking 质量可能更高(没有噪声干扰),但部署到真机后立即崩溃——真机的摩擦、电机特性和传感器噪声与仿真不同。DR 是 zero-shot sim-to-real 的必要条件。
HumanPlus 在 mjlab 中的等价路径
如果你想在 mjlab 中复现 HumanPlus 的 HST,有以下选项:
- 最简路径:用 Ch15 的 BeyondMimic + 整个 AMASS 数据集训练。BeyondMimic 的 MLP 策略不如 transformer 强大,但对于大多数遥操作场景够用。
- 进阶路径:修改 RSL-RL 的 actor 为 transformer 架构,在 mjlab 的 tracking task 中训练。需要自己实现 transformer actor 和 obs 历史窗口化。
- 最佳路径:等待 mjlab 社区贡献的 transformer actor 模块——社区活跃,可能已有或即将有实现。
练习
- [架构分析题] HST 使用 decoder-only transformer。如果改用 encoder-only(BERT 风格)会有什么问题?提示:考虑因果性——策略只能看到过去,不能看到未来。
- [跨章综合题,Ch14+Ch15+Ch16] HumanPlus 的 HST 和 Ch14 的 HOVER、Ch15 的 BeyondMimic 都是 tracking policy。列出三者在以下维度的对比:(a) 训练数据规模,(b) 支持的控制模态,(c) 网络架构,(d) 部署平台。
- [设计题] 如果你要用 HumanPlus 的方法让 G1(而不是 H1)学会穿鞋,需要哪些修改?列出 retarget、HST 训练和 HIT 数据收集三个方面的具体变化。
16.8 精读:HDMI 单目视频 → loco-manipulation ⭐⭐
这一节解决什么问题:理解 HDMI 如何从单目视频中同时提取人体和物体轨迹,训练人形的全身交互技能。
HDMI 的独特价值
前面的方法(CALM、TextOp、HumanPlus)都关注人体运动——机器人在空旷空间中移动。HDMI(Weng, Li et al., CMU, arXiv 2509.16757)解决的是人与物体的交互——从视频中同时提取人的运动和物体的轨迹,训练机器人的 loco-manipulation 技能。
三阶段管线
Stage 1: 视频提取(离线)
单目 RGB 视频
→ 人体姿态估计 (SMPL)
→ 物体检测 + 位姿估计 (6-DoF)
→ retarget 到 G1
→ 输出: (human_motion, object_trajectory) 配对
Stage 2: RL 训练(离线)
co-tracking reward:
r = w_h * r_human_tracking + w_o * r_object_tracking + w_i * r_interaction
三个关键设计:
1. 统一物体表示
2. Residual action space
3. 统一交互 reward
Stage 3: Zero-shot 策略部署(无需 fine-tuning)
ONNX → G1 真机
注意: 论文真机实验在机器人 pelvis 和被操作物体上贴 mocap markers,
以获取 root link 全局位姿和物体状态作为 policy observation,
并非仅靠机载视觉。论文 Limitations 将"转向机载 RGB/depth"列为未来方向。
三个关键工程设计
1. 统一物体表示
不同物体(门、箱子、杯子)有不同的几何形状和交互方式。HDMI 使用一个统一的 6-DoF 表示——不管什么物体,都用 (position, orientation) + 动力学状态表示:
# 统一物体表示
object_obs = torch.cat([
object_position, # (3,) 物体中心世界坐标
object_orientation, # (4,) 物体朝向四元数
object_linear_vel, # (3,) 物体线速度
object_angular_vel, # (3,) 物体角速度
relative_position, # (3,) 物体相对于机器人的位置
relative_orientation, # (4,) 物体相对于机器人的朝向
], dim=-1) # 总共 20 维
为什么使用统一表示而不是针对每种物体设计不同的 obs? 因为统一表示让不同物体类型复用同一套观察接口和训练框架——门、箱子、行李箱都用同一组物体状态字段,省去为每类物体重写 obs 结构。需要强调的是:HDMI 论文的 Limitations 明确说明当前是每个技能训练一个专用策略(one policy per skill),并非单一策略 zero-shot 泛化到任意新物体;跨新物体的鲁棒性主要依赖训练随机化覆盖,而不是"见过门和箱子就能直接处理没见过的抽屉"。
2. Residual Action Space
标准 action space 是关节位置增量 \(a = q_{default} + \Delta q\),策略需要从零学习完整的关节控制。HDMI 使用 residual action:
其中 \(q_{ref}(t)\) 来自视频提取的参考动作。策略只需要学习"相对于参考动作的微调"。
# Residual action space 的实现
class ResidualActionEnv(BaseEnv):
def apply_actions(self, actions):
"""
actions: (N, 29) — residual action from policy
"""
# 获取当前帧的参考动作
ref_action = self.motion_ref.get_joint_pos(self.current_frame) # (N, 29)
# 最终 action = reference + residual
final_action = ref_action + actions * self.residual_scale
# residual_scale 通常设为 0.1-0.3(限制 residual 幅度)
# 应用到 PD 控制器
self.robot.set_joint_position_target(final_action)
工程好处: - 探索空间被压缩——residual 只需要在 ±0.3 rad 内搜索 - 训练初期策略输出接近零 → 执行的动作接近参考 → 已经有合理的行为 - 对于接触丰富的任务(开门),接触点附近需要极精确的关节控制——residual 让策略只需微调
反事实推理:如果不用 residual 而是标准 action space 会怎样? 策略需要从零学习"走到门前"+"伸手"+"抓把手"+"推门"的完整动作序列。在接触丰富的任务中,探索空间极大,PPO 需要大量样本才能发现"抓住把手"这个关键行为。Residual action 把参考动作提供的"大致正确"作为起点,策略只需要学习"微调"来应对物理差异。
3. 统一交互 Reward
# HDMI 的统一交互 reward(概念性实现)
class HDMIReward:
def __init__(self):
self.w_body = 1.0 # 人体跟踪权重
self.w_object = 0.5 # 物体跟踪权重
self.w_contact = 0.3 # 接触奖励权重
def compute(self, env):
# 人体跟踪(复用 Ch15 的 body tracking)
r_body = body_tracking_exp(env, sigma=0.1)
r_joint = joint_tracking_exp(env, sigma=0.3)
# 物体跟踪(HDMI 新增)
obj_pos_err = torch.norm(
env.object.root_pos - env.ref_object_pos, dim=-1
)
r_obj_pos = torch.exp(-obj_pos_err ** 2 / 0.05 ** 2)
obj_ori_err = quat_diff_rad(
env.object.root_quat, env.ref_object_quat
)
r_obj_ori = torch.exp(-obj_ori_err ** 2 / 0.2 ** 2)
# 交互 reward(HDMI 新增)
# 当手接触物体时给 bonus
hand_obj_dist = torch.norm(
env.robot.hand_pos - env.object.root_pos, dim=-1
)
r_contact = torch.where(
hand_obj_dist < 0.05, # 手距物体 < 5cm
torch.ones_like(hand_obj_dist),
torch.zeros_like(hand_obj_dist),
)
total = (self.w_body * (r_body + r_joint)
+ self.w_object * (r_obj_pos + r_obj_ori)
+ self.w_contact * r_contact)
return total
HDMI 的视频提取管线细节
HDMI 需要同时从视频中提取人体和物体的轨迹——这比只提取人体更复杂:
视频 → 分别提取(按 HDMI 官方仓库的 processing steps):
人体: GVHMR 估计 SMPL 运动 → GMR / LocoMujoco 转为机器人 body/joint 状态
物体:
1. 提取物体轨迹: position、orientation、velocities
2. 把物体元数据写入 meta.json,并将物体 body 状态拼接到机器人 body 状态
得到形如 [T, B_robot + B_object, 3/4] 的张量
对齐: 人体(机器人)与物体在同一时间和坐标系下
输出: (robot_motion, object_trajectory) 配对
注意:HDMI 官方管线用的是 GVHMR + GMR/LocoMujoco,不是 WHAM/Detic/DINO;物体 6-DoF 估计工具(如 BundleSDF/FoundationPose)是可替代的通用方法,并非 HDMI 论文/仓库所采用的组件。
关键结果
| 任务 | 真机结果 | 说明 |
|---|---|---|
| 开门穿越 (Door) | 67 次连续 | 双向开门 + 穿越 |
| 行李箱 (Suitcase) | 7 次连续成功 | 接触丰富的操作 |
| 面包箱搬运 (Breadbox) | 2 次完整试验(含 180° 转身) | 抓取 + 搬运 |
| 泡沫垫搬运 (Foam mats) | 抓取 + 侧步 + 放置成功 | 全身协调 |
| Truman's Bow | 3 次完整序列(上台阶/鞠躬/坐下/挥手/跳) | 长序列全身协调 |
| 仿真任务 | 14 种 | 包括上述 + 更多 |
67 次连续开门穿越是 HDMI 最亮眼的结果——说明策略不仅能做一次,而且有足够的鲁棒性反复执行。这得益于 DR(不同门的位置、方向、阻力都随机化了)和 residual action(接触点精确控制)。
HDMI 与其他视频学习方法的对比
| 维度 | HDMI | HumanPlus | KungfuBot (Ch15.5) |
|---|---|---|---|
| 视频提取 | 人体 + 物体 | 人体 only | 人体 only |
| 物体交互 | ✅ 核心能力 | ❌ | ❌ |
| 训练方法 | RL + residual action | RL (PPO) | RL (PPO + BLO) |
| 部署机器人 | G1 | H1 | G1 |
| 典型任务 | 开门、搬箱子 | 穿鞋、叠衣服 | 功夫、跑酷 |
| 关键创新 | co-tracking + unified reward | HST + 40-demo learning | physics filter + BLO |
⚠️ 常见陷阱
⚠️ 编程陷阱:物体位姿估计的累积漂移。视频中物体位姿的估计会随时间累积误差——特别是旋转估计。可用 BundleSDF/FoundationPose 等通用 6-DoF 跟踪方法减少漂移(注意这些不是 HDMI 官方管线的组件,HDMI 提取的是 object trajectory 并写入 meta.json),但累积误差不能完全消除。
💡 概念误区:HDMI 可以学任意交互。HDMI 需要从视频中提取物体轨迹——这要求物体在视频中始终可见且可跟踪。对于小物体、透明物体或形变物体,物体跟踪的准确度会下降。
练习
- [分析题] HDMI 的 residual action space 和标准 action space 的区别是什么?在什么任务上 residual 更有优势?
- [设计题] 如果你要用 HDMI 学习"从桌子上拿起杯子喝水",需要从视频中提取什么信息?列出人体和物体各需要的轨迹。
16.9 选型决策:MoCap/视频/文本 ⭐⭐
这一节解决什么问题:在三种动作来源之间做出工程选型决策。
三种来源的完整对比
| 维度 | MoCap | 视频 | 文本 |
|---|---|---|---|
| 精度 | 极高 (<1mm) | 中等 (~5cm) | 低 (~10cm+) |
| 成本 | 极高(设备+场地+演员) | 低(相机+视频源) | 极低(键盘) |
| 灵活性 | 低(需预录) | 高(任何视频) | 极高(打字即可) |
| 实时性 | 否(需预录) | 可(实时相机,如 HumanPlus) | 可(实时文本,如 TextOp) |
| 物体交互 | 可(需特殊设备) | 可(HDMI co-tracking) | 受限(需 VLA) |
| 物理一致性 | 高(真实运动) | 需 physics filter | 需 tracker |
| 数据规模 | 有限(40h AMASS) | 无限(互联网视频) | 无限(想象力) |
| Retarget 难度 | 中(SMPL→G1) | 中(SMPL→G1) | 低(直接在 G1 空间) |
| 代表工具 | AMASS + BeyondMimic | WHAM + HDMI | CALM/TextOp |
| 代表系统 | Ch15 BeyondMimic | HumanPlus / HDMI | TextOp / LeVERB |
选型决策树(完整版)
你要解决什么问题?
│
├── "精确复现特定动作"(如特定舞蹈编排、科研复现)
│ └── 有 MoCap 设备?
│ ├── 是 → 自己录 MoCap → AMASS 格式 → BeyondMimic
│ └── 否 → AMASS 数据集中有类似动作?
│ ├── 是 → AMASS 子集 → BeyondMimic
│ └── 否 → 视频录制 → WHAM → retarget → BeyondMimic
│
├── "从视频学新技能"(如模仿 YouTube 视频中的运动)
│ ├── 只有人体运动?(走路、跑步、舞蹈)
│ │ └── WHAM → retarget → physics filter → BeyondMimic/AMP
│ └── 包含物体交互?(开门、搬东西)
│ └── HDMI (人体 + 物体 co-tracking → residual RL)
│
├── "文本控制实时交互"(如语音指令控制机器人服务员)
│ ├── 需要视觉理解?(看到桌子上的物体做决策)
│ │ └── LeVERB (VLA)
│ └── 纯文本指令?("走过去"、"跳一下")
│ └── TextOp (RobotMDAR + Tracker)
│
├── "遥操作 + 自主学习"(如工厂教工人教机器人)
│ └── HumanPlus (单 RGB 遥操 → 40 demos → HIT 自主)
│
├── "风格化运动"(如"像老年人一样走路"的风格控制)
│ └── CALM (text embedding → 条件判别器)
│
└── "统一控制器"(单一策略处理多种控制输入)
└── 多种信息源(文本+关键帧+目标位置)?
└── MaskedMimic (motion inpainting)
工程项目的典型选型
| 项目类型 | 推荐方案 | 理由 | 开发周期 |
|---|---|---|---|
| 学术论文 demo | BeyondMimic + 精选 MoCap | 精度最高,结果最好看 | 1-2 周 |
| 展会演示 | TextOp + 预设 prompt 库 | 交互性强,观众可以尝试 | 2-3 周 |
| 遥操作数据收集 | HumanPlus HST | 高效数据收集管线 | 1-2 周(训 HST) |
| 仓库搬运 POC | HDMI + 视频 demo | 物体交互能力 | 3-4 周 |
| 服务机器人 MVP | TextOp + LeVERB 混合 | 文本导航 + 视觉抓取 | 4-6 周 |
| 运动技能库 | AMASS + PHC PMCP | 大规模不遗忘 | 2-4 周 |
数据混合策略
实际项目中,通常不会只用一种数据来源——而是混合使用:
| 策略 | 组合方式 | 适用场景 | 工程注意 |
|---|---|---|---|
| MoCap + 文本 | MoCap 训练 tracker,CALM 加文本条件 | 精确 + 灵活 | text 标注覆盖 MoCap 数据 |
| MoCap + 视频 | MoCap 做基础,视频做领域增强 | 训练数据扩增 | retarget 质量对齐 |
| 视频 + 文本 | 视频 + BABEL 标注 → TextOp | 全自动管线 | 需要 BABEL 标注 |
| 全部混合 | AMASS + 视频 + BABEL → 大规模统一训练 | 通用 tracker | 数据质量分层管理 |
数据质量分层管理:混合使用不同来源的数据时,需要根据数据质量分配权重:
# 数据混合采样(概念性)
data_sources = {
"mocap_amass": {"weight": 0.5, "quality": "high"}, # MoCap: 高质量
"video_wham": {"weight": 0.3, "quality": "medium"}, # 视频: 中等质量
"generated_textop": {"weight": 0.2, "quality": "low"}, # 文生: 低质量
}
# 高质量数据更多采样,低质量数据作为增强
# 但不能完全不用低质量数据——它们提供了多样性
技术路线的发展趋势
| 趋势 | 2023 | 2024 | 2025-2026 | 预测 |
|---|---|---|---|---|
| 精度 | MoCap 主导 | 视频精度提升 | 文生精度改善 | 三者差距缩小 |
| 实时性 | 离线为主 | HumanPlus 实时遥操 | TextOp 实时文本 | 全部实时 |
| 物体交互 | 几乎无 | ExBody 初步 | HDMI 67次开门 | 通用交互 |
| 端到端 | 无 | 初步探索 | LeVERB 58.5% | VLA 主导 |
对工程师的建议:2025-2026 年是过渡期。如果你现在开始一个项目,分层架构(TextOp 模式)的可调试性和可靠性更好。但如果你在做中长期研究,端到端 VLA(LeVERB 模式)是值得投入的方向——它的任务泛化能力会随着 VLM 的进步而快速提升。
⚠️ 常见陷阱
🧠 思维陷阱:选最先进的方法就对了。先进不等于适合。如果你只需要让 G1 走到目的地,Ch14 的 velocity task 就够了——不需要 TextOp 或 LeVERB。方法的选择应该基于任务需求,不是论文的新旧。
💡 概念误区:多种数据源混合总是更好。如果低质量数据(如噪声大的视频估计)占比过高,会拉低整体训练效果——策略学到"平均化"行为。混合时必须加权——高质量数据主导,低质量数据辅助。
⚠️ 编程陷阱:不同来源的数据坐标系不一致。MoCap 数据、WHAM 输出、TextOp 生成的数据可能使用不同的坐标系约定(Y-up vs Z-up、不同的四元数约定)。混合前必须统一。
练习
- [决策题] 以下三个场景分别推荐什么方案?(a) 让 G1 在展览中表演太极拳,(b) 让 G1 在仓库中搬运箱子,(c) 让 G1 根据语音指令做日常动作。解释每个选择的理由。
- [设计题] 你需要为一个机器人服务员项目选择动作来源方案。要求:(1) 机器人需要走路送餐,(2) 需要避开障碍物,(3) 需要把盘子放在桌上。设计一个混合方案,说明每个子任务使用什么来源、什么算法。
- [跨章综合题,Ch14+Ch15+Ch16] 回顾 Ch14 的 velocity task、Ch15 的 motion tracking、和本章的多模态获取。它们形成了一个能力递进链路:velocity → tracking → multimodal。画出这条链路的技术依赖图,标注每一步复用了前一步的什么组件。
本章小结
| 知识点 | 核心要点 | 难度 |
|---|---|---|
| 动作来源三个时代 | MoCap → 视频 → 文本,精度-成本-灵活性 trade-off | ⭐⭐ |
| MDM/VQ-VAE | 扩散生成运动学轨迹 + token 化动作序列 | ⭐⭐ |
| CALM | text embedding 注入条件判别器,~30行配置差异 | ⭐⭐⭐ |
| MaskedMimic | 部分约束 → 补全完整动作,两阶段(PPO expert + BC 蒸馏) | ⭐⭐⭐ |
| TextOp | RobotMDAR + BeyondMimic Tracker 两层架构,实时文本控制 | ⭐⭐⭐ |
| LeVERB | 端到端 VLA,System 1(WBC) + System 2(VLM) | ⭐⭐ |
| 视频管线 | WHAM → SMPL → retarget → filter → tracking | ⭐⭐ |
| HumanPlus | HST(AMASS+PPO) + HIT(40 demos),单 RGB 遥操作 | ⭐⭐⭐ |
| HDMI | 视频 co-tracking 人体+物体 → residual action RL → 67 次开门 | ⭐⭐ |
| 选型决策 | MoCap/视频/文本 → 精度-成本-实时性决策树 | ⭐⭐ |
本章与其他章节的关系
| 本章知识 | 前置来源(回顾) | 后续应用(预告) |
|---|---|---|
| CALM 文本条件控制 | Ch15 AMP 判别器 | Ch20 多模态全身控制 |
| MaskedMimic inpainting | Ch14 HOVER mask + Ch15 BC | Ch20 任务级控制 |
| TextOp 两层架构 | Ch15 BeyondMimic tracker | Ch22 自定义 task |
| HumanPlus HST | Ch15 tracking + AMASS | Ch23 sim-to-real |
| HDMI co-tracking | Ch15 tracking + Ch17 manipulation | Ch20 loco-manipulation |
| 视频管线 (WHAM→retarget→filter) | Ch15 physics filter | Ch22 数据管线 |
| LeVERB CVAE | Ch09 Teacher-Student + Ch15 tracker | Ch22 VLA 集成 |
| VQ-VAE tokenization | Ch06 Reward 设计(连续→离散) | Ch20 技能组合 |
关键数字速查
| 数字 | 含义 | 来源 |
|---|---|---|
| 512 | CLIP ViT-B/32 text embedding 维度 | 16.1/16.2 |
| ~30 行 | AMP → CALM 配置差异 | 16.2 ProtoMotions |
| 0.7 | MaskedMimic Phase 2 默认 mask_ratio | 16.3 |
| 50 帧 | TextOp RobotMDAR chunk_length (= 1 秒 @ 50Hz) | 16.4 |
| ~20ms | TextOp RobotMDAR 单 chunk 推理时间 | 16.4 |
| 58.5% | LeVERB 总体成功率 | 16.5 |
| 150+ | LeVERB 评估任务数 | 16.5 |
| ~5cm | WHAM 身体姿态估计精度 | 16.6 |
| 25 Hz | HumanPlus 相机频率 | 16.7 |
| 40 | HumanPlus HIT 每任务 demo 数 | 16.7 |
| 19 | HumanPlus HST 输出维度 (H1) | 16.7 |
| 67 | HDMI 连续开门穿越次数 | 16.8 |
| 14 | HDMI 仿真中的任务数 | 16.8 |
| 32 | LeVERB CVAE latent_dim | 16.5 |
本章建立的核心能力检查
| 能力 | 验证方式 | 对应小节 |
|---|---|---|
| 理解三种动作来源的 trade-off | 能为具体项目写选型报告 | 16.1, 16.9 |
| 运行 CALM 文本控制 | ProtoMotions CALM 训练收敛 | 16.2 |
| 理解 MaskedMimic mask 机制 | 能解释不同 mask 对应什么控制 | 16.3 |
| 理解 TextOp 两层架构 | 能画出完整数据流 | 16.4 |
| 执行视频→G1 管线 | YouTube → MuJoCo 回放成功 | 16.6 |
| 精读 HumanPlus | 能解释 HST/HIT 每个组件 | 16.7 |
| 精读 HDMI | 能解释 residual action 的工程好处 | 16.8 |
| 做出选型决策 | 能为 3 种场景推荐合适方案 | 16.9 |
累积项目 C(续):本章新增模块
模块清单
| 模块 | 状态 | 说明 |
|---|---|---|
| CALM 文本控制 (ProtoMotions) | ✅ | AMP → CALM 配置切换 |
| MaskedMimic 理解 | ✅ | 两阶段训练 + mask 机制 |
| TextOp 文生动作 | ✅ | RobotMDAR + Tracker 两层架构 |
| 视频 → G1 管线 | ✅ | WHAM → retarget → filter → tracking |
| HumanPlus HST 理解 | ✅ | transformer + AMASS + PPO |
| HDMI co-tracking 理解 | ✅ | 人体 + 物体 + residual action + 统一 reward |
| LeVERB VLA 理解 | ✅ | System 1/2 + CVAE latent action |
| 选型决策框架 | ✅ | 三种来源对比 + 决策树 |
实践里程碑(建议用时 5-6 天)
| 里程碑 | 预计用时 | 完成标准 | 前置 |
|---|---|---|---|
| M1: CALM 训练 | 6h | ProtoMotions 中 CALM 收敛,3 个 prompt 生成不同动作 | Ch15 M3 完成 |
| M2: TextOp sim-to-sim | 4h | 键盘输入文本→G1 在 MuJoCo 中执行 | Ch15 M1 完成 |
| M3: 视频→G1 管线 | 6h | 选一段 YouTube 视频→WHAM→retarget→MuJoCo 回放 | Ch15 M2 完成 |
| M4: 视频→tracking | 6h | M3 的动作→BeyondMimic 训练→G1 物理执行 | M3 |
| M5: HumanPlus 精读 | 3h | 能画出 HST 架构图并解释每个组件 | — |
| M6: HDMI 精读 | 3h | 能画出三阶段管线并解释 residual action | — |
| M7: 选型报告 | 2h | 为一个具体项目写选型决策文档 | M1-M6 |
总计 ~30 GPU-hours(RTX 4090)。M1(CALM)和 M2(TextOp sim-to-sim)是最有价值的实践——它们让你亲手体验"打字→机器人动"的全流程。M3-M4 是完整的视频管线实践。M5-M7 是精读和综合应用。
从本章到下一章
本章的多模态动作获取建立了"从文本/视频/相机获取参考动作"的能力。但这些能力集中在全身运动——走路、跑步、舞蹈、遥操作。Ch17(机械臂与灵巧手操作)将转向精细操作——抓取、放置、装配。你会发现本章的很多方法(如 HDMI 的 residual action、HumanPlus 的 demo learning)在操作场景中也有对应——但 MDP 设计(obs 中有 object pose、action 是末端位置或关节角度)会有本质区别。
技术路线延续:
Ch14 人形 velocity (基础运动)
→ Ch15 Motion Imitation (跟踪参考动作)
→ Ch16 多模态获取 (文本/视频 → 参考动作) ← 本章
→ Ch17 操作 (抓取/放置/装配)
→ Ch20 全身控制 (locomotion + manipulation)
累积项目完成检查
| 能力 | 验证方式 | 对应小节 |
|---|---|---|
| 理解 MoCap/视频/文本三种来源的 trade-off | 能为具体项目写选型报告 | 16.1, 16.9 |
| 在 ProtoMotions 中运行 CALM | CALM 训练收敛,文本控制有效 | 16.2 |
| 理解 MaskedMimic 的 mask 机制 | 能解释不同 mask 对应什么控制方式 | 16.3 |
| 理解 TextOp 的两层架构 | 能画出 RobotMDAR + Tracker 数据流 | 16.4 |
| 执行视频→G1 管线 | 从 YouTube 视频到 MuJoCo 回放成功 | 16.6 |
| 精读 HumanPlus | 能解释 HST/HIT 的训练和部署流程 | 16.7 |
| 精读 HDMI | 能解释 co-tracking 和 residual action | 16.8 |
延伸阅读
学术论文
| 资料 | 难度 | 会议/期刊 | 说明 |
|---|---|---|---|
| Tevet et al., "Human Motion Diffusion Model (MDM)," 2023 | ⭐⭐ | ICLR 2023 | 文生动作的里程碑 |
| Zhang et al., "T2M-GPT," 2023 | ⭐⭐ | CVPR 2023 | VQ-VAE + GPT 自回归 |
| Tessler et al., "CALM," 2023 | ⭐⭐⭐ | SIGGRAPH 2023 | 条件对抗潜模型 |
| Tessler et al., "MaskedMimic," 2024 | ⭐⭐⭐ | SIGGRAPH Asia 2024 | 统一 motion inpainting |
| "TextOp," 2026 | ⭐⭐⭐ | arXiv 2602.07439(预印本) | 实时文生动作控制 |
| Fu et al., "HumanPlus," 2024 | ⭐⭐⭐ | CoRL 2024 | 单 RGB 遥操作全栈 |
| Weng et al., "HDMI," 2025 | ⭐⭐⭐ | arXiv 2509.16757 | 视频 → loco-manipulation |
| Xue et al., "LeVERB," 2025 | ⭐⭐⭐ | arXiv 2506.13751(投稿 ICLR 2026,审稿中) | 层级 latent VLA |
| Radford et al., "CLIP," 2021 | ⭐⭐ | ICML 2021 | 文本-图像对齐基础 |
工具和代码
| 资料 | 难度 | 说明 |
|---|---|---|
| NVlabs/ProtoMotions | ⭐⭐⭐ | CALM + MaskedMimic 统一框架 |
| TeleHuman/TextOp | ⭐⭐⭐ | 实时文生动作,含预训练模型 |
| MarkFzp/humanplus | ⭐⭐⭐ | HST + HIT 全栈代码 |
| yohanshin/WHAM | ⭐⭐ | 全局平移准确的视频姿态估计 |
| shubham-goel/4DHumans | ⭐⭐ | 全身+手部视频估计 |
阅读路线
- 最小路线(文本控制):16.1→16.2 CALM 训练
- 标准路线(文本+视频):上述 + 16.4 TextOp + 16.6 视频管线
- 进阶路线(全栈理解):上述 + 16.7 HumanPlus + 16.8 HDMI
- 研究路线:上述 + 16.3 MaskedMimic + 16.5 LeVERB
🔧 故障排查手册
| # | 症状 | 可能原因 | 排查步骤 | 相关小节 |
|---|---|---|---|---|
| 1 | CALM 对所有文本生成相同动作 | text embedding 未正确注入 | 打印判别器输入维度,确认包含 condition_dim | 16.2 |
| 2 | CALM 动作不自然 | 训练数据文字标注覆盖不足 | 检查 manifest 中的 text 字段覆盖率 | 16.2 |
| 3 | MaskedMimic Phase 2 loss 不降 | mask_ratio 太高 | 降低到 0.5,逐步增大 | 16.3 |
| 4 | TextOp 响应延迟大 | RobotMDAR 推理太慢 | 检查 GPU 利用率,减小 chunk_length | 16.4 |
| 5 | TextOp tracker 跟不上生成动作 | 训练数据不含生成数据 | 加入 RobotMDAR 生成数据到 tracker 训练集 | 16.4 |
| 6 | 视频 retarget 后脚穿地 | 坐标系未转换 (Y-up → Z-up) | 检查旋转变换 | 16.6 |
| 7 | WHAM 估计抖动严重 | 视频质量差或遮挡 | 用低通滤波器平滑 | 16.6 |
| 8 | HumanPlus 遥操作延迟 | 25Hz 相机 → 50Hz 策略的插值问题 | 检查线性插值逻辑 | 16.7 |
| 9 | HDMI 物体跟踪漂移 | 累积位姿误差 | 用通用 6-DoF 跟踪(如 BundleSDF/FoundationPose)或增加关键帧校正 | 16.8 |
| 10 | 混合数据训练效果差 | 数据质量权重不对 | 高质量数据主导(≥50%),低质量辅助(≤20%) | 16.9 |
Debug Checklist
文生动作(CALM/TextOp)
- [ ] CLIP model 版本和 condition_dim 匹配(ViT-B/32 → 512)
- [ ] CLIP encoder 冻结(requires_grad=False)
- [ ] Motion manifest 包含 text 字段
- [ ] 训练数据覆盖目标 text prompt 的语义
- [ ] TextOp 的 tracker 训练数据包含 RobotMDAR 生成数据
- [ ] chunk_length 合理(50 帧 = 1 秒 @ 50Hz)
视频管线
- [ ] 坐标系对齐(Y-up → Z-up)
- [ ] 帧率对齐(视频 FPS → 50 Hz)
- [ ] WHAM 输出包含 global_orient 和 transl
- [ ] retarget 后 MuJoCo 回放检查通过
- [ ] physics filter 通过率 > 50%
- [ ] 关节角度在 G1 限位范围内
HumanPlus 遥操作
- [ ] 外部 RGB 相机 ≥ 25 FPS
- [ ] WHAM + HaMeR 实时运行无掉帧
- [ ] retarget 频率与策略频率对齐
- [ ] HST 在目标机器人上 zero-shot 验证通过
附录 A:文生动作算法谱系
Motion Diffusion Model (MDM, 2023)
│ CLIP text → diffusion → motion sequence
│
├── T2M-GPT (2023): VQ-VAE + GPT 自回归
│ │ 离散 token + 语言模型
│ │
│ └── MotionGPT (2024): 统一语言+动作 token
│
├── MoMask (2024): masked + VQ-VAE + transformer
│
├── MoMaDiff (2025): frame-wise VAE + masked autoregressive diffusion
│
└── DART (2025): diffusion + autoregressive
│
└── TextOp RobotMDAR (2026): DART 重构 + G1 部署
各方法的关键差异
| 方法 | 动作表示 | 生成方式 | 条件输入 | 生成长度 |
|---|---|---|---|---|
| MDM | 连续 | 非自回归扩散 | 文本 | 固定 |
| T2M-GPT | 离散 token | GPT 自回归 | 文本 | 可变 |
| MoMask | 离散 token | Masked diffusion | 文本 + mask | 可变 |
| DART | 连续 chunk | 自回归扩散 | 文本 + 上下文 | 流式 |
| TextOp | 连续 (G1 29-DoF) | 自回归扩散 | 文本 + 上下文 | 流式 |
TextOp RobotMDAR 和 DART 的区别:RobotMDAR 使用机器人骨骼表示(G1 29-DoF),DART 使用 SMPL 表示(72 维)。机器人骨骼表示避免了 retarget 误差——生成的动作可以直接被 tracker 消费。
附录 B:Retarget 工具选型
| 工具 | 输入 | 输出 | 特点 |
|---|---|---|---|
| PHC convert_amass.py | AMASS .npz | IsaacGym pickle | 最成熟,SMPL→多种人形 |
| ProtoMotions retarget | AMASS .npz | ProtoMotions .npy | PyRoki-based,多 simulator |
| TextOp retarget | SMPL → G1 29-DoF | CSV / NPZ | G1 专用,质量高 |
| HOVER human2humanoid | SMPL → H1/G1 | Isaac Lab 格式 | HOVER 子模块 |
| HumanPlus retarget | SMPL-X → H1 33-DoF | IsaacGym | 含手部 retarget |
| 手动 IK | 任意 3D joints | 任意机器人 | 最灵活但最费力 |
选择建议:如果用 mjlab → TextOp retarget(G1 专用)。如果用 ProtoMotions → 内置 retarget。如果用 IsaacGym → PHC convert_amass.py 或 HumanPlus retarget。
附录 C:CLIP Text Embedding 速查
常用 CLIP 模型
| 模型 | Embedding 维度 | 推理速度 | 精度 |
|---|---|---|---|
| ViT-B/32 | 512 | 快 (~5ms) | 中 |
| ViT-B/16 | 512 | 中 (~8ms) | 高 |
| ViT-L/14 | 768 | 慢 (~15ms) | 最高 |
CALM 和 TextOp 通常使用 ViT-B/32——512 维足够表达动作语义,推理速度最快。
Text Prompt 设计经验
好的 text prompt 应该包含三个维度:
- 动作类型:"walk", "run", "jump", "kick"
- 风格描述:"slowly", "quickly", "gracefully", "aggressively"
- 方向/空间:"forward", "in a circle", "to the left"
示例:
| Prompt | 预期效果 |
|---|---|
| "walk forward slowly" | 慢速直线行走 |
| "run forward quickly" | 快速直线跑步 |
| "jump in place" | 原地跳跃 |
| "wave right hand" | 右手挥手 |
| "sit down carefully" | 缓慢坐下 |
| "dance energetically" | 活泼舞蹈 |
不好的 prompt: - 太模糊:"do something" → CALM 不知道做什么 - 太具体:"raise left arm to 45 degrees while bending right knee 30 degrees" → 超出 CALM 的控制精度 - 超出训练分布:"moonwalk while juggling" → 训练数据中可能没有
附录 D:视频姿态估计工具对比
安装和使用
WHAM(推荐用于全身+全局平移):
git clone https://github.com/yohanshin/WHAM.git
cd WHAM && pip install -e .
bash fetch_demo_data.sh
python demo.py --video input.mp4 --output_pth results/ --save_pkl
4D-Humans(HMR2.0,全身 SMPL body 重建与跟踪;不输出手部):
git clone https://github.com/shubham-goel/4D-Humans.git
cd 4D-Humans && pip install -e .
# 视频跟踪使用 track.py(官方命令),source 可为视频/帧目录/YouTube 链接
python track.py video.source="input.mp4"
HaMeR(专用手部估计,与 WHAM 配合):
git clone https://github.com/geopavlakos/hamer.git
cd hamer && pip install -e .
python demo.py --video_path input.mp4 --output_dir results/ --save_hands
精度对比
下表精度/FPS 为典型量级,具体值依 benchmark、硬件、分辨率与实现而异,不是固定指标。
| 工具 | 身体精度 | 手部精度 | 全局平移 | FPS |
|---|---|---|---|---|
| WHAM | ~4cm | 无 | ✅ 好 | ~20 |
| 4D-Humans | ~5cm | 无(仅 SMPL body) | ⚠️ 弱 | ~15 |
| HybrIK | ~5cm | 无 | ⚠️ 弱 | ~30 |
| HaMeR | 无 | ~2cm | N/A | ~25 |
组合推荐: - 全身运动(走、跑):WHAM alone - 全身+手部(遥操作):WHAM(身体)+ HaMeR(手部) - 快速原型:4D-Humans(全身 SMPL body 跟踪,不含手部)
附录 E:多模态管线实验记录模板
experiment:
name: g1_text_walk_v1
date: 2026-05-21
pipeline: TextOp / CALM / Video / HumanPlus
robot: Unitree G1 (29-DoF)
data_source:
type: text / video / mocap
# 文本:
prompt: "walk forward slowly"
clip_model: ViT-B/32
# 视频:
video_path: data/youtube_dance.mp4
pose_estimator: WHAM
retarget_tool: TextOp retarget
physics_filter_pass_rate: 0.85
# MoCap:
mocap_file: data/amass/walking_01.npz
generation:
method: TextOp_RobotMDAR / CALM / direct_tracking
chunk_length: 50
inference_time_ms: 18
tracking:
method: BeyondMimic / HST
mpjpe_mm: 55
episode_length_ratio: 0.88
deployment:
target: sim / sim2sim / real
success_rate: 0.90
observations:
quality: "动作自然,过渡平滑"
issues:
- "高速时手臂摆动略不自然"
- "转向时有 0.3s 延迟"
next_steps:
- "增大 shoulder_pitch std"
- "减小 chunk_length 到 30"
附录 F:本章系统的技术栈总结
| 系统 | 高层 | 低层 | 数据 | 部署 |
|---|---|---|---|---|
| CALM | 条件判别器 | PPO actor | AMASS + BABEL text | ONNX |
| MaskedMimic | BC transformer | PPO expert | AMASS + 多模态标注 | ONNX |
| TextOp | RobotMDAR (DART) | BeyondMimic tracker | AMASS + BABEL + LAFAN1 | ONNX + Jetson |
| HumanPlus | HIT (decoder transformer) | HST (decoder transformer) | AMASS + 40 real demos | IsaacGym → H1 |
| HDMI | N/A (单层 RL) | PPO + residual action | 视频 co-tracking | ONNX → G1 |
| LeVERB | VLM (System 2) | WBC (System 1) | 合成渲染 demo | Isaac Lab → G1 |
共同点:所有系统都建立在 RL-based motion tracking 的基础上——无论高层用的是什么方法(判别器/扩散/transformer/VLM),低层都需要一个物理可行的 tracking policy 来执行动作。这就是 Ch15 的 tracker 在整个体系中的核心地位。
附录 G:SMPL 表示速查
SMPL 模型参数
SMPL(Skinned Multi-Person Linear model)是几乎所有视频姿态估计和动作生成方法的标准人体表示。理解 SMPL 的参数对于 retarget 至关重要。
| 参数 | 维度 | 含义 |
|---|---|---|
betas |
(10,) | 体形参数——控制身高、胖瘦等 |
global_orient |
(3,) | root 朝向——axis-angle 表示 |
body_pose |
(69,) | 23 个身体关节 × 3 axis-angle |
transl |
(3,) | root 全局平移 |
left_hand_pose |
(45,) | SMPL-X 扩展:15 个手指关节 |
right_hand_pose |
(45,) | SMPL-X 扩展:15 个手指关节 |
expression |
(10,) | SMPL-X 扩展:面部表情 |
SMPL 关节到 G1 关节的映射
SMPL 有 24 个关节(每个 3-DoF ball joint),G1 有 29 个关节(每个 1-DoF hinge joint)。映射不是一一对应的——需要 IK 求解。
SMPL 关节 (24个): G1 关节 (29个):
0: pelvis 0-2: left_hip (3 DoF)
1: left_hip 3: left_knee (1 DoF)
2: right_hip 4-5: left_ankle (2 DoF)
3: spine1 6-8: right_hip (3 DoF)
4: left_knee 9: right_knee (1 DoF)
5: right_knee 10-11: right_ankle (2 DoF)
6: spine2 12-14: waist (3 DoF)
7: left_ankle 15-17: left_shoulder (3 DoF)
8: right_ankle 18: left_elbow (1 DoF)
9: spine3 19-20: left_wrist (2 DoF)
10: left_foot 21-23: right_shoulder (3 DoF)
11: right_foot 24: right_elbow (1 DoF)
12: neck 25-26: right_wrist (2 DoF)
13: left_collar 27-28: head (2 DoF)
14: right_collar
15: head
16: left_shoulder
17: right_shoulder
18: left_elbow
19: right_elbow
20: left_wrist
21: right_wrist
22: left_hand
23: right_hand
关键映射问题: - SMPL 的 hip 是 1 个 3-DoF ball joint → G1 的 hip 是 3 个 1-DoF hinge joint(需要分解) - SMPL 有 3 个 spine 关节 → G1 只有 1 个 waist (3-DoF)(需要合并) - SMPL 的 left_foot/right_foot 在 G1 中没有直接对应(G1 没有脚趾关节)
坐标系约定
| 系统 | 上方向 | 前方向 | 四元数约定 |
|---|---|---|---|
| SMPL | Y-up | -Z | — |
| MuJoCo | Z-up | X | (w, x, y, z) |
| PhysX/IsaacGym | Z-up | X | (x, y, z, w) |
Y-up → Z-up 转换:
# 坐标系转换
def y_up_to_z_up(position):
"""SMPL (Y-up) → MuJoCo (Z-up)"""
# x → x, y → z, z → -y
return np.stack([position[..., 0],
-position[..., 2],
position[..., 1]], axis=-1)
这个转换是整个视频管线最容易出错的地方——忘记转换会导致 G1 "躺着"或"面朝天花板"。每次 retarget 后都要用 MuJoCo 回放检查。
附录 H:本章技术路线的时间线
| 年份 | 方法 | 关键贡献 | 本章位置 |
|---|---|---|---|
| 2021 | CLIP | text-image 对齐预训练 | 16.1 基础 |
| 2021 | AMP | 判别器替代 hand-crafted reward | Ch15 前置 |
| 2023 | MDM | 文生动作扩散模型 | 16.1 |
| 2023 | T2M-GPT | VQ-VAE + GPT 动作生成 | 16.1 |
| 2023 | CALM | 条件判别器 + text embedding | 16.2 |
| 2024 | MaskedMimic | 统一 motion inpainting | 16.3 |
| 2024 | HumanPlus | 单 RGB 遥操作 + 40-demo 学习 | 16.7 |
| 2025 | HDMI | 视频 co-tracking 人体+物体 | 16.8 |
| 2025 | DART | 自回归扩散 | 16.4 前置 |
| 2025 | LeVERB | 端到端 VLA + CVAE | 16.5 |
| 2026 | TextOp | 实时文生动作部署 | 16.4 |
| 2026 | SafeFlow | TextOp + 安全门控 | 延伸 |
趋势:从 2023 年的"概念验证"(MDM/CALM 在仿真中展示可行性)到 2025-2026 年的"工程落地"(TextOp/HDMI 在真机上部署),领域正在快速成熟。下一个前沿可能是多模态统一——一个模型同时接受文本、视觉、触觉输入,生成包含 locomotion + manipulation 的全身控制信号。
各系统的 GitHub 活跃度(截至 2026 年)
| 仓库 | ⭐ 数 | 维护状态 | 推荐场景 |
|---|---|---|---|
| NVlabs/ProtoMotions | ~1.4k | ✅ 积极维护 | CALM/MaskedMimic 研究和教学 |
| TeleHuman/TextOp | ~370 | ✅ 活跃 | 实时文生动作,含预训练模型 |
| MarkFzp/humanplus | ~1.0k | ✅ 活跃 | HST + HIT 全栈遥操作 |
| yohanshin/WHAM | ~800 | ✅ 活跃 | 视频 → SMPL 姿态估计 |
工程建议:2026 年开始做多模态动作获取,最推荐的入门路径是 ProtoMotions CALM(文本控制的学术基础)或 TextOp(最接近产品级的文生动作系统)。HumanPlus 适合需要遥操作数据收集管线的项目。HDMI 适合需要物体交互的 loco-manipulation 项目。
从本章到后续章节的技术承接
| 本章能力 | 直接承接的后续章节 | 承接内容 |
|---|---|---|
| CALM 文本控制 | Ch20 全身控制 | 文本作为高层控制信号 |
| MaskedMimic inpainting | Ch20 统一控制器 | mask 机制复用 |
| TextOp 两层架构 | Ch22 自定义 env | 两层架构范式 |
| 视频管线 | Ch22 数据管线 | WHAM→retarget→filter |
| HumanPlus 遥操作 | Ch23 sim-to-real | 数据收集和部署 |
| HDMI co-tracking | Ch20 loco-manipulation | 人体+物体联合跟踪 |
结语:动作来源的选择——MoCap、视频还是文本——不是"哪个更好"的问题,而是"任务需要什么"的问题。本章展示了每种来源的完整工程管线和代表系统(CALM/MaskedMimic、TextOp、HumanPlus、HDMI、LeVERB),以及它们之间的混合策略。所有这些系统都建立在 Ch15 的 motion tracking 基础之上——Ch15 的 tracker 是 Ch16 所有方法的物理执行层。如果你能理解"文本/视频 → 运动学轨迹 → tracking policy → 关节控制"这条链路中每一步的工程实现和限制,你就掌握了多模态动作获取的核心工程能力。
下一章(Ch17 机械臂与灵巧手操作)将从全身运动转向精细操作——MDP 设计、reward 和 obs 都会有本质区别。但本章建立的选型框架(MoCap vs 视频 vs 文本)和分层架构思想(高层决策 + 低层执行)在操作场景中同样适用。
本章的核心收获:不是记住每个系统的细节——而是建立"文本/视频/MoCap → 运动学轨迹 → tracking policy → 物理执行"这条完整链路的工程直觉。新系统出现时,你只需要问三个问题:(1) 它替换了链路中的哪个环节?(2) 它的输入和输出格式是什么?(3) 它与现有环节的接口如何对齐?