3. 执行器与传感器:给机器人装上“肌肉”和“神经”

上一章我们把四足机器人的骨架搭好了,但说实话,那只是一堆不会动的几何体。想让它在 MuJoCo 里跑起来,得给它装上两样东西:执行器(Actuator)——相当于肌肉,负责发力;传感器(Sensor)——相当于神经,负责感知。

我个人习惯把这一步叫做“赋予机器人生命力”。没有执行器,关节就是摆设;没有传感器,强化学习智能体就是个瞎子。今天我们就来搞定这两样。

核心要点:执行器定义“如何动”,传感器定义“如何感知”。两者缺一不可。

3.1 执行器(Actuator):给关节装上电机

MuJoCo 支持多种执行器类型,但做四足机器人强化学习,我们几乎只用一种——位置伺服电机(position servo)。说白了,就是给每个关节设定一个目标角度,然后电机自己 PID 算力矩去追那个角度。

为什么不用力矩控制?嗯,这里要注意:强化学习初期策略乱跳,纯力矩控制很容易把机器人甩飞。位置伺服天然带阻尼,训练稳定得多。我在项目中试过纯力矩,结果前 100 万步机器人一直在原地抽搐……后来老老实实换回位置伺服。

3.1.1 电机定义语法

执行器定义在 <actuator> 标签内。每个关节对应一个 <motor> 或 <position> 子标签。我们来看一个标准配置:

<actuator>
    <!-- 左前腿髋关节 -->
    <position name="FL_hip" joint="FL_hip" kp="100" kv="10" forcerange="-50 50"/>
    <!-- 左前腿膝关节 -->
    <position name="FL_knee" joint="FL_knee" kp="100" kv="10" forcerange="-50 50"/>

    <!-- 右前腿髋关节 -->
    <position name="FR_hip" joint="FR_hip" kp="100" kv="10" forcerange="-50 50"/>
    <!-- 右前腿膝关节 -->
    <position name="FR_knee" joint="FR_knee" kp="100" kv="10" forcerange="-50 50"/>

    <!-- 左后腿髋关节 -->
    <position name="RL_hip" joint="RL_hip" kp="100" kv="10" forcerange="-50 50"/>
    <!-- 左后腿膝关节 -->
    <position name="RL_knee" joint="RL_knee" kp="100" kv="10" forcerange="-50 50"/>

    <!-- 右后腿髋关节 -->
    <position name="RR_hip" joint="RR_hip" kp="100" kv="10" forcerange="-50 50"/>
    <!-- 右后腿膝关节 -->
    <position name="RR_knee" joint="RR_knee" kp="100" kv="10" forcerange="-50 50"/>
</actuator>

3.1.2 参数详解

参数 含义 推荐值(四足) 说明
name 电机名称 腿_关节 建议统一命名规范,方便后续代码索引
joint 绑定的关节名 与 body 定义一致 必须与 <joint> 的 name 完全匹配
kp 位置增益(刚度) 50~200 越大响应越快,但容易震荡
kv 速度增益(阻尼) 5~20 抑制震荡,建议 kp/kv ≈ 10
forcerange 最大输出力矩范围 -50 50 限制电机暴力输出,保护仿真稳定
我的调参经验:kp 从 100 开始试。如果机器人走路时关节抖动,就加大 kv 或者减小 kp。如果跟不上目标角度,就加大 kp。我曾经在一条腿上试了 30 多组参数才找到最佳组合——别怕试错。

3.2 接触传感器(Touch Sensor):让机器人“感觉”到地面

强化学习里,智能体怎么知道脚有没有着地?靠接触传感器。MuJoCo 的接触传感器可以检测两个几何体之间的接触力。我们通常在每个脚掌底部装一个。

你想想看,没有接触传感器,智能体连自己摔倒了都不知道,还怎么学走路?

3.2.1 定义接触传感器

传感器定义在 <sensor> 标签内。接触传感器用 <touch> 子标签:

<sensor>
    <touch name="FL_foot_touch" site="FL_foot_site"/>
    <touch name="FR_foot_touch" site="FR_foot_site"/>
    <touch name="RL_foot_touch" site="RL_foot_site"/>
    <touch name="RR_foot_touch" site="RR_foot_site"/>
</sensor>

这里有个关键点:site 属性引用的是 site 而不是 body。所以我们需要先在脚掌位置定义一个 site:

<body name="FL_foot" pos="0 0 -0.3">
    <geom type="sphere" size="0.04" rgba="0 0 1 1"/>
    <site name="FL_foot_site" pos="0 0 0" size="0.01"/>
</body>

site 可以理解为“虚拟点”,专门给传感器用的。它不参与碰撞,只提供位置和方向信息。

