模块 2:认识 T800 机器人仿真模型

第一章:两个核心坐标系

第二章:结合T800人形机器人理解坐标系


做过人形机器人仿真开发的朋友,大概率踩过坐标系的坑:明明写的控制代码逻辑没问题,机器人肢体却乱飞、姿态错乱、碰撞检测异常。很多时候不是动力学算法出错,而是分不清相对坐标(局部坐标)和绝对坐标(世界坐标)

本课时以 T800 人形机器人模型为载体,讲解 MuJoCo 仿真环境下两套坐标系的含义、API 读取方式、适用场景,以及二者之间的转换逻辑,是后续 PD 控制、AI 策略生成、人形行走任务的前置基础。


第一章:两个核心坐标系

1.1 绝对坐标(世界坐标系 World Frame)

世界坐标系是 MuJoCo 仿真环境里固定不变的全局参考框架,原点一般在仿真地面,Z 轴竖直向上,X 向前,Y 向左,遵循右手坐标系规则。

  • 含义:所有 link/body 相对于仿真场景地面的位置姿态,不受机器人自身转动、关节摆动影响。
  • 作用:描述机器人整体在环境中的位置、双脚落地位置、机器人和场景物体(木块、障碍物)之间的空间关系。
  • MuJoCo 数据接口:
# body的世界坐标位置
body_world_pos = data.xpos[body_id]
# body的世界旋转矩阵
body_world_mat = data.xmat[body_id].reshape(3,3)
# body的世界四元数姿态
body_world_quat = data.xquat[body_id]

xpos/xmat/xquat存储的都是绝对世界坐标系下的位姿,仿真每一步主循环会自动更新这些数值

1.2相对坐标(局部坐标系 Local Frame)

相对坐标是以 ** 父 body(父连杆)** 作为参考基准,每个连杆、关节都拥有自己的局部坐标系。

  • 含义:子部件相对于父连杆的偏移、旋转。T800 机器人是树形运动学树,躯干是根节点,大腿相对于躯干、小腿相对于大腿,全部使用相对坐标描述。
  • 作用:机器人模型 MJCF 文件定义时,body 的pos属性默认就是相对父 body 的局部坐标;关节运动输出、PD 控制目标位置,大多是在局部坐标系下表达。
  • MuJoCo 数据接口:
# qpos里面关节角度,是关节自身局部坐标系的转动量
joint_qpos = data.qpos[joint_id]

⚠️重点区分:MJCF 配置文件写的 pos 是相对父物体;仿真运行时 data.xpos 返回的是世界绝对坐标,二者不要混淆MuJoCo。

一张表快速对比

维度绝对坐标(世界坐标)相对坐标(局部坐标)
参考基准仿真环境地面,固定不变父连杆 body,随父部件运动变化
获取接口data.xpos / data.xquatqpos、模型 mjcf 中的 pos 属性
典型用途判断机器人是否摔倒、与环境物体交互关节控制、PD 控制器目标设置、模型建模
变化特性机器人全身移动就会改变父不动,子部件才会改变

第二章:结合 T800 人形机器人理解坐标系

2.1T800机器人的坐标系

T800 人形机器人拥有大量 link(连杆)与 joint(关节),整棵运动学树从躯干根节点向下分支:躯干→大腿→小腿→足部;躯干→大臂→小臂→手部。

  1. 躯干的世界坐标:读取躯干 body 的xpos,可以直接拿到 T800 机器人整个人在仿真地面上的三维位置。机器人向前走,躯干的 x 轴世界坐标就持续变大。
  2. 大腿相对躯干坐标:大腿连杆的位置,是相对于躯干的局部坐标系。当机器人躯干发生倾斜旋转,大腿的局部坐标基准也跟着旋转。
  3. 足部的两套坐标
    • 绝对坐标:脚在地面上的落点,用来做落地检测、判断步长;
    • 相对坐标:脚相对于躯干的偏移,用于逆运动学求解、规划腿部摆动轨迹。

利用代码先来查看一下mujoco仿真平台下T800各个关节的坐标。

提示语:帮我编写一个脚本叫07_T800_body_pos.py用于获取机器人每个body的body pos,并且把他们打印出来

"""
07_T800_body_pos.py
获取并打印 T800 机器人每个 body 的位置 (x, y, z)
- body_pos:   XML 中定义的相对父 body 的偏移
- xpos:       仿真计算后的世界坐标 (需 mj_forward 后才有值)
运行: D:/miniconda3/envs/t800/python.exe 07_T800_body_pos.py
"""
import mujoco

model = mujoco.MjModel.from_xml_path("t800.xml")
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)  # 计算运动学, 填充 xpos

print(f"body 总数: {model.nbody}")
print("-" * 78)
print(f"{'#':>3}  {'名称':<28} {'相对父body pos (x,y,z)':<28} {'世界坐标 (x,y,z)':<24}")
print("-" * 78)

for i in range(model.nbody):
    name = mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_BODY, i) or "(world)"
    rel = model.body_pos[i]          # 相对父 body 的偏移
    world = data.xpos[i]             # 世界坐标系下的位置
    print(f"{i:>3}  {name:<28} "
          f"({rel[0]:7.3f},{rel[1]:7.3f},{rel[2]:7.3f})   "
          f"({world[0]:7.3f},{world[1]:7.3f},{world[2]:7.3f})")
