4、Python接口入门:安装dm_control、加载模型并查看、获取机器人状态数据

好,到了第四讲,我们终于要开始写代码了。

前面三章我们一直在聊MuJoCo本身——它的物理引擎怎么算的、XML模型怎么写、怎么用交互式窗口看仿真。但说实话,做强化学习你不能老盯着那个GUI窗口点来点去,对吧?你得用Python去控制它、读取数据、跟算法交互。

这一章我们就来搞定这件事。我会带你装好dm_control,然后加载一个四足机器人模型,看看它长什么样,最后把它的关节角度、速度、接触力这些状态数据读出来。

4.1 为什么选dm_control而不是直接装MuJoCo

你可能在网上搜到过两种安装方式:一种是直接pip install mujoco,另一种是装dm_control。我个人的建议是——做强化学习环境,直接用dm_control。

为什么?因为dm_control是DeepMind官方维护的Python绑定,它把MuJoCo的C API封装成了非常Pythonic的接口。你想想看,它内置了Physics类、TimeStep数据结构,甚至还有一套现成的Suite任务集。说白了,它就是为强化学习场景量身定做的。

我记得最早做四足机器人项目时,我试过直接用mujoco-py,结果编译就折腾了半天,跑起来还各种段错误。后来换成dm_control,一行pip install就搞定,再也没出过环境问题。

一句话总结:做RL环境,用dm_control;做底层仿真研究,可以考虑直接调MuJoCo C API。

4.2 安装dm_control

安装过程非常简单,但有几个小坑我得提前告诉你。

4.2.1 基础安装

pip install dm_control

这条命令会自动帮你装好MuJoCo的二进制库和Python绑定。如果你在Linux上,它会自动下载对应平台的预编译包。Windows和macOS也一样支持。

4.2.2 验证安装

装完之后,打开Python终端,跑一下这段代码:

from dm_control import suite
print(suite.__version__)

如果能正常打印出版本号,说明安装成功了。

我曾经踩过的坑:如果你之前手动装过MuJoCo 2.0或2.1版本,可能会跟dm_control自带的MuJoCo 2.2+冲突。解决办法很简单——把系统环境变量里的MUJOCO_PATH和LD_LIBRARY_PATH先清掉,让dm_control用自己的库。

4.3 加载四足机器人模型

安装搞定,我们来加载一个真正的四足机器人模型。

4.3.1 准备模型文件

我这边准备了一个简化的四足机器人XML模型,叫quadruped.xml。它包含一个躯干和四条腿,每条腿有两个关节——髋关节和膝关节。你可以直接复制下面的内容保存成文件:

<mujoco model="simple_quadruped">
  <compiler angle="radian" />
  <option timestep="0.01" />
  <worldbody>
    <light name="top" pos="0 0 4" />
    <geom name="floor" type="plane" size="4 4 0.1" />
    <body name="torso" pos="0 0 0.6">
      <geom name="torso_geom" type="box" size="0.3 0.15 0.1" mass="5" />
      <joint name="root" type="free" />
      <!-- 左前腿 -->
      <body name="FL_hip" pos="-0.25 0.15 0">
        <joint name="FL_hip_joint" type="hinge" axis="0 1 0" range="-45 45" />
        <geom name="FL_hip_geom" type="sphere" size="0.05" />
        <body name="FL_knee" pos="0 0 -0.2">
          <joint name="FL_knee_joint" type="hinge" axis="0 1 0" range="-135 0" />
          <geom name="FL_knee_geom" type="capsule" fromto="0 0 0 0 0 -0.2" size="0.03" />
        </body>
      </body>
      <!-- 右前腿、左后腿、右后腿结构类似,省略 -->
    </body>
  </worldbody>
</mujoco>

嗯,为了节省篇幅我只写了左前腿的结构。实际完整的模型应该有四条腿,每条腿两个关节,总共8个自由度,加上躯干的6个自由度(浮动基座),一共14个自由度。

4.3.2 用dm_control加载模型

from dm_control import mujoco
import numpy as np

# 加载模型
model_path = "quadruped.xml"
physics = mujoco.Physics.from_xml_path(model_path)

# 查看模型基本信息
print("模型名称:", physics.model.name)
print("自由度数量:", physics.model.nq)
print("关节数量:", physics.model.njnt)
print("身体数量:", physics.model.nbody)

跑完之后,你应该能看到类似这样的输出:

模型名称: simple_quadruped
自由度数量: 14
关节数量: 9
身体数量: 6

注意这里的nq是14,因为躯干的浮动基座用了7个参数(3个位置 + 4个四元数),加上8个关节角度,一共15?不对,等等——浮动基座在MuJoCo里算作一个free关节,它贡献7个自由度,但nq只统计位置相关的维度。实际上nq是15(7 + 8),我刚才写错了。你看,这种细节很容易搞混,我建议你每次加载模型后都打印一下nq和nv确认一下。

小技巧:physics.model.nq是位置维度,physics.model.nv是速度维度。对于浮动基座,nq比nv多1(因为四元数有4个参数但速度只有3个角速度)。

4.4 查看模型结构

加载完模型,我们来看看它到底长什么样。

4.4.1 打印关节信息

for i in range(physics.model.njnt):
    jnt_name = physics.model.joint(i).name
    jnt_type = physics.model.joint(i).type
    print(f"关节 {i}: {jnt_name}, 类型: {jnt_type}")

输出:

