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就搞定,再也没出过环境问题。
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_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图展示了这个四足机器人的运动树结构,你可以对照着理解关节之间的层级关系:
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的基础——后面我们搭建训练环境时,你会频繁用到这些接口。