人形机器人WBT代码框架分析
最新更新于: 2026年4月25日晚上9点07分
本文学习下近几年开源的基于g1-29dof的动捕重定向数据全身追踪算法 (Whole Body Tracking, WBT),对使用的obs,奖励,网络进行分析,包含:
- Q. Liao et al., “BeyondMimic: From Motion Tracking to Versatile Humanoid Control via Guided Diffusion,” arXiv.org. Accessed: Mar. 17, 2026. Available: https://arxiv.org/abs/2508.08241v4
BeyondMimic
GitHub - HybridRobotics/whole_body_tracking这个源代码中使用了很多没用的wandb上传功能,还把motion data也上传了,普通账号无法上传因此修改了一版本GitHub - wty-yy-mini/whole_body_tracking,使用方法根据我的修改版本的README.md顺次执行即可。
代码框架
whole_body_tracking
├── data # 追踪数据
│ ├── dailylife_back_2_3.csv
│ └── dailylife_back_2_3.npz
├── logs # 训练结果
│ └── rsl_rl
│ └── g1_flat
│ └── 2026-04-14_16-28-15_dailylife_back_2_3
│ ├── exported
│ │ └── policy.onnx
│ └── model_2500.pt
├── scripts
│ ├── csv_to_npz.py # qpos的csv转为npz
│ ├── replay_npz.py # 追踪数据可视化
│ └── rsl_rl
│ ├── cli_args.py
│ ├── play.py
│ └── train.py
└── source
└── whole_body_tracking
├── pyproject.toml
├── setup.py
└── whole_body_tracking
├── robots
│ ├── actuator.py
│ ├── g1.py
│ └── smpl.py
├── tasks
│ └── tracking
│ ├── config
│ │ ├── g1
│ │ │ ├── agents
│ │ │ │ └── rsl_rl_ppo_cfg.py
│ │ │ └── flat_env_cfg.py
│ │ └── humanoid
│ │ ├── agents
│ │ │ └── rsl_rl_ppo_cfg.py
│ │ └── flat_env_cfg.py
│ ├── mdp
│ │ ├── commands.py
│ │ ├── events.py
│ │ ├── observations.py
│ │ ├── rewards.py
│ │ └── terminations.py
│ └── tracking_env_cfg.py
└── utils
├── exporter.py
└── my_on_policy_runner.py重定向数据处理
重定向csv源文件,例如lafan1数据集 - g1,仅包含30fps的 qpos 数据,数据维度为 (T, 3+4+29) 分别表示:
- root_pos: (x, y, z)
- root_rot: (x, y, z, w) 四元数
- dof: 29个关节的旋转角度
使用 csv_to_npz.py 脚本将30fps的csv文件转换为50fps的npz文件,npz中key包含:
- fps: 50
- joint_pos: (T, 29) 关节位置
- joint_vel: (T, 29) 关节速度
- body_pos_w: (T, 30, 3) 每个body在世界坐标系下的位置
- body_quat_w: (T, 30, 4) 每个body在世界坐标系下的旋转四元数
- body_lin_vel_w: (T, 30, 3) 每个body在世界坐标系下的线速度
- body_ang_vel_w: (T, 30, 3) 每个body在世界坐标系下的角速度
具体计算方法如下:
MotionLoader数据处理
这部分完成qpos数据读取,差值得到目标频率数据,差分得到速度数据
MotionLoader 读取csv文件,得到 qpos 数据,获得 root 相对世界坐标系的pos, rot_wxyz(转化下),以及dof关节位置,将当前数据频率转化为目标频率,通过差值得到:
设要划分的时间段为 ,按导出频率 线性划分,可得到离散时间索引 ,则计算得到最近索引 ,比例为 ,分别进行两种差值:
- 线性插值:dof_pos, root_pos
- 球面线性差值:root_rot
补全所需的速度信息:
torch.gradient: 计算出root_lin_vel, dof_vel,- so3旋转差分: 计算出root_ang_vel
仿真器正向运动学求解
通过IsaacSim(Mujoco应该也行),仅执行正运动学(无需 sim.step()),得到每个body在世界坐标系下位置(body_* 的数据),仿真器中执行正向求解所需的信息包含:
- root state: pos, rot, lin_vel, ang_vel
- dof state: dof_pos, dof_vel
IsaacLab中进行运动学推理核心逻辑如下(可参考manager_based_env.py中step函数):
主要实例对象包含两个:
sim: SimulationContext:仿真器上下文对象,是对IsaacSim所支持的仿真器的包装,例如PhysXscene: InteractiveScene:场景对象,包含了各种entity,例如机器人robot: Articulation,资产ground: AssetBase等
这里scene就是用户调用sim的接口,scene为sim创建entity更新entity状态,当sim物理步进推进后,再更新scene状态,获取我们所需数据,再进一步用scene控制机器人entity,具体流程如下:
- scene更新sim状态,例如
- 写入指定数据
robot.write_root_state_to_sim(root_states)修改root_state,robot.write_joint_state_to_sim(joint_pos, joint_vel)修改joint_state scene.write_data_to_sim()将调用每个entity的write_data_to_sim函数将各自所需更新的数据写入sim中
- 写入指定数据
sim.step()推进仿真器状态(当前正运动学可视化中无需)sim.render()渲染当前sim界面scene.update(sim.get_physics_dt()),推进当前scene时间戳,自动更新每个entity状态,例如:- 对于机器人信息
Articulation会在调用时直接获取sim信息,这里只更新必须的joint_acc状态
- 对于机器人信息
这些信息我们在 MotionLoader 中都处理出来了并满足目标频率,直接写入sim中即可,通过 scene.update 后直接从 Articulation 信息中读取出正向运动学信息,就是我们要的模仿数据了。
启发式超参数设计
在原论文中附录 Heuristically Designed Paramters 中提到,他们设计关节PD系数和动作缩放系数具体如下:
设 为固有频率 10Hz(natural frequency), 为阻尼比(damping ratio), 为第j个关节的转子惯性(rotor inertia), 为齿轮比(gear ratio),则 为关节的反射惯性(reflected inertia (aramature) of the joint)
第j个关节PD系数定义为:,
动作缩放系数计算使用了
代码中见GitHub - whole_body_tracking/robots/g1.py开头和结尾部分,这样设计的好处是可以不用手动调参,直接根据当前机器人的物理属性设计出合理的PD系数用于训练和真机。
PD系数设计原理如下
令 为目标位置, 为位置误差,则 ,关节动力学方程为
带入启发式PD系数
这就是关于 的二阶线性常微分方程,超参数为 ,这样有两个好处:
- 避免关节的物理差异,公式中消去了关节差异
- 统一超参数: 为关节响应的快慢, 为阻尼大小,当 像弹簧震荡, 平滑过渡到目标位置
观测
BeyondMimic给出的版本是无法上真机的观测,宇树unitree_rl_lab能上真机的版本里面删除了motion_anchor_pos_b和base_lin_vel
Actor观测
| 变量名 | 维度 | 说明 | 是否带噪声 |
|---|---|---|---|
| command | 29+29 | 目标关节位置joint_pos和速度joint_vel | 否 |
| motion_anchor_pos_b | 3 | 目标anchor在机器人当前anchor下的位置 | 是 |
| motion_anchor_ori_b | 6 | 目标anchor在机器人当前anchor下的旋转矩阵前两列 | 是 |
| base_lin_vel | 3 | 在本体坐标系下的root线速度 | 是 |
| base_ang_vel | 3 | 在本体坐标系下的root角速度 | 是 |
| joint_pos_rel | 29 | 关节相对默认姿态的偏移 | 是 |
| joint_vel | 29 | 关节速度 | 是 |
| last_actions | 29 | 上一时刻的动作 | 否 |
线速度和角速度都是相对于世界坐标系,然后平移到本体坐标系下的
总计 58 + 3 + 6 + 3 + 3 + 29 + 29 + 29 = 160 维观测,这里的anchor_body指的就是torso_link也就是机器人躯干,注意torso_link并不是root,G1的默认root是pelvis对应的link,这里有如下的变化关系:
pelvis (root/base)
-> waist_yaw_link
-> waist_roll_link
-> torso_linkCritic特权观测
| 变量名 | 维度 | 说明 |
|---|---|---|
| 不带噪声的Actor观测 | 160 | 全部不带噪声 |
| body_pos | 14 * 3 | 指定的14个关键body在机器人当前anchor坐标系下的位置 |
| body_ori | 14 * 6 | 指定的14个关键body在机器人当前anchor坐标系下的旋转矩阵前两列 |
总计 160 + 14*3 + 14*6 = 286 维观测,这里14个关键body配置MotionCommand.motion.body_names为:
# 下半身
pelvis
left_hip_roll_link # 髋
left_knee_link # 膝
left_ankle_roll_link # 踝
right_hip_roll_link
right_knee_link
right_ankle_roll_link
# 躯干
torso_link
# 上半身
left_shoulder_roll_link # 肩
left_elbow_link # 肘
left_wrist_yaw_link # 腕
right_shoulder_roll_link
right_elbow_link
right_wrist_yaw_link奖励
前置定义
首先引入Gaussian型指数奖励(Gaussian-shaped exponential reward),设当前状态和目标的误差为 ,总计 个状态,则平均误差定义为 ,我们期望其服从均值为0,方差为 的Gaussian分布 ,则Gaussian型指数奖励定义为(把常数项都省略掉)
记两个旋转变换之间的最小旋转角度为(四元数同样可以算出该角度)
定义一些后续奖励设计会用到的变量名:
在当前时刻下,参考动作中的body 在世界坐标系下的位置为 ,旋转矩阵为
当前机器人的body 相对世界坐标系下的位置为 ,旋转矩阵为
其中有一个body我们记为anchor(锚点,代码中为torso_link),对应的索引记为
总共包含9个奖励,分为如下这几项介绍
anchor body pose 误差
位置误差:
旋转误差:
参考body pose 误差
由于机器人在执行过程中存在位置和旋转误差,因此需要引入平移/旋转补偿(position/rotation compensation),BeyondMimic中使用的补偿计算方法如下:
- 位置补偿为 ,注意这里只有z轴使用参考位置,要求机器人保持参考高度
- 旋转补偿为 ,这里只考虑绕z轴的yaw旋转补偿,表示提取旋转矩阵中绕z轴的yaw角,表示绕z轴旋转角的旋转矩阵
则 对应了一个仿射变换(平移+旋转),只需将body都经过该变换到新的坐标系下即可,第 个body的目标位置和旋转为(相对世界坐标系)
还记得在观测 - Critic特权观测中定义了14个关键body,索引记为 ,则body pose误差奖励定义为
- 位置补偿误差:
- 旋转补偿误差:
- 线速度全局误差:
- 角速度全局误差:
正则奖励
- L2一阶高频动作惩罚:
- L1软关节限制惩罚:
- 误接触惩罚: