流程
目前主流机器人舞蹈开发主要分为以下几个步骤(超链接均为github):
- 获得指定舞蹈的视频或动捕数据.bvh
- 将上述数据转为SMPL格式,目前SMPLX更常用,对于视频可以使用TRAM(ASAP使用)或GVHMR(宇树官方使用),对于动捕直接使用GMR重定向或bvh2smpl(待验证)
- 将SMPL数据重定向到机器人的各个关节,可用算法H2O或OmniH2O(ASAP使用),也可GMR(宇树官方使用)
- 通过DeepMimic系列RL算法训练(目前BeyondMimic较常用),仿真通常使用IsaacGym(较老)或IsaacLab配合Isaacsim(较新)等
- 将.onnx动作策略部署至mujoco进行sim2sim验证
- 将.onnx动作策略部署至实机进行sim2real测试,目前推荐代码简洁robomimic-deploy,多合一robojudo。
以上1-3为数据处理阶段,目的是给训练算法提供可用的高质量数据。
第4步训练通常被认为是决定最终效果的关键步骤,也是算法工程师主要的工作内容,包括对算法的改进如与最新算法的结合、调参等。一个熟练的训练通常耗时20小时(4090),但新算法的调试可能需要一个月。
之所以使用IsaacLab训练、用mujoco验证是因为前者容易上手训练速度快,后者仿真更精准但学习较难。而最近的mujocolab结合了两者的优势,使用IsaacLab的API和mujoco的物理引擎,其RL算法使用BeyondMimic,其动作数据使用wandb线上存取,并于昨天(2025-10-10)上线了第一个训练案例:G1回旋踢
MJLAB的使用说明
虽然github上已有详细的readme,但其中存在若干不详尽或错误,因此记录。
1. 关于mjlab本体
- 安装方式建议使用Method 1, Option A, 提示错误时按提示加上--dev。
-
run demo没必要。 - 使用wandb时
- 在终端输入
wandb login或wandb login --relogin,粘贴API key。 - 在wandb新建一个名为Motions的registry,类型为all types。
- 将
csv_to_npz.py中的wandb.init(..., entity='<自己的team名字如: unitree>')。 - 训练第一步使用csv_to_npz.py,会在wandb生成一个csv_to_npz的project并将动作存在其下,例如fkdance。
- 在终端输入
MUJOCO_GL=egl uv run src/mjlab/scripts/csv_to_npz.py --input-file /path/to/motion.csv --output-name /path/to/motion.npz --input-fps 30 --output-fps 50 --render
更新后的csv_to_npz可以在前后加入过渡动作,以使得开始前和结束后平滑过渡:
python src/mjlab/scripts/csv_to_npz.py --input_file data/tu/unitree_joint_data_tu3_2.csv --output_name tu3_2 --render --add-start-transition --add-end-transition --pad-duration 1.5 --transition-duration 1.5
- train时
--registry-name设置为unitree/csv_to_npz/fkdance(默认使用最新版,也可以自己指定版本,如fkdance:v0),训练时会生成一个mjlab的project,并在其下生成一个随机码(例如fk1234fk)的run代表本次训练,点进该run,点击overview的页签,其中有一条run path,用于play时的--wandb-run-path
MUJOCO_GL=egl uv run train Mjlab-Tracking-Flat-Unitree-G1 \
--registry-name unitree/csv_to_npz/fkdance \ #npz动作路径
--env.scene.num-envs 4096
- 离线train脚本:
MUJOCO_GL=egl uv run train Mjlab-Tracking-Flat-Unitree-G1-No-State-Estimation \
--motion_file artifacts/local/conduct1min.npz \
--env.scene.num-envs 4096
- 中断后继续训练:
uv run train Mjlab-Tracking-Flat-Unitree-G1-No-State-Estimation --motion_file artifacts/local/tu3_2.npz --env.scene.num-envs 4096 --agent.resume True --agent.load_run "2025-12-12_17-48-20"
- play时修改wandb-run-path即可。
uv run play Mjlab-Tracking-Flat-Unitree-G1 \
--wandb-run-path unitree/mjlab/fk1234fk
- 联网跑经常面临连接问题,因此可以本地跑策略,要给出.pt文件和.npz动作文件路径:
uv run play Mjlab-Tracking-Flat-Unitree-G1-No-State-Estimation-Play \
--checkpoint_file logs/rsl_rl/g1_tracking/2025-11-14_11-45-58/model_14999.pt \
--motion_file artifacts/fkdance\:v0/motion.npz
agent配置文件
路径:/mjlab/src/mjlab/tasks/tracking/config/g1/rl_cfg.py
可修改save_interval, max_iterations等。
PS
- 训练时任务可用Mjlab-Tracking-Flat-Unitree-G1-No-State-Estimation,其obs为154维,少了motion anchor的pos以及base_lin_vel。
- 单独运行
csv_to_npz.py时本地是没有.npz文件的,还需要下载一下,创建一个download_npz.py如下:
import wandb
run = wandb.init()
artifact = run.use_artifact('unitree/csv_to_npz/fkdance:v0', type='motions')
artifact_dir = artifact.download()
原始视频通过GVHMR以及GMR、mjlab的处理流程
- 按readme安装GVHMR之后使用代码转换
python tools/demo/demo.py --video=../motion_data/..
注意:之前同名的输出results会影响再次输出,因此要先删掉。
- 按readme安装GMR之后运行。
python scripts/gvhmr_to_robot.py \
--gvhmr_pred_file ../GVHMR/outputs/demo/tennis/hmr4d_results.pt \
--robot unitree_g1 --record_video \
--save_path outputs/tennis_motion.pkl # 自定义保存路径
- 通过mjlab转换pkl文件为csv:
python src/mjlab/scripts/pkl_to_csv_gmr.py \
--pkl-file ../motion_data/..pkl \
--csv-file ../motion_data/cartwheel.csv \
--add-start-transition --add-end-transition \
--transition-duration 1.0 \
--pad-duration 1.0
注意:mjlab的仓库中的pkl_to_csv.py有问题(2025-11-26),其中第85、86行的.frames .fps要改成dict调用key的方式fps = data["fps"]。对于gmr转换的pkl,需要将81-93行改为:
with open(pkl_file, "rb") as f:
# 直接读取你的 pkl 字典(无需 RobustUnpickler,因为你的 pkl 是标准字典)
data = pickle.load(f)
# 从你的 pkl 字典中提取关键字段
fps = data["fps"]
root_pos = data["root_pos"] # (N, 3):x,y,z
root_rot = data["root_rot"] # (N, 4):你的 pkl 中是 xyzw 四元数
dof_pos = data["dof_pos"] # (N, D):关节角度(D 是机器人自由度,如 G1 是 20 左右)
# 关键:重组为脚本需要的 frames 数组(格式:每一行 = 3位姿 + 3旋转 + D关节)
# 第一步:将你的四元数(xyzw)转换为脚本需要的“轴角”(3维向量)
print("Converting quaternion (xyzw) to axis-angle (3D vector)...")
root_rot_axisangle = []
for quat in root_rot:
# 四元数格式:x,y,z,w(你的 pkl 中存储的格式)
rotation = Rotation.from_quat(quat) # 直接用 scipy 转换
rot_vec = rotation.as_rotvec() # 转为轴角(3维向量)
root_rot_axisangle.append(rot_vec)
root_rot_axisangle = np.array(root_rot_axisangle) # (N, 3)
# 第二步:拼接成 frames 数组(顺序:root_pos(3) + 轴角(3) + 关节角度(D))
frames = np.concatenate([root_pos, root_rot_axisangle, dof_pos], axis=1)
original_duration = frames.shape[0] / fps
print(f"Loaded motion with shape: {frames.shape}")
print(f"FPS: {fps}")
print(
f"Original duration: {original_duration:.2f} seconds ({frames.shape[0]} frames)"
)
AMASS通过GMR转换
以CMU为例(fps=120)
GMR环境转原始数据为pkl,此时脚本默认fps=30:
python scripts/smplx_to_robot.py --smplx_file ../motion_data/CMU/05/05_05_stageii.npz --robot unitree_g1 --save_path ../motion_data/pkl/amass0505.pkl --rate_limit --record_video
mjlab转pkl_to_csv:
python src/mjlab/scripts/pkl_to_csv_gmr.py --pkl-file ../motion_data/pkl/amass0505.pkl --csv-file ../motion_data/csv/amass0505.csv --add-start-transition --add-end-transition --transition-duration 1.0 --pad-duration 1.0
mjlab转csv_to_npz,fps转为50:
MUJOCO_GL=egl uv run src/mjlab/scripts/csv_to_npz.py --input-file ../motion_data/csv/amass0505.csv --output-name artifacts/local/amass0505 --input-fps 30 --output-fps 50 --render
以上fps的先降低后升高能够消除原始数据的波动,不必修改。
AMASS数据集中CMU数据集
其官方网站中包含了对该数据集的说明。数据格式是SMPL系列,分为140+个包,命名中只有序号,具体动作内容可在网站查询。其中87~90包含一些酷炫动作。使用gmr可直接转换为pkl并保存视频。
数据集
AMASS数据集
*amass.npz 是 AMASS 数据集(Animation of 3D Human Motion)中常见的压缩文件格式,存储了人体运动捕捉(MoCap)数据(如关节角度、位置、旋转等)。.npz 是 NumPy 的压缩归档格式,可通过 Python 的 numpy 库直接读取。
import numpy as np
# 读取 .npz 文件
amass_data = np.load("amass.npz") # 替换为你的文件路径
# 查看文件中包含的所有数据键(keys)
print("包含的数据集键:", amass_data.files)
可能的结果: 包含的数据集键: ['trans', 'poses', 'betas', 'dmpls', 'gender', 'mocap_framerate', ...]
其中
- trans 根节点(通常是骨盆)的平移数据(3D 位置,单位:米) (N, 3) N 为帧数
- poses 人体关节的旋转数据(轴角格式,前 3 个值是根节点旋转,后续每 3 个值对应一个关节) (N, 156) 52 个关节 ×3
- betas 人体形状参数(SMPL 模型的体型参数,前 10 个为主) (10,) 或 (N, 10)
- dmpls 动态形状参数(可选,用于精细刻画运动中的体型变化) (N, 8) 或 (N, 12)
- gender 性别标签('male'/'female'/'neutral') 字符串
- mocap_framerate 运动捕捉的帧率(每秒帧数) 整数(如 100)
- frame_time 每帧的时间间隔(秒),通常为 1/mocap_framerate 浮点数(如 0.01)
- id 运动数据的唯一标识(如动作名称) 字符串
从加载的 amass_data 中提取具体数据,例如获取关节旋转和根节点平移:
# 提取核心数据
trans = amass_data["trans"] # 根节点平移 (N, 3)
poses = amass_data["poses"] # 关节旋转 (N, 156)
betas = amass_data["betas"] # 体型参数 (10,)
fps = amass_data["mocap_framerate"] # 帧率
gender = amass_data["gender"] # 性别
# 打印数据形状
print(f"总帧数:{trans.shape[0]}")
print(f"根节点平移形状:{trans.shape}")
print(f"关节旋转形状:{poses.shape}")
print(f"帧率:{fps} FPS")
print(f"性别:{gender}")
若要直观查看运动序列,可结合 SMPL 模型和可视化工具(如 pyrender):
import pyrender
import trimesh
from smplx import SMPL # 需要安装 smplx 库(处理人体模型)
# 加载 SMPL 模型(需提前下载 SMPL 模型文件,如 smplh中性模型)
smpl_model = SMPL(
model_path="path/to/smpl/models", # 替换为你的 SMPL 模型路径
gender=gender.decode() if isinstance(gender, bytes) else gender,
batch_size=1
)
# 提取第 i 帧的姿势和形状
i = 100 # 第100帧
root_rot = poses[i, :3] # 根节点旋转(轴角)
body_poses = poses[i, 3:66] # 身体关节旋转(前21个关节,63维)
betas_i = betas[:10] # 前10个形状参数
# 用 SMPL 模型生成网格
output = smpl_model(
betas=betas_i[None, :], # 扩展为 batch 维度
global_orient=root_rot[None, :], # 根节点旋转
body_pose=body_poses[None, :] # 身体关节旋转
)
vertices = output.vertices[0].detach().numpy() # 顶点坐标
faces = smpl_model.faces # 面索引
# 用 trimesh 和 pyrender 可视化
mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
scene = pyrender.Scene()
mesh_node = pyrender.Mesh.from_trimesh(mesh)
scene.add(mesh_node)
viewer = pyrender.Viewer(scene, use_raymond_lighting=True)
注意:可视化需要 SMPL 模型文件(需从 SMPL 官网 申请)和
smplx库(pip install smplx)。
SMPL数据集
SMPL(Skinned Multi-Person Linear Model)是一种参数化人体模型,通过少量参数(形状、姿势)即可生成逼真的 3D 人体网格,广泛用于计算机视觉(如人体姿态估计)、动画生成、机器人模仿学习等领域。“SMPL 格式数据” 指适配该模型的输入 / 输出数据,核心围绕 “形状参数” 和 “姿势参数” 展开,同时包含模型结构相关的固定信息(如顶点、骨骼权重)。
公开SMPL数据集下载。
一、SMPL 模型的核心参数(数据核心)
SMPL 模型的输入数据主要分为两类:形状参数(Betas) 和姿势参数(Pose),二者共同决定人体的 3D 形态。对于通用的机器人运动,还有一个根部平移(Trans)。
1. 形状参数(Betas,β)
- 作用:定义人体的静态体型特征(如身高、胖瘦、肩宽、腿长等),刻画个体差异。
- 维度:默认 10 维(部分扩展模型如 SMPL-H/SMPL-X 支持 20 维),前 3-5 维贡献了 95% 以上的体型差异,后续维度对应细微特征(如手指粗细)。
-
数据格式:
- 单个个体:1D 数组
(10,)或(20,)(固定体型,如 “标准男性”“标准女性”)。 - 序列数据(如运动捕捉):2D 数组
(N, 10)(N 为帧数,若体型不变,所有帧的 Betas 相同)。
- 单个个体:1D 数组
-
取值范围:通常在
[-3, 3]之间(超出范围可能生成不自然体型)。
2. 姿势参数(Pose,θ)
- 作用:定义人体的动态姿态(如关节旋转、身体朝向),决定人体的动作状态。
-
维度与结构:
- SMPL 模型包含 24个关节(根节点 + 23 个身体关节,不含手指),每个关节的旋转用轴角(Axis-Angle) 表示(3 个数值,描述旋转轴和角度),因此总维度为
24×3=69。 - 姿势参数通常拆分为两部分:
- 全局朝向(Global Orientation):前 3 个值,描述人体根节点(通常是骨盆)在世界坐标系中的旋转(如面向 X 轴正方向、侧身等)。
- 身体关节旋转(Body Pose):后 69 个值(23个关节 ×3),描述颈部、肩部、髋部、膝盖等关节的旋转。
- SMPL 模型包含 24个关节(根节点 + 23 个身体关节,不含手指),每个关节的旋转用轴角(Axis-Angle) 表示(3 个数值,描述旋转轴和角度),因此总维度为
-
数据格式:
- 单帧姿势:1D 数组
(72,)。 - 运动序列:2D 数组
(N, 72)(N 为帧数,每帧对应一个姿势)。
- 单帧姿势:1D 数组
- 注意:轴角格式需通过罗德里格斯公式(Rodrigues' Formula)转换为旋转矩阵,才能用于计算关节变换。
3. 根部平移(Trans,p)
维度同上
二、SMPL 模型的输出数据(网格与骨骼)
输入 Betas 和 Pose 后,SMPL 模型会输出人体的 3D 网格和骨骼信息,是后续可视化、动画生成的核心数据。
1. 顶点网格(Vertices)
- 作用:3D 人体的表面网格,由大量顶点组成(SMPL 默认 6890 个顶点)。
-
数据格式:
- 单帧网格:2D 数组
(6890, 3)(每个顶点对应 3 个坐标值(x, y, z),世界坐标系)。 - 运动序列:3D 数组
(N, 6890, 3)(N 为帧数,每帧对应一个网格)。
- 单帧网格:2D 数组
-
用途:直接用于可视化(如通过
pyrender、MeshLab渲染人体)、碰撞检测(机器人交互场景)。
2. 骨骼权重(Blend Weights)
- 作用:描述每个顶点受哪些关节旋转的影响(蒙皮权重),用于计算关节运动时顶点的位置变化(线性混合蒙皮,Linear Blend Skinning)。
-
数据格式:2D 数组
(6890, 23)(每个顶点对应 23 个关节的权重值,权重和为 1)。 - 特点:SMPL 模型的骨骼权重是固定的(随模型文件存储,不随姿势 / 形状变化),无需实时计算。
3. 关节位置(Joints)
- 作用:人体骨骼的关节中心点坐标(如骨盆、肩关节、膝关节),用于骨骼结构分析、运动学计算。
-
数据格式:
- 单帧关节:2D 数组
(23, 3)(23 个关节,每个关节的 3D 坐标)。 - 运动序列:3D 数组
(N, 23, 3)。
- 单帧关节:2D 数组
-
分类:
- 局部关节位置:相对于父关节的坐标(运动学树结构)。
- 全局关节位置:在世界坐标系中的坐标(需结合全局朝向和根节点平移计算)。
三、SMPL 相关扩展模型的格式差异
SMPL 有多个扩展版本,数据格式在基础上略有调整,需注意区分:
| 模型版本 | 核心差异 | 姿势参数维度 | 顶点数 | 适用场景 |
|---|---|---|---|---|
| SMPL | 基础人体模型(无手指、表情) | 69(23 关节 ×3) | 6890 | 全身运动捕捉、动画生成 |
| SMPL-H | 增加手部模型(15 个手指关节) | 165(55 关节 ×3) | 6890 | 需精细手部动作的场景(如手势识别) |
| SMPL-X | 增加面部表情(50 个面部关节)+ 手部 | 225(75 关节 ×3) | 10475 | 表情 + 手部 + 身体的完整人体建模(如虚拟人) |
| SMPL-A | 适配动物模型(如狗、马) | 随动物骨骼数量变化 | 随模型变化 | 动物运动捕捉、仿真 |
四、SMPL 格式数据的存储与读取
SMPL 格式数据通常以以下方式存储,读取时需对应模型版本:
1. 模型文件(固定结构)
SMPL 模型的固定参数(如基础形状顶点、骨骼权重、关节父节点关系)存储在 .pkl(Python pickle)文件中,需从官方网站申请(需学术许可)。常见文件包括:
-
SMPL_NEUTRAL.pkl(中性体型)、SMPL_MALE.pkl(男性)、SMPL_FEMALE.pkl(女性)。 - 文件内包含的关键键(key):
-
v_template:基础形状顶点(6890, 3)。 -
weights:骨骼权重(6890, 23)。 -
kintree_table:骨骼运动树(父关节索引)。 -
shapedirs:形状基函数(用于计算 Betas 对顶点的影响)。 -
posedirs:姿势基函数(用于计算 Pose 对顶点的影响)。
-
2. 运动数据文件(动态参数)
SMPL 格式的运动数据(Betas+Pose + 根节点平移)常存储在 .npz(NumPy 压缩文件)或 .json 中,例如 AMASS 数据集、MPI-INF-3DHP 数据集:
- 典型
.npz文件包含的键:-
betas:形状参数(10,)或(N, 10)。 -
poses:姿势参数(N, 69)(SMPL)或(N, 165)(SMPL-H)。 -
trans:根节点平移(N, 3)(描述人体在世界坐标系中的位置变化,如行走时的位移)。 -
mocap_framerate:帧率(如 30 FPS、100 FPS)。
-
3. 读取工具
-
Python 库:
-
smplx:官方推荐的 SMPL 系列模型库,支持加载模型、输入参数生成网格(pip install smplx)。 -
numpy:读取.npz格式的运动数据(np.load("smpl_data.npz"))。 -
trimesh/pyrender:可视化 SMPL 顶点网格(渲染 3D 人体)。
-
-
示例代码(用
smplx加载模型并生成网格):import numpy as np from smplx import SMPL # 1\. 加载SMPL模型(需指定模型文件路径) smpl = SMPL( model_path="path/to/smpl/models", # 存放.pkl文件的目录 gender="neutral", # 中性/男性/女性 batch_size=1 # 批量处理帧数(1表示单帧) ) # 2\. 准备输入参数(示例:标准姿势,轻微胖体型) betas = np.array([[0.5, 0.2, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=np.float32) # 形状参数(10维) global_orient = np.array([[0, 0, 0]], dtype=np.float32) # 全局朝向(无旋转) body_pose = np.array([[0]*66], dtype=np.float32) # 身体关节(无旋转,标准站立) # 3\. 生成人体网格 output = smpl( betas=betas, global_orient=global_orient, body_pose=body_pose ) # 4\. 提取输出数据 vertices = output.vertices[0].detach().numpy() # 顶点网格 (6890, 3) joints = output.joints[0].detach().numpy() # 关节位置 (23, 3) print(f"顶点数:{vertices.shape[0]}, 关节数:{joints.shape[0]}")
五、SMPL 格式数据的核心应用场景
- 人体姿态估计:从 2D 图像 / 视频中预测 SMPL 的 Pose 参数,反推 3D 人体姿态。
- 运动捕捉数据处理:将 MoCap 设备(如 Vicon)的原始关节数据转换为 SMPL Pose,用于动画生成。
- 机器人模仿学习:让机器人通过 SMPL 格式的人类运动数据,学习抓取、行走等动作。
- 虚拟人 / 元宇宙:结合 SMPL-X 的表情和手部参数,构建可交互的虚拟人形象。
总结
SMPL 格式数据的核心是 “少参数控制多维度人体形态”,关键在于理解:
输入:Betas(体型)+ Pose(姿态)+ trans(根节点平移);
输出:Vertices(网格)+ Joints(关节)+ weights(骨骼权重);
扩展:SMPL-H/X 等版本增加了手指、表情参数,需匹配对应维度的 Pose 数据。
处理时需注意模型版本与数据维度的匹配,常用smplx库加载模型、numpy读取数据,结合可视化工具验证结果。
算法
TRAM: Global Trajectory and Motion of 3D Humans from in-the-wild Videos
TRAM(Global Trajectory and Motion of 3D Humans from in-the-wild Videos)是由宾夕法尼亚大学研究团队提出的开源4D人体捕捉系统,专注于从非受控的真实场景视频中精准重建3D人体的全局轨迹与运动状态,为计算机视觉领域的人体运动分析提供了创新解决方案。
核心目标
在计算机视觉领域,从普通视频中捕捉人体运动时,若不考虑相机运动,多数方法仅能在相机坐标系中重建人体,难以结合环境准确分析人体动作。TRAM旨在解决这一问题,通过两阶段方法实现世界空间中3D人体运动的精准恢复,既捕捉人体运动学姿态,又获取其在世界空间中的全局轨迹。对于人型机器人舞蹈的任务,可以根据人形动作视频得到SMPL的全局动作轨迹。
工作原理与流程
TRAM集成多种先进技术,通过多步骤协作实现3D人体运动重建,具体流程如下:
- 相机位姿估计:运行改进的DROID-SLAM算法,对人体区域进行遮罩处理,降低动态人体对相机运动估计的干扰,同时结合ZoeDepth预测视频深度信息,提升场景重建精度,并利用场景背景推导运动比例尺,确定相机在全局坐标系中的轨迹与姿态。
- 人体检测跟踪与运动捕捉:采用Detectron2、Segment-Anything进行人体检测与分割,结合DEVA-Track-Anything实现复杂场景下的人体跟踪;再通过VIMO视频变换模型,以恢复的相机为度量尺度参考帧,回归人体运动学运动,精准估计3D人体姿态。
- 结果整合与输出:整合相机位姿数据与人体运动数据,将人体运动映射到全局坐标系,渲染输出包含3D人体全局轨迹与运动的最终视频,所有中间结果与最终数据会保存在对应文件夹中。
如果需要复现视频的人形机器人动作,在通过TRAM获得SMPL数据集后,可以利用isaacgym中的maskedmimic模仿SMPL中的动作,通过仿真即可清洗掉其中不可能实现的动作(sim2data,验证其动作在仿真中可行)。后将其重定向为机器人的动作,此部分技术代表为H2O与OmniH2O(human2humanoid代码),具体原理可参照此文。