3、仿真环境搭建:MuJoCo/Gym环境配置、自定义关节环境、仿真与实物的差距分析
说实话,搞机器人控制这么多年,我踩过最大的坑就是——仿真里跑得飞起,一上真机就趴窝。
所以这一章,咱们不整虚的。我带你手把手把仿真环境搭起来,再聊聊那些仿真和实物之间「说不清道不明」的差距。你想想看,如果连仿真都跑不通,谈何强化学习落地?
3.1 MuJoCo 与 Gym 环境配置
MuJoCo 现在已经是机器人仿真界的标配了。为什么?因为它快,而且物理引擎准。我最早用 Bullet,后来换成 MuJoCo,嗯,回不去了。
3.1.1 安装与基础配置
我个人习惯用 conda 管理环境,干净利落。直接上命令:
# 创建虚拟环境
conda create -n robot_rl python=3.9
conda activate robot_rl
# 安装 MuJoCo(2.1.0 版本最稳)
pip install mujoco==2.1.0
# 安装 Gym 接口
pip install gymnasium
pip install gymnasium-robotics
# 验证安装
python -c "import mujoco_py; print('MuJoCo OK')"
3.1.2 快速测试一个环境
装好了,咱们跑个简单的。看看能不能让一个机械臂动起来:
import gymnasium as gym
import time
env = gym.make('FetchReach-v2', render_mode='human')
obs, info = env.reset()
for _ in range(100):
action = env.action_space.sample()
obs, reward, terminated, truncated, info = env.step(action)
env.render()
time.sleep(0.02)
env.close()
看到那个机械臂在动了吗?如果没看到,检查一下你的显示器是不是接了独显。我有个学生用笔记本集显跑,画面卡成 PPT,后来换了台式机才流畅。
3.2 自定义关节环境
标准环境够用吗?说实话,大部分时候不够。你要控制的可能是四足机器人、灵巧手,甚至是某个非标关节。这时候就得自己动手了。
3.2.1 理解 MuJoCo 的 XML 模型
MuJoCo 用 XML 描述机器人。核心就三个东西:body(刚体)、joint(关节)、geom(几何形状)。
我给你看一个最简单的单关节模型:
<mujoco model="simple_arm">
<worldbody>
<body name="base" pos="0 0 0">
<geom type="box" size="0.1 0.1 0.05" rgba="0.5 0.5 0.5 1"/>
<joint name="shoulder" type="hinge" axis="0 0 1" pos="0 0 0.05"/>
<body name="link1" pos="0 0 0.15">
<geom type="cylinder" size="0.03 0.15" rgba="0.2 0.6 0.8 1"/>
<joint name="elbow" type="hinge" axis="0 1 0"/>
<body name="link2" pos="0 0 0.3">
<geom type="sphere" size="0.05" rgba="0.8 0.2 0.2 1"/>
</body>
</body>
</body>
</worldbody>
</mujoco>
这个模型描述了一个两连杆机械臂。肩关节绕 Z 轴旋转,肘关节绕 Y 轴旋转。说白了,就是一个简易版的 SCARA。
3.2.2 封装成 Gym 环境
有了模型,怎么把它变成强化学习能用的环境?继承 gymnasium.Env 就行。我一般这么写:
import gymnasium as gym
from gymnasium import spaces
import numpy as np
import mujoco
class SimpleArmEnv(gym.Env):
def __init__(self, xml_path='simple_arm.xml'):
super().__init__()
# 加载模型
self.model = mujoco.MjModel.from_xml_path(xml_path)
self.data = mujoco.MjData(self.model)
# 动作空间:两个关节的扭矩
self.action_space = spaces.Box(
low=-1.0, high=1.0, shape=(2,), dtype=np.float32
)
# 观测空间:关节角度 + 角速度
self.observation_space = spaces.Box(
low=-np.inf, high=np.inf, shape=(4,), dtype=np.float32
)
def reset(self, seed=None):
mujoco.mj_resetData(self.model, self.data)
return self._get_obs(), {}
def step(self, action):
# 施加控制
self.data.ctrl[:] = action
mujoco.mj_step(self.model, self.data)
# 计算奖励(这里用简单的距离奖励)
obs = self._get_obs()
reward = -np.linalg.norm(obs[:2]) # 让关节回到零位
terminated = False
return obs, reward, terminated, False, {}
def _get_obs(self):
return np.concatenate([
self.data.qpos[:2], # 关节角度
self.data.qvel[:2] # 关节速度
])
3.3 仿真与实物的差距分析
好,环境搭好了,策略也训练了。然后呢?上真机?别急,先看看这张图:
3.3.1 差距到底在哪?
我总结下来,差距主要来自三个方面:
- 动力学模型误差——仿真里的摩擦系数是常数,实物不是。关节间隙、皮带弹性,这些仿真很难精确建模。
- 感知与执行的不确定性——仿真里你读到的关节角度是精确值,实物上编码器有量化误差,电机有死区。我曾经遇到过仿真里能精确停到 0.01 弧度,真机上 ±0.05 弧度就算不错了。
- 时序与延迟——仿真里 step() 是同步的,实物上控制周期可能抖动,传感器数据有延迟。这个在高速运动中特别致命。
3.3.2 怎么缩小差距?
说实话,完全消除差距是不可能的。但我们可以让它「足够小」。我个人常用的方法:
- 域随机化(Domain Randomization):训练时随机化物理参数。比如质量 ±20%,摩擦系数 ±50%。这样策略学出来更鲁棒。
- 系统辨识:先采集真机数据,再用优化算法拟合仿真参数。我习惯用 CMA-ES 做这个,效果不错。
- 加入噪声模型:在仿真中人为加入传感器噪声和执行器延迟。让策略提前适应「不完美」的世界。
🔑 核心观点: 仿真不是用来「复现」现实的,而是用来「覆盖」现实的不确定性。你覆盖的场景越多,策略上真机就越稳。
3.3.3 一个实战案例
我之前做一个四足机器人项目,仿真里能跑 3m/s,真机上 1m/s 就开始摔。查了三天,最后发现是仿真里用的电机模型太理想——没有考虑电流饱和。
解决办法?在仿真里加了一个简单的扭矩限幅:
# 在 step() 中加入电机饱和模型
max_torque = 2.0 # Nm
action_clipped = np.clip(action, -max_torque, max_torque)
# 再加一个一阶低通滤波,模拟电机响应延迟
self.filtered_action = 0.8 * self.filtered_action + 0.2 * action_clipped
self.data.ctrl[:] = self.filtered_action
就这么两行代码,真机速度直接提到了 2.5m/s。你想想看,有时候差距就在这些细节里。
3.4 本章小结
这一章我们干了三件事:
- 搭好了 MuJoCo + Gym 的仿真环境
- 学会了自定义关节环境,从 XML 模型到 Gym 封装
- 分析了仿真与实物的差距,并给出了实用的桥接方法
记住一句话:仿真环境是你的「训练场」,不是你的「避风港」。在仿真里跑得再好,也不代表真机上能行。但反过来,如果仿真都跑不好,真机就更别想了。
下一章,我们会真正开始用强化学习来训练关节控制策略。到时候你会发现,环境搭得好不好,直接决定了训练效率。嗯,先把基础打牢吧。
可可精品资料库