3. 环境搭建:基于PyBullet/MuJoCo的机器人仿真环境配置与力矩控制接口
说实话,做强化学习控制,最头疼的往往不是算法本身,而是环境搭不起来。我见过太多人卡在仿真器配置上,一卡就是一周。今天咱们就把这事彻底搞定。
你想想看,力矩控制跟位置控制不一样。位置控制你发个目标角度,电机自己就闭环了。但力矩控制,你得直接跟电机电流打交道,仿真器得暴露底层接口。PyBullet和MuJoCo,是目前最主流的两个选择。
核心观点:仿真环境不是越复杂越好,关键是力矩控制接口要干净、实时性要够、状态观测要全。我个人的经验是,先跑通一个简单的单关节,再扩展到多关节。
3.1 为什么选PyBullet和MuJoCo?
市面上仿真器不少,Gazebo、Webots、V-REP……但我为什么推荐这两个?
- PyBullet:开源、Python原生、安装简单。我在项目里用它做快速原型验证,一天就能搭完。
- MuJoCo:物理精度高、计算快。2019年开源后,学术界基本都转过去了。DeepMind的很多工作都基于它。
做个对比你就明白了:
| 特性 | PyBullet | MuJoCo |
|---|---|---|
| 安装难度 | pip install pybullet 一行搞定 | 需要编译或下载预编译包 |
| 力矩控制 | setJointMotorControl2 直接支持 | 通过 mjModel.actuator 接口 |
| 物理精度 | 中等,适合快速验证 | 高,适合精细控制 |
| 渲染速度 | OpenGL,较慢 | 原生渲染,极快 |
| 社区生态 | ROS集成好 | 强化学习社区主流 |
我个人习惯是:原型阶段用PyBullet,正式实验切MuJoCo。这样既快又稳。
3.2 PyBullet环境搭建与力矩控制接口
先讲PyBullet。安装真的简单:
pip install pybullet
然后加载一个机器人模型。我常用的是KUKA LBR iiwa,它有7个关节,力矩控制接口很干净。
import pybullet as p
import pybullet_data
# 连接仿真器
p.connect(p.GUI)
p.setAdditionalSearchPath(pybullet_data.getDataPath())
# 加载机器人
plane_id = p.loadURDF("plane.urdf")
robot_id = p.loadURDF("kuka_iiwa/model.urdf", [0, 0, 0])
# 设置重力
p.setGravity(0, 0, -9.81)
嗯,这里要注意:力矩控制需要设置控制模式。默认是位置控制,你得显式切换。
# 获取关节数量
num_joints = p.getNumJoints(robot_id)
# 设置每个关节为力矩控制模式
for joint_idx in range(num_joints):
p.setJointMotorControl2(
bodyUniqueId=robot_id,
jointIndex=joint_idx,
controlMode=p.TORQUE_CONTROL,
force=0 # 初始力矩为0
)
我曾经踩过的坑:忘记设置最大力(force参数),结果力矩控制根本不起作用。仿真器会默认用位置控制,你发的力矩指令被忽略了。检查方法很简单——打印关节状态,看实际力矩是不是跟随你的指令。
发送力矩指令的接口长这样:
# 定义力矩指令(7个关节)
torque_cmd = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
# 发送力矩
for joint_idx in range(num_joints):
p.setJointMotorControl2(
bodyUniqueId=robot_id,
jointIndex=joint_idx,
controlMode=p.TORQUE_CONTROL,
force=torque_cmd[joint_idx]
)
# 步进仿真
p.stepSimulation()
读取状态也很关键。强化学习需要观测关节角度、角速度、力矩:
def get_robot_state(robot_id, num_joints):
joint_states = p.getJointStates(robot_id, range(num_joints))
positions = []
velocities = []
torques = []
for state in joint_states:
positions.append(state[0]) # 关节角度
velocities.append(state[1]) # 关节角速度
torques.append(state[3]) # 关节力矩(传感器读数)
return positions, velocities, torques
小技巧:PyBullet的力矩传感器读数在state[3],但注意它返回的是施加在关节上的力矩,不是电机指令。如果你要算控制误差,得自己对比指令和读数。
3.3 MuJoCo环境搭建与力矩控制接口
MuJoCo的安装稍微麻烦一点。我建议用mujoco-py这个Python绑定:
pip install mujoco-py
但你可能需要先装一些系统依赖。Ubuntu下:
sudo apt install libosmesa6-dev libgl1-mesa-glx libglfw3
MuJoCo用XML文件描述模型。一个简单的力矩控制机器人模型长这样:
<mujoco model="simple_arm">
<worldbody>
<body name="base" pos="0 0 0">
<joint name="joint1" type="hinge" axis="0 0 1"/>
<geom type="box" size="0.05 0.05 0.1" pos="0 0 0.1"/>
<body name="link1" pos="0 0 0.2">
<joint name="joint2" type="hinge" axis="0 1 0"/>
<geom type="box" size="0.05 0.05 0.1" pos="0 0 0.1"/>
</body>
</body>
</worldbody>
<actuator>
<motor joint="joint1" name="motor1" ctrlrange="-10 10" ctrllimited="true"/>
<motor joint="joint2" name="motor2" ctrlrange="-10 10" ctrllimited="true"/>
</actuator>
</mujoco>
注意看<actuator>部分。这里定义了电机执行器,类型是motor。MuJoCo的力矩控制就是通过这个接口实现的。
加载并运行:
import mujoco_py
import numpy as np
# 加载模型
model = mujoco_py.load_model_from_path("simple_arm.xml")
sim = mujoco_py.MjSim(model)
# 获取执行器数量
num_actuators = model.nu
# 设置力矩指令(直接对应执行器)
ctrl = np.zeros(num_actuators)
ctrl[0] = 1.0 # 关节1施加1Nm
ctrl[1] = -0.5 # 关节2施加-0.5Nm
# 发送指令
sim.data.ctrl[:] = ctrl
# 步进仿真
sim.step()
读取状态的方式:
# 关节角度
qpos = sim.data.qpos.copy()
# 关节角速度
qvel = sim.data.qvel.copy()
# 关节力矩(传感器)
torque = sim.data.qfrc_actuator.copy()
关键区别:MuJoCo的力矩控制是直接通过执行器接口,不需要像PyBullet那样切换模式。你给ctrl赋值,它就当力矩用。但要注意,执行器的ctrlrange限制了力矩范围,别超了。
3.4 两种仿真器的力矩控制对比
我整理了一张表,方便你对照:
| 操作 | PyBullet | MuJoCo |
|---|---|---|
| 设置力矩模式 | setJointMotorControl2(controlMode=p.TORQUE_CONTROL) | 无需设置,默认就是力矩 |
| 发送力矩 | force=torque_cmd | sim.data.ctrl[:] = torque_cmd |
| 读取关节角度 | getJointStates()[0] | sim.data.qpos |
| 读取关节速度 | getJointStates()[1] | sim.data.qvel |
| 读取力矩传感器 | getJointStates()[3] | sim.data.qfrc_actuator |
| 步进仿真 | stepSimulation() | sim.step() |
说实话,从接口简洁度来看,MuJoCo更胜一筹。但PyBullet的调试可视化更好,你能直接看到关节在动。
3.5 环境搭建的核心逻辑
不管用哪个仿真器,强化学习环境的结构是一样的。我画了张图,帮你理清思路:
这个流程其实很直观。智能体输出力矩,仿真器执行物理,返回状态,然后算奖励。循环往复。
3.6 避坑指南与个人经验
最后,分享几个我踩过的坑:
- 仿真步长不匹配:PyBullet默认步长是1/240秒,MuJoCo是0.002秒。强化学习里,控制频率和仿真频率要分开。我习惯用控制频率100Hz,仿真频率1000Hz,中间做多次子步。
- 力矩限制:真实机器人力矩有限,仿真里也要加。不然策略学出来全是大力出奇迹,部署到真机上直接烧电机。
- 初始状态随机化:强化学习需要泛化能力。我每次重置环境时,会随机化关节角度和速度,让策略适应不同初始条件。
我曾经犯过的错:在PyBullet里用力矩控制,但忘了设置setPhysicsEngineParameter的enableConeFriction。结果摩擦力模型不对,仿真出来的力矩和真实物理差很多。检查方法:用手推机器人,看力矩传感器读数是否合理。
好了,环境搭建就讲到这里。你先把PyBullet跑通,再试MuJoCo。两个都熟了,后面做力矩控制算法就顺手了。