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 环境搭建的核心逻辑

不管用哪个仿真器,强化学习环境的结构是一样的。我画了张图,帮你理清思路:

强化学习力矩控制环境核心逻辑 强化学习智能体 动作:力矩指令 τ 仿真器(PyBullet/MuJoCo) 状态:q, q̇, τ_sensor 奖励函数 r(s, a) 状态反馈 说明: 智能体输出力矩指令 τ → 仿真器执行物理仿真 → 返回状态 q, q̇, τ_sensor → 计算奖励 → 更新策略 虚线表示状态反馈回路,用于下一时刻的决策。

这个流程其实很直观。智能体输出力矩,仿真器执行物理,返回状态,然后算奖励。循环往复。

3.6 避坑指南与个人经验

最后,分享几个我踩过的坑:

  • 仿真步长不匹配:PyBullet默认步长是1/240秒,MuJoCo是0.002秒。强化学习里,控制频率和仿真频率要分开。我习惯用控制频率100Hz,仿真频率1000Hz,中间做多次子步。
  • 力矩限制:真实机器人力矩有限,仿真里也要加。不然策略学出来全是大力出奇迹,部署到真机上直接烧电机。
  • 初始状态随机化:强化学习需要泛化能力。我每次重置环境时,会随机化关节角度和速度,让策略适应不同初始条件。

我曾经犯过的错:在PyBullet里用力矩控制,但忘了设置setPhysicsEngineParameter的enableConeFriction。结果摩擦力模型不对,仿真出来的力矩和真实物理差很多。检查方法:用手推机器人,看力矩传感器读数是否合理。

好了,环境搭建就讲到这里。你先把PyBullet跑通,再试MuJoCo。两个都熟了,后面做力矩控制算法就顺手了。