我曾经踩过的坑:忘记定义 site,直接把 body 名填进 touch 传感器的 site 属性。结果 MuJoCo 报错“找不到 site”,我排查了半小时才发现。记住:touch 传感器只认 site,不认 body。

3.2.2 接触传感器的输出

接触传感器返回的是一个 标量值——接触力的大小(单位:牛顿)。在强化学习里,我们通常把它二值化:

  • 力 > 0 → 脚着地(接触)
  • 力 = 0 → 脚悬空(未接触)

这个信息对步态学习至关重要。比如,我们希望四足机器人在 trot 步态下,对角腿同时着地——没有接触反馈,智能体根本不知道自己的脚在哪。

3.3 关节位置传感器(Joint Pos):读取关节角度

关节位置传感器返回每个关节的 当前角度(单位:弧度)。这是强化学习观测空间(observation space)里最基础的信息之一。

3.3.1 定义关节位置传感器

<sensor>
    <jointpos name="FL_hip_pos" joint="FL_hip"/>
    <jointpos name="FL_knee_pos" joint="FL_knee"/>
    <jointpos name="FR_hip_pos" joint="FR_hip"/>
    <jointpos name="FR_knee_pos" joint="FR_knee"/>
    <jointpos name="RL_hip_pos" joint="RL_hip"/>
    <jointpos name="RL_knee_pos" joint="RL_knee"/>
    <jointpos name="RR_hip_pos" joint="RR_hip"/>
    <jointpos name="RR_knee_pos" joint="RR_knee"/>
</sensor>

注意:joint 属性引用的是关节名,不是 body 名。这个传感器直接读取 <joint> 的当前 qpos 值。

3.3.2 为什么需要关节位置传感器?

你可能觉得奇怪:执行器不是已经知道目标角度了吗?为什么还要传感器读实际角度?

原因很简单:目标角度 ≠ 实际角度。位置伺服电机只是“努力”去追目标,但由于惯性、外力、力矩限制,实际角度总会有些偏差。强化学习智能体需要知道真实状态,而不是理想状态。

我在项目中做过对比实验:用目标角度代替实际角度作为观测,训练出来的策略在仿真里看着还行,一迁移到实物机器人上就摔——因为实物电机的跟随误差比仿真大得多。所以,老老实实用传感器读真实值。

3.4 完整传感器配置一览

把上面所有传感器整合到一起,我们的 <sensor> 段长这样:

<sensor>
    <!-- 接触传感器 -->
    <touch name="FL_foot_touch" site="FL_foot_site"/>
    <touch name="FR_foot_touch" site="FR_foot_site"/>
    <touch name="RL_foot_touch" site="RL_foot_site"/>
    <touch name="RR_foot_touch" site="RR_foot_site"/>

    <!-- 关节位置传感器 -->
    <jointpos name="FL_hip_pos" joint="FL_hip"/>
    <jointpos name="FL_knee_pos" joint="FL_knee"/>
    <jointpos name="FR_hip_pos" joint="FR_hip"/>
    <jointpos name="FR_knee_pos" joint="FR_knee"/>
    <jointpos name="RL_hip_pos" joint="RL_hip"/>
    <jointpos name="RL_knee_pos" joint="RL_knee"/>
    <jointpos name="RR_hip_pos" joint="RR_hip"/>
    <jointpos name="RR_knee_pos" joint="RR_knee"/>
</sensor>

一共 4 个接触传感器 + 8 个关节位置传感器 = 12 个传感器。这 12 个值构成了强化学习观测空间的核心部分。

3.5 本章知识结构图

下面这张图帮你理清执行器和传感器的关系:

执行器与传感器知识结构 四足机器人控制 执行器(肌肉) 位置伺服电机(position) 参数:kp, kv, forcerange 8个电机(4腿×2关节) 传感器(神经) 接触传感器(touch) 关节位置传感器(jointpos) 12个传感器(4触+8位) 执行器定义“如何动”,传感器定义“如何感知” 两者共同构成强化学习的环境交互接口

3.6 验证配置

写完 XML 后,怎么知道对不对?我一般用 MuJoCo 自带的 compile 功能快速验证:

import mujoco

# 加载模型
model = mujoco.MjModel.from_xml_path("quadruped.xml")

# 检查执行器数量
print(f"执行器数量: {model.nu}")  # 应该输出 8

# 检查传感器数量
print(f"传感器数量: {model.nsensor}")  # 应该输出 12

# 打印所有传感器信息
for i in range(model.nsensor):
    print(f"传感器 {i}: {model.sensor(i).name}")

如果输出符合预期,恭喜你——你的四足机器人现在既有肌肉又有神经了。

小技巧:把这段验证代码保存为 check_model.py,每次修改 XML 后跑一遍。我习惯把它加到 Git 的 pre-commit hook 里,防止提交坏掉的模型文件。