具身智能机器人实战教程——模块2(2)
模块 2:认识 T800 机器人仿真模型
第一章:T800中的Link(body)和Joint(joint)
第二章:代码实操
第三章:MujocoAPI介绍
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
第一章:T800中的Link(body)和 Joint(joint)
3.2.1. from_xml_path(加载本地机器人模型)
3.3.1. mujoco.mj_step(model, data)
3.4.1. mj_name2id(通过名称查索引|最常用)
前言
上一节课我们成功把众擎 T800 人形机器人 MJCF 模型加载进 MuJoCo 仿真环境,了解了MjModel静态模型、MjData动态数据对象,学会了基础参数打印。
本节课重点搞懂 MuJoCo 仿真里面最核心两个概念:link(body 刚体连杆)与joint(关节)。这是理解人形机器人运动学的基石,后续读取关节角度、写 PD 控制器、设计强化学习观测空间全部建立在这两个概念之上。
前置环境:已经激活
t800conda 虚拟环境,T800 的 xml 模型与 mesh 网格文件完整。
提示:以下是本篇文章正文内容,下面案例可供参考
第一章:T800中的Link(body)和 Joint(joint)
在 MuJoCo MJCF 模型语法中,body 就等价于我们常说的 link(连杆)。
1.1 Link /body(刚体连杆)
- body(连杆):机器人物理刚体单元,对应现实机器人的躯干、大腿、小腿、上臂、小臂等实体零部件;它保存质量、惯性物理属性。
geom几何体依附在 body 上面,决定这个连杆长什么样子、碰撞边界;body 本身没有外观。- MuJoCo 模型是树形嵌套结构:子 body 写在父 body 标签内部,表达父子从属关系,类似 ROS 的 TF 树。
类比人体:每一块骨头就对应一个 body(link)。
1.2 Joint(关节)
- joint 写在子 body 内部,用来定义「子 body 相对于父 body」可以怎么运动;如果 body 内部不写 joint,那么这个刚体就会被焊死在父刚体上,完全不能动。
- joint 决定自由度:铰链
hinge只能绕轴旋转;free自由关节给机身根部 6 个自由度(3 平移 + 3 旋转)。
类比人体:骨头和骨头中间的韧带、转动部位就是 joint 关节。
⚠️重要区分(新手极易混淆)
- body(link):刚体零件,有质量惯性;
- joint:运动约束,负责产生相对运动,本身没有质量。
1.3T800 机器人树形结构简单示意
plaintext
worldbody(仿真世界根节点)
└── root_body(机器人根刚体,free自由关节)
├── torso 躯干(body)
│ ├── head 头部(body)
│ ├── left_arm 左手臂(body)
│ └── right_arm 右手臂(body)
├── left_leg 左腿(body)
└── right_leg 右腿(body)
整个机器人就是一棵运动学树,一层一层嵌套。
1.4编程要点
操作 T800 机器人关节,一律使用名称查找 ID,禁止硬编码数字下标,提升代码健壮性,适配后续模型迭代修改。

在操作 T800 机器人关节的时候,我们会遇到一个非常典型的开发痛点:人认识关节名字,但是 MuJoCo 底层只认数字索引。
从示意图可以看到:
- 程序员视角:我们可以直观理解语义名称,比如
右肩关节,需求是 “抬起右胳膊”,可读性很高,符合人的思维。 - 计算机底层视角:程序内部没有 “右肩关节” 这个文字概念,它只识别数字编号,例如
关节#17,通过数组下标去读写角度、施加力矩。
很多新手会直接把数字索引写死在代码里面,例如直接写data.qpos[17]去操作右肩关节。
❗这是一个高风险错误写法。
如果后续 T800 的 MJCF (XML) 模型文件发生改动:调整关节定义顺序、新增 / 删除连杆关节,所有关节的数字索引就会全部发生偏移错乱。原本的关节#17不再对应右肩关节,控制逻辑全部失效,而且这种 bug 很难排查。
MuJoCo 给出的解决方案:名称与索引互相转换 API:不要手动记忆、硬写数字下标,使用mj_name2id()通过关节名称反向查询运行时索引。不管模型内部顺序如何变化,只要关节名称不变,代码就可以正常工作。
import mujoco
# 通过关节名字获取id,解耦名字和数字编号
right_shoulder_id = mujoco.mj_name2id(
model,
mujoco.mjtObj.mjOBJ_JOINT,
"right_shoulder"
)
# 获取该关节在qpos数组中的偏移
qpos_idx = model.jnt_qposadr[right_shoulder_id]
# 读取关节角度
shoulder_angle = data.qpos[qpos_idx]
与之对应的反向接口mj_id2name(),可以通过索引拿到关节名称,用于打印日志调试。
第二章:代码实操
2.1打印机器人所有关节信息
提示语:帮我写一个脚本05_T800_joint.py,打印t800机器人的所有关节