###
在Anaconda Prompt软件中执行这些命令
###
(base) C:\Users\qx>conda activate t800        ##激活虚拟环境

(t800) C:\Users\qx>cd Desktop\T800\code

(t800) C:\Users\qx\Desktop\T800\code>cd ../

(t800) C:\Users\qx\Desktop\T800>cd code2      ##定位到代码存放位置

(t800) C:\Users\qx\Desktop\T800\code2>python 07_T800_body_pos.py  ##执行代码

2.2坐标系使用情况

实际开发中,经常需要互相转换:

  1. 拿到世界坐标系下目标点,需要转换到机器人局部坐标系,再给到关节控制器;
  2. 读取关节局部运动数据,需要转换为世界坐标,判断肢体有没有碰撞地面。

MuJoCo 内部已经提供完整变换矩阵,通过data.xmat旋转矩阵,就可以完成局部↔世界坐标的矢量变换。

本课时只建立概念,具体矩阵运算会在后续课时的仿真调试、二次开发环节实战演示。

✅使用绝对坐标场景

  1. 判断机器人是否摔倒:读取躯干、头部的世界坐标高度;
  2. 环境交互任务:T800 踢木块、手持物体走路,计算机器人身体和场景物体之间距离;
  3. 观测空间构建(后面 AI 强化学习会用到):获取机器人各个部位在全局环境中的位置作为观测输入。

✅使用相对坐标场景

  1. PD 控制写关节目标,关节驱动都是基于局部坐标系;
  2. MJCF 模型编写、修改连杆位置;
  3. 机器人本体内部运动规划,肢体之间的相对摆动。

我们可以结合2.1中获取的机器人坐标生成机器人骨架

-----------------------------------------------------------------------------
  0  world                        (  0.000,  0.000,  0.000)   (  0.000,  0.000,  0.000)
  1  LINK_BASE                    (  0.000,  0.000,  1.030)   (  0.000,  0.000,  1.030)
  2  LINK_HIP_PITCH_L             (  0.000,  0.079,  0.000)   (  0.000,  0.079,  1.030)
  3  LINK_HIP_ROLL_L              (  0.005,  0.040, -0.108)   (  0.005,  0.119,  0.922)
  4  LINK_HIP_YAW_L               (  0.009,  0.000, -0.081)   (  0.014,  0.119,  0.842)
  5  LINK_KNEE_PITCH_L            ( -0.038,  0.000, -0.288)   ( -0.024,  0.119,  0.554)
  6  LINK_ANKLE_PITCH_L           ( -0.003,  0.000, -0.439)   ( -0.026,  0.119,  0.115)
  7  LINK_ANKLE_ROLL_L            (  0.001,  0.000, -0.038)   ( -0.025,  0.119,  0.077)
  8  LINK_FOOT_L                  (  0.000,  0.000,  0.000)   ( -0.025,  0.119,  0.077)
  9  LINK_HIP_PITCH_R             (  0.000, -0.079,  0.000)   (  0.000, -0.079,  1.030)
 10  LINK_HIP_ROLL_R              (  0.005, -0.040, -0.108)   (  0.005, -0.119,  0.922)
 11  LINK_HIP_YAW_R               (  0.009,  0.000, -0.081)   (  0.014, -0.119,  0.842)
 12  LINK_KNEE_PITCH_R            ( -0.038,  0.000, -0.288)   ( -0.024, -0.119,  0.554)
 13  LINK_ANKLE_PITCH_R           ( -0.003,  0.000, -0.439)   ( -0.026, -0.119,  0.115)
 14  LINK_ANKLE_ROLL_R            (  0.001,  0.000, -0.038)   ( -0.025, -0.119,  0.077)
 15  LINK_FOOT_R                  (  0.000,  0.000,  0.000)   ( -0.025, -0.119,  0.077)
 16  LINK_WAIST_YAW               (  0.000,  0.000,  0.104)   (  0.000,  0.000,  1.134)
 17  LINK_SHOULDER_PITCH_L        ( -0.011,  0.163,  0.279)   ( -0.011,  0.163,  1.413)
 18  LINK_SHOULDER_ROLL_L         (  0.000,  0.063, -0.015)   ( -0.011,  0.226,  1.397)
 19  LINK_SHOULDER_YAW_L          (  0.000,  0.009, -0.083)   ( -0.011,  0.234,  1.315)
 20  LINK_ELBOW_PITCH_L           (  0.002,  0.014, -0.151)   ( -0.009,  0.248,  1.164)
 21  LINK_ELBOW_YAW_L             ( -0.002,  0.009, -0.132)   ( -0.011,  0.257,  1.032)
 22  LINK_WRIST_END_L             (  0.029,  0.012, -0.132)   (  0.018,  0.270,  0.900)
 23  LINK_SHOULDER_PITCH_R        ( -0.011, -0.163,  0.279)   ( -0.011, -0.163,  1.413)
 24  LINK_SHOULDER_ROLL_R         (  0.000, -0.063, -0.015)   ( -0.011, -0.226,  1.397)
 25  LINK_SHOULDER_YAW_R          (  0.000, -0.009, -0.083)   ( -0.011, -0.234,  1.315)
 26  LINK_ELBOW_PITCH_R           (  0.002, -0.014, -0.151)   ( -0.009, -0.248,  1.164)
 27  LINK_ELBOW_YAW_R             ( -0.002, -0.009, -0.132)   ( -0.011, -0.257,  1.032)
 28  LINK_WRIST_END_R             (  0.029, -0.012, -0.132)   (  0.018, -0.270,  0.900)
 29  LINK_HEAD_PITCH              ( -0.011,  0.000,  0.386)   ( -0.011,  0.000,  1.520)
 30  LINK_HEAD_YAW                (  0.000,  0.000,  0.040)   ( -0.011,  0.000,  1.560)

