人形机器人平衡控制与外力抗扰恢复(humanoid_push_recovery)
任务「人形机器人物理仿真」下的稳定性与抗扰恢复方向实现:自建双足人形 MJCF 模型的 底盘 freejoint 完全自由(不做速度锁定),机器人依靠双脚与地面的真实接触以及关节 力矩站立;模块实现 ZMP / 捕获点稳定性判据 与 踝策略 + 髋策略 的恢复控制律, 并定量给出「扰动力 → 最大质心偏移 → 恢复 / 跌倒」的完整恢复包线。
与同分类其它人形模块的区别(下列模块在各自的 PR 中提交,尚未合并到本分支, 为避免死链此处只做文字对比、不提供跳转):
| 模块 | 输入 | 关注点 |
|---|---|---|
| humanoid_keyboard_control | 键盘速度指令 | 指令执行准确性(运动学层面) |
| humanoid_gait_control | 步态周期 | 行走步态生成与跟踪 |
| humanoid_push_recovery(本模块) | 外力扰动 | 外力下的稳定性判据与恢复能力 |
本模块的机器人不是靠改写 freejoint 速度「站住」的,而是真实受力平衡——这一点是 与键盘控制模块的本质区别,也是能够研究「被推是否跌倒」的前提。
1. 系统架构
┌──────────────── 外力扰动 ────────────────┐
│ /balance/push (Vector3) 或 keyboard │
▼ │
torso: data.xfrc_applied (6 维力/力矩) │
│ │
▼ │
站姿 PD (15 关节) ──► 关节力矩 ──► MuJoCo 物理引擎 │
▲ │
│ 关节目标修正 │
┌────┴─────────────────────────┐ │
│ 平衡控制器 BalanceController │ │
│ · 踝策略:躯干倾角 + 质心偏差 │ │
│ → 踝力矩 (N·m) │ │
│ · 髋策略:质心偏差/速度 → 髋力矩 │ │
└────▲─────────────────────────┘ │
│ │
┌────┴─────────────────────────┐ │
│ 稳定性判据(balance/ 子包) │◄───────────────┘
│ · 支撑多边形(脚底 8 角点凸包)│
│ · ZMP(LIPM 近似) │
│ · 捕获点 ξ = x + v/√(g/h) │
└──────────────────────────────┘
判据与策略的代码基础为开源项目
PrageethM1702/Bipedal-Walking-and-Push-Recovery
(Apache-2.0,改写适配见 3.4 节与包内 THIRD_PARTY_NOTICES.md)。
2. 依赖安装与运行
2.1 环境与依赖安装
# 1) 安装 ROS2 Humble(Ubuntu 22.04):
# 参考官方文档 https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debs.html
# 2) 安装 Python 依赖(在**仓库根目录**执行;mujoco / numpy / pyyaml)
pip3 install -r src/humanoid/humanoid_push_recovery/requirements.txt
# 3) 可视化窗口依赖(仅 Linux 桌面需要;若提示缺少 GLFW 再执行)
sudo apt install -y libglfw3
ROS 依赖(rclpy、geometry_msgs、sensor_msgs、std_msgs、tf2_ros、
ros2launch)在 colcon build 时自动解析,无需手动安装。
2.2 源码放入工作空间与构建
mkdir -p ~/ros2_ws/src
cp -r src/humanoid/humanoid_push_recovery ~/ros2_ws/src/
cd ~/ros2_ws
colcon build --symlink-install --packages-select humanoid_push_recovery
source install/setup.bash
注意:执行完本节后当前目录是
~/ros2_ws,而本包被拷贝到~/ros2_ws/src/humanoid_push_recovery(不含humanoid/这一层)。
2.3 standalone(无需 ROS,最快验证)
standalone 模式不依赖 ROS,也不要求先 colcon build。下面的路径以「仓库克隆在
~/ros2」为例,请替换成自己的实际路径:
cd ~/ros2/src/humanoid/humanoid_push_recovery # ← 换成你自己的仓库路径
python3 main.py --check # 模型/判据/控制器 自检
python3 main.py # 演示:一次推扰 + 平衡恢复(打开可视化窗口)
python3 main.py --force 180 # 指定扰动力 (N)
python3 main.py --direction 90 # 扰动方向(0=前向, 90=侧向, 180=后向)
python3 main.py --no-hip # 关闭髋策略(仅踝策略,对照用)
python3 main.py --sweep # 测定各方向临界冲量并与策略对照
python3 main.py --capture assets_out # 采集关键帧/动图/时序CSV/metrics.json
python3 main.py --test # 自动化测试(38 项)
若运行的是 2.2 节拷贝到工作空间的那一份,路径为
~/ros2_ws/src/humanoid_push_recovery(同样没有 humanoid/ 这一层)。
无显示环境(SSH / 容器)下渲染需要 EGL:
export MUJOCO_GL=egl
2.4 ROS2 Humble launch
cd ~/ros2_ws
colcon build --symlink-install --packages-select humanoid_push_recovery
source install/setup.bash
ros2 launch humanoid_push_recovery main.launch.py
焦点保持在启动终端,按键盘施加扰动:
| 按键 | 动作 |
|---|---|
W / S |
向前 / 向后推 |
A / D |
向左 / 向右推 |
Q / E |
左前 / 右后斜向推 |
↑ / ↓ |
同 W / S(方向键) |
← / → |
同 A / D(方向键) |
+ / - |
推扰力度 ±20 N |
空格 |
复位 |
ESC |
退出 |
另开终端查看状态:
ros2 topic echo /balance/status # stable / recovering / fallen
ros2 topic echo /balance/stability_margin # 捕获点到支撑边界的稳定裕度 (m)
ros2 topic echo /balance/zmp # 零矩点
ros2 run tf2_ros tf2_echo world humanoid/base_link # 验证 TF 可读取
2.5 可视化窗口(看到人形机器人界面)
standalone 演示与 ros2 launch 默认都会打开 MuJoCo 可视化窗口,窗口内可实时看到
机器人被推后的位移与姿态变化,相机自动跟踪躯干。
| 运行环境 | 能否看到窗口 | 说明 |
|---|---|---|
| 本机 Ubuntu 桌面 / 虚拟机桌面终端 | ✅ | 直接运行即可 |
| SSH 远程登录 | 需 X11 转发 | 用 ssh -X user@host 登录,或先 export DISPLAY=:0 |
| 无显示服务器(纯命令行 / CI) | ❌ | standalone 加 --no-viewer;ROS2 用 visualize:=false |
无图形界面时程序会打印提示并自动退化为无窗口模式,物理仿真、话题发布与自动化测试不受影响。
3. 实现要点
3.1 自建双足模型(models/humanoid_balance.xml)
15 关节 = 双腿(髋 roll / 髋 pitch / 膝 pitch / 踝 pitch / 踝 roll)×2 + 双臂
(肩 pitch / 肘 pitch)×2 + 颈 ×1,全部由 motor 驱动。与同分类其它模块自建双足
模型的关键区别:
- 底盘 freejoint 不做速度锁定:机器人的站立完全由双脚与地面的接触力和关节力矩决定;
- 脚掌前后长度加大到 0.22 m,以获得更大的踝关节力矩权限;
- 脚掌与地面摩擦单独加大(
friction="1.4 0.7 0.2"); - 外力扰动施加在
torsobody 上(data.xfrc_applied)。
3.2 站姿 PD 控制
每个关节的驱动力矩为 PD 控制,目标姿态为直立位(全 0):
其中 kp=120、kd=6;髋/踝 roll 关节取 kp_roll=400 抗侧倾;力矩限幅
±300 N·m。只有站姿 PD 时机器人会因倒立摆本质而逐渐前倾并跌倒,这正是需要
平衡控制器的原因(见 5.2 节对照实验)。
3.3 稳定性判据
- 支撑多边形:取双脚底面 8 个角点,用 Andrew 单调链求凸包;
- ZMP(零矩点):采用线性倒立摆近似 质心加速度由质心速度的有限差分求得(并做一阶低通抑制差分噪声);
- 捕获点:, ;稳定裕度定义为捕获点到支撑多边形边界的有符号距离, 裕度 > 0 表示仅靠踝/髋策略仍可停下,< 0 表示必须跨步。
实现注记:实测在 MuJoCo 3.16 中
data.subtree_linvel[0]恒为 0, 因此质心速度统一由data.subtree_com[0]的有限差分求得(见sim_core.py)。
3.4 恢复控制律:踝策略 + 髋策略(输出关节力矩)
平衡控制器输出的是关节力矩修正量(N·m),而不是关节目标角度。原因:踝 pitch 关节的实际行程只有 ±0.9 rad,若用「关节目标」表达平衡修正,为获得足够的恢复力矩 必须下发远超行程的目标(例如 3 rad ≈ 172°),既不合理、又会立刻被关节限位截断, 物理含义模糊。改用力矩量纲后,限幅直接表示关节力矩权限,整定也更有依据。
控制律与几何判据的代码基础是开源项目
PrageethM1702/Bipedal-Walking-and-Push-Recovery
(Apache License 2.0),其 balance/ 下的捕获点、支撑多边形、踝策略、髋策略四个
文件改写进本模块的 humanoid_push_recovery/balance/ 子包(改写点逐文件在文件头注明,
汇总见包内 THIRD_PARTY_NOTICES.md)。必要改动有三处:原项目是 PyBullet 位置控制
机器人、策略输出关节目标角偏置;本站模型是 MuJoCo 力矩控制、踝关节行程有限,
故 ① 输出改为关节力矩;② 倾角输入改用躯干轴倾角与其低通速率;③ 为消除稳态偏差,
在倾角项之外保留质心位置/速度反馈项。
踝策略(主力):倾角 PD 项(来自上游实现)+ 质心反馈项:
其中 为躯干轴相对竖直方向的前倾量(lean_x)。侧向 roll 轴同构,
符号取反(见 3.5)。
髋策略(增强):踝策略饱和后,通过摆动上半身产生角动量继续把质心拉回:
力矩由 sim_core.step() 叠加到站姿 PD 力矩上,再一并限幅到 torque_limit。
(髋策略可开关:hip_enable / --no-hip,用于「仅踝 vs 踝+髋」对照实验。)
3.5 符号约定与整定(踩过的坑,供复现参考)
控制律的符号若写反,会形成正反馈直接把机器人推倒,因此模块把符号自检写进了 自动化测试(测试项 7,四个方向逐一标定)。实测符号约定(均指正力矩):
| 关节 | 正力矩作用下质心移动方向 | 控制律符号 |
|---|---|---|
| 踝 pitch | 向后(−x) | +(k_p e_x + k_d v_x) |
| 踝 roll | 向 +y(左) | −(k_p e_y + k_d v_y) |
| 髋 pitch | 向前(+x) | −(k_p e_x + k_d v_x) |
| 髋 roll | 向 −y(右) | +(k_p e_y + k_d v_y) |
另一个关键整定点:侧向 roll 关节的力矩权限远大于 pitch。早期实现用「关节目标」
表达控制量时,沿用 pitch 的增益(等价于约 300 N·m 的滚转力矩)会把机器人
抛离地面(根高从 1.084 m 跳到 1.203 m)而立刻跌倒;改为力矩量纲后,侧向单独整定
并单独限幅(kp_ankle_roll=4800 N·m/m、kd_ankle_roll=600 N·m·s/m、
ankle_roll_torque_limit=180 N·m,约为 pitch 的 60%)。
引入上游的倾角 PD 项后又有两条实测结论(都写进了参数文件注释):
- 倾角项不能单独用:只用倾角 PD 时,重力力矩与 PD 力矩的平衡点不在竖直位,
静立漂移 10~25 mm(
kp_lean=600时甚至 220 mm 后跌倒);倾角项与质心反馈项组合 时反而更好——静立漂移 0.65 mm,前向 120 N 推扰最大偏移从 43.6 mm 降到 34.5 mm, 所以最终控制律是「倾角 P + 质心 PD」的叠加; - 倾角微分项默认关闭(
kd_ankle_lean = 0):lean由躯干旋转矩阵差分得到, 噪声大,未低通时很小的微分增益就会把控制器推向高频震荡、静立即倒;加了 一阶低通(sim_core.LEAN_RATE_FILTER = 0.7)后kd_lean=150能稳定,但临界冲量 反而下降,故默认取 0,参数保留以便复现该对比。 倾角比例增益取kp_ankle_lean = 300 N·m/rad(该值下静立与三向抗扰均通过, 150 N·m/rad 与 300 N·m/rad 相当,再往上则因踝力矩饱和出现极限环)。
3.6 能力边界(重要)
- 本模块的恢复能力完全来自踝/髋关节力矩,未实现跨步恢复(stepping)。 当扰动超过临界冲量时机器人会跌倒——这是由脚掌几何与关节力矩权限决定的物理上限, 不是控制器参数没调好。以踝策略的静态权限估算:踝关节可提供的最大恢复力矩 约为 每只脚,而倒立摆的倾覆力矩为 ,由此给出的可恢复质心偏移 已接近实测临界值;
- 模型为自建简化双足,连杆长度、质量分布与摩擦均与真实人形机器人不同, 临界冲量的绝对值不可外推到实机,但「扰动力—偏移—跌倒」的定性规律与 策略间的相对提升具有参考意义;
- 评价指标只描述本仿真模型在上述参数下的行为,不代表硬件实验结论。
4. 效果图
推扰—恢复过程的自动演示(150 N 前向推扰,固定侧视相机 + 初始质心参考线,
图像由 python3 main.py --capture <目录> --no-viewer --force 150 从真实仿真渲染采集):
| 静立站姿 | 被推瞬间 | 恢复中 | 恢复稳定 |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
完整动画:
说明:
- 演示使用固定侧视相机(模型内
demo_side,不跟随机器人),并地面上画出 初始质心参考线(机器人脚下那根白色细条,采集时自动移到初始质心正下方), 因此「被推 → 质心偏移 → 拉回」的整个过程在画面中可直接读出; - 本模块不生成、不提交数据曲线图(已移除 matplotlib 绘图代码)。恢复过程的时序
原始数据在采集时导出为
recovery_series.csv(时间 / 质心偏移 / 躯干倾角 / 踝与髋 力矩),需要曲线时请用这份实测数据自行绘制;上表四个关键帧与动画均来自同一次 真实仿真运行。
5. 验证结果
以下 5.1~5.4 的数值在 Windows 11(MuJoCo 3.16.0 / Python 3.13 / numpy) 上实测,
指令:python3 main.py --sweep、python3 main.py --test、python3 main.py --check。
复现命令与原始数据导出方式见第 2、4 节(--capture 会同时导出时序 CSV)。
物理量(质心漂移、最大偏移、恢复时间、临界冲量)为确定性仿真结果, 与机器无关;只有仿真吞吐是机器相关的(见 5.4)。5.5 的 ROS2 集成验证在 上一修订版(commit 78fce38)的虚拟机中完成,参数加载与 launch 机制在本版 未改动;本版在其基础上重整了控制律代码与增益。
5.1 站立稳定性
| 指标 | 结果 |
|---|---|
| 无扰动自由站立(8 s) | 不跌倒,质心漂移 0.55 mm,根高 1.084 m |
| 躯干倾角(静立) | 1.11° |
| 静立稳定裕度(捕获点到支撑边界) | 93.1 mm |
| 支撑多边形尺寸 | 前后 220 mm × 侧向 460 mm |
5.2 抗扰恢复(对照实验)
关闭平衡控制器时,120 N×(0.08 s) 推扰即跌倒(最大质心偏移 373 mm)—— 说明本模型的站立不是靠锁定底盘维持的,平衡控制器是必需的。
开启平衡控制器后:
| 扰动 | 最大质心偏移 | 恢复时间 | 恢复后残差 |
|---|---|---|---|
| 前向 120 N (9.6 N·s) | 36.3 mm | 0.84 s | 0.49 mm |
| 侧向 150 N (12.0 N·s) | 34.5 mm | 0.74 s | 5.94 mm |
| 斜向 45° 120 N (9.6 N·s) | 29.9 mm | 未达标* | 8.23 mm |
* 斜向推扰后质心停在 8.2 mm 的稳态偏移上(未进入 <5 mm 判据),该偏移由 roll 轴 恢复力矩与重力的平衡点决定,约为侧向支撑宽度的 1.8%,远在支撑多边形之内—— 属于「稳定但未回到原点」,不是跌倒或震荡。
5.3 临界冲量(二分法测定,扰动持续 0.08 s)
| 方向 | 仅踝策略 | 踝+髋策略 | 提升 |
|---|---|---|---|
| 前向 (+x) | 147.3 N / 11.79 N·s | 163.7 N / 13.10 N·s | +11.1% |
| 侧向 (+y) | 226.6 N / 18.13 N·s | 284.6 N / 22.77 N·s | +25.6% |
| 后向 (−x) | 40.1 N / 3.21 N·s | 41.0 N / 3.28 N·s | +2.4% |
结论与分析:
- 踝策略是站立抗扰的主力,髋策略在不改变任何硬件参数的前提下把可恢复冲量 提升 11.1%(前向)/ 25.6%(侧向),同时降低了最大偏移与恢复时间;
- 侧向抗扰能力显著强于前向(18.13 vs 11.79 N·s),因为双足站立时侧向支撑宽度 (460 mm,靠双足共同支撑)大于前后支撑长度(220 mm,靠单脚脚掌);
- 后向抗扰最弱(3.21 N·s),因为脚掌在踝关节前方的延伸(0.12 m)明显长于后方 (0.07 m),向后倾覆时可用的踝关节力矩权限最小——这解释了为什么人从背后被推更容易 向前扑倒;
- 超过临界冲量后必然跌倒,属于 3.6 节所述物理上限;要进一步提升需要引入 跨步恢复(stepping),这是本模块明确未实现的边界。
5.4 仿真性能
| 指标 | 结果 |
|---|---|
| 仿真吞吐 | 9,047 ~ 14,691 step/s |
| 实时因子 | 18.1× ~ 29.4×(随机器负载波动) |
吞吐随机器变化:同一模块在 Ubuntu 22.04 虚拟机(2 vCPU)上约为 9,433 step/s (18.9×)。两种环境都远超实时要求(演示只需 >1×)。
5.5 ROS2 集成验证(Ubuntu 22.04.5 + Humble 实测)
在虚拟机中执行 colcon build --symlink-install --packages-select humanoid_push_recovery
(构建 1.20 s 成功,两个可执行文件 balance_sim_node / push_teleop_node 注册正常),
随后 ros2 launch humanoid_push_recovery main.launch.py visualize:=false 验证:
| 验证项 | 命令 | 结果 |
|---|---|---|
| 参数文件确实被加载 | 先把 config/params.yaml 的 kp_ankle 改成显眼值 12345.0,再 ros2 param get /balance_sim_node kp_ankle |
返回 12345.0 ✅(证明 launch 加载了 params.yaml,而非节点内硬编码默认值) |
| launch 参数覆盖生效 | ros2 param get /balance_sim_node visualize |
返回 False ✅(visualize:=false 覆盖了参数文件里的 true) |
| 按节点分节生效 | ros2 param get /push_teleop_node force |
返回 120.0 ✅(遥控节点只读 /push_teleop_node 段) |
| 话题完整 | ros2 topic list |
/balance/com、/balance/zmp、/balance/capture_point、/balance/stability_margin、/balance/status、/balance/push、/balance/reset、/joint_states、/tf 共 9 个全部存在 ✅ |
| 状态与裕度 | ros2 topic echo /balance/status、/balance/stability_margin |
stable / 93.7 mm(与 standalone 的 93.1 mm 一致)✅ |
| 扰动—恢复闭环 | ros2 topic pub --once /balance/push Vector3 "{x: 120.0}" |
扰动后状态仍为 stable,质心偏移 0.3 mm、稳定裕度稳定在 87.0 mm,与 standalone 结果一致 ✅ |
节点日志节选:
[balance_sim_node]: 平衡仿真节点已启动 | 物理 500 Hz | 发布 30 Hz | 髋策略 开 | 模型 .../share/humanoid_push_recovery/models/humanoid_balance.xml
[balance_sim_node]: status=stable 偏移= 0.2 mm 倾角= 1.3° 根高=1.084 m 裕度= +93.3 mm
[balance_sim_node]: 施加扰动 120.0 N (冲量 9.60 N·s)
[balance_sim_node]: status=stable 偏移= 0.3 mm 倾角= 0.6° 根高=1.084 m 裕度= +87.0 mm
6. 参考
- 人形机器人基础
- 代码基础(捕获点判据 / 支撑多边形几何 / 踝策略 / 髋策略):
PrageethM1702/Bipedal-Walking-and-Push-Recovery,
Apache License 2.0。改写适配说明见 3.4 节与本包
THIRD_PARTY_NOTICES.md - 本包 ROS2 包结构(
package.xml/setup.py/launch/config)参照仓库内 既有模块的 ament_python 惯例 - MuJoCo 文档:freejoint / xfrc_applied / Renderer
- 线性倒立摆与捕获点:Kajita et al., Biped Walking Pattern Generation by using Preview Control of Zero-Moment Point, ICRA 2003;Pratt et al., Capture Point: A Step toward Humanoid Push Recovery, Humanoids 2006
7. AI 声明
本模块在 AI 助手协助下完成:稳定性判据与踝/髋策略的代码基础来自上述 Apache-2.0
开源项目(改写为 MuJoCo 力矩控制实现),MJCF 模型、ROS2 节点、参数整定与评测脚本
为在开源基础上开发;代码与文档由本人负责。本文所有实测数字均由本机运行
main.py --sweep / main.py --test / main.py --capture 得到,未做任何未运行的
声明;恢复过程的时序原始数据(时间 / 质心偏移 / 倾角 / 关节力矩)可用
python3 main.py --capture <目录> 一键导出为 recovery_series.csv 复核。
仓库内不提交任何数据曲线图,第 4 节图片全部为真实仿真渲染截图与动图。