生成的代码如下
"""
05_T800_joint.py
打印 T800 机器人的所有关节信息(名称、类型、所属 body、qpos 起始索引与维度)
运行: D:/miniconda3/envs/t800/python.exe 05_T800_joint.py
"""
import mujoco
model = mujoco.MjModel.from_xml_path("t800.xml")
# 关节类型 -> (名称, qpos维度)
JOINT_TYPE = {
mujoco.mjtJoint.mjJNT_FREE: ("free", 7),
mujoco.mjtJoint.mjJNT_BALL: ("ball", 4),
mujoco.mjtJoint.mjJNT_SLIDE: ("slide", 1),
mujoco.mjtJoint.mjJNT_HINGE: ("hinge", 1),
}
print(f"关节总数: {model.njnt}")
print("-" * 72)
print(f"{'索引':>4} {'名称':<30} {'类型':<6} {'所属 body':<18} {'qpos_adr':>8} {'qpos_dim':>8}")
print("-" * 72)
for i in range(model.njnt):
name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_JOINT, i) or "(无名)"
jt = int(model.jnt_type[i])
jtype_name, qpos_dim = JOINT_TYPE.get(jt, (str(jt), 0))
body = model.body(int(model.jnt_bodyid[i])).name
qposadr = int(model.jnt_qposadr[i])
print(f"{i:>4} {name:<30} {jtype_name:<6} {body:<18} {qposadr:>8} {qpos_dim:>8}")
执行下列命令运行代码
(base) C:\Users\qx>conda activate t800 ##激活环境
(t800) C:\Users\qx>cd Desktop\T800\code2 ##跳转到代码存储位置
(t800) C:\Users\qx\Desktop\T800\code2>python 05_T800_joint.py #运行代码

从打印信息可以看出有25个关节,T800一共是有26个关节,其中只有25个主动关节可以供我们使用进行二次开发。下文的单词需要认识。
| 关节英文名称 | 中文释义 |
|---|---|
| J00_HIP_PITCH_L | 左髋关节‑俯仰 |
| J01_HIP_ROLL_L | 左髋关节‑横滚 |
| J02_HIP_YAW_L | 左髋关节‑偏航 |
| J03_KNEE_PITCH_L | 左膝关节‑俯仰 |
| J04_ANKLE_PITCH_L | 左踝关节‑俯仰 |
| J05_ANKLE_ROLL_L | 左踝关节‑横滚 |
| J06_HIP_PITCH_R | 右髋关节‑俯仰 |
| J07_HIP_ROLL_R | 右髋关节‑横滚 |
| J08_HIP_YAW_R | 右髋关节‑偏航 |
| J09_KNEE_PITCH_R | 右膝关节‑俯仰 |
| J10_ANKLE_PITCH_R | 右踝关节‑俯仰 |
| J11_ANKLE_ROLL_R | 右踝关节‑横滚 |
| J12_TORSO_YAW | 躯干腰部‑偏航 |
| J13_SHOULDER_PITCH_L | 左肩关节‑俯仰 |
| J14_SHOULDER_ROLL_L | 左肩关节‑横滚 |
| J15_SHOULDER_YAW_L | 左肩关节‑偏航 |
| J16_ELBOW_PITCH_L | 左肘关节‑俯仰 |
| J17_ELBOW_YAW_L | 左肘关节‑偏航 |
| J18_SHOULDER_PITCH_R | 右肩关节‑俯仰 |
| J19_SHOULDER_ROLL_R | 右肩关节‑横滚 |
| J20_SHOULDER_YAW_R | 右肩关节‑偏航 |
| J21_ELBOW_PITCH_R | 右肘关节‑俯仰 |
| J22_ELBOW_YAW_R | 右肘关节‑偏航 |
| J23_HEAD_PITCH | 头部‑俯仰 |
| J24_HEAD_YAW | 头部‑偏航 |
| 英文 | 中文 | 说明 |
|---|---|---|
| PITCH | 俯仰 | 关节向前 / 向后摆动 |
| ROLL | 横滚 | 关节向左右翻转 |
| YAW | 偏航 | 关节水平旋转(左右转头 / 转胯) |
| L | Left | 机器人左侧 |
| R | Right | 机器人右侧 |
| HIP | 髋关节 | 大腿根部关节 |
| KNEE | 膝关节 | 膝盖关节 |
| ANKLE | 踝关节 | 脚踝关节 |
| SHOULDER | 肩关节 | 大臂根部关节 |
| ELBOW | 肘关节 | 胳膊肘关节 |
| TORSO | 躯干 / 腰部 | 躯体腰部关节 |
| HEAD | 头部 | 头部关节 |
2.2打印机器人所有连杆信息
提示语:帮我写一个脚本06_T800_body.py,打印t800机器人的所有连杆,包括名字、尺寸、重量信息,格式化输出
"""
06_T800_body.py
打印 T800 机器人的所有 body(连杆)信息(含质量、惯性、子连杆数、几何体数)
运行: D:/miniconda3/envs/t800/python.exe 06_T800_body.py
"""
import mujoco
import numpy as np
model = mujoco.MjModel.from_xml_path("t800.xml")
# 预计算每个 body 的子连杆数和几何体数
child_count = np.zeros(model.nbody, dtype=int)
for i in range(model.nbody):
pid = int(model.body_parentid[i])
if pid >= 0:
child_count[pid] += 1
body_geomnum = np.zeros(model.nbody, dtype=int)
for i in range(model.nbody):
body_geomnum[i] = int(model.body_geomnum[i])
# 总质量(除 world)
total_mass = float(np.sum(model.body_mass[1:]))
print(f"body(连杆)总数: {model.nbody} 总质量: {total_mass:.3f} kg")
print("-" * 92)
print(f"{'#':>3} {'名称':<28} {'父 body':<18} {'质量/kg':>8} "
f"{'子连杆':>6} {'几何体':>6} {'惯性 Ixx Iyy Izz':>24}")
print("-" * 92)
for i in range(model.nbody):
name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_BODY, i) or "(world)"
parent_id = int(model.body_parentid[i])
parent = (mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_BODY, parent_id)
or "(world)") if parent_id >= 0 else "(无)"
mass = float(model.body_mass[i])
inertia = model.body_inertia[i]
print(f"{i:>3} {name:<28} {parent:<18} {mass:>8.4f} "
f"{child_count[i]:>6} {body_geomnum[i]:>6} "
f"{inertia[0]:>8.5f} {inertia[1]:>8.5f} {inertia[2]:>8.5f}")