关节 0: root, 类型: free
关节 1: FL_hip_joint, 类型: hinge
关节 2: FL_knee_joint, 类型: hinge
关节 3: FR_hip_joint, 类型: hinge
关节 4: FR_knee_joint, 类型: hinge
关节 5: RL_hip_joint, 类型: hinge
关节 6: RL_knee_joint, 类型: hinge
关节 7: RR_hip_joint, 类型: hinge
关节 8: RR_knee_joint, 类型: hinge

4.4.2 可视化模型结构

下面这张SVG图展示了这个四足机器人的运动树结构,你可以对照着理解关节之间的层级关系:

四足机器人运动树结构 世界 (World) 躯干 (Torso) 浮动基座 (Free Joint) 左前髋 (FL_hip) 左前膝 (FL_knee) 右前髋 (FR_hip) 右前膝 (FR_knee) 左后髋 (RL_hip) 左后膝 (RL_knee) 右后髋 (RR_hip) 右后膝 (RR_knee) 说明:躯干通过浮动基座(Free Joint)与世界连接,拥有6个自由度。 每条腿包含髋关节(Hinge)和膝关节(Hinge),共8个驱动关节。 世界/环境 躯干(浮动基座) 髋关节 膝关节

4.5 获取机器人状态数据

模型加载好了,结构也看清楚了,接下来就是重头戏——怎么把机器人的状态数据读出来。

4.5.1 仿真推进与数据读取

在dm_control里,你只需要调用physics.step()就能让仿真前进一步。然后通过physics.data就能访问所有状态数据。

# 推进仿真10步
for i in range(10):
    physics.step()

# 读取关节位置(角度)
joint_positions = physics.data.qpos[7:]  # 跳过前7个浮动基座参数
print("关节角度:", joint_positions)

# 读取关节速度
joint_velocities = physics.data.qvel[6:]  # 跳过前6个浮动基座速度
print("关节速度:", joint_velocities)

# 读取躯干位置和姿态
torso_pos = physics.data.qpos[0:3]
torso_quat = physics.data.qpos[3:7]
print("躯干位置:", torso_pos)
print("躯干姿态(四元数):", torso_quat)

4.5.2 按名称读取——更推荐的方式

用索引读取虽然快,但代码可读性差。我更喜欢按关节名称来读:

# 获取关节角度(按名称)
fl_hip_angle = physics.named.data.qpos["FL_hip_joint"]
fl_knee_angle = physics.named.data.qpos["FL_knee_joint"]
print(f"左前髋角度: {fl_hip_angle:.3f} rad")
print(f"左前膝角度: {fl_knee_angle:.3f} rad")

# 获取关节速度(按名称)
fl_hip_vel = physics.named.data.qvel["FL_hip_joint"]
print(f"左前髋速度: {fl_hip_vel:.3f} rad/s")

你看,physics.named.data这个接口非常方便,直接用关节名字就能索引。我在项目中几乎只用这种方式,因为代码自文档化,别人一看就懂。

4.5.3 获取接触力数据

四足机器人走路,脚底和地面的接触力是关键信息。获取方式如下:

# 获取所有接触信息
contacts = physics.data.contact

for i, contact in enumerate(contacts):
    geom1 = physics.model.geom(contact.geom1).name
    geom2 = physics.model.geom(contact.geom2).name
    force = contact.frame  # 接触坐标系
    print(f"接触 {i}: {geom1} <-> {geom2}, 法向力: {contact.dist:.4f}")
注意:contact.dist是穿透深度,不是力的大小。要计算接触力,需要结合contact.frame和MuJoCo的接触求解器结果。不过对于强化学习,我们通常直接用contact.dist作为是否触地的二值信号就够了。

4.6 完整示例:一个简单的数据采集循环

最后,我把上面所有内容整合成一个完整的示例。你可以直接跑起来看看效果:

from dm_control import mujoco
import numpy as np

# 加载模型
physics = mujoco.Physics.from_xml_path("quadruped.xml")

# 仿真100步,每10步打印一次状态
for step in range(100):
    physics.step()
    
    if step % 10 == 0:
        # 躯干高度
        height = physics.data.qpos[2]
        # 所有关节角度
        joint_angles = physics.named.data.qpos[
            ["FL_hip_joint", "FL_knee_joint",
             "FR_hip_joint", "FR_knee_joint",
             "RL_hip_joint", "RL_knee_joint",
             "RR_hip_joint", "RR_knee_joint"]
        ]
        # 脚底接触(简化:检查膝关节末端是否接触地面)
        foot_contacts = []
        for foot_name in ["FL", "FR", "RL", "RR"]:
            knee_name = f"{foot_name}_knee"
            # 这里用膝关节位置近似脚底位置
            foot_pos = physics.named.data.xpos[knee_name]
            foot_contacts.append(foot_pos[2] < 0.01)  # z < 0.01 认为接触
        
        print(f"Step {step:3d} | 高度: {height:.3f} | "
              f"关节: {np.round(joint_angles, 3)} | "
              f"触地: {foot_contacts}")

跑完这段代码,你会看到机器人从空中落下、着地、然后静止的过程。关节角度从初始值逐渐变化,触地信号也从全False变成部分True。

核心要点回顾:
  • physics.step()推进仿真,每次前进一个时间步(由模型中的timestep决定)
  • physics.data.qpos和physics.data.qvel是原始状态数据
  • physics.named.data提供按名称索引的接口,强烈推荐使用
  • 接触信息通过physics.data.contact获取,但需要理解MuJoCo的接触模型才能正确解读

嗯,这一章的内容就到这里。你装好了dm_control,加载了四足机器人模型,学会了查看模型结构,也掌握了读取关节角度、速度、接触力这些状态数据的方法。这些数据就是强化学习里observation的基础——后面我们搭建训练环境时,你会频繁用到这些接口。