提示语:基于上面的世界坐标信息,帮我写一个matplotlib的代码叫08_T800_body_xyz.py,展示机器人的骨架

这个命令执行需要安装一个包,名为matplotlib,可以执行下面这个命令安装,或者让trae给你安装

###
在Anaconda Prompt软件中执行这些命令
###
conda activate t800
pip install matplotlib
"""
08_T800_body_xyz.py
用 matplotlib 3D 可视化 T800 机器人的骨架 (基于 body 世界坐标 + 父子连线)
运行: D:/miniconda3/envs/t800/python.exe 08_T800_body_xyz.py
"""
import mujoco
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D  # noqa: F401

model = mujoco.MjModel.from_xml_path("t800.xml")
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)

# 收集每个 body 的世界坐标
n = model.nbody
pos = np.zeros((n, 3))
names = []
for i in range(n):
    pos[i] = data.xpos[i]
    names.append(mujoco.mj_id2name(model, mujoco.mjtObj.mjOBJ_BODY, i) or "world")

# 父子关系构成骨架线
edges = []
for i in range(1, n):  # 跳过 world(0)
    pid = int(model.body_parentid[i])
    edges.append((pid, i))

# 按部位分组着色
LEG_L = {2, 3, 4, 5, 6, 7, 8}
LEG_R = {9, 10, 11, 12, 13, 14, 15}
ARM_L = {17, 18, 19, 20, 21, 22}
ARM_R = {23, 24, 25, 26, 27, 28}
HEAD = {29, 30}
TORSO = {1, 16}

def color_of(i):
    if i in LEG_L:
        return "blue"
    if i in LEG_R:
        return "orange"
    if i in ARM_L:
        return "green"
    if i in ARM_R:
        return "red"
    if i in HEAD:
        return "purple"
    return "black"  # torso / base / world


fig = plt.figure(figsize=(6, 8))
ax = fig.add_subplot(111, projection="3d")

# 画骨架连线
for a, b in edges:
    color = color_of(b)  # 以子节点的部位上色
    ax.plot([pos[a, 0], pos[b, 0]],
            [pos[a, 1], pos[b, 1]],
            [pos[a, 2], pos[b, 2]],
            color=color, linewidth=2.0)

# 画关节点
for i in range(1, n):  # 跳过 world
    ax.scatter(pos[i, 0], pos[i, 1], pos[i, 2],
              color=color_of(i), s=40, edgecolors="black", linewidths=0.5, depthshade=True)

# 标注关键关节名
labels = {1: "BASE", 16: "WAIST", 5: "KNEE_L", 12: "KNEE_R",
          17: "SHOULDER_L", 23: "SHOULDER_R", 22: "WRIST_L", 28: "WRIST_R",
          30: "HEAD"}
for idx, label in labels.items():
    ax.text(pos[idx, 0], pos[idx, 1], pos[idx, 2] + 0.03,
            label, fontsize=7, color="black")

ax.set_xlabel("X (m)")
ax.set_ylabel("Y (m)")
ax.set_zlabel("Z (m)")
ax.set_title("T800 Robot Skeleton (Body Positions)")

# 等比例 + 朝向
ax.set_box_aspect((1, 1, 1.5))
all_pts = pos[1:]  # 不含 world
ax.set_xlim(all_pts[:, 0].min() - 0.3, all_pts[:, 0].max() + 0.3)
ax.set_ylim(all_pts[:, 1].min() - 0.3, all_pts[:, 1].max() + 0.3)
ax.set_zlim(0, all_pts[:, 2].max() + 0.1)

ax.view_init(elev=15, azim=-75)
plt.tight_layout()
plt.show()

总结

本节课是从看懂模型走向写运动控制代码的关键分水岭。后面 PD 控制、强化学习观测空间、踢木块、手持物体行走任务,全部依赖这套坐标系知识。

  • 绝对坐标:以仿真世界地面为基准,做环境交互、全局状态观测;
  • 相对坐标:以父连杆为基准,做关节控制、机器人本体运动规划;
  • MuJoCo 中data.xpos/xquat获取世界坐标;qpos、MJCF pos 字段代表局部相对坐标;
  • 人形仿真大量 bug 根源,都是坐标系混用,调试时优先打印坐标做校验。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