| 连杆英文名称 | 中文释义 |
|---|---|
| world | 世界根坐标系(仿真全局世界) |
| LINK_BASE | 机身基座(整机根连杆) |
| LINK_HIP_PITCH_L | 左髋关节‑俯仰连杆 |
| LINK_HIP_ROLL_L | 左髋关节‑横滚连杆 |
| LINK_HIP_YAW_L | 左髋关节‑偏航连杆 |
| LINK_KNEE_PITCH_L | 左膝关节‑俯仰连杆 |
| LINK_ANKLE_PITCH_L | 左踝关节‑俯仰连杆 |
| LINK_ANKLE_ROLL_L | 左踝关节‑横滚连杆 |
| LINK_FOOT_L | 左脚足部连杆 |
| LINK_HIP_PITCH_R | 右髋关节‑俯仰连杆 |
| LINK_HIP_ROLL_R | 右髋关节‑横滚连杆 |
| LINK_HIP_YAW_R | 右髋关节‑偏航连杆 |
| LINK_KNEE_PITCH_R | 右膝关节‑俯仰连杆 |
| LINK_ANKLE_PITCH_R | 右踝关节‑俯仰连杆 |
| LINK_ANKLE_ROLL_R | 右踝关节‑横滚连杆 |
| LINK_FOOT_R | 右脚足部连杆 |
| LINK_WAIST_YAW | 腰部偏航躯干连杆 |
| LINK_SHOULDER_PITCH_L | 左肩关节‑俯仰连杆 |
| LINK_SHOULDER_ROLL_L | 左肩关节‑横滚连杆 |
| LINK_SHOULDER_YAW_L | 左肩关节‑偏航连杆 |
| LINK_ELBOW_PITCH_L | 左肘关节‑俯仰连杆 |
| LINK_ELBOW_YAW_L | 左肘关节‑偏航连杆 |
| LINK_WRIST_END_L | 左手腕末端连杆 |
| LINK_SHOULDER_PITCH_R | 右肩关节‑俯仰连杆 |
| LINK_SHOULDER_ROLL_R | 右肩关节‑横滚连杆 |
| LINK_SHOULDER_YAW_R | 右肩关节‑偏航连杆 |
| LINK_ELBOW_PITCH_R | 右肘关节‑俯仰连杆 |
| LINK_ELBOW_YAW_R | 右肘关节‑偏航连杆 |
| LINK_WRIST_END_R | 右手腕末端连杆 |
| LINK_HEAD_PITCH | 头部‑俯仰连杆 |
| LINK_HEAD_YAW | 头部‑偏航连杆 |
| 英文关键词 | 中文含义 | 说明 |
|---|---|---|
| BASE | 基座 | 机器人整机根部主体连杆 |
| HIP | 髋关节 | 大腿根部 |
| KNEE | 膝关节 | 膝盖部位 |
| ANKLE | 踝关节 | 脚踝部位 |
| FOOT | foot | 足部 |
| WAIST | 腰部 | 躯干腰段 |
| SHOULDER | 肩关节 | 大臂根部 |
| ELBOW | 肘关节 | 胳膊肘 |
| WRIST_END | 腕部末端 | 手腕末端虚拟连杆,常用于末端点位观测 |
| HEAD | 头部 | 机器人头部 |
| PITCH | 俯仰 | 前后摆动 |
| ROLL | 横滚 | 左右翻转 |
| YAW | 偏航 | 水平旋转 |
| L | Left | 机器人本体左侧 |
| R | Right | 机器人本体右侧 |
第三章:MujocoAPI
MuJoCo 底层基于 C 语言高性能物理引擎,Python 版本是官方原生轻量绑定,所有 API 命名统一 mj 前缀,结构清晰、学习成本极低。不同于传统深度学习框架,MuJoCo API 完全围绕「物理仿真」设计,所有功能只分为三大类:
-
模型加载 API:读取机器人 MJCF/XML 模型,生成静态结构数据
-
仿真步进 API:物理解算、状态更新、动力学计算
-
信息查询 API:通过名称/索引获取机器人关节、连杆、姿态数据
本章节全部为T800 机器人开发实战高频API,是后续关节控制、姿态解算、强化学习观测设计的基础。
3.1两大核心对象
MuJoCo 所有操作都围绕两个对象,也是我们项目代码的固定模板:
3.1.1. MjModel(静态模型)
只读、固定不变,代表机器人的「硬件图纸」。
存储:连杆结构、关节数量、关节限位、电机参数、质量、惯性、碰撞参数等固定信息。
3.1.2. MjData(动态数据)
每帧持续更新,代表机器人「实时状态」。
存储:关节角度、角速度、机身位置、速度、控制力矩、接触力等动态数据。
核心口诀:Model 管结构,Data 管状态
3.2模型加载类 API
3.2.1. from_xml_path(加载本地机器人模型)
最常用、项目必备,读取本地 T800.xml 模型文件。
model = mujoco.MjModel.from_xml_path("t800.xml")
作用:解析 MJCF 模型,生成完整机器人结构树(连杆、关节、执行器)。
3.2.2. MjData(model)(初始化仿真状态)
data = mujoco.MjData(model)
根据模型结构,自动初始化 qpos、qvel、ctrl 等状态数组
3.3仿真核心 API
3.3.1. mujoco.mj_step(model, data)
整个仿真项目最重要的函数。
每调用一次,引擎完成一次完整物理解算:
-
重力计算
-
碰撞检测
-
动力学更新
-
更新 qpos、qvel、机身姿态
人形机器人行走、平衡、动态全部依靠该函数迭代。
3.3.2. viewer.sync()
同步渲染画面,保证仿真画面与物理计算同步。
3.4名称与索引互查 API
这一组 API 彻底解决新手写死关节下标导致模型更新就崩代码的问题。
3.4.1. mj_name2id(通过名称查索引|最常用)
根据关节/连杆的英文名字,自动获取运行时索引,代码永久兼容。
joint_id = mujoco.mj_name2id( model, mujoco.mjtObj.mjOBJ_JOINT, "J00_HIP_PITCH_L" )
支持查询:关节、连杆、执行器、传感器等所有对象。
2. mj_id2name(通过索引查名称|调试用)
遍历打印所有关节、连杆名称,用于项目结构查看、日志输出。
name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_JOINT, idx)
3.5核心数据数组 API
T800 所有控制、观测全部基于以下三个数组:
3.5.1. data.qpos 关节位置/姿态数组
存储:机身位置 + 机身四元数 + 所有关节角度。
长度对应 model.nq,MuJoCo 全部使用弧度单位。
3.5.2. data.qvel 关节速度数组
存储:机身移动速度、旋转速度、各关节角速度。
长度对应 model.nv。
3.5.3. data.ctrl 控制指令数组
机器人控制唯一入口
我们写的 PD 控制、AI 策略输出的力矩,全部写入 data.ctrl,下发给机器人电机。
长度对应 model.nu(执行器数量)
3.6模型维度参数 API
这组参数对应你前面的 T800 结构图,是观测空间、算法维度设计依据:
-
model.nq:广义位置维度(姿态总参数数量)
-
model.nv:广义速度维度(运动自由度总数)
-
model.njnt:可控关节总数
-
model.nbody:机器人连杆刚体总数
-
model.nu:可控电机执行器数量
3.7关节偏移查询 API
3.7.1. model.jnt_qposadr
获取指定关节在 qpos 数组中的下标偏移,精准读取单个关节角度。
3.7.2. model.jnt_dofadr
获取指定关节在 qvel 数组中的下标偏移,读取单个关节角速度。
✅ 规范写法:通过名称查 ID → 通过 ID 查偏移 → 读取角度/速度
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)