P0.D 无人机仿真环境——从 Gazebo SITL 到 GPU 并行 RL 工厂¶
性质:工程实践教学 | ⚪ 部分共享——仿真-真机切换的设计模式与机械臂 P0.2 共享(Bridge/Adapter 抽象),但具体仿真器(Flightmare / Isaac Sim / gym-pybullet-drones vs Gazebo / MuJoCo)和控制接口(MAVLink / CTBR vs ros2_control / JointTrajectory)完全不同。 | 难度跨度:⭐ ~ ⭐⭐⭐⭐ | 预计精读:10-14 小时
一句话定位:仿真是无人机算法的"风洞"——你 90% 的代码会先在仿真里跑通再上真机。本章不教任何一种控制或规划算法,而是教**怎么搭好那个让算法跑起来的台子**:从 Gazebo+PX4 的物理保真 SITL 闭环,到 Flightmare 的 Unity 光真渲染,到 Aerial Gym / OmniDrones 的 4096 并行 RL 工厂,讲透**物理保真度 / 渲染真实度 / 并行规模"不可能三角"**的取舍逻辑,给出一棵可直接套用的选型决策树,并拆解 sim-to-real gap 的六大来源——因为搭仿真的终极目的,是让仿真里学到的东西能搬到真机上不崩。
环境配置指南¶
本章涉及六个仿真器,没有一个环境能同时装下所有——Gazebo/PX4 走 ROS 2 + apt 路线,Flightmare 要 Unity + ZeroMQ,Aerial Gym/OmniDrones 要 NVIDIA Isaac(CUDA + 闭源运行时),它们的 Python/CUDA 依赖互相冲突。正确做法是按用途切分 conda/docker 环境,下文给出每条路线的独立配置。先读这一节再动手,能省掉至少一整天的依赖地狱。
系统要求¶
| 维度 | 入门路线(gym-pybullet-drones) | SITL 路线(Gazebo + PX4) | RL 路线(Aerial Gym / OmniDrones) |
|---|---|---|---|
| 操作系统 | Ubuntu 20.04/22.04 / macOS / Windows | Ubuntu 22.04(Gazebo Harmonic 不支持 20.04) | Ubuntu 22.04(Isaac 官方支持) |
| GPU | 不需要(纯 CPU) | 不需要(Gazebo 用 OpenGL 软渲染也能跑) | NVIDIA RTX,显存 \(\ge\) 8 GB(4096 环境建议 \(\ge\) 16 GB) |
| CUDA | — | — | 11.8 / 12.x(须与 PyTorch 匹配,见 v8 Ch36) |
| 内存 | 8 GB | 8 GB | \(\ge\) 16 GB(Isaac Sim 建议 32 GB) |
| 磁盘 | 2 GB | 10 GB(PX4 源码 + Gazebo 资产) | 30 GB+(Isaac Sim 容器约 20 GB) |
为什么 RL 路线门槛陡增:gym-pybullet-drones 在 CPU 上跑单个 PyBullet 实例,笔记本即可;而 Aerial Gym / OmniDrones 的核心价值是"千级并行",这只有把整个仿真状态张量放进 GPU 显存、用 CUDA kernel 一次步进所有环境才能实现——所以它**强依赖 NVIDIA 显卡**,AMD/Intel 显卡和 macOS 完全无法运行。这是一个"想要并行红利就必须交的硬件税"。
版本兼容表¶
| 组件 | 推荐版本 | 最低版本 | 最高测试版本 | 备注 |
|---|---|---|---|---|
| Python | 3.10 | 3.8 | 3.11 | Isaac Lab 当前主推 3.10;gym-pybullet-drones 需 \(\ge\)3.8 |
| PyTorch | 2.1.0 | 1.13 | 2.4.0 | 须与 CUDA 版本严格匹配 |
| PX4-Autopilot | v1.15 | v1.13 | main | v1.14 起默认仿真器从 Gazebo Classic 切到 Gazebo(gz) |
| Gazebo | Harmonic (gz-harmonic) | Garden | Harmonic | Harmonic 仅支持 Ubuntu 22.04+;20.04 须用 Gazebo Classic 11 |
| ROS 2 | Humble | Foxy | Jazzy | Humble 对应 Ubuntu 22.04,是当前 LTS 主力 |
| gym-pybullet-drones | 1.0.0 | 0.5.2 | main | 依赖 pybullet>=3.2、gymnasium>=0.28、stable-baselines3>=2.0 |
| Flightmare | master (2021) | — | master | 已停止维护,Unity 渲染端需 2020.1.10f1;物理端 C++14 |
| Isaac Sim | 4.5 / 5.1 | 4.2 | 5.1 | Pegasus Simulator v5.1 对应 Isaac 5.1,v4.5.1 对应 Isaac 4.5 |
| Isaac Gym (Preview 4) | 1.0rc4 | 1.0rc3 | 1.0rc4 | Aerial Gym 依赖;NVIDIA 已停更,被 Isaac Lab 取代但仍可用 |
| Aerial Gym Simulator | main (2025) | — | main | 依赖 Isaac Gym Preview 4 + Python 3.8 |
| OmniDrones | 0.1 | — | devel | 依赖 Isaac Sim + Isaac Lab(旧称 Orbit) |
| stable-baselines3 | 2.3.0 | 2.0.0 | 2.3.0 | 入门 RL 用;gymnasium API |
版本锁定建议:仿真器生态版本耦合极强,建议锁到精确小版本(如
pybullet==3.2.6),而不是只锁大版本。原因是 PyBullet/Gazebo/Isaac 经常在小版本里改物理默认参数或 API 签名——一次pip install -U可能让你昨天跑通的训练今天就报错。Isaac 生态尤其敏感:Isaac Sim 4.5 和 5.1 的 Python 扩展 API 有 breaking changes,Pegasus / OmniDrones 必须配对应版本,错配会在 import 阶段直接崩。
安装步骤¶
路线 A:gym-pybullet-drones(入门,5 分钟,任意系统)¶
# 1) 建独立 conda 环境,避免污染系统 Python
conda create -n drones python=3.10 -y
conda activate drones
# 2) 克隆并安装(editable 模式,方便改源码学习)
git clone https://github.com/utiasDSL/gym-pybullet-drones.git
cd gym-pybullet-drones
pip install -e . # 会自动拉 pybullet / gymnasium / stable-baselines3
# 3) 冒烟测试:弹出 PyBullet GUI,看 Crazyflie 悬停
python gym_pybullet_drones/examples/pid.py
# 成功标志:弹出窗口,4 个 Crazyflie 在 PID 控制下飞 8 字轨迹
路线 B:Gazebo + PX4 SITL(物理保真闭环,Ubuntu 22.04)¶
# 1) 拉 PX4 源码(含子模块,约 2 GB)
git clone https://github.com/PX4/PX4-Autopilot.git --recursive
cd PX4-Autopilot
# 2) 一键装齐工具链(编译器、Python 依赖、Gazebo)
bash ./Tools/setup/ubuntu.sh
# 装完务必重启终端(刷新用户组/环境变量)
# 3) 编译并启动 SITL + Gazebo(首次编译约 10-20 分钟)
make px4_sitl gz_x500
# 成功标志:Gazebo 窗口出现 X500 四旋翼,终端打印 "INFO [commander] Ready for takeoff!"
gz_x500这个 target 名拆解:gz表示用新 Gazebo(不是gazebo-classic),x500是机型(Holybro X500 V2 四旋翼,PX4 的默认参考机架)。换成gz_x500_depth会加一个深度相机,gz_standard_vtol是垂直起降固定翼。target 名 = 仿真器前缀 + 机型,这个命名规律记住后你能自己推断几十个组合。
路线 C:Aerial Gym Simulator(GPU 并行 RL,Ubuntu 22.04 + NVIDIA)¶
# 前置:已装 NVIDIA 驱动 + CUDA(见 v8 Ch36),nvidia-smi 能看到显卡
# 1) 下载 Isaac Gym Preview 4(需在 NVIDIA 开发者官网注册下载)
# 解压后:
cd isaacgym/python && pip install -e .
# 验证:python examples/joint_monkey.py 能弹出窗口
# 2) 装 Aerial Gym
git clone https://github.com/ntnu-arl/aerial_gym_simulator.git
cd aerial_gym_simulator && pip install -e .
# 3) 跑 1024 并行位置控制示例
python aerial_gym/examples/position_control_example.py
# 成功标志:一个窗口里 1024 个四旋翼同时悬停/移动,终端打印 FPS(应达数万 steps/s)
Isaac Gym vs Isaac Lab 的命名混乱预警:NVIDIA 的无人机 RL 工具链有一段"改名史"——Isaac Gym (Preview 4) 是独立的旧 GPU 物理库(Aerial Gym 依赖它),已停止更新;Isaac Sim 是带 Omniverse 渲染的大型仿真平台;Isaac Lab(旧称 Orbit)是建在 Isaac Sim 之上的 RL 框架(OmniDrones 依赖它)。三者不是版本递进关系而是**并存的不同栈**。本章 RL 路线给两条:Aerial Gym(轻、快、几何控制)走 Isaac Gym;OmniDrones(机型多、与 Isaac Lab 生态打通)走 Isaac Sim。详细区分见 §P0.D.6。
Quick Start(5 分钟跑通最小 RL 训练)¶
下面这条路径让你在**最便宜的硬件(纯 CPU 笔记本)上、5 分钟内**亲手训练出一个会悬停的强化学习策略——目的是先建立"RL 训练无人机确实可行"的直觉,再回头啃原理。
conda activate drones # 接路线 A 的环境
cd gym-pybullet-drones
# 用内置脚本训练 HoverAviary(PPO,stable-baselines3)
python gym_pybullet_drones/examples/learn.py --multiagent false
# 预期输出(节选):
[INFO] BaseAviary.__init__() loaded parameters from the drone's .urdf:
[INFO] m 0.027000, L 0.039700, ... # Crazyflie 质量 27g、轴距 39.7mm
---------------------------------
| rollout/ | |
| ep_len_mean | 178 | # 回合长度(越长 = 飞得越久没坠毁)
| ep_rew_mean | -42.3 | # 回合奖励(向 0 收敛 = 越来越稳)
| time/ | |
| fps | 2100 | # CPU 单环境约 2k steps/s
---------------------------------
...
Reached the target reward, stopping training. # 约 3-5 万步后达标
成功标志:ep_rew_mean 从 -200 左右单调上升到 -50 以内,最后弹出回放窗口看到 Crazyflie 稳定悬停在目标点。这 5 分钟你已经完整体验了"环境→PPO→策略"的最小闭环——本章后半程就是把这个闭环从 CPU 2k steps/s 拉到 GPU 4096 环境 4000 万 steps/s,并讲清楚每一步为什么这么搭。
前置自测¶
开始前先回答下面 5 个问题。答不出 2 题以上,建议先回前置章节补齐——本章假设你已理解四旋翼的基本动力学和 RL 训练流程,欠了账会在 §P0.D.2(控制接口)和 §P0.D.6(RL 管线)卡住。
-
四旋翼的 6 自由度刚体动力学方程包含哪几部分? 推力和力矩是怎么从 4 个电机转速合成出来的?为什么这个映射叫"控制分配(Control Allocation)"? (答不出 → 回 D1 微分平坦 / D2 几何控制)
-
强化学习里 on-policy 算法(如 PPO)的训练吞吐量为什么受"环境采样速度"主导? 如果单个环境每秒只能跑 100 步,1 亿步样本需要多久?并行 4096 个环境能把这个时间缩短到什么量级? (答不出 → 回足式 RL Part 0 / v8 Ch36 CUDA 基础)
-
CUDA 的"主机-设备数据传输(host-device transfer)"为什么慢? 在训练循环里频繁调用
tensor.cpu()会发生什么?这和"GPU 并行仿真把状态留在显存里"有什么关系? (答不出 → 回 v8 Ch36 CUDA 基础) -
域随机化(Domain Randomization)的核心假设是什么? 它是想让仿真更逼真,还是让策略更鲁棒?这两种表述的区别为什么重要? (答不出 → 回足式 RL sim-to-real 章节 / 机械臂 P0.2)
-
MAVLink 是什么? PX4/ArduPilot 这类飞控固件和上位机(地面站 / 机载电脑)之间靠什么协议通信?仿真时这个协议链路是怎么被"接管"的? (答不出 → 回 D8 敏捷飞行平台 §Agilicious / 本章 §P0.D.3)
参考答案要点(先自己答,再对照):
-
四旋翼动力学 = 平动(牛顿第二定律,\(m\ddot{\mathbf{p}} = m\mathbf{g} + \mathbf{R}\mathbf{f}\),\(\mathbf{f}\) 为机体系总推力)+ 转动(欧拉方程,\(\mathbf{J}\dot{\boldsymbol{\omega}} = \boldsymbol{\tau} - \boldsymbol{\omega}\times\mathbf{J}\boldsymbol{\omega}\))。4 个电机转速 \(n_i\) 经 \(f_i = c_T n_i^2\) 得单桨推力,再经一个固定的 \(4\times4\) 混控矩阵(mixer)合成总推力 \(T = \sum f_i\) 和三轴力矩 \(\boldsymbol{\tau}\)。这个 \(4\times4\) 矩阵的逆映射(从期望 \(T,\boldsymbol{\tau}\) 反算每个电机转速)就是控制分配。
-
PPO 每次策略更新前要先用当前策略采集一批轨迹(rollout),这一步是纯仿真步进,无法靠 GPU 算力加速——只能靠"同时跑很多个环境"提高吞吐。单环境 100 步/秒 → 1 亿步需 ~278 小时(11.6 天);4096 并行(假设线性加速到 ~40 万步/秒)→ 约 4 分钟。这就是 GPU 并行仿真把"周级训练"压到"分钟级"的根本原因。
-
CPU 内存和 GPU 显存是两块物理隔离的内存,
tensor.cpu()触发一次 PCIe 总线上的数据拷贝并强制 CUDA 流同步(等所有未完成的 GPU kernel 跑完),打断异步流水线。GPU 并行仿真的核心优化正是**让物理状态、观测、动作、奖励全程留在显存里**,整个 rollout 不发生一次 host-device 传输——一旦你在训练循环里 print 一个 GPU tensor,吞吐可能掉 10-100 倍。 -
域随机化在每个(或每批)仿真回合里随机扰动物理参数(质量、惯量、电机时间常数、推力系数、延迟、噪声等)。它的目的**不是让仿真更逼真,而是让策略对参数变化更鲁棒**——真实世界只是随机化覆盖的参数分布中的一个点。这个区别重要在于:追求"逼真"会陷入无止境的精细建模,而追求"鲁棒"只需让随机化范围**包住**真实值即可,工程上可达且可量化。
-
MAVLink(Micro Air Vehicle Link)是无人机领域事实标准的轻量二进制消息协议,飞控固件(PX4/ArduPilot)和地面站(QGroundControl)、机载电脑(用 MAVROS / MAVSDK)之间靠它通信。仿真时,飞控固件的传感器输入不再来自真实 IMU/GPS,而是由仿真器通过 MAVLink 的
HIL_*系列消息(HITL)或内部 simulator 接口(SITL)注入——固件以为自己在飞,实际上"眼睛"接的是仿真器。
本章目标¶
学完本章后,你应该能够:
- **画出**任意无人机仿真器的"三层解耦"架构——物理引擎(刚体+气动)/ 渲染引擎(RGB/深度/事件相机)/ 控制接口(MAVLink/CTBR/RPM),并据此判断一个新仿真器属于哪一代、强在哪一层
- **解释**物理保真度、渲染真实度、并行规模三者构成的"不可能三角",并能针对一个具体任务(如视觉避障 RL、VIO 数据生成、控制器 HITL 验证)说出该优先牺牲哪个维度
- 从零搭通 Gazebo + PX4 的 SITL 闭环,理解 SITL/HITL 的数据注入点差异,能解释 lockstep 模式为什么对确定性复现至关重要
- **配置并运行**一个 4096 并行环境的 RL 训练(Aerial Gym 或 OmniDrones),看懂
num_envs、dt、decimation、max_episode_length等核心配置项,知道每个值怎么取、改了会怎样 - 系统列举 sim-to-real gap 的六大来源(动力学参数 / 气动残差 / 电机与执行器 / 传感器 / 延迟 / 接触),并对每一类说出至少一种缩小手段
- **使用决策树**为给定项目选对仿真器——状态策略 RL、视觉策略 RL、经典论文复现、入门教学各应落到哪个工具
- 理解 "同一份代码跑仿真和真机"的 Bridge/Adapter 设计模式(Agilicious 范式),并能说出它和机械臂 ros2_control 抽象的异同
本章知识导航¶
本章的知识结构是一棵以"怎么搭好无人机算法的仿真台子"为根的树。树干是"先认识工具全景,再理解取舍逻辑,然后动手搭两类代表性环境(SITL 闭环 + RL 工厂),最后回答终极问题:怎么让仿真里的东西搬到真机不崩"。
无人机仿真器全景与四代演进 (§P0.D.1 选型矩阵)
│
▼
为什么没有"最好"的仿真器? (§P0.D.2 三层解耦 + 不可能三角)
│
├─→ 控制接口:CTBR/SRT/RPM 三选一 (§P0.D.3) ──→ 决定 sim-to-real 难度
│
├─→ SITL/HITL 闭环:Gazebo + PX4 (§P0.D.4) ──→ 物理保真路线
│ └─ lockstep / MAVLink 注入点 / QGC
│
├─→ 渲染保真路线:Flightmare + 3DGS (§P0.D.5) ──→ 视觉策略路线
│
├─→ 并行 RL 工厂:Aerial Gym / OmniDrones (§P0.D.6) ──→ 训练速度路线
│ └─ num_envs / dt / decimation / 观测-动作-奖励
│
├─→ sim-to-real gap 六大来源 (§P0.D.7) ──→ 终极问题
│ └─ 域随机化 / 系统辨识 / 残差建模 / sim2sim 验证
│
└─→ Bridge/Adapter:一份代码跑仿真和真机 (§P0.D.8)
│
▼
选型决策树 + 完整选型工作流 (§P0.D.9)
| 小节 | 主题 | 难度 | 一句话 |
|---|---|---|---|
| §P0.D.1 | 仿真器全景与四代演进 | ⭐ | 六大仿真器,从 Gazebo 到 3DGS 的代际地图 |
| §P0.D.2 | 三层解耦与不可能三角 | ⭐⭐ | 物理/渲染/并行三者不可兼得 |
| §P0.D.3 | 控制接口 CTBR/SRT/RPM | ⭐⭐⭐ | 动作空间选错,sim-to-real 直接崩 |
| §P0.D.4 | Gazebo + PX4 SITL 搭建 | ⭐⭐ | 物理保真闭环,飞控在环 |
| §P0.D.5 | 渲染保真:Flightmare 与 3DGS | ⭐⭐⭐ | 光真渲染喂给视觉策略 |
| §P0.D.6 | 并行 RL 工厂 | ⭐⭐⭐ | 4096 环境,周级→分钟级 |
| §P0.D.7 | sim-to-real gap 六大来源 | ⭐⭐⭐⭐ | 仿真到真机为什么会崩、怎么救 |
| §P0.D.8 | Bridge/Adapter 一码两用 | ⭐⭐⭐ | Agilicious 范式,仿真真机零重编切换 |
| §P0.D.9 | 选型决策树与工作流 | ⭐⭐ | 给项目选对工具的可执行流程 |
两条阅读线:
- 工程搭建线(会用就行):§P0.D.1 → §P0.D.4 → §P0.D.6 → §P0.D.9,重点是把环境跑起来、把训练跑通
- 原理理解线(理解取舍):§P0.D.1 → §P0.D.2 → §P0.D.3 → §P0.D.7 → §P0.D.8,重点是搞懂"为什么这么选"和 sim-to-real
无论哪条线,§P0.D.1 和 §P0.D.2 都是必读——它们是全章选型判断的地基。
前置知识桥接¶
回顾 D1/D2(微分平坦与几何控制):D1 建立了四旋翼"给定平坦输出曲线即可代数恢复全部状态与控制"的核心性质,D2 给出了 SE(3) 上的几何控制器。本章不重复这些控制算法,但它们是仿真要"装进去"的东西——当你在 Aerial Gym 里看到"parallelized geometric controllers for SE(3)",那正是 D2 几何控制器的 GPU 并行版本;当你在 PX4 SITL 里看到 rate loop,那是 D2 内环的固件实现。仿真器是控制算法的容器,理解控制才能理解仿真器在仿真什么。
回顾 D8(敏捷飞行平台 §Agilicious):D8 讲透了 Agilicious 的 Pilot→Bridge→Guard 三层架构,强调"Bridge 让同一份 C++ 二进制不重编即可切换 RotorS / Flightmare / 真机"。本章 §P0.D.8 把这个模式抽象成通用的 Bridge/Adapter 设计原则,并和机械臂的 ros2_control 抽象对比——你会看到无人机和机械臂在"仿真真机切换"这件事上殊途同归。如果 D8 没读,这里只需记住一个结论:好的仿真集成不是"为仿真写一套代码、为真机写另一套",而是把"仿真还是真机"收敛成一个可替换的底层适配器。
回顾足式 RL Part 0(sim-to-real 与域随机化):足式方向已经建立了"GPU 并行(IsaacGym/IsaacLab)→ PPO → 域随机化 → 真机部署"的完整心智模型。本章的 RL 部分(§P0.D.6、§P0.D.7)直接复用这套工具链——PPO、并行环境、域随机化的概念完全相通。差异只在两点:① 无人机动作空间是 4D CTBR 而非 12D 关节位置(§P0.D.3 专门讲);② 无人机 sim-to-real 的主要 gap 来源是气动残差和电机延迟,而非接触模型(§P0.D.7 专门讲)。把足式的经验迁过来,只需替换这两个无人机特有的部件。
回顾 v8 Ch36(CUDA 基础):GPU 并行仿真的全部魔法都建立在"把状态张量留在显存、用 CUDA kernel 一次步进所有环境"上。Ch36 学的 host-device 传输开销、CUDA 流、显存管理,本章 §P0.D.6 会直接用上——尤其是"为什么训练循环里不能 print GPU tensor"这个高频陷阱。
如果跳过本章会怎样¶
跳过 P0.D,你会在两个具体的地方撞墙。
场景一:"我的控制器在 MATLAB 里仿真完美,上真机就炸。" 你跳过了仿真器的物理保真度讨论,用一个理想的点质量模型验证了控制器,所有响应都漂亮。但真机上电机有 ~25 ms 时间常数、桨叶有 blade flapping、高速下有气动阻力——这些你的理想仿真一个都没有。结果是控制器在仿真里的增益在真机上要么发散要么迟钝。没有本章对 sim-to-real gap 六大来源(§P0.D.7)的系统拆解,你会把宝贵的真机时间浪费在"为什么仿真和真机不一样"的盲目试错上,甚至摔坏硬件。
场景二:"我的 RL 策略训练要两周,根本没法迭代。" 你跳过了 GPU 并行仿真,用 gym-pybullet-drones 的单 CPU 环境训练一个视觉避障策略。单环境 ~2000 steps/s,一个像样的策略要 5 亿步——按这个速度要训 3 天,而你的奖励函数还要调十几版。没有本章对并行 RL 工厂(§P0.D.6)的讲解,你不知道 Aerial Gym 能用 4096 并行把同样的训练压到一小时内,也不知道 CTBR 动作空间(§P0.D.3)为什么能让训出来的策略真的能上真机。你会在"训练太慢"和"训出来上不了真机"之间反复横跳。
预计阅读时间¶
| 模式 | 时长 | 适合 |
|---|---|---|
| 精读 | 10-14 小时 | 第一次系统学无人机仿真:逐节读动机→搭建→验证,亲手跑通 SITL 和一个 RL 训练,做完每节练习。建议分 3-4 次。 |
| 速读 | 3-4 小时 | 有 ROS/RL 基础、想建立全局图景:读每节的"动机"和对比表、§P0.D.2 不可能三角、§P0.D.9 决策树,跳过具体安装命令和代码走读。 |
| 速查 | 30-60 分钟 | 已搭过、回来查特定配置:直接定位到对应小节,看配置表 + 命令 + 选型决策树。 |
科研发展脉络¶
在钻进具体工具前,先把无人机仿真这条线的来龙去脉理清——知道每一代"从哪来、解决了前代什么痛点、又留下什么给后人",比孤立地记工具名有用得多。无人机仿真经历了四代演进:
| 阶段 | 年代 | 代表 | 特征 |
|---|---|---|---|
| G1 Gazebo 时代 | 2013-2018 | RotorS(ethz-asl, ~1.5k★) | Gazebo Classic 插件;ODE/DART 物理;URDF 定义;所有早期论文的标配 |
| G2 Unity/UE 光真时代 | 2018-2021 | AirSim(Microsoft, ~18k★)、Flightmare(UZH RPG, ~1.3k★) | 光真渲染 + 轻量物理;RGB/深度相机仿真;PX4 HITL/SITL |
| G3 GPU 并行时代 | 2022-2024 | Aerial Gym(Isaac Gym)、OmniDrones(Isaac Sim)、gym-pybullet-drones | 4096+ 并行环境;RL 训练从周→分钟;当前 RL 研究标配 |
| G4 3DGS 渲染时代 | 2024-2025 | FiGS/SOUS VIDE(Stanford MSL) | 真实场景 3DGS 重建 → 130 fps 渲染 → 视觉策略训练;下一代范式 |
这条线的主线逻辑:每一代都在"补上一代最痛的短板"。G1 Gazebo 物理够用但渲染丑、跑得慢,喂不了视觉策略——于是 G2 接 Unity/UE 把渲染做到光真。G2 渲染漂亮但物理引擎是串行的,RL 采样慢得令人发指——于是 G3 把整个仿真搬进 GPU,4096 环境并行,训练从周压到分钟。G3 的渲染又是合成的、和真实场景有 gap——于是 G4 用 3DGS 把真实拍摄的场景重建成可渲染资产,让视觉策略直接在"真实场景的数字孪生"里训练。
本质洞察:四代演进不是"后一代全面取代前一代",而是**每一代在某个维度上做到极致,代价是牺牲另一个维度**。RotorS 至今仍是论文复现的金标准(因为它和 PX4 的接口最经典、最多人验证过);Flightmare 的物理其实比 Gazebo 简单,但渲染碾压;Aerial Gym 并行无敌,但渲染只是 ray-casting 的深度图。没有"最先进"的仿真器,只有"最匹配你任务"的仿真器——这正是 §P0.D.2"不可能三角"和 §P0.D.9"决策树"要解决的核心问题。
关键实验室脉络:ETH ASL(RotorS 奠基 Gazebo 范式)→ UZH RPG(Flightmare 把视觉竞速带火,也是 Agilicious 的源头)→ NTNU ARL(Aerial Gym 把几何控制 GPU 并行化)→ 清华 thu-uav(OmniDrones / SimpleFlight,把 CTBR + 零样本 sim-to-real 做成基准)→ Stanford MSL(FiGS,开 3DGS 仿真先河)。这条线和 D8/D9(敏捷飞行、RL)的实验室脉络高度重叠——做仿真器的人往往就是做控制和 RL 的人,因为只有他们最清楚仿真要"装"什么、什么 gap 最致命。
本章符号与术语约定¶
| 符号/缩写 | 含义 | 首见 |
|---|---|---|
| SITL | 软件在环(Software-In-The-Loop) | §P0.D.4 |
| HITL | 硬件在环(Hardware-In-The-Loop) | §P0.D.4 |
| CTBR | 集体推力 + 机体角速度(Collective Thrust & Body Rates) | §P0.D.3 |
| SRT | 单旋翼推力(Single-Rotor Thrust) | §P0.D.3 |
| RPM | 转速指令(Revolutions Per Minute,电机转速) | §P0.D.3 |
| MAVLink | 微型飞行器通信链路协议 | §P0.D.4 |
| BEM | 叶素动量理论(Blade Element Momentum),高保真气动模型 | §P0.D.5 |
| 3DGS | 三维高斯泼溅(3D Gaussian Splatting),新型渲染/重建 | §P0.D.5 |
num_envs |
并行环境数 | §P0.D.6 |
decimation |
物理步与控制步的比值(控制降采样因子) | §P0.D.6 |
| lockstep | 锁步模式(仿真与飞控严格同步推进) | §P0.D.4 |
| DR | 域随机化(Domain Randomization) | §P0.D.7 |
| SysID | 系统辨识(System Identification) | §P0.D.7 |
§P0.D.1 仿真器全景与四代演进——先认识工具箱 ⭐¶
模块功能与在管线中的位置¶
在搭任何环境之前,你得先知道工具箱里有哪些工具、各自擅长什么。这一节是全章的"地图"——把六个主流无人机仿真器摊开比一遍,让你在后面每一节深入某个工具时,始终知道它在整张地图上的位置。
动机:为什么不能"随便选一个仿真器"¶
设想你刚接到一个无人机项目,第一反应可能是"装个最流行的仿真器就开干"。这是新手最常踩的坑。无人机仿真器之间的差异**不是"好用/难用"的程度差异,而是"能不能做这件事"的能力差异**:
- 你想训一个**视觉避障 RL 策略**,选了 RotorS——结果发现它渲染丑、又是单环境串行,采样慢到训不动,渲染质量也喂不出能迁移到真机的视觉特征。
- 你想做**飞控控制器的 HITL 验证**,选了 Aerial Gym——结果发现它根本不接 PX4/MAVLink,它的设计目标是 RL 训练,不是飞控在环。
- 你想**复现一篇 2017 年的经典规划论文**,选了 OmniDrones——结果论文的代码是基于 RotorS 的 Gazebo 接口写的,你得把整套接口重写一遍。
每一个错配都意味着几天甚至几周的返工。所以选型不是"挑一个顺眼的",而是"先想清楚任务要什么,再去地图上找那个匹配的点"。
反面:如果只用一个仿真器走天下会怎样¶
反事实:假设你强行用一个仿真器(比如 Gazebo)做完所有事会怎样? → 视觉策略训练:Gazebo 的 OGRE 渲染质量中等且无法 GPU 批量出图,视觉 RL 采样速度比 Flightmare 慢一个数量级。 → 大规模 RL:Gazebo 物理是 CPU 串行的,开 100 个并行实例就把 CPU 跑满了,远达不到 RL 需要的 4096 并行。 → 真实场景导航:Gazebo 只能渲染人工建模的场景,无法重现真实世界的纹理和光照细节。 结论:一个仿真器最多在地图的一个角落做到优秀。专业团队的常态是"手里有 3-4 个仿真器,按任务切换"——这正是本章要教你建立的工具意识。
理论:六大仿真器选型矩阵¶
下面这张表是全章最重要的参考,建议截图存下。每一列都是后续小节会深入展开的维度:
| 仿真器 | 物理保真度 | 渲染 | GPU 并行 | 控制接口 | 飞控集成 | 最适场景 |
|---|---|---|---|---|---|---|
| gym-pybullet-drones | 中(多模式 6-DoF,可选阻力/地效/下洗模型) | 低(OpenGL) | 仅 CPU | RPM/PID/velocity | 无(自带简化控制器) | 入门、Crazyflie 原型、多机教学 |
| Flightmare | 中(6-DoF + 可选 BEM 气动) | Unity 光真,渲染达 230 Hz | 部分(百级 CPU 向量化) | CTBR + 自定义 | PX4 SITL/HITL | 视觉竞速、VIO 训练、人机交互 |
| Aerial Gym | 中(6-DoF + SE(3) 几何控制) | GPU 光线投射(深度/分割) | 千级~百万级并行 | CTBR/RPM/几何控制 | 无(专注 RL) | 杂乱场景避障 RL、电机指令策略 |
| OmniDrones | 中(6-DoF + 4 种机型 + 过驱动) | Isaac Sim 光真 | 千级并行 | CTBR/RPM | 无(专注 RL) | SimpleFlight 基准、高并行多机 RL |
| RotorS | 中(Gazebo ODE/DART) | Gazebo 低真(OGRE) | 无(单实例) | RPM/wrench | PX4/经典控制器 | 经典论文复现、传感器仿真 |
| AirSim | 中(simple_flight 内置) | UE 光真 | 单实例 | RPM/velocity | PX4 SITL/HITL | 已归档,不推荐新项目 |
| Pegasus(Isaac Sim) | 高(Isaac PhysX) | Isaac 光真 | 多机(非 RL 千级) | PX4/ArduPilot/自定义 | PX4/ArduPilot 原生 | Isaac 生态下的 PX4 SITL + 光真 |
怎么读这张表:从右往左读最有效——先在"最适场景"列找到和你任务最像的那一行,再回头看它在物理/渲染/并行三列上的取舍是否你能接受。比如你要做"杂乱场景避障 RL",定位到 Aerial Gym 行,看到它"渲染只是 GPU 光线投射的深度图"——如果你的策略只用深度图(很多避障策略确实如此),完全够用;但如果你需要 RGB 语义,就得换 Flightmare 或加 3DGS。
几个容易混淆的点澄清:
- gym-pybullet-drones 的"多模式物理":它不是只有一个理想模型,而是提供
PYB(纯 PyBullet 刚体)、DYN(显式动力学积分)、PYB_GND(加地面效应)、PYB_DRAG(加阻力)、PYB_DW(加下洗 downwash,多机互扰)等多档保真度。教学价值极高——你能亲手看到"加上阻力模型后悬停油门怎么变"。 - AirSim 为什么"不推荐新项目":AirSim 是微软出品、曾经的光真仿真王者(UE 渲染),但微软已于 2022 年**正式归档(archived)**该仓库,停止维护。它的精神继任者是闭源的 Project AirSim 和社区 fork(如 Colosseum)。新项目若要 UE 级光真 + PX4,更推荐 Pegasus(Isaac Sim)或仍活跃的 fork。本章把它列出来是因为大量存量论文和教程基于它,你迟早会遇到。
- Pegasus 不是独立仿真器,而是 Isaac Sim 的"无人机扩展":它把 PX4/ArduPilot 接进 Isaac Sim,让你在 Isaac 的光真 PhysX 环境里跑飞控在环。它和 OmniDrones 都建在 Isaac Sim 上,但定位不同:Pegasus 偏"PX4 SITL + 光真渲染 + 多机",OmniDrones 偏"纯 RL 训练 + 高并行"。
历史:每一代仿真器是被什么"逼"出来的¶
选型矩阵给了静态的对比,但要真正理解这些工具,得知道它们各自是被什么痛点"逼"出来的——历史比参数更能解释"为什么它长这样"。
G1(RotorS,2013-2018)——被"没有标准无人机仿真"逼出来。在 RotorS 之前,做无人机的人各自用 MATLAB/Simulink 搭简化模型,没有统一的、带传感器仿真的、能接控制器的平台。ETH ASL 的 RotorS 第一次把"多旋翼模型 + IMU/相机传感器 + Gazebo 物理 + ROS 接口"打包成一个标准件,让全世界的论文能在同一个平台上复现彼此的工作。它的设计完全是 Gazebo 范式——每个传感器、每个旋翼都是一个 Gazebo 插件(plugin),通过 wrench(力/力矩)消息施加到刚体上。这个"插件化"设计直到今天仍是 Gazebo 系仿真的标志。RotorS 解决的核心痛点是"标准化",代价是继承了 Gazebo 的全部局限——渲染丑、单实例、慢。
G2(Flightmare/AirSim,2018-2021)——被"视觉算法没法在仿真里训练"逼出来。2018 年前后,视觉惯性里程计(VIO)、基于学习的视觉导航、视觉竞速开始火,但 Gazebo 的 OGRE 渲染质量喂不动这些算法——仿真图像和真实相机差太远,训出来上真机就废。微软的 AirSim 用虚幻引擎(UE)、UZH RPG 的 Flightmare 用 Unity,第一次把"游戏级光真渲染"带进无人机仿真。Flightmare 更进一步,把渲染和物理彻底解耦(ZeroMQ 异步连接,见 §P0.D.5),让渲染追求光真的同时物理仍能飞速积分。G2 解决的核心痛点是"渲染喂不动视觉算法",代价是物理引擎相对简单、并行能力有限(百级 CPU)。
G3(Aerial Gym/OmniDrones,2022-2024)——被"RL 采样太慢,训练以周计"逼出来。2021 年后,深度强化学习成为无人机控制和导航的热点,但 G1/G2 都是 CPU 串行或弱并行的——一个像样的 RL 策略要训几天到几周,根本没法迭代调参。NVIDIA 的 Isaac Gym 证明了"把整个仿真搬进 GPU、几千环境并行"能把训练从周压到分钟。NTNU ARL 的 Aerial Gym(基于 Isaac Gym)和清华的 OmniDrones(基于 Isaac Sim)把这套 GPU 并行范式带进无人机领域,分别走"极致并行+几何控制"和"光真渲染+生态完整"两条路。G3 解决的核心痛点是"RL 采样吞吐",代价是强依赖 NVIDIA GPU、渲染要么简化(Aerial Gym 只出深度)要么吃显存(OmniDrones 光真)。
G4(FiGS/3DGS,2024-2025)——被"合成渲染再逼真也和真实有 gap"逼出来。G2/G3 的渲染再好,渲的也是"人工建模的场景",和真实世界的纹理、光照、材质仍有差距,视觉策略的 sim-to-real gap 主要卡在这里。3DGS(三维高斯泼溅)反转了思路——不再"建一个场景努力像真实",而是"拍真实场景重建成可渲染孪生"(见 §P0.D.5)。Stanford MSL 的 FiGS/SOUS VIDE 用 3DGS 重建真实场景作渲染器,把视觉策略的渲染 gap 压到极小。G4 解决的核心痛点是"合成渲染的真实 gap",代价是要先去真实场景采集重建、且 3DGS 本身不含物理(要另配碰撞几何)。
本质洞察(贯穿四代的一条暗线):把四代的"痛点—代价"连起来看,有一条清晰的暗线——每一代都在"把仿真的某个维度推向真实",但推一个维度就要在另一个维度上付出代价。G1 推"标准化"(牺牲渲染速度),G2 推"渲染真实"(牺牲物理与并行),G3 推"采样吞吐"(牺牲渲染或绑定 GPU),G4 推"渲染的真实 gap"(牺牲采集成本和物理完整性)。这条暗线就是 §P0.D.2"不可能三角"的历史版本——不可能三角不是某一代的技术局限,而是仿真这件事的根本约束,每一代只是在三角的不同顶点之间重新选择落点。 理解了这条暗线,你看任何未来的新仿真器都能立刻问出关键问题:"它把哪个维度推向了真实?又在哪个维度上付了代价?"
多视角理解:用"汽车测试"类比仿真器分层¶
跨领域类比(标注边界):无人机仿真器的分代,像汽车工业的三种测试设施: - G1 Gazebo \(\approx\) 整车试验台架:能测动力学和控制,但环境是简化的、慢的,一次测一辆。 - G2 Flightmare/AirSim \(\approx\) 驾驶模拟器(带逼真画面):画面拟真,主要测"司机/感知系统看到画面后的反应",但底盘物理是简化的。 - G3 Aerial Gym/OmniDrones \(\approx\) 数字孪生 + 大规模蒙特卡洛:同时跑几千个虚拟测试,专为"用海量数据训练决策系统"设计,但每个实例的物理和画面都做了取舍。
像的地方:都是"用虚拟替代真实,在不同保真度上换取不同好处"。 不像的地方:汽车测试设施之间是流程上的先后关系(先台架后路试),而仿真器之间**不是先后而是并列**——你可能在 Aerial Gym 训完策略后,回到 Flightmare 做视觉验证,再到 Gazebo+PX4 做飞控在环,最后上真机。不要把这个类比延伸成"先用 G1 再用 G3"的线性流程。
配置详解:怎么快速判断一个新仿真器属于哪一代¶
无人机仿真生态每年都冒出新工具。与其记住每一个,不如掌握一套"三问法"快速给它在地图上定位:
| 问题 | 看什么 | 推断 |
|---|---|---|
| 渲染是什么引擎? | OpenGL/OGRE → G1;Unity/UE → G2;GPU 光线投射/Isaac → G3;3DGS → G4 | 决定能不能喂视觉策略 |
| 能并行多少环境? | 单实例 → G1/G2;千级 GPU → G3 | 决定 RL 采样速度 |
| 暴露什么控制接口? | RPM/wrench → 偏经典控制;CTBR → 偏 RL;MAVLink → 偏飞控在环 | 决定能做什么类型的项目 |
举例:你看到一个新仿真器宣称"基于 Isaac Sim、支持 8192 并行、动作空间是 CTBR"——三问法立刻告诉你这是个 G3 的 RL 训练工具,和 OmniDrones 是同类,适合状态/视觉策略训练,但大概率不接 PX4(不适合飞控在环)。这套定位法比记工具名重要得多,因为工具会过时,分层逻辑不会。
⚠️ 常见陷阱¶
💡 概念误区:把"星标数高"等同于"最适合我的项目"
新手想法:"AirSim 有 18k 星、Gazebo 用的人最多,那就用它们准没错"
实际上:星标数反映的是历史影响力和社区规模,不是技术适配度。AirSim 已归档
停止维护;Gazebo 用户多是因为它通用(机器人都用),但在无人机 RL
这个细分场景上它既不快也不并行。星标数和"你这个具体任务的最优选择"
几乎无关。
正确理解:选型只看"任务-能力匹配度"(§P0.D.9 决策树),把星标数当作"社区
支持好不好找答案"的次要参考,而非首要决策依据。
🔧 配置陷阱:以为装一个仿真器就能覆盖入门到 RL 的全流程
错误做法:只建一个 conda 环境,想把 gym-pybullet-drones、PX4、Isaac 全装进去
现象:pip 依赖冲突——PyBullet 要的 numpy 版本和 Isaac 要的对不上,
Isaac Gym 锁 Python 3.8 而 Isaac Lab 要 3.10,装到一半互相覆盖
根本原因:六大仿真器分属三条技术栈(PyBullet/Gazebo/Isaac),它们的底层
依赖(CUDA、Python、物理库)互相不兼容
正确做法:按用途切分独立环境——drones 环境装 gym-pybullet-drones,
系统级装 PX4+Gazebo(apt),aerialgym 环境装 Isaac Gym 系。
用 conda env 或 docker 隔离,别想着"一锅炖"
💡 概念误区:认为渲染质量高的仿真器物理也一定更准
新手想法:"Flightmare/AirSim 画面这么逼真,物理肯定也比 Gazebo 准"
实际上:渲染和物理是两套完全独立的引擎(见 §P0.D.2 三层解耦)。Flightmare
的物理引擎其实比 Gazebo 简单(默认是轻量 6-DoF 积分),它的卖点是
Unity 渲染而非物理保真。画面逼真 ≠ 动力学准确,这是两个正交的维度
正确理解:判断物理保真度要看动力学模型(是否含气动残差、电机时间常数、
blade flapping),判断渲染要看渲染引擎——两者分开评估,不能用
一个推断另一个
练习¶
-
【选型练习·定位】 访问以下三个仓库的 README:Aerial Gym(ntnu-arl)、Pegasus Simulator、gym-pybullet-drones。用本节的"三问法"分别判断它们的渲染引擎、并行能力、控制接口,填一张三行的表。验收标准:你的判断和本节选型矩阵对应行一致。预计 30 分钟。
-
【对比练习·反事实】 假设你要做一个"室内无人机用深度相机避障"的 RL 项目。分别论证:① 为什么 RotorS 不合适(从并行和渲染两方面);② 为什么 AirSim 不推荐(从维护状态);③ 为什么 Aerial Gym 合适(从并行和深度渲染)。每条至少 2 句话,落到具体能力维度,不要泛泛而谈。预计 20 分钟。
-
【思考题·代际预判】 四代演进的主线是"每代补上一代最痛的短板"。基于这个规律,你认为 G5 最可能补什么短板?(提示:G4 的 3DGS 解决了"真实场景渲染",但它有什么仍未解决的痛点?想想"真实场景的物理交互"和"场景泛化"。)写出你的预测和理由,无标准答案。预计 15 分钟。
§P0.D.2 三层解耦与"不可能三角"——为什么没有最好的仿真器 ⭐⭐¶
模块功能与在管线中的位置¶
上一节给了"是什么"的地图,这一节回答"为什么是这样"——为什么仿真器之间是并列而非递进、为什么没有一个全能选手。答案藏在两个结构性事实里:仿真器内部是三层解耦的,而这三层在资源上构成一个不可能三角。理解了这一节,你就能自己推断任意仿真器的取舍,而不必死记选型表。
动机:从"仿真器是一个黑盒"到"仿真器是三个可拆的盒子"¶
新手眼里仿真器是一个整体黑盒——"我给它指令,它给我画面和状态"。但只要你想深入定制(换渲染、换物理、换控制接口),就必须把这个黑盒拆开。拆开后你会发现,几乎所有现代无人机仿真器都由**三个可独立替换的层**组成:
┌─────────────────────────────────────────────────────────┐
│ 控制接口层 (Control Interface) │
│ ├─ 接收什么指令:RPM / CTBR / velocity / MAVLink │
│ └─ 把指令翻译成施加到刚体上的力和力矩 │
├─────────────────────────────────────────────────────────┤
│ 物理引擎层 (Physics / Dynamics) │
│ ├─ 刚体动力学:6-DoF 牛顿-欧拉方程积分 │
│ ├─ 气动模型:阻力 / 地效 / 下洗 / BEM(可选,分档) │
│ └─ 输出:位置、速度、姿态、角速度(仿真"真值") │
├─────────────────────────────────────────────────────────┤
│ 渲染引擎层 (Rendering) │
│ ├─ 把物理真值 + 场景几何 → 传感器图像 │
│ ├─ RGB / 深度 / 分割 / 事件相机 / 点云 │
│ └─ 引擎:OpenGL / Unity / UE / Isaac RTX / 3DGS │
└─────────────────────────────────────────────────────────┘
本质洞察:Flightmare 论文最核心的设计贡献,就是把渲染引擎和物理引擎**彻底解耦、用 ZeroMQ 异步消息连接**——这意味着你可以让物理引擎以 200 kHz 飞速积分,同时渲染引擎以 230 Hz 出图,两者互不阻塞。理解了这个解耦,你才明白为什么 Flightmare 能"物理简单但渲染光真"——因为这两层根本是两个独立进程,各自优化各自的。这不是 Flightmare 一家的设计,而是整个现代仿真器的通用架构。
反面:如果三层不解耦会怎样¶
反事实:假如三层是焊死的(像早期一些一体化仿真器)会怎样? → 想换更逼真的渲染:必须连物理一起重写,因为渲染直接读物理的内部状态结构。 → 想做纯状态 RL(不需要图像):还是得跑完整的渲染管线,白白浪费 GPU。 → 想让物理跑快点(RL 采样):渲染拖住物理,因为它们在同一个同步循环里。 结论:三层解耦的价值,正是**让你按需关闭/替换某一层**。RL 训练状态策略时,Aerial Gym 可以完全不出图(关掉渲染层),把全部 GPU 算力给物理并行——这就是它快的原因之一。
理论:物理保真度 / 渲染真实度 / 并行规模的"不可能三角"¶
三层解耦解释了"仿真器可拆",但还没解释"为什么不能三层都做到极致"。答案是这三个维度**抢同一份计算资源**,构成一个不可能三角——你最多同时优化两个,第三个必然妥协:
物理保真度 (Physics Fidelity)
高保真气动 / BEM / 电机动力学
╱╲
╱ ╲
╱ ╲
╱ 你只 ╲
╱ 能选这 ╲
╱ 个三角形 ╲
╱ 内部的一个 ╲
╱ 点 ╲
╱________________╲
渲染真实度 并行规模
(Visual Fidelity) (Parallelism)
Unity/UE/3DGS 4096+ GPU 环境
| 选两个,牺牲第三个 | 代表仿真器 | 牺牲了什么 | 适合什么 |
|---|---|---|---|
| 物理保真 + 渲染真实 | Pegasus(Isaac Sim)、AirSim | 牺牲并行(单实例/少量) | 飞控在环 + 光真感知验证 |
| 渲染真实 + 并行规模 | OmniDrones(部分)、3DGS 系 | 牺牲物理保真(简化动力学换吞吐) | 视觉策略大规模训练 |
| 物理保真 + 并行规模 | Aerial Gym | 牺牲渲染真实(只出深度/分割图,无光真 RGB) | 状态/深度策略大规模训练 |
这个三角为什么是硬约束:本质是计算预算的分配问题。高保真物理(每步解 BEM 气动、积分电机动力学)每个环境的单步成本高 → 同样算力下能并行的环境数就少;高真实渲染(光追、全局光照)每帧 GPU 成本高 → 要么降帧率要么减并行;大规模并行要求每个环境足够轻 → 物理和渲染都得简化。三者抢的是同一块 GPU 的 FLOPs 和显存,按下葫芦浮起瓢。
多视角理解:不可能三角的双重解读¶
双重解读:这个不可能三角可以从两个角度理解: - 角度1(资源约束视角):三个维度抢同一份 GPU 算力,是个硬性的"零和"分配——这是上面讲的。 - 角度2(任务需求视角):好消息是,没有任何单一任务同时需要三个维度都拉满。视觉避障 RL 需要"渲染 + 并行",但它的策略对气动残差不敏感(飞得慢),物理可以简化;飞控控制器验证需要"物理 + 渲染",但它一次只测一架,不需要并行;状态策略 RL 需要"物理 + 并行",它根本不看图像,渲染可以关掉。
把两个角度合起来看,不可能三角就从"令人沮丧的限制"变成了"清晰的选型指南":先问你的任务真正需要哪两个维度,第三个大胆牺牲。这正是 §P0.D.9 决策树的理论基础。
理论-工程桥接:三层解耦如何指导你的代码组织¶
正因为仿真器是三层解耦的,你自己的算法代码也应该按这三层的边界来组织——这不是巧合,而是让代码能跨仿真器移植的关键。具体地: - 你的**控制器**只应依赖"物理真值"(位置/速度/姿态),不应直接调用某个仿真器的 API——这样换仿真器时控制器零修改。 - 你的**感知模块**只应依赖"传感器图像"的标准格式(如 ROS
sensor_msgs/Image),不关心图像是 Unity 还是 Isaac 渲染的。 - "当前用哪个仿真器"这件事,应该收敛到一个**适配层(Bridge)**——这正是 §P0.D.8 要展开的 Agilicious 范式。换句话说,仿真器的三层解耦架构,反过来约束了你应该怎么写算法代码:跟着仿真器的接缝切分你的模块,移植成本最低。
配置详解:怎么在实践中"关掉一层"¶
理解三层解耦后,最实用的技能是"按需关层"。下表给出三个仿真器关闭渲染层(做纯状态 RL)的典型配置:
| 仿真器 | 关闭渲染的配置 | 效果 |
|---|---|---|
| gym-pybullet-drones | gui=False(构造 Aviary 时) |
不开 PyBullet GUI,无渲染开销,采样提速 |
| Aerial Gym | headless=True + 任务不含 camera sensor |
完全跳过 GPU 光线投射,全部算力给物理 |
| OmniDrones / Isaac | headless=True + enable_cameras=False |
不初始化 RTX 渲染管线,显存省一大块 |
本质洞察(关层的代价):关掉渲染层不是"免费提速"——它意味着你的策略**只能用状态信息(位置/速度/姿态),看不到图像**。如果你的最终任务需要视觉(避障、目标跟踪),训练时关了渲染,部署时却要看图像,就会有巨大的 observation gap。所以"关哪一层"必须由**最终部署形态**倒推:部署看图像 → 训练就不能关渲染;部署只用状态/位置(如轨迹跟踪)→ 训练大胆关渲染换速度。
⚠️ 常见陷阱¶
💡 概念误区:认为"提高仿真保真度"总是好的、越高越好
新手想法:"既然要 sim-to-real,那物理和渲染当然越逼真越好"
实际上:保真度的提升有边际递减且有代价。把物理从 6-DoF 升到含 BEM 气动,
单步成本可能翻数倍,并行环境数砍半,训练时间翻倍——但如果你的策略
飞得慢(气动残差本就可忽略),这份保真度对最终性能毫无帮助,纯属
浪费算力。盲目堆保真度是新手常见的"伪精确"陷阱
正确理解:保真度要"按需匹配任务的敏感维度"。先问"我的任务对哪个物理效应
敏感",只在那个维度上加保真度。飞得慢的避障任务不需要 BEM,飞
100km/h 的竞速任务才需要(见 §P0.D.7)
🔧 配置陷阱:做纯状态 RL 却忘了关渲染,白白拖慢训练
错误做法:用 OmniDrones 训一个轨迹跟踪策略(只用位置/速度观测),但用默认
配置启动,渲染层全程开着
现象:训练 FPS 远低于预期,GPU 显存占用居高不下,开不了更多并行环境
根本原因:默认配置初始化了 RTX 渲染管线,每步都在出图——但你的观测里根本
没用到图像,这些渲染计算全是浪费
正确做法:状态策略训练务必 headless=True 且关闭相机传感器。验证方法:对比
开/关渲染的 FPS,关掉后应有可观提速;用 nvidia-smi 看显存应明显下降
⚠️ 编程陷阱:跨仿真器移植时,控制器直接耦合了某仿真器的状态接口
错误做法:控制器里写死 env.get_pybullet_state() 这类某仿真器专有 API 来读状态
现象:想从 gym-pybullet-drones 换到 Aerial Gym 时,控制器整个报错,因为
Aerial Gym 根本没有这个 API,状态是 GPU tensor 而非 PyBullet 返回值
根本原因:违反了三层解耦原则——控制器应只依赖"物理真值"的抽象(位置/速度/
姿态的标准结构),而不是某个仿真器的具体接口
正确做法:在控制器和仿真器之间加一个薄适配层,把各仿真器的状态统一成你自己
的 State 结构。控制器只认 State,不认仿真器(详见 §P0.D.8)
练习¶
-
【分析练习·三角定位】 在不可能三角里标出本章六大仿真器的位置(每个画一个点,靠近它优化的两个顶点、远离它牺牲的顶点)。然后用一句话说明每个点"牺牲了哪个维度"。验收标准:和 §P0.D.1 选型矩阵的物理/渲染/并行三列一致。预计 25 分钟。
-
【配置练习·关层提速】 用 gym-pybullet-drones 跑同一个
HoverAviary训练两次:第一次gui=True,第二次gui=False。记录两次的 FPS(终端会打印)。计算提速倍数。验收标准:gui=False应明显更快(通常 1.5-3x)。思考:如果是 GPU 仿真器,关渲染的提速会更大还是更小?为什么?预计 30 分钟。 -
【综合思考题·任务驱动选维度】 对下面三个任务,分别说出"需要哪两个维度、可以牺牲哪个",并据此从六大仿真器里选一个:① 训练一个 100km/h 竞速的状态策略;② 训练一个室内 RGB 视觉目标跟踪策略,需要真实纹理;③ 验证一个新设计的 MPC 控制器在 PX4 上的表现。每个任务的论证至少 3 句话。预计 30 分钟。
§P0.D.3 控制接口:CTBR / SRT / RPM——选错动作空间,sim-to-real 直接崩 ⭐⭐⭐¶
模块功能与在管线中的位置¶
控制接口是三层解耦里最容易被忽视、却最影响 sim-to-real 成败的一层。它回答一个问题:你的算法(控制器或 RL 策略)输出什么,仿真器/真机接收什么? 这个"什么"的选择——是直接给电机转速(RPM)、给每个桨的推力(SRT)、还是给"集体推力 + 机体角速度"(CTBR)——决定了你的策略要不要操心电机动力学、ESC 延迟、桨叶气动这些最难仿真准的东西。选对了,sim-to-real 顺滑;选错了,仿真里完美的策略上真机直接发散。
动机:从"无人机怎么被控制"说起¶
先回到物理。一架四旋翼最底层的执行器是 4 个电机,每个电机带一个桨,转起来产生推力。理论上,控制无人机就是控制这 4 个电机的转速。但"控制 4 个电机转速"这个动作空间,离"我想让无人机往哪飞"隔着好几层:
你的意图:往前飞
↓ ???(怎么翻译?)
4 个电机各转多少 RPM
↓ 电机+ESC 动力学(~25 ms 时间常数,有延迟)
4 个桨各产生多少推力(还受桨叶气动、来流速度影响)
↓ 混控矩阵
机体受到的总推力 T + 三轴力矩 τ
↓ 刚体动力学
加速度、角加速度
↓ 积分
无人机的运动
问题来了:你的策略应该在这条链的**哪一层**插入?插得越靠下(越接近电机),策略要替你操心的底层物理就越多;插得越靠上(越接近意图),底层物理就被"外包"给了别的模块。这就是 RPM / SRT / CTBR 三种接口的本质区别——它们是策略接入控制链的三个不同高度的切入点。
反面:如果让策略直接输出电机转速(最低层接口)会怎样¶
反事实:假设你训一个 RL 策略,让它直接输出 4 个电机的 RPM(或等价的单桨推力 SRT),会发生什么? → 策略必须**学会补偿电机动力学**——电机从当前转速到目标转速有 ~25 ms 的过渡,策略得隐式建模这个延迟。 → 策略必须**学会补偿 ESC(电调)非线性**——油门到转速不是线性的。 → 策略必须**学会补偿 blade flapping**(桨叶在高速来流下的挥舞效应)和来流相关的推力变化。 → 这些效应**恰恰是仿真里最难建准的**(见 §P0.D.7)。策略在仿真里学到的补偿,是针对"仿真版电机动力学"的,和真机的电机动力学有 gap → 上真机直接崩。
这就是为什么早期"端到端 RL 直接出电机指令"的工作 sim-to-real 极其困难——策略把整条控制链的底层物理都背在自己身上,而这些物理仿真又仿不准。
理论:三种控制接口的完整对照¶
| 接口 | 全称 | 动作维度 | 策略输出 | 谁来处理电机动力学 | sim-to-real 难度 |
|---|---|---|---|---|---|
| RPM | Revolutions Per Minute | 4D | 4 个电机的目标转速 | 策略自己(隐式) | 高 |
| SRT | Single-Rotor Thrust | 4D | 4 个桨的期望推力 | 策略自己(隐式) | 高 |
| CTBR | Collective Thrust & Body Rates | 4D | 1D 总推力 + 3D 机体角速度 | 固件 rate loop(2-8 kHz PID) | 低 |
CTBR 的 4 个分量是什么:
- \(c\):质量归一化的集体推力(collective thrust),单位是加速度(m/s\(^2\))。物理意义是"我想让无人机沿机体 z 轴产生多大的加速度"。归一化后与无人机质量无关,这是它鲁棒的关键之一。典型范围 \([0, 2g]\)(0 到两倍重力加速度)。
- \(\omega_x, \omega_y, \omega_z\):期望的机体三轴角速度(body rates),单位 rad/s。物理意义是"我想让无人机以多快的角速度绕机体 x/y/z 轴旋转"。典型范围 \([-10, 10]\) rad/s。
CTBR 怎么落到电机:策略输出 CTBR 后,不是策略、而是飞控固件里一个 2-8 kHz 的高频 rate PID 回路(Betaflight / PX4 的 rate loop),把"期望角速度"和"IMU 实测角速度"的误差转成力矩指令,再经混控矩阵 + 电机模型落到每个电机。关键在于:电机动力学、ESC 延迟、blade flapping 这些难仿真的东西,全被这个 2-8 kHz 的固件回路"吃掉"了,策略根本看不见它们。
为什么 CTBR 赢——Kaufmann ICRA 2022 的基准结论¶
这不是拍脑袋的选择,而是有严格基准支撑的。Kaufmann 等人在 ICRA 2022 系统对比了四种动作空间(含 SRT、CTBR、线速度、位置)的 sim-to-real 表现,结论很硬:
本质洞察:CTBR 之所以是无人机 RL 的"正确动作空间",本质是它**把控制问题在"难仿真的快变量"和"易仿真的慢变量"之间切了一刀**——电机时间常数(~25 ms)、ESC 延迟、桨叶气动属于"快变量",仿真误差大,CTBR 把它们外包给 2-8 kHz 的固件 rate loop;留给策略的是慢得多的刚体姿态/位置问题(时间尺度 ~100 ms),这部分刚体动力学仿真很准。策略只学仿得准的那部分,仿不准的那部分交给固件——这就是 sim-to-real 鲁棒性的来源。
实验结果:在质量/惯量扰动下,SRT(单桨推力)策略急剧崩溃(因为它隐式学了针对特定电机动力学的补偿,扰动一来补偿就错),而 CTBR 保持稳定(因为电机层的补偿由固件实时做,策略只管刚体层,对参数扰动天然鲁棒)。
多视角理解:CTBR 对照足式 RL 的"关节位置"¶
这是从足式 RL 迁移到无人机 RL 最关键的概念对照——如果你做过足式,这张表能让你瞬间理解无人机 RL 的动作空间设计:
| 维度 | 足式 RL | 无人机 RL |
|---|---|---|
| 动作空间 | 12D 关节位置偏移 \(\times\) action_scale(0.25) |
4D CTBR(质量归一化总推力 + 3 轴机体角速度) |
| 内环(谁吃底层动力学) | PD 驱动器 200 Hz–1 kHz | Betaflight/PX4 rate PID 2–8 kHz |
action_scale |
0.25 rad(约 \(\pm\)14°) | 推力 \([0, 2g]\) + 角速度 \([-10, 10]\) rad/s |
| 物理意义 | 关节角度目标 | 质量归一化加速度方向 + 旋转速度 |
| 难仿真的底层被谁吃掉 | 关节摩擦/电机由 PD 内环吃 | 电机/ESC/桨叶由 rate loop 吃 |
跨领域类比(标注边界):CTBR 之于无人机 RL,就像关节位置目标之于足式 RL——两者都是"中层指令":高于电机/力矩(不直接碰最难仿的执行器动力学),低于轨迹/落脚点(仍保留足够的控制带宽)。 像的地方:都依赖一个高频底层回路(无人机的 rate loop / 足式的 PD 驱动器)来吃掉难仿真的执行器动力学,让策略只管仿得准的刚体层。 不像的地方:足式的关节位置是"位置"指令(积分两次到力矩),无人机的 body rate 是"速度"指令(微分一次到角加速度)——一个是位置环外包,一个是角速度环外包。另外足式 12D(每条腿 3 关节\(\times\)4 腿),无人机只有 4D,控制冗余度差异巨大。不要把"维度"这个类比延伸过头。
配置详解:在各仿真器里选控制接口¶
| 仿真器 | 默认接口 | 怎么切到 CTBR | 备注 |
|---|---|---|---|
| gym-pybullet-drones | RPM(ActionType.RPM) |
用 ActionType.PID 或自定义 |
也支持 ONE_D_RPM、VEL 等多档,教学友好 |
| Aerial Gym | 可配(几何控制 / CTBR / RPM) | 在 task config 里选 control_mode |
默认提供 SE(3) 几何控制器作为底层 |
| OmniDrones | 可配(多 control mode) | controller 选 CTBR | SimpleFlight 基准就用 CTBR |
| Flightmare | CTBR + 自定义 | 原生 CTBR 接口 | 视觉竞速论文标配 |
| PX4 SITL(offboard) | 多种(含 CTBR) | MAVROS 发 AttitudeTarget(带 body_rate + thrust) |
真机部署时同一接口,见 §P0.D.8 |
理论-工程桥接:CTBR 不只是 RL 的选择,也是真机部署的接口标准。当你训完一个输出 CTBR 的策略,部署到 PX4 真机时,是通过 MAVROS 的
AttitudeTarget消息发送(thrust, body_rate_x/y/z)——和仿真里完全一样的 4 个数。正因为接口一致,"仿真训练→真机部署"才能做到策略权重直接搬过去不改接口(§P0.D.8)。如果你训练用 RPM、部署用 CTBR,中间还要插一层转换,gap 又多一处。接口一致性本身就是 sim-to-real 的一部分。
⚠️ 常见陷阱¶
💡 概念误区:认为"动作空间越底层(越接近电机)越精确、越好"
新手想法:"直接控制电机 RPM 不是最精确吗?中间少了翻译损失"
实际上:底层接口确实"无翻译损失",但代价是策略必须自己隐式建模电机动力学、
ESC 延迟、桨叶气动——而这些恰恰是仿真最仿不准的部分。策略学到的是
"仿真版底层物理"的补偿,和真机不匹配,sim-to-real 直接崩。精确性
(仿真内)和鲁棒性(跨仿真-真机)是两回事
正确理解:sim-to-real 要的是鲁棒性,不是仿真内精确性。CTBR 故意"不那么底层",
把难仿的快变量外包给固件,换来跨域鲁棒——这是 Kaufmann ICRA 2022
的核心结论
⚠️ 编程陷阱:CTBR 的推力分量忘了做质量归一化,导致换机型策略失效
错误做法:策略输出的"推力"用绝对力(牛顿),训练时用 27g 的 Crazyflie 参数
现象:策略在训练机型上工作,换到 250g 的机型(或真机质量与仿真略有偏差)
时,同样的推力指令产生完全不同的加速度,策略行为异常
根本原因:绝对推力和质量耦合——同样 1N 推力,27g 无人机和 250g 无人机的
加速度差近 10 倍。策略隐式记住了训练机型的质量
正确做法:CTBR 的推力分量用"质量归一化推力"(单位 m/s²,即期望加速度),
与质量解耦。这样策略输出的是"我想要多大加速度",落到不同质量的
机体时由底层自动换算成对应的力。这是 CTBR 鲁棒性的关键设计之一
🔧 配置陷阱:角速度指令范围设得过大,rate loop 跟不上反而震荡
错误做法:把 body rate 动作范围设成 [-30, 30] rad/s 以为"控制权限更大更灵活"
现象:策略学会输出极端角速度指令,但真机的 rate loop(受电机带宽限制)跟不上,
实际角速度远小于指令,且在指令突变处剧烈震荡
根本原因:body rate 指令范围必须落在底层 rate loop(和电机带宽)能实际跟踪的
范围内。超出范围的指令是"空头支票",固件无法兑现,反而引入跟踪误差
正确做法:把 body rate 范围设在 [-10, 10] rad/s 量级(典型四旋翼 rate loop
可跟踪范围),并在训练奖励里加动作平滑项(惩罚相邻动作的差分),
避免策略输出物理上无法实现的突变指令(见 §P0.D.7 的动作平滑)
练习¶
-
【对比练习·接口推演】 不查表,自己推一遍:如果一个策略输出 SRT(4 个桨推力),它在 sim-to-real 时会暴露哪些"难仿真的底层物理"?CTBR 把这些中的哪些外包给了固件?列一张两列对照表(SRT 暴露 vs CTBR 外包)。验收标准:至少覆盖电机时间常数、ESC 延迟、桨叶气动三项。预计 20 分钟。
-
【配置练习·动作空间切换】 用 gym-pybullet-drones 跑
HoverAviary,分别用ActionType.RPM和ActionType.PID(更高层)各训一个悬停策略,对比:① 哪个收敛更快?② 哪个最终奖励更高?记录两者的训练曲线。思考:为什么更高层的动作空间通常更容易训练?(提示:动作空间越高层,策略要学的"映射复杂度"越低。)预计 40 分钟。 -
【综合思考题·跨章迁移】 结合足式 RL 的经验回答:足式 RL 用 12D 关节位置、无人机 RL 用 4D CTBR,两者背后是同一个设计原则。用一句话概括这个原则,然后分别说明:① 足式里"谁"扮演了无人机 rate loop 的角色?② 如果有人非要让足式机器人直接输出关节力矩(最底层),会遇到和无人机"直接输出 RPM"类似的什么问题?预计 25 分钟。
§P0.D.4 Gazebo + PX4 SITL 搭建——物理保真闭环,飞控在环 ⭐⭐¶
模块功能与在管线中的位置¶
这一节走"物理保真路线"的代表搭建:把真实的飞控固件(PX4)和物理仿真器(Gazebo)连成一个闭环,让飞控以为自己在控制一架真无人机。这是无人机工程的"基本功"——几乎所有涉及 PX4/ArduPilot 的项目(消费级、行业级无人机的主流飞控)都要先在这个闭环里验证。它的价值不在"快"(它是单实例、不并行的),而在"真"——飞控固件的代码原封不动地跑,状态估计、模式切换、故障保护逻辑全都在环。
动机:为什么要让"真飞控"在环¶
设想你写了一个新的轨迹跟踪控制器,想在无人机上跑。你有两个选择:
- 选择 A:自己写一个简化的无人机模型(点质量 + 理想推力),在 Python 里仿真控制器。
- 选择 B:让真实的 PX4 固件跑在仿真里,你的控制器通过 PX4 的标准接口(offboard 模式)控制它。
选择 A 快,但你验证的只是"控制器 + 理想模型"。真机上,你的控制器指令要经过 PX4 的状态估计器(EKF2)、模式管理(commander)、混控、故障保护——这一整套固件逻辑选择 A 完全没覆盖。等你上真机才发现 EKF2 估计有延迟、commander 在某个边界拒绝了你的指令、故障保护误触发——这些 bug 选择 A 永远暴露不出来。
选择 B(SITL)让真飞控在环,你验证的是"控制器 + 真固件 + 物理模型"的完整闭环。代价是慢、单实例、要装一堆东西。但对于会上真机的项目,这个代价必须付——因为飞控固件的复杂逻辑,只有让它真的跑起来才能验证。
本质洞察:SITL 的价值不是"仿真物理"(那是 Gazebo 的事),而是"仿真整个飞控软件栈"。PX4 在 SITL 里运行的是**和真机几乎完全相同的固件代码**——同样的 EKF2、同样的 commander、同样的参数系统。所以 SITL 暴露的是"软件层"的问题(估计、模式、保护逻辑),这正是简化的理想仿真永远覆盖不到的盲区。
反面:SITL 和 HITL 的区别——数据从哪注入¶
SITL 之外还有一个 HITL(硬件在环),新手常搞混。它们的本质区别是**仿真数据注入飞控软件栈的"位置"不同**:
SITL(软件在环):
Gazebo 物理 ──传感器真值──→ [PX4 固件运行在 PC 上]──控制指令──→ Gazebo
↑
固件是为仿真专门编译的(posix 版)
数据注入点"深":执行了更多标准系统代码
HITL(硬件在环):
Gazebo 物理 ──HIL_* MAVLink 消息──→ [PX4 固件运行在真 Pixhawk 上]
↑
固件是真机固件(和飞真无人机一模一样)
数据注入点"浅":commander/sensors 进入 HIL 模式,
绕过部分正常功能,直接吃仿真传感器
| 维度 | SITL | HITL |
|---|---|---|
| 固件跑在哪 | 开发 PC(专门编译的 posix 版) | 真 Pixhawk 硬件(真机固件) |
| 数据注入点 | 深(执行更多标准系统代码) | 浅(commander/sensors 有 HIL 模式,绕过部分功能) |
| 测的是什么 | 软件逻辑 + 物理 | 硬件特性 + 真实计算资源约束 |
| 典型用途 | 日常开发、CI 自动测试、算法验证 | 上真机前的最后一道关、测硬件相关问题 |
| 连接方式 | 进程间(本机 UDP) | USB/UART 连真硬件 |
多视角理解(什么时候用哪个): - 角度1(开发阶段视角):SITL 用于"日常迭代"——它快、可脚本化、能进 CI,你 95% 的时间都在用它。HITL 用于"临门一脚"——上真机前,把固件刷进真 Pixhawk,验证"在真实计算资源(有限的 MCU 算力、真实的传感器驱动)下还跑得动吗"。 - 角度2(覆盖盲区视角):SITL 覆盖不到"硬件相关"的问题——比如你的控制器在 PC 上跑很快,但烧进 Pixhawk 的 480MHz MCU 后算不过来、超了控制周期。HITL 专门暴露这类问题。 不像的地方:SITL 在 PC 上跑,算力近乎无限,掩盖了真机的算力约束;HITL 用真硬件,但 Gazebo 仍在 PC 上,所以它测的是"真固件硬件 + 仿真物理",物理仍是仿的。两者都不能替代真飞。
理论:SITL 闭环的完整数据流¶
┌──────────────────────────────────────────────────────────────┐
│ Gazebo(gz-sim) │
│ ├─ 物理引擎:积分 X500 四旋翼的 6-DoF 动力学 │
│ ├─ gz_x500 模型:含 4 个旋翼关节 + IMU/GPS/磁力计传感器插件 │
│ └─ 输出:IMU/GPS/气压 真值(加可选噪声) │
│ ↕ (gz-transport / MAVLink over UDP) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PX4 SITL(px4 进程,posix 编译) │ │
│ │ ├─ EKF2:融合 IMU/GPS → 状态估计(位置/速度/姿态) │ │
│ │ ├─ commander:模式管理(manual/offboard/...)+ 故障保护 │ │
│ │ ├─ 位置/姿态/rate 控制器(你也可以从外部 offboard 接管) │ │
│ │ └─ 混控 + 电机模型 → 4 个旋翼转速指令 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ↕ (MAVLink over UDP, 默认端口 14540/14580) │
└──────────────────────────────────────────────────────────────┘
↕ MAVLink
┌──────────────────────┐ ┌──────────────────────────────────┐
│ QGroundControl │ │ 你的代码(MAVROS / MAVSDK / pymavlink)│
│ (地面站,看状态/发指令)│ │ (offboard 控制、读状态、跑你的算法) │
└──────────────────────┘ └──────────────────────────────────┘
配置详解:lockstep 模式——为什么对确定性复现至关重要¶
PX4 SITL 默认启用 lockstep(锁步)模式,这是个容易被忽略但极重要的配置:
| 模式 | 行为 | 优点 | 缺点 |
|---|---|---|---|
| lockstep 开(默认) | 仿真和飞控**严格同步推进**:Gazebo 每步等飞控处理完才推进,反之亦然 | 完全确定性可复现——同样输入永远同样输出;不受 PC 算力波动影响 | 不能比实时快(受限于飞控处理速度);若飞控某步卡住,仿真也停 |
| lockstep 关 | 仿真按真实墙钟时间跑,飞控尽力跟上 | 可比实时快;更接近真机的异步特性 | 不确定——PC 卡一下,时序就变,难以复现 bug |
本质洞察:lockstep 解决的是仿真界一个根本矛盾——真实时间 vs 仿真时间的解耦。真机上,传感器和控制器在物理时间里异步运行,谁也不等谁。但仿真里如果也这样,PC 的算力波动(后台开了个浏览器)就会改变时序,让 bug 无法复现。lockstep 让仿真时间"按步走而非按墙钟走"——Gazebo 推进一个 dt,必然等 PX4 处理完这个 dt,时序完全由步数决定、与真实耗时无关。这是"仿真可复现"和"仿真像真机的异步性"之间的取舍:调试和 CI 要可复现(开 lockstep),压力测试要逼真异步(关 lockstep)。
代码走读:用 MAVSDK 让无人机起飞悬停(offboard 控制最小示例)¶
SITL 跑起来后,最常见的需求是"用代码控制它"。下面用 MAVSDK-Python(PX4 官方推荐的高层 SDK)实现起飞→悬停→降落,逐行讲解:
import asyncio
from mavsdk import System
from mavsdk.offboard import OffboardError, VelocityBodyYawspeed
async def run():
# 1) 连接到 SITL。udpin://0.0.0.0:14540 是 PX4 SITL 默认对外的 offboard 端口
drone = System()
await drone.connect(system_address="udpin://0.0.0.0:14540")
# 2) 等待飞控连接确认(is_connected 为 True)
async for state in drone.core.connection_state():
if state.is_connected:
print("已连接到飞控")
break
# 3) 等待 GPS 定位和 home 位置就绪——没这一步直接 arm 会被 commander 拒绝
async for health in drone.telemetry.health():
if health.is_global_position_ok and health.is_home_position_ok:
print("定位就绪,可以起飞")
break
# 4) 解锁(arm)并起飞到默认高度
await drone.action.arm() # arm = 让电机可转;真机这一步要格外小心
await drone.action.takeoff() # 起飞到参数 MIS_TAKEOFF_ALT(默认 2.5m)
await asyncio.sleep(5) # 等它爬升稳定
# 5) 切 offboard 模式前,必须先发一个 setpoint,否则切换会失败
# (PX4 要求 offboard 流在切换前已有数据流,防止"切进去没指令")
await drone.offboard.set_velocity_body(
VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0)) # 机体系零速度=悬停
try:
await drone.offboard.start() # 正式接管,从此你的代码完全控制无人机
except OffboardError as e:
print(f"offboard 启动失败: {e._result.result}")
await drone.action.disarm()
return
# 6) 发一段机体系速度指令:以 1 m/s 向前飞 5 秒
await drone.offboard.set_velocity_body(
VelocityBodyYawspeed(1.0, 0.0, 0.0, 0.0)) # vx=1, vy=0, vz=0, yaw_rate=0
await asyncio.sleep(5)
# 7) 停下并降落
await drone.offboard.set_velocity_body(
VelocityBodyYawspeed(0.0, 0.0, 0.0, 0.0))
await asyncio.sleep(2)
await drone.action.land()
if __name__ == "__main__":
asyncio.run(run())
逐行要点解析:
- 第 2 行
udpin://0.0.0.0:14540:这是 PX4 SITL 暴露给外部 offboard 程序的标准端口。14540 给 offboard SDK,14580 给 QGroundControl——记住这两个端口能省很多"连不上"的排查时间。 - 第 3 步等定位(关键,新手最常漏):PX4 的 commander 有严格的预飞检查(preflight check)——没有有效的全局定位和 home 位置,
arm()会被直接拒绝并报 "Arming denied"。SITL 里 GPS 真值很快就绪,但代码上必须等。这正是 §动机里说的"真固件逻辑在环"——理想仿真不会有这个检查,SITL 会。 - 第 5 步先发 setpoint 再 start(offboard 协议要求):PX4 规定,切入 offboard 模式前,offboard setpoint 流必须已经在发送(频率 > 2 Hz)。否则 PX4 认为"你要接管但没准备好指令",拒绝切换或立即触发故障保护。这是个固件层的硬约束,很多人卡在这里。
- 第 6 步机体系速度:
VelocityBodyYawspeed(vx, vy, vz, yaw_rate)是机体坐标系下的速度指令——vx=1是"沿机头方向 1 m/s",不管机头朝哪。如果要世界系(北东地),用set_velocity_ned。
运行验证:怎么确认 SITL 闭环真的在工作¶
# 终端 1:启动 SITL + Gazebo
cd PX4-Autopilot && make px4_sitl gz_x500
# 终端 2:启动 QGroundControl(下载 AppImage 后)
./QGroundControl.AppImage
# 验证点 1:QGC 自动连上,地图上出现无人机图标,姿态仪表跳动
# 终端 3:跑上面的 MAVSDK 脚本
python takeoff_hover.py
# 验证点 2:Gazebo 里无人机起飞、前飞、降落;
# QGC 的高度曲线和速度曲线与脚本指令吻合;
# PX4 终端打印模式切换日志:MANUAL → TAKEOFF → OFFBOARD → LAND
成功标志三连:① Gazebo 里看到无人机按脚本动作;② QGC 遥测曲线与指令吻合;③ PX4 终端的模式切换日志符合预期。三者都对,才说明"物理-固件-你的代码"三方闭环真正打通了——任何一环断了,都会在这三个验证点之一暴露。
⚠️ 常见陷阱¶
🔧 配置陷阱:在 Ubuntu 20.04 上尝试用 Gazebo Harmonic / make gz_x500
错误做法:在 20.04 上 git pull 最新 PX4 然后 make px4_sitl gz_x500
现象:编译或启动时报 Gazebo(gz-sim)找不到 / 版本不兼容;apt 装不上 gz-harmonic
根本原因:Gazebo Harmonic(及 Garden)不支持 Ubuntu 20.04——20.04 的系统库
版本太老。PX4 在 20.04 上的官方仿真器是 Gazebo Classic 11
正确做法:要么升级到 Ubuntu 22.04 用 Gazebo Harmonic(make px4_sitl gz_x500),
要么在 20.04 上用 Gazebo Classic(make px4_sitl gazebo-classic_iris)。
注意两者 target 名不同:gz_ 前缀是新 Gazebo,gazebo-classic_ 是旧的
💡 概念误区:以为 SITL 跑通 = 真机一定能飞(混淆"软件验证"和"物理验证")
新手想法:"SITL 里控制器表现完美,PX4 没报任何错,上真机肯定没问题"
现象:上真机后控制器震荡或发散,明明 SITL 里好好的
根本原因:SITL 验证的是"软件逻辑 + 仿真物理"。仿真物理用的是理想化的电机/
气动模型,没有真机的电机延迟、桨叶气动残差、传感器真实噪声、机架
振动。这些物理 gap(见 §P0.D.7)SITL 覆盖不到
正确理解:SITL 通过 = "软件层没问题",不等于"物理层没问题"。完整流程应是
SITL(软件)→ HITL(硬件软件)→ 真机小范围测试 → 逐步放开。SITL
是必要不充分条件
⚠️ 编程陷阱:offboard 控制时 setpoint 发送频率过低,触发故障保护
错误做法:offboard 模式下,控制循环里 sleep(0.5),即 2 Hz 发 setpoint
现象:飞机刚切进 offboard 就自己退出(fallback 到 hold/land),日志报
"Offboard mission stopped" 或类似
根本原因:PX4 要求 offboard setpoint 至少 2 Hz(实际建议 ≥10 Hz,留余量)。
一旦某两个 setpoint 间隔超过约 0.5 秒,commander 判定"offboard 信号
丢失",触发故障保护自动接管——这是防止"上位机死机后无人机失控"的
安全机制
正确做法:offboard 控制循环频率设到 20-50 Hz(间隔 20-50ms),远高于 2 Hz
下限。即使你的算法算得慢,也要用上一帧的指令"垫"着持续发送,保持
setpoint 流不断
练习¶
-
【管线搭建·SITL 闭环】 按路线 B 装好 PX4 + Gazebo,跑通
make px4_sitl gz_x500并连上 QGroundControl。然后运行本节的 MAVSDK 起飞脚本。验收标准:Gazebo 里无人机完成起飞→前飞→降落,QGC 遥测曲线与指令吻合。预计 90 分钟(含首次编译)。 -
【配置练习·lockstep 对比】 查 PX4 文档找到关闭 lockstep 的方法(编译选项 / 参数)。分别在 lockstep 开/关下跑同一段起飞脚本两次,观察:① 开 lockstep 时两次运行的遥测曲线是否完全一致?② 关 lockstep 时呢?用这个实验理解"确定性可复现"的含义。预计 40 分钟。
-
【集成测试·故障保护触发】 故意把 offboard 控制循环的发送频率从 20 Hz 降到 1 Hz,运行脚本,观察 PX4 终端日志。验收标准:你应该看到飞机自动退出 offboard 模式 + 故障保护日志。然后改回 20 Hz 验证恢复正常。思考:这个安全机制在真机上保护了什么场景?预计 30 分钟。
§P0.D.5 渲染保真路线:Flightmare 与 3DGS——把"看得见"喂给视觉策略 ⭐⭐⭐¶
模块功能与在管线中的位置¶
上一节的 Gazebo+PX4 走"物理保真"路线,渲染只是配角。这一节走另一条岔路——渲染保真路线:当你的算法依赖图像(视觉避障、视觉竞速、VIO、视觉伺服),仿真的渲染质量就从配角变成主角。Flightmare 是这条路线的经典代表(Unity 光真),3DGS 是它的下一代(真实场景重建)。这一节回答"怎么让仿真出的图像,逼真到能训出可迁移到真机的视觉策略"。
动机:为什么物理保真路线喂不动视觉策略¶
回到 §P0.D.4 的 Gazebo+PX4——它物理很真,但渲染是 Gazebo 的 OGRE 引擎,质量中等、不能 GPU 批量出图。如果你想训一个"看着 RGB 图像避障"的策略,会撞上两堵墙:
- 质量墙:OGRE 渲染的图像和真实相机拍的差距大(纹理、光照、材质都简化了)。策略在这种图像上学到的视觉特征,上真机看到真实图像就失效——这是视觉的 sim-to-real gap。
- 速度墙:Gazebo 单实例渲染,出图慢。视觉 RL 要海量图像样本,Gazebo 的出图速度撑不起来。
本质洞察:视觉策略的 sim-to-real gap,主战场不在物理而在像素。一个飞得慢的避障策略,对气动残差几乎不敏感(物理 gap 小),但对"仿真图像 vs 真实图像"的差异极其敏感(渲染 gap 大)。这就是为什么视觉任务要专门走渲染保真路线——你优化的应该是像素的真实度,而不是动力学的精度。这和 §P0.D.2 不可能三角"按任务敏感维度配资源"完全一致。
反面:如果用低真渲染训视觉策略会怎样¶
反事实:假设你用 Gazebo 的低真渲染训一个 RGB 避障策略会怎样? → 策略可能学到"仿真特有的视觉捷径"——比如仿真里障碍物边缘有特定的锯齿/色块,策略靠这些非真实特征判断距离。 → 上真机:真实障碍物没有这些锯齿,策略的"捷径"失效,避障行为崩溃。 → 这叫"渲染 overfitting"——策略过拟合到仿真渲染器的特性,而非真实世界的视觉规律。 结论:渲染保真不足时,视觉策略学到的是"仿真渲染器的指纹"而非"真实视觉规律"。这就是为什么 Flightmare 用 Unity(接近真实的光照材质),以及为什么 3DGS(直接重建真实场景)是下一代——渲染越接近真实,可学的"捷径"越少,迁移越好。
理论:Flightmare 的双引擎解耦架构¶
Flightmare 是 §P0.D.2"三层解耦"的最纯粹体现——它把渲染和物理拆成两个独立进程,用消息队列连接:
┌────────────────────────────┐ ┌────────────────────────────┐
│ Flightmare 渲染引擎 │ │ Flightmare 物理引擎 │
│ (Unity,独立进程/可独立运行)│ ZeroMQ │ (flightlib,C++) │
│ ├─ 光真 RGB │◀───────▶│ ├─ 轻量 6-DoF 刚体积分 │
│ ├─ 深度 / 分割 │ 异步消息 │ ├─ 可选 BEM 气动模型 │
│ ├─ 点云提取 │ │ ├─ 多达 ~150 架并行(CPU) │
│ └─ 渲染达 230 Hz │ │ └─ 物理达 200,000 Hz │
│ ├─ 可接 VR 头显 │ │ └─ OpenAI Gym 风格 Python │
│ └─ 多模态传感器套件 │ │ wrapper + stable-baselines│
└────────────────────────────┘ └────────────────────────────┘
这个架构的几个关键设计点:
- ZeroMQ 异步连接:渲染和物理用高性能异步消息库 ZeroMQ 通信,互不阻塞。物理可以飞速积分(200 kHz),渲染按自己的节奏出图(230 Hz)——两个数字差三个数量级,正说明它们完全解耦。
- 物理简单、渲染光真:Flightmare 的物理引擎其实比 Gazebo 简单(默认轻量 6-DoF,BEM 气动是可选的)。它的卖点从来不是物理,而是 Unity 渲染。这印证了 §P0.D.1 的陷阱"渲染好 ≠ 物理准"。
- 百级 CPU 向量化:物理端能并行 ~150 架无人机,随机动作下达 20 万 steps/s,一分钟收集数百万样本。注意这是 CPU 向量化(不是 GPU),所以规模是百级而非千级——这也是它被 G3 的 Aerial Gym/OmniDrones(GPU 千级)超越的地方。
- 多模态 + VR:除 RGB 外提供深度、分割、点云,甚至能接 VR 头显做人在环交互——这套丰富的传感器套件是它做 VIO/感知研究的优势。
多视角理解:Flightmare 对照 Gazebo 的"分工哲学"¶
跨领域类比(标注边界):Flightmare 的双引擎解耦,像电影工业的"动作捕捉 + CG 渲染"分离——动捕棚(物理引擎)只管捕捉演员的运动数据,快而精确;渲染农场(Unity 渲染引擎)拿运动数据生成逼真画面,慢而精美。两者完全分离,各自优化。 像的地方:都是"运动/物理"和"视觉/渲染"分两个独立系统,用数据流连接,各自做到极致。 不像的地方:电影里动捕和渲染是离线串行的(先捕后渲);Flightmare 里物理和渲染是在线并行的(同时跑,实时交互)——ZeroMQ 让它们实时双向通信,而非离线单向传递。不要把"离线串行"的印象套到 Flightmare 上。
理论:3DGS 渲染——下一代视觉仿真范式(G4)¶
Flightmare 用 Unity 渲染的是"人工建模的场景"——美术建的房间、街道。但人工建模再精细,和真实世界仍有 gap。3DGS(3D Gaussian Splatting,三维高斯泼溅) 是 2024-2025 兴起的下一代方案,思路根本不同:
传统渲染(Flightmare/Gazebo):人工建模场景 → 渲染 → 图像(和真实有 gap)
3DGS 渲染(FiGS/SOUS VIDE):
真实场景拍一组照片 ──→ 3DGS 重建 ──→ 真实场景的"数字孪生"
↓ 130 fps 渲染
几乎和真实照片一样的图像
↓
训练视觉策略(在真实场景的孪生里)
↓
部署到同一个真实场景 → gap 极小
本质洞察:3DGS 把视觉 sim-to-real 的逻辑**反过来了**——传统方法是"建一个仿真场景,努力让它像真实";3DGS 是"拍真实场景,重建成可渲染的孪生"。前者是"从仿真逼近真实",后者是"从真实生成仿真"。后者的渲染 gap 天然极小,因为它的"场景"本就来自真实照片。Stanford MSL 的 FiGS/SOUS VIDE 用这套方法:3DGS 重建真实场景 → 130 fps 渲染 → MPC 专家蒸馏成视觉策略 → 105 次真机验证,这可能让 3DGS 替代 Flightmare 成为下一代视觉策略训练平台。
3DGS 的局限(为什么还没完全取代 Flightmare):
| 维度 | 3DGS 强 | 3DGS 弱 |
|---|---|---|
| 渲染真实度 | 极高(来自真实照片) | — |
| 场景获取 | — | 要先去真实场景拍照重建,不能凭空造 |
| 物理交互 | — | 3DGS 是"渲染表示",不含碰撞几何/物理属性,要另配物理 |
| 场景泛化 | — | 一个重建场景训的策略,换场景要重新拍重建(泛化是开放问题) |
| 动态物体 | — | 标准 3DGS 重建的是静态场景,动态障碍物支持仍在发展 |
配置详解:选渲染路线的决策维度¶
| 你的视觉任务 | 推荐 | 理由 |
|---|---|---|
| 只用深度图(很多避障策略) | Aerial Gym(GPU 光线投射深度) | 深度图不需要光真 RGB,GPU 出图最快 |
| 需要光真 RGB、合成场景够用 | Flightmare(Unity)/ Pegasus(Isaac) | Unity/Isaac 光真渲染,场景可自由设计 |
| 需要在特定真实场景部署、要极小渲染 gap | 3DGS(FiGS 系) | 真实场景孪生,渲染 gap 最小 |
| 已归档但存量多 | AirSim(UE) | 仅用于复现老工作,新项目不选 |
理论-工程桥接:渲染路线的选择直接决定你的数据管线长什么样。选 Aerial Gym(深度)→ 你的策略观测是深度张量,训练全程在 GPU,无需 ROS 图像话题。选 Flightmare(RGB)→ 你要起 Unity 渲染进程、走 ZeroMQ 收图、可能转 ROS
Image话题。选 3DGS → 你的管线最前面多了"真实场景采集 + 3DGS 重建"这一大块离线预处理。渲染路线不是一个孤立的开关,它牵动整条数据管线的形态——选型时要把这条管线的工程量一并算进去。
⚠️ 常见陷阱¶
💡 概念误区:以为渲染保真路线就一定要放弃物理保真
新手想法:"Flightmare 物理简单,那做视觉任务就只能要画面不要物理了"
实际上:渲染和物理是解耦的两层,可以独立提升。Flightmare 默认物理简单,但
它支持可选的 BEM 气动模型;更进一步,Pegasus(Isaac Sim)能同时给
光真渲染和高保真 PhysX 物理——代价是牺牲并行(回到不可能三角)。
"渲染好就物理差"是 Flightmare 的具体取舍,不是物理定律
正确理解:渲染和物理是正交维度。具体仿真器在两者上有各自取舍,但你可以根据
任务选"两者都要"的工具(Pegasus),只是要付出并行规模的代价
🔧 配置陷阱:用合成渲染训视觉策略,却不做任何视觉域随机化
错误做法:在 Flightmare/Isaac 里用固定的光照、纹理、相机参数训 RGB 策略
现象:仿真里策略视觉表现完美,上真机(光照不同、纹理不同、相机有畸变)后
视觉识别全乱,避障/跟踪失效
根本原因:固定渲染条件让策略过拟合到那一组特定的视觉外观。真实世界的光照、
纹理、相机畸变千变万化,策略没见过就泛化不了
正确做法:视觉策略训练必须做视觉域随机化——随机化光照方向/强度、纹理、相机
内参/畸变/噪声、甚至物体颜色。让策略学"形状/结构"而非"特定外观"。
这是视觉 sim-to-real 的核心手段(见 §P0.D.7)
💡 概念误区:认为 3DGS 重建的场景可以直接拿来做物理碰撞仿真
新手想法:"3DGS 重建了真实场景,那无人机撞墙、避障的物理碰撞也能在里面算吧"
实际上:3DGS 是一种"渲染表示"——它用一堆带颜色和透明度的高斯椭球拟合场景的
外观,本质是为了"出图好看",并不天然包含碰撞几何(mesh)或物理属性
(质量、摩擦)。无人机在纯 3DGS 场景里"撞墙"没有物理反应
正确理解:3DGS 解决的是"渲染 gap",不是"物理仿真"。完整方案要把 3DGS 渲染
和一个物理引擎(提供碰撞几何)组合——通常从同一组照片另外重建一个
碰撞 mesh,或用占据栅格表示几何。渲染和物理仍是解耦的两层
练习¶
-
【对比练习·渲染 gap 来源】 列举"仿真 RGB 图像"和"真实相机图像"之间至少 5 个差异维度(提示:光照、纹理、相机畸变、噪声、动态范围……)。对每个维度,说明它可能让视觉策略学到什么"仿真捷径"。验收标准:5 个维度都给出具体的捷径例子。预计 25 分钟。
-
【思考题·3DGS 范式反转】 用自己的话解释:"传统渲染是从仿真逼近真实,3DGS 是从真实生成仿真"这句话。然后回答:为什么"从真实生成仿真"的渲染 gap 天然更小?它的代价(相比传统渲染)是什么?预计 20 分钟。
-
【综合思考题·任务到渲染路线】 对下面三个视觉任务,分别选渲染路线(Aerial Gym 深度 / Flightmare RGB / 3DGS)并论证:① 室内深度避障 RL;② 需要识别特定 logo 的视觉伺服,部署在固定的实验室;③ 通用的 RGB 目标跟踪,要在各种未知环境用。每个论证 \(\ge\)3 句,落到"任务需要什么渲染特性"。预计 30 分钟。
§P0.D.6 并行 RL 工厂:Aerial Gym / OmniDrones——4096 环境,周级压到分钟级 ⭐⭐⭐¶
模块功能与在管线中的位置¶
这一节走"训练速度路线"的代表搭建:用 GPU 并行仿真把强化学习的采样速度拉到极致。它是 G3 时代的核心,也是当前无人机 RL 研究的标配。前面 §P0.D.4 的 SITL 单实例、§P0.D.5 的 Flightmare 百级 CPU 向量化,到这里跃升到**千级到百万级 GPU 并行**——同样一个悬停策略,gym-pybullet-drones 单 CPU 要训几小时,Aerial Gym 几千并行环境一分钟搞定。这一节是本章配置最密集的部分,逐项拆解 RL 环境的核心配置(num_envs、dt、decimation、观测-动作-奖励),并讲清每个值怎么取、改了会怎样。
动机:为什么 RL 必须并行——采样吞吐是瓶颈¶
回顾前置自测第 2 题。PPO 这类 on-policy 算法有个根本特性:每次更新策略前,必须用当前策略重新采集一批新轨迹(旧策略采的数据用一次就得扔)。这个采样步骤是纯仿真步进——它**不吃 GPU 的算力(矩阵乘),只吃仿真步进速度**。所以 RL 训练的吞吐瓶颈不在神经网络(那很快),而在"每秒能仿多少步"。
单环境(gym-pybullet-drones, CPU):~2,000 steps/s
一个像样的视觉避障策略需要 ~5 亿步
→ 5e8 / 2e3 = 250,000 秒 ≈ 2.9 天(还没算调参的反复!)
4096 并行环境(Aerial Gym, GPU):~数千万 steps/s(电机指令策略可 <1 分钟训完)
同样 5 亿步 → 几十秒到几分钟
差距是**三到四个数量级**。这不是"快一点",而是"从不可能到可能"——2.9 天一次的迭代,没人能用它调参;一分钟一次的迭代,你一天能试几十版奖励函数。GPU 并行仿真不是优化,是 RL 研究的入场券。
本质洞察:GPU 并行仿真的核心魔法不是"用了 GPU",而是"让整个 rollout 全程不离开显存"。传统做法是 CPU 仿真→数据拷到 GPU→训练,每步都有 host-device 传输。GPU 并行仿真把物理状态、观测、动作、奖励全部表示成 GPU 张量,用 CUDA kernel 一次步进所有 4096 个环境,整个采样过程零拷贝。这就是为什么前置自测第 3 题强调"训练循环里不能 print GPU tensor"——一次
.cpu()就打断这个零拷贝流水线,吞吐崩盘。
反面:如果在 GPU 并行训练里频繁取数据到 CPU 会怎样¶
反事实:假设你在 Aerial Gym 的训练循环里加了
print(obs.cpu().numpy())调试会怎样? → 每步.cpu()触发 GPU→CPU 的 PCIe 传输 + CUDA 流强制同步(等所有 kernel 跑完)。 → 4096 个环境的观测张量每步都拷一遍,PCIe 带宽成瓶颈。 → 更糟的是同步——GPU 本来可以异步流水线执行多个 kernel,.cpu()强制"等齐",流水线断了。 → 结果:训练速度从数千万 steps/s 暴跌到几十万甚至更低,GPU 利用率从 95% 跌到个位数。 结论:这正是工程实践规范里的经典编程陷阱。GPU 并行训练的调试**绝不能用.cpu()取中间值**——要用框架的 logger(TensorBoard 记标量)或定期torch.save张量离线分析。
理论:Aerial Gym vs OmniDrones——两条 GPU 并行路线¶
两者都是 G3 GPU 并行,但底层栈和定位不同:
| 维度 | Aerial Gym(NTNU ARL) | OmniDrones(清华 thu-uav) |
|---|---|---|
| 底层 | Isaac Gym (Preview 4) | Isaac Sim + Isaac Lab |
| 渲染 | GPU 光线投射(深度/分割,无光真 RGB) | Isaac Sim 光真渲染 |
| 并行规模 | 千级到百万级(轻量物理) | 千级 |
| 内置控制器 | SE(3) 几何控制器(并行化,支持欠/全/过驱动) | 多 control mode(含 CTBR) |
| 机型 | 任意多旋翼(高度可配) | 4 种机型 + 多传感器 |
| 杀手锏 | 电机指令策略 <1 分钟、视觉导航 <1 小时 | 与 Isaac Lab 生态打通、SimpleFlight 基准 |
| 适合 | 杂乱场景避障、极致并行 | 高保真渲染需求、多机、基准研究 |
多视角理解(怎么选): - 角度1(你要不要光真 RGB):要 RGB → OmniDrones(Isaac Sim 光真);只要深度/状态 → Aerial Gym(更快、更并行)。 - 角度2(你要不要极致并行):要把并行推到百万级(如海量电机指令策略)→ Aerial Gym(Isaac Gym 更轻);千级够用且要生态完整 → OmniDrones(Isaac Lab 工具链)。 - 角度3(你的底层栈历史包袱):已在用 Isaac Gym 的足式项目 → Aerial Gym 无缝;已在用 Isaac Lab → OmniDrones 顺手。
配置详解:RL 环境的核心配置项逐个拆解¶
这是本节的核心。下面是一个典型四旋翼悬停 RL 任务的配置(以 Isaac 生态的通用写法为例),逐项讲透每个值怎么来、改了会怎样:
class HoverTaskConfig:
# ===== 并行规模 =====
num_envs = 4096 # 并行环境数:决定采样吞吐和显存占用
env_spacing = 2.0 # 环境间隔(米):可视化时各环境在世界里的物理间距
# ===== 时间步 =====
sim_dt = 0.005 # 物理步长(秒)= 200 Hz 物理仿真
decimation = 4 # 控制降采样:每 4 个物理步执行 1 个控制步
# → 控制频率 = (1/sim_dt) / decimation = 200/4 = 50 Hz
max_episode_length_s = 10.0 # 单回合最长时间(秒)→ 步数 = 10/(sim_dt*decimation)=500
# ===== 动作空间(CTBR,见 §P0.D.3)=====
action_dim = 4 # [质量归一化推力, ωx, ωy, ωz]
thrust_range = [0.0, 2.0] # 推力范围(单位 g,即 0 到 2 倍重力加速度)
bodyrate_range = [-6.0, 6.0] # 机体角速度范围(rad/s)
# ===== 观测空间 =====
# 位置误差(3) + 线速度(3) + 旋转矩阵(9) + 角速度(3) + 上一动作(4) = 22 维
obs_dim = 22
# ===== 域随机化(见 §P0.D.7)=====
randomize_mass = True
mass_range = [0.8, 1.2] # 质量 ±20% 随机
randomize_inertia = True
inertia_range = [0.8, 1.2]
逐项深度解析(每一项都按"是什么/怎么取/改了会怎样/和谁耦合"四维讲):
num_envs = 4096——并行环境数¶
| 维度 | 说明 |
|---|---|
| 是什么 | 同时仿真的独立环境(无人机)数量。GPU 一个 kernel 一次步进所有这些环境 |
| 怎么取 | 来自"GPU 显存 vs 并行效率"的平衡点。4096 是经验甜点:足够多让采样高效,又不撑爆 16GB 显存 |
| 改大(如 8192) | 采样吞吐↑(更多并行),但显存占用↑(可能 OOM);超过某点后并行效率边际递减 |
| 改小(如 512) | 显存↓(小显卡能跑),但采样吞吐↓,训练墙钟时间↑;太小(<256)PPO 的批次方差大,训练不稳 |
| 和谁耦合 | 与 PPO 的 batch_size、mini_batch 耦合——num_envs × rollout_length 决定每次更新的样本量。改 num_envs 要同步检查批次配置 |
sim_dt = 0.005 和 decimation = 4——物理步长与控制降采样¶
这两个**必须一起理解**,它们共同决定物理频率和控制频率:
| 维度 | 说明 |
|---|---|
| 为什么物理要比控制快 | 物理积分步长太大会数值发散(积分误差累积);但控制不需要这么高频(策略 50 Hz 决策足够,且匹配真机 CTBR 指令频率 50-100 Hz)。所以"物理高频积分、控制低频决策",中间用 decimation 桥接 |
| sim_dt 怎么取 | 典型 0.005(200Hz)。可在 0.001-0.01 间调;太大(>0.02)刚体积分不稳定甚至发散;太小(<0.001)算力浪费在过度精细的积分上 |
| decimation 怎么取 | 由"想要的控制频率"反推:decimation = f_物理 / f_控制。想要 50Hz 控制、200Hz 物理 → decimation=4 |
| 关键耦合陷阱 | 改了 sim_dt 必须同步改 decimation,否则控制频率会漂移!比如 sim_dt 改成 0.0025(400Hz)但 decimation 还是 4 → 控制变成 100Hz,策略的时间感知全乱,训练行为异常 |
本质洞察(decimation 的本质):decimation 本质是**解耦"物理精度"和"决策频率"两个不该绑定的东西**。物理积分要高频是为了数值稳定(纯数学需求),策略决策频率要匹配真机控制接口(工程需求)——这是两个独立的约束。如果没有 decimation,你被迫让策略以 200Hz 决策,既浪费(策略不需要这么快)又和真机 50Hz 的 CTBR 接口对不上。decimation 让两者各取所需:物理 200Hz 稳,控制 50Hz 准。
max_episode_length_s——回合长度¶
| 维度 | 说明 |
|---|---|
| 是什么 | 单个训练回合的最长时间,到点就 reset(无论成功失败)。换算成步数 = length_s × f_控制 |
| 怎么取 | 要覆盖任务的完整时间尺度。悬停 10 秒够;长轨迹跟踪要更长。太短策略学不到长程行为,太长每回合采样慢、稀疏奖励难传播 |
| 改了会怎样 | 太短→策略只见过任务前段,泛化差;太长→单回合占样本多,探索效率低 |
| 和谁耦合 | 与奖励的时间尺度、reset 条件耦合 |
观测空间 22 维——为什么是这些量¶
# 22 维观测 = 位置误差(3) + 线速度(3) + 旋转矩阵(9) + 角速度(3) + 上一动作(4)
obs = torch.cat([
target_pos - drone_pos, # 3D 位置误差:策略要"往目标飞"的核心信号
drone_linvel, # 3D 线速度:判断当前运动状态,防止冲过头
rot_matrix.reshape(-1, 9), # 9D 旋转矩阵:表示姿态,比欧拉角无奇异、比四元数无双覆盖
drone_angvel, # 3D 角速度:姿态变化率,匹配 CTBR 动作的物理量
last_action, # 4D 上一动作:让策略感知自己上一步做了什么,利于动作平滑
], dim=-1)
为什么姿态用旋转矩阵(9D)而非欧拉角(3D)或四元数(4D):
| 表示 | 维度 | 问题 |
|---|---|---|
| 欧拉角 | 3 | 万向锁奇异——某些姿态下表示退化,梯度爆炸,策略学不动 |
| 四元数 | 4 | 双重覆盖——\(q\) 和 \(-q\) 表示同一姿态,神经网络要学这个对称性,徒增难度 |
| 旋转矩阵 | 9 | 维度高但**无奇异、无双覆盖、连续**——神经网络最友好。代价仅是多 5 维输入,可忽略 |
理论-工程桥接:观测里放"上一动作"(
last_action)不是凑数——它是**动作平滑**的关键。RL 策略容易输出剧烈抖动的动作(这一步满推力、下一步零推力),这在仿真里可能"最优",但真机的电机和 rate loop 跟不上(见 §P0.D.3 陷阱)。把上一动作放进观测,配合奖励里的"动作差分惩罚",策略就能学会平滑过渡——这是把 sim-to-real 的物理约束"编码进训练"的典型手法。
代码走读:观测-动作-奖励的最小实现¶
def compute_reward(drone_state, target, action, last_action):
"""四旋翼悬停奖励——位置/速度跟踪 + 姿态正则 + 动作平滑 + 存活"""
pos_err = torch.norm(target - drone_state.pos, dim=-1) # 位置误差范数
vel_norm = torch.norm(drone_state.linvel, dim=-1) # 速度大小
# 1) 位置跟踪:误差越小奖励越高(用 exp 让奖励在目标附近平滑饱和)
r_pos = torch.exp(-1.5 * pos_err)
# 2) 速度正则:悬停时希望速度趋零(防止策略学会"高速画圈经过目标")
r_vel = torch.exp(-0.5 * vel_norm)
# 3) 姿态正则:希望机体 z 轴接近世界 z 轴(别翻过来)
upright = drone_state.rot_matrix[..., 2, 2] # R[2,2] = 机体 z 在世界 z 的投影
r_upright = torch.clamp(upright, 0.0, 1.0)
# 4) 动作平滑:惩罚相邻动作差分(关键的 sim-to-real 项!)
r_smooth = torch.exp(-0.1 * torch.norm(action - last_action, dim=-1))
# 5) 存活奖励:每步活着给一点,鼓励不坠毁
r_alive = 0.1
reward = (1.0 * r_pos + 0.3 * r_vel + 0.2 * r_upright
+ 0.2 * r_smooth + r_alive)
return reward
def check_termination(drone_state, episode_step, max_steps):
"""回合终止条件:坠毁/翻覆/超时"""
crashed = drone_state.pos[..., 2] < 0.1 # 高度过低=坠地
flipped = drone_state.rot_matrix[..., 2, 2] < 0.0 # z 轴朝下=翻覆
too_far = torch.norm(drone_state.pos, dim=-1) > 10.0 # 飞太远=失控
timeout = episode_step >= max_steps
done = crashed | flipped | too_far | timeout
return done
奖励设计的几个关键决策:
- 用
exp(-误差)而非-误差:指数形式让奖励在目标附近平滑饱和(接近目标时奖励增益变缓),避免策略为了最后一点点误差做剧烈动作。这是连续控制 RL 的常用技巧。 - 速度正则不能少(反例警示):如果只奖励位置跟踪、不惩罚速度,策略可能学会"高速反复冲过目标点"——每次经过目标都拿位置奖励,但根本没"停下来悬停"。加速度正则强制它真正稳定下来。
- 动作平滑项是 sim-to-real 的桥:
r_smooth惩罚动作突变,直接对应 §P0.D.3 的"body rate 不能突变"陷阱。仿真里它可能略微降低位置精度,但换来的是真机上能实际执行的平滑指令——这是用奖励函数为真机的物理约束"买保险"。
运行验证:怎么确认 4096 并行训练在正常工作¶
# 跑 Aerial Gym 的位置控制训练
python aerial_gym/rl_training/train.py task=position_setpoint num_envs=4096
# 监控三件事:
# 1) 终端的 FPS——应达数百万 steps/s 量级(电机指令策略)
# 2) reward 曲线——应单调上升后收敛(用 TensorBoard 看)
# 3) nvidia-smi——GPU 利用率应稳定在 90%+(说明没有 host-device 同步瓶颈)
成功标志:① FPS 达到预期量级(Aerial Gym 电机指令策略可达每秒数百万步);② TensorBoard 里 reward 单调上升并收敛;③ nvidia-smi 显示 GPU 利用率 90%+(若利用率忽高忽低或偏低,多半是训练循环里有 .cpu() 或 Python 端瓶颈)。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:在 GPU 并行训练循环里调 .cpu()/.numpy()/.item() 取中间值调试
错误做法:训练循环里加 print(reward.mean().item()) 或 obs.cpu().numpy() 看数值
现象:训练速度暴跌 10-100 倍,nvidia-smi 显示 GPU 利用率从 95% 跌到个位数
根本原因:.cpu()/.item() 触发 GPU→CPU 传输并强制 CUDA 流同步,打断异步流水线。
4096 环境每步同步一次,开销被放大到不可接受
正确做法:① 用框架自带 logger(TensorBoard)记标量,它内部批量异步处理;
② 必须看张量就 torch.save() 定期存盘,训练后离线分析;
③ 监控指标用低频采样(每 N 步取一次),别每步取
🔧 配置陷阱:改了 sim_dt 却忘了同步改 decimation,控制频率悄悄漂移
错误做法:为了"物理更精确"把 sim_dt 从 0.005 改成 0.0025,decimation 仍是 4
现象:训练曲线和之前完全不同,策略行为异常;上真机后指令频率对不上
根本原因:sim_dt=0.0025 → 物理 400Hz,decimation=4 → 控制变成 100Hz(原来
是 50Hz)。控制频率翻倍,策略的"每步代表多长时间"的隐含假设全变了,
而真机 CTBR 接口若仍是 50Hz,训练和部署的控制频率不一致
正确做法:sim_dt 和 decimation 必须联动——确定目标控制频率(如匹配真机 50Hz),
然后 decimation = f_物理 / 50。改 sim_dt 时重算 decimation 保持控制
频率不变。把 f_控制 当作"不变量"来锁定
🔧 配置陷阱:num_envs 设太大直接 CUDA out of memory
错误做法:8GB 显卡上直接 num_envs=8192 跑带相机的视觉任务
现象:启动时报 CUDA out of memory,或跑几步后 OOM 崩溃
根本原因:每个环境的状态张量 + (若有)渲染缓冲都占显存,8192 环境 × 视觉
观测远超 8GB。视觉任务的单环境显存占用比状态任务高一个量级
正确做法:从小往大试 num_envs(512→1024→2048…),用 nvidia-smi 看显存占用,
留 10-20% 余量。视觉任务的 num_envs 通常比纯状态任务小一个量级
(状态 4096,视觉可能只能 512-1024)。OOM 时优先降 num_envs 或
降相机分辨率
练习¶
-
【配置练习·num_envs 扫描】 用 Aerial Gym 或 OmniDrones 跑同一个悬停任务,把
num_envs依次设为 256/1024/4096,记录每档的:① FPS(steps/s);② nvidia-smi 显存占用;③ 达到相同 reward 阈值的墙钟时间。画出"num_envs vs FPS"和"num_envs vs 墙钟时间"两条曲线。验收标准:你应观察到 FPS 随 num_envs 上升但边际递减、墙钟时间下降。预计 60 分钟。 -
【配置练习·decimation 联动验证】 把 sim_dt 从 0.005 改成 0.0025,故意不改 decimation(保持 4),跑训练,对比改之前的训练曲线。然后正确地把 decimation 改成 8(保持控制频率 50Hz 不变),再跑一次。验收标准:第二次(正确联动)的曲线应和原始曲线接近,第一次(错误)的应明显偏离。用这个实验内化"sim_dt 和 decimation 必须联动"。预计 45 分钟。
-
【管线搭建·加一个观测项】 为悬停任务的观测增加一项"到目标的距离的标量"(在原 22 维基础上加 1 维变 23 维)。修改 obs 拼接代码和 obs_dim 配置,确保网络输入维度同步更新。跑通训练,对比加这一项前后的收敛速度。验收标准:训练能正常跑通不报维度错误。思考:这一项是冗余的(位置误差里已含距离信息)还是有用的?为什么?预计 50 分钟。
-
【集成测试·奖励项消融】 把奖励函数里的"动作平滑项
r_smooth"权重设为 0,重新训练,对比:① 训练曲线(位置精度可能略升);② 训出来的动作序列的抖动程度(画 action 随时间的曲线)。验收标准:去掉平滑项后,动作曲线明显更抖。思考:为什么"仿真里更优"的策略(无平滑)反而更难上真机?预计 50 分钟。
§P0.D.7 sim-to-real gap 六大来源——仿真到真机为什么会崩、怎么救 ⭐⭐⭐⭐¶
模块功能与在管线中的位置¶
这一节是全章的"终极问题"。前面六节教你把各种仿真台子搭起来、把训练跑通——但搭仿真的**终极目的**,是让仿真里学到/验证过的东西能搬到真机上不崩。这一节系统拆解"仿真和真机为什么不一样"(sim-to-real gap)的六大来源,并对每一类给出缩小手段。它是 §P0.D.2 不可能三角"按任务敏感维度配资源"的落地——只有知道 gap 来自哪里,你才知道该在哪个维度上加保真度、加随机化。
动机:那个最痛的瞬间——"仿真完美,真机炸机"¶
几乎每个做无人机的人都经历过这个瞬间:策略/控制器在仿真里跑了上千次,每次都完美悬停、精准跟踪。满怀信心上真机,结果——要么剧烈震荡,要么直接发散摔机。这个落差就是 sim-to-real gap。
问题不在于"gap 存在"(gap 永远存在),而在于**你能不能预判 gap 来自哪里、提前在仿真里把它"喂"给策略**。盲目试错代价极高(摔一架真机几千到几万块,还可能伤人)。系统化地拆解 gap 来源,是把"撞运气"变成"工程可控"的唯一办法。
本质洞察:sim-to-real gap 的本质,不是"仿真不够逼真"这一个笼统的问题,而是六个可分别量化、分别治理的具体差异。新手把它当成一团迷雾("仿真和真机就是不一样"),专家把它拆成六个抽屉,每个抽屉有对应的诊断方法和治理手段。本节的全部价值,就是把这团迷雾拆成六个抽屉——拆开了,gap 就从"玄学"变成"工程"。
反面:如果不系统拆解 gap,只靠"调到仿真里更好"会怎样¶
反事实:假设你不理会 gap 来源,只一味地"让策略在仿真里表现更好"会怎样? → 你会把策略过拟合到仿真的具体参数(特定质量、特定电机模型、特定噪声)。 → 仿真里的"更好",恰恰意味着"更依赖仿真的具体设定"——也就是 gap 更大。 → 这是个反直觉的陷阱:仿真性能和 sim-to-real 鲁棒性常常是负相关的。一个在仿真里"刚刚好"但鲁棒的策略,比一个仿真里"完美"但脆弱的策略更能上真机。 结论:sim-to-real 的目标不是"最大化仿真性能",而是"最大化跨域(仿真→真机)的鲁棒性"。这正是域随机化的哲学(见下文),也是为什么 §P0.D.2 反复强调"按任务匹配保真度"而非"无脑堆保真度"。
理论:sim-to-real gap 的六大来源(系统性分类)¶
系统性分类(穷举式维度,而非随意举例):sim-to-real gap 按"差异发生在控制链的哪一环"穷举为六类。这个分类法的价值是给你一个"思考框架"——遇到新的炸机问题,逐个抽屉排查,而非随意猜。
仿真 ─────────────────────────────────────────────→ 真机
│ │
① 动力学参数 gap ② 气动残差 gap │
(质量/惯量/重心 (高速阻力/下洗/ │
与真机不符) 地效,仿真没建或建错) │
│ │
③ 电机/执行器 gap ④ 传感器 gap │
(电机时间常数/ (IMU/相机噪声、偏置、 │
ESC 延迟/桨叶气动) 畸变,仿真过于干净) │
│ │
⑤ 延迟 gap ⑥ 接触/外扰 gap │
(通信/计算/控制 (桨叶触碰、风扰、 │
回路延迟) 地面交互,仿真理想化) │
| 来源 | 仿真里的样子 | 真机里的样子 | 缩小手段 |
|---|---|---|---|
| ① 动力学参数 | 用标称质量/惯量/重心 | 装了电池/相机后实际值有偏差 | 系统辨识(SysID) 测准真机参数 + 域随机化 覆盖偏差 |
| ② 气动残差 | 默认无气动或简化阻力 | 高速下阻力/下洗/地效显著 | BEM/残差模型 建准 + 用真飞数据**拟合残差** |
| ③ 电机/执行器 | 理想推力或简单一阶模型 | ~25ms 时间常数 + ESC 延迟 + blade flapping | **CTBR 接口**外包给固件(§P0.D.3)+ 域随机化电机参数 |
| ④ 传感器 | 干净的真值或弱噪声 | IMU 偏置漂移、相机畸变/噪声/曝光 | 传感器噪声建模 + **域随机化**噪声/偏置/畸变 |
| ⑤ 延迟 | 零延迟或固定延迟 | 通信+计算+控制回路的可变延迟 | 延迟随机化 + 观测里加历史帧 + 训练时注入延迟 |
| ⑥ 接触/外扰 | 理想化或无 | 风扰、桨叶触碰、地面效应 | 外力随机化 + 训练时加随机推力扰动 |
逐类深入:每个抽屉怎么诊断、怎么治¶
① 动力学参数 gap——最基础、最该先治的¶
诊断:真机起飞后,对比"仿真悬停油门"和"真机悬停油门"——如果真机油门明显更高,说明真机更重(你的仿真质量偏小)。治理:先做系统辨识(SysID)把真机的质量、惯量、推力系数测准,让仿真的标称值对齐真机;再用域随机化(如质量 \(\pm\)20%)覆盖残余偏差和电池/载荷变化。
本质洞察(SysID vs 域随机化的分工):这两个手段不是二选一,而是配合——SysID 负责"对准中心",域随机化负责"覆盖范围"。SysID 把仿真的标称参数移到真机参数附近(减小系统性偏差),域随机化在这个中心周围撒一片分布(覆盖随机性偏差和你测不准的部分)。只做 SysID 不做 DR,策略对"测量误差"和"参数漂移"脆弱;只做 DR 不做 SysID,随机化范围要开得很大才能包住真机,导致策略保守、性能差。先 SysID 对准、再 DR 覆盖,是参数 gap 治理的黄金组合。
② 气动残差 gap——飞得越快越致命¶
诊断:低速悬停/慢飞时仿真和真机吻合,但一加速(>5 m/s)真机就"跟不上仿真预测"——这是气动残差(阻力、下洗、机身气动)在作祟。治理:① 用高保真气动模型(如 BEM 叶素动量理论)替换默认的无气动/简单阻力;② 更实用的是用真飞数据拟合一个"残差模型"——让仿真物理 = 标称模型 + 数据驱动的残差补偿。
跨章桥接(回顾 D8 敏捷飞行):D8 讲过,当四旋翼从 5 m/s 推到 100 km/h,气动残差从"可忽略的二阶噪声"变成"主导的一阶效应"。这正是 §P0.D.2 不可能三角的活例——慢飞任务气动 gap 小,可以用简单物理换并行;竞速任务气动 gap 大,必须上 BEM 高保真物理(即使牺牲并行)。任务的速度决定了气动保真度的必要性,进而决定了你在不可能三角里的取舍点。
③ 电机/执行器 gap——CTBR 已经帮你治了大半¶
诊断:策略输出剧烈/高频的动作,但真机的实际响应"软绵绵"跟不上——电机时间常数和 ESC 延迟把高频指令"滤掉"了。治理:最有效的是**用 CTBR 动作空间**(§P0.D.3)——把电机层的快变量外包给 2-8 kHz 固件 rate loop,策略根本不碰这些难仿的东西。剩余的(rate loop 本身的带宽限制)用电机参数域随机化 + 动作平滑奖励覆盖。
④ 传感器 gap——视觉策略的主战场¶
诊断:状态策略(用位置/速度)通常传感器 gap 小(动捕/GPS 真值较准);视觉策略 gap 大——仿真图像太干净,真机相机有畸变、噪声、曝光变化、运动模糊。治理:视觉域随机化(随机光照、纹理、相机内参/畸变/噪声)+ IMU 噪声建模(偏置漂移、随机游走)。目标是让策略学"结构/形状"而非"特定外观"(呼应 §P0.D.5 陷阱)。
⑤ 延迟 gap——最隐蔽、最容易被忽略¶
诊断:仿真里策略稳定,真机里出现"延迟诱发的震荡"——指令到执行有几十毫秒延迟,策略基于"过时的观测"做决策,形成正反馈震荡。治理:① 训练时注入随机延迟(observation delay + action delay);② 观测里加入历史帧,让策略"看到"延迟的影响并学会补偿;③ 用 CTBR 等高层接口减少策略对精确时序的依赖。
多视角理解(延迟为什么这么阴险): - 角度1(控制理论视角):延迟在频域里是相位滞后,吃掉系统的相位裕度,把原本稳定的闭环推向震荡甚至发散。这是经典控制的老问题,在 RL 里换了张皮但本质一样。 - 角度2(RL 视角):延迟破坏了"马尔可夫性"——当前观测不再充分描述系统状态(真实状态还包含"在途的指令")。策略基于不完整状态决策,自然次优甚至失稳。 两个角度指向同一个治理思路:把延迟"显式化"——要么在观测里加历史帧(恢复马尔可夫性),要么训练时注入延迟(让策略见过它)。不要假装延迟不存在。
⑥ 接触/外扰 gap——长尾但真实¶
诊断:大多数时候吻合,但偶发的外部扰动(一阵风、桨叶蹭到障碍、贴地飞行的地面效应)让真机偏离仿真预测。治理:训练时加随机外力推扰(random force perturbation)、随机风场;贴地任务专门建地面效应模型。
理论:域随机化——贯穿六类 gap 的总线手段¶
注意上面六类 gap 的治理手段里,"域随机化(DR)"反复出现——它是 sim-to-real 的总线手段。核心做法是每个(批)回合随机扰动物理参数:
def apply_domain_randomization(env, cfg):
"""每个回合 reset 时随机化物理参数——sim-to-real 的核心手段"""
n = env.num_envs
# ① 动力学参数
env.mass = sample_uniform(cfg.mass_range, n) # 质量 ±20%
env.inertia = sample_uniform(cfg.inertia_range, n) # 惯量 ±20%
env.com_offset = sample_uniform(cfg.com_range, n) # 重心偏移
# ③ 电机参数
env.motor_tau = sample_uniform(cfg.motor_tau_range, n) # 电机时间常数
env.thrust_coeff = sample_uniform(cfg.thrust_range, n) # 推力系数
# ④ 传感器
env.imu_bias = sample_normal(cfg.imu_bias_std, n) # IMU 偏置
env.obs_noise_std = cfg.obs_noise_std # 观测噪声
# ⑤ 延迟
env.action_delay = sample_randint(cfg.delay_range, n) # 动作延迟步数
# ⑥ 外扰
env.ext_force = sample_uniform(cfg.force_range, n) # 随机外力
本质洞察(域随机化的真正含义,呼应前置自测第 4 题):域随机化**不是让仿真更逼真,而是让策略更鲁棒**。真实世界只是随机化覆盖的参数分布中的**一个点**。这个表述的区别极其重要: - "让仿真逼真"是无止境的——你永远能找到更精细的建模,且不知道够不够。 - "让策略鲁棒"是可达且可量化的——只要随机化范围**包住**真实值,策略对范围内任意参数都鲁棒,自然包括真机这个点。
换句话说,域随机化把"精确建模真机"(难、不可达)这个问题,转化成"让策略对一片参数分布都能工作"(可达、可验证)。这是 sim-to-real 范式的根本智慧——不追求复制真实,而追求覆盖真实。
对比性思维("不是 X 而是 Y"): - 域随机化**不是**为了模拟某个具体的真机,**而是**为了让策略对"任何落在分布内的真机"都能用。 - 缩小 sim-to-real gap **不是**靠"无限提高仿真保真度",**而是**靠"在敏感维度上系统辨识对准 + 域随机化覆盖"。 - 一个好的 sim-to-real 策略**不是**仿真性能最高的那个,**而是**在仿真性能和跨域鲁棒性之间取得平衡的那个。
理论-工程桥接:sim2sim 验证——上真机前的最后一道软件关¶
在烧钱上真机前,有一个低成本的中间验证:sim2sim——把在仿真器 A(训练用,如 Aerial Gym)训好的策略,拿到仿真器 B(验证用,如 gym-pybullet-drones 或 Flightmare)里跑。
本质洞察(sim2sim 为什么有效):sim2sim 利用了一个朴素但强大的逻辑——如果你的策略连"另一个仿真器"都迁移不过去,它绝无可能迁移到真机。仿真器 A 和 B 的物理模型有差异(不同的积分器、不同的气动假设),这个差异是真机 gap 的"廉价代用品"。策略能跨过 A→B 这道坎,是它能跨过 A→真机的必要条件(虽不充分)。sim2sim 让你用"零成本、零风险"的方式提前筛掉那些过拟合到训练仿真器的脆弱策略,把宝贵的真机时间留给真正有希望的策略。
完整的 sim-to-real 流水线因此是:
仿真器 A 训练(Aerial Gym,4096 并行,含域随机化)
↓ 导出 .pt / .onnx
仿真器 B 验证(gym-pybullet-drones / Flightmare,sim2sim) ← 廉价软件关
↓ 通过
HITL 验证(真 Pixhawk + Gazebo) ← 测硬件算力约束
↓ 通过
真机小范围测试(限定动作范围、有安全网/Guard) ← 见 §P0.D.8
↓ 逐步放开动作范围
真机完整部署
⚠️ 常见陷阱¶
💡 概念误区:认为"仿真中跑通 = 真机能用"
新手想法:"仿真里策略表现很好,直接部署到真机应该没问题"
实际上:sim-to-real gap 普遍存在且来自六个独立来源——动力学参数、气动残差、
电机/执行器、传感器、延迟、接触外扰。仿真中"完美"的策略可能过拟合到
仿真的具体参数,在真机上完全失效
正确理解:仿真是快速迭代工具,不是真实环境的替代。部署前必须:① 系统辨识对准
关键参数;② 域随机化覆盖偏差;③ sim2sim 验证;④ 真机小范围测试 +
逐步放开动作范围。每一步都是为了系统性地填那六个抽屉里的 gap
💡 概念误区:以为域随机化范围开得越大越好("覆盖越宽越鲁棒")
新手想法:"既然要覆盖真机,那随机化范围开到最大不就最保险了?"
实际上:随机化范围过大会让任务"无解"或策略过度保守——如果质量随机化范围是
±90%,策略要同时应对极轻和极重两种极端,只能学一个"谁都不得罪"的
平庸策略,性能大幅下降,甚至训不收敛
正确理解:随机化范围应"恰好包住真机参数的不确定区间",不多不少。先用 SysID
把不确定区间缩小(对准中心),再用略大于该区间的范围做 DR。范围由
"你对真机参数的不确定度"决定,而非越大越好。鲁棒性和性能之间有权衡
🔧 配置陷阱:训练时不注入延迟,部署后被延迟诱发震荡
错误做法:仿真训练时假设零延迟(观测立即可得、动作立即生效)
现象:仿真完美,真机上出现持续的高频震荡,越快的动作震得越厉害
根本原因:真机有几十毫秒的观测+控制延迟。零延迟训练的策略基于"即时观测"
做激进决策,到真机上这些决策基于"过时观测",形成延迟正反馈震荡
正确做法:训练时注入随机延迟(observation/action delay),并在观测里加历史帧
让策略感知并补偿延迟。延迟是六大 gap 里最隐蔽的,务必显式建模——
它不像质量偏差那样直观,但杀伤力极大
练习¶
-
【分析练习·gap 归因】 给定三个"仿真完美、真机失败"的症状,判断各自最可能属于六大 gap 的哪一类,并给出诊断方法:① 真机悬停油门比仿真高 15%;② 低速正常但一加速到 8 m/s 就掉高度;③ 策略在真机上以约 5 Hz 持续震荡。每个给出 gap 归类 + 诊断 + 治理手段。验收标准:归类正确且诊断方法可操作。预计 30 分钟。
-
【配置练习·域随机化范围扫描】 在你的悬停 RL 任务里,把质量域随机化范围依次设为 ±5%、±20%、±50%、±90%,各训一个策略,记录:① 训练能否收敛;② 最终仿真位置精度。验收标准:你应观察到范围过大(±90%)时性能明显下降甚至难收敛。用这个实验内化"随机化范围有最优区间,非越大越好"。预计 60 分钟。
-
【综合思考题·sim2sim 验证设计】 你用 Aerial Gym 训了一个悬停策略,想在上真机前用 gym-pybullet-drones 做 sim2sim 验证。设计这个验证流程:① 怎么把策略从 Aerial Gym 导出并加载到 gym-pybullet-drones?(提示:.onnx / .pt + 接口适配)② 两个仿真器的观测/动作定义可能不一致,怎么对齐?③ 什么样的 sim2sim 结果说明"可以上真机了",什么样说明"还得回去改"?预计 40 分钟。
-
【跨章综合题·端到端 sim-to-real 链路】 综合 §P0.D.3(CTBR)、§P0.D.6(并行训练)、§P0.D.7(gap + DR)三节,画出一条完整的"从零训练一个能上真机的悬停策略"的流水线图,标注每一环:用什么仿真器、什么动作空间、加哪些域随机化、怎么 sim2sim 验证、怎么逐步上真机。这是把本章 RL 相关内容串成网络的综合练习。预计 50 分钟。
§P0.D.8 Bridge/Adapter——一份代码跑仿真和真机 ⭐⭐⭐¶
模块功能与在管线中的位置¶
前面七节解决了"怎么搭各种仿真台子"和"怎么缩小 sim-to-real gap"。这一节解决一个工程组织问题:怎么让你写的算法代码,不重写就能在不同仿真器和真机之间切换? 答案是 Bridge/Adapter 设计模式——它把"当前跑在哪(仿真器 A / 仿真器 B / 真机)"收敛成一个可替换的底层适配器,让上层算法对此无感。这是 §P0.D.2"三层解耦反过来约束你的代码组织"的具体落地,也是 D8 Agilicious 架构的核心思想抽象。
动机:从"为每个目标写一套代码"的噩梦说起¶
设想你不用任何抽象,直接为每个仿真器写代码。你的控制器演进路径会是这样:
第 1 周:在 gym-pybullet-drones 里写控制器 → 调用 PyBullet 专有 API 读状态
第 3 周:要做视觉验证,换 Flightmare → 控制器全改,因为状态接口完全不同
第 6 周:要做 PX4 在环,换 Gazebo+PX4 → 又全改,状态走 MAVROS 话题
第 9 周:上真机 → 再改一遍,状态走真机 MAVLink
四套几乎相同的控制逻辑,分散在四份代码里。改一个控制 bug 要改四处,漏改一处就在某个目标上出错。这是没有抽象的必然结局——算法逻辑被"运行目标"的细节污染、绑死。
本质洞察:这个噩梦的根源,是把两件本该分离的事**焊在了一起**——"控制逻辑是什么"(算法)和"状态从哪来、指令往哪去"(目标接口)。Bridge/Adapter 模式的全部价值,就是把这两件事重新分开:算法只依赖一个抽象的状态/指令接口,"当前目标是谁"由一个可插拔的 Bridge 实现。一份算法代码,N 个 Bridge,零重写切换。
反面:如果算法直接耦合具体目标会怎样¶
反事实:假设你的 RL 策略推理代码里直接写死了
mavros.publish(AttitudeTarget(...))会怎样? → 想在 gym-pybullet-drones 里快速验证这个策略:不行,gym-pybullet-drones 不接 MAVROS,得改成它的 step() 接口。 → 想在 Flightmare 里测视觉:又得改成 Flightmare 的 CTBR 接口。 → 每换一个验证环境就改一次推理代码,改的过程中很容易引入"仿真版和真机版行为不一致"的 bug——而这种 bug 极难发现,因为你以为跑的是"同一个策略"。 结论:算法直接耦合目标,不仅是"麻烦",更是 sim-to-real 的隐性 gap 来源——"仿真用的代码"和"真机用的代码"不是同一份,它们之间的任何细微差异都是新的 gap。Bridge 模式从根上消除这类 gap:物理上保证仿真和真机跑的是同一份算法二进制。
理论:Agilicious 的 Pilot→Bridge→Guard 三层架构(回顾 D8)¶
D8 详细讲过 Agilicious(Science Robotics 2022)的架构,这里从"仿真集成"的角度重新审视它——它是 Bridge 模式在无人机领域的标杆实现:
┌─ Guard(独立安全管线,mocap/LIO 估计器)──────────────────┐
│ ├─ 若 Pilot 违反约束(位姿偏差/围栏/超时)→ Guard 接管 │
│ └─ Guard 独立运行,永远不依赖 Pilot 的估计器 │
├─ Pilot(用户实验管线)───────────────────────────────────┤
│ ├─ Pipeline = {Estimator, Sampler, Reference, │
│ │ Controller, Bridge} │
│ ├─ Controller: MPC / DFBC / INDI / RL / 自定义 │
│ │ └─ 通过 Factory(YAML) 运行时切换 │
│ └─ Estimator: VIO / mocap / LIO │
├─ Bridge(硬件/仿真适配层)★ 本节焦点 ────────────────────┤
│ ├─ RotorS / Flightmare / Agisim / 真机 UART │
│ └─ 同一份 C++17 二进制,不重编即切换目标 │
└──────────────────────────────────────────────────────────┘
Bridge 层就是这里的 Adapter——它定义一个统一接口("接收 CTBR 指令、返回状态"),然后为每个目标(RotorS / Flightmare / Agisim 仿真器 / 真机 UART)写一个实现。Pilot 里的控制器只认 Bridge 接口,通过 YAML 配置选用哪个 Bridge 实现,C++17 二进制一次编译,切换目标不重编。
本质洞察(Guard 为什么是独立管线):Guard 的存在揭示了 sim-to-real 的一个深层智慧——真机实验的安全网不能依赖被测对象本身。你的实验性 MPC/RL 控制器(Pilot)可能发散,如果安全检查也用 Pilot 的估计器,那 Pilot 崩了安全检查也崩。所以 Guard 用独立的估计器(mocap/LIO)独立运行,Pilot 一违反约束(位姿偏差/越界/超时),Guard 立即接管执行紧急悬停。这是 70 km/h 飞行时唯一的安全网,也是为什么 30+ 篇后续论文基于 Agilicious 做实验而没摧毁硬件。仿真里你可以随便崩(reset 即可),真机里 Guard 是你和炸机之间的最后一道墙。
代码走读:一个最小的 Bridge 抽象(教学示意)¶
下面用 Python 示意 Bridge 模式的骨架(真实的 Agilicious 是 C++17,这里抽象其精神):
from abc import ABC, abstractmethod
class DroneState:
"""统一的状态结构——算法只认这个,不认任何仿真器/真机的专有格式"""
def __init__(self, pos, vel, rot_matrix, angvel):
self.pos = pos # 世界系位置
self.vel = vel # 世界系线速度
self.rot_matrix = rot_matrix # 姿态(旋转矩阵,无奇异)
self.angvel = angvel # 机体系角速度
class CTBRCommand:
"""统一的指令结构——CTBR(见 §P0.D.3)"""
def __init__(self, thrust, body_rate):
self.thrust = thrust # 质量归一化集体推力
self.body_rate = body_rate # 机体三轴角速度 [ωx, ωy, ωz]
class Bridge(ABC):
"""适配层抽象基类:所有目标(仿真器/真机)实现同一接口"""
@abstractmethod
def get_state(self) -> DroneState: ... # 拿统一状态
@abstractmethod
def send_command(self, cmd: CTBRCommand): ... # 发统一指令
# ---- 具体 Bridge 实现:每个目标一个 ----
class PyBulletBridge(Bridge):
def __init__(self, aviary): self.aviary = aviary
def get_state(self):
raw = self.aviary.get_state() # PyBullet 专有格式
return DroneState(raw[:3], raw[3:6], quat_to_mat(raw[6:10]), raw[10:13])
def send_command(self, cmd):
self.aviary.step(ctbr_to_rpm(cmd)) # 翻译成 PyBullet 要的 RPM
class MAVROSBridge(Bridge):
"""真机/PX4 SITL 适配——和 §P0.D.4 的 offboard 一致"""
def __init__(self, mavros): self.mavros = mavros
def get_state(self):
odom = self.mavros.get_odometry() # MAVROS 话题
return DroneState(odom.pos, odom.vel, odom.rot, odom.angvel)
def send_command(self, cmd):
self.mavros.publish_attitude_target( # AttitudeTarget 消息
thrust=cmd.thrust, body_rate=cmd.body_rate)
# ---- 上层算法:完全不知道底下是仿真还是真机 ----
def control_loop(bridge: Bridge, policy):
"""同一份控制循环,跑仿真还是真机只取决于传入哪个 bridge"""
while True:
state = bridge.get_state() # 只认 DroneState
action = policy(state) # 算法逻辑,与目标无关
bridge.send_command(CTBRCommand(action.thrust, action.body_rate))
# 切换目标 = 换一个 bridge 实例,control_loop 和 policy 一字不改:
control_loop(PyBulletBridge(aviary), policy) # 跑仿真
# control_loop(MAVROSBridge(mavros), policy) # 跑真机——同一份 policy!
这段代码的关键设计点:
DroneState/CTBRCommand是"通用货币":算法只和这两个结构打交道。PyBullet 返回的数组、MAVROS 返回的 odometry,都在各自的 Bridge 里翻译成DroneState。算法看不到原始格式差异。control_loop和policy与目标完全解耦:切换仿真/真机,只换Bridge实例,这两个函数一字不改。这就是"一份代码跑仿真和真机"。- CTBR 作为统一指令的妙处(呼应 §P0.D.3):正因为仿真和真机都用 CTBR 接口,
CTBRCommand才能当通用货币。如果仿真用 RPM、真机用 CTBR,Bridge 接口就难以统一——接口一致性(§P0.D.3)是 Bridge 模式能成立的前提。
多视角理解:Bridge 模式对照机械臂的 ros2_control¶
跨领域类比(标注边界):无人机的 Bridge 模式,和机械臂的
ros2_control硬件抽象层(HardwareInterface)是同一个设计思想的两种实现。 像的地方:两者都把"算法逻辑"和"具体硬件/仿真"解耦——机械臂的控制器(如JointTrajectoryController)只认ros2_control的标准接口(读关节状态、写关节指令),底下接的是 Gazebo 仿真还是真实机械臂,由一个可插拔的HardwareInterface实现决定。和无人机的 Bridge 切换仿真/真机一模一样的精神。 不像的地方:① 实现重量不同——ros2_control是重型的 pluginlib 插件体系(工控级、强类型、生命周期管理完备),无人机的 Bridge(如 Agilicious)是轻量的 C++ Factory(嵌入式友好、header-only 倾向)。② 接口语义不同——机械臂接口是关节空间(position/velocity/effort),无人机是 CTBR(推力+角速度)。③ 实时性要求不同——机械臂 1kHz、无人机内环 2-8kHz。 不要把这个类比延伸成"两者可以互换框架"——它们的设计思想相通,但具体实现因领域约束(嵌入式 vs 工控、CTBR vs 关节)而高度不同。这正是本章开头"性质:部分共享"的含义:设计模式共享,具体实现完全不同。
⚠️ 常见陷阱¶
⚠️ 编程陷阱:Bridge 抽象做了,但状态/指令的坐标系/单位约定不统一
错误做法:PyBulletBridge 返回的角速度是世界系,MAVROSBridge 返回的是机体系,
但都塞进同一个 DroneState.angvel 字段不加说明
现象:同一份控制器在仿真里正常,切到真机角速度反馈"方向不对",控制器行为诡异
根本原因:Bridge 统一了"数据结构",但没统一"数据语义"(坐标系、单位、方向约定)。
DroneState 的字段格式一样,含义却不一样,比格式不一样更隐蔽
正确做法:在 DroneState/CTBRCommand 的定义里明确写死语义契约(如"angvel 一律
机体系、rad/s、右手系"),每个 Bridge 实现负责把原始数据转换到这个
统一语义。语义契约比数据结构更重要——这是抽象的真正难点
💡 概念误区:以为"有了 Bridge 抽象,仿真和真机就完全一样了"
新手想法:"既然一份代码跑仿真和真机,那仿真测过的真机肯定也对"
实际上:Bridge 统一的是"软件接口",消除的是"代码不一致"这一类 gap。但它
完全不触及 §P0.D.7 的六大物理 gap——仿真的电机模型、气动、传感器噪声
和真机仍然不同。Bridge 让仿真和真机跑同一份代码,但它们的"物理世界"
依然不同
正确理解:Bridge 解决"代码层一致性",域随机化/SysID 解决"物理层 gap"。两者
正交、缺一不可。有了 Bridge 还是要做 §P0.D.7 的全套 sim-to-real
工作。Bridge 是必要条件,不是充分条件
🔧 配置陷阱:真机实验没有独立安全网(Guard),实验控制器一发散就炸机
错误做法:把实验性的 RL/MPC 控制器直接接上真机,安全检查复用控制器自己的估计器
现象:控制器发散时,依赖同一估计器的安全检查也失效,无人机失控坠毁
根本原因:安全网依赖了"被测对象本身"——Pilot 崩了,挂在 Pilot 上的安全检查
也跟着崩,形同虚设
正确做法:仿照 Agilicious 的 Guard——用独立的估计器(mocap/LIO)、独立运行的
安全管线监控 Pilot,一旦位姿偏差/越界/超时就接管紧急悬停。真机实验的
安全网必须独立于被测对象。仿真里可以省略 Guard(崩了 reset 即可),
真机绝不能省
练习¶
-
【管线搭建·实现一个 Bridge】 基于本节的
Bridge抽象骨架,为 gym-pybullet-drones 实现一个完整的PyBulletBridge(含 CTBR→RPM 的转换)。然后写一个最简单的 P 控制器(只用位置误差),通过control_loop驱动它悬停。验收标准:无人机能悬停在目标点。预计 60 分钟。 -
【对比练习·语义契约】 列举
DroneState的每个字段(pos/vel/rot/angvel)可能存在的"语义歧义"(坐标系、单位、方向、原点)。为每个字段写一条明确的语义契约。验收标准:契约足够明确,任何人读了都能写出行为一致的 Bridge 实现。预计 25 分钟。 -
【综合思考题·Bridge vs ros2_control】 用一段话总结:无人机 Bridge 和机械臂 ros2_control 在"设计思想"上的共同点,以及在"具体实现"上的三个关键差异(实现重量、接口语义、实时性)。然后回答:为什么无人机不直接用 ros2_control,而要自己搞一套轻量 Bridge?(提示:嵌入式算力、C++ 版本、控制频率。)预计 30 分钟。
§P0.D.9 选型决策树与完整工作流——给项目选对工具 ⭐⭐¶
模块功能与在管线中的位置¶
这是全章的收口。前面八节给了全景(§P0.D.1)、取舍逻辑(§P0.D.2)、各路线的搭建(§P0.D.4-6)、sim-to-real(§P0.D.7)和代码组织(§P0.D.8)。这一节把它们浓缩成一个**可直接执行的选型决策树 + 工作流**——下次接到无人机项目,照着走就能选对仿真器、规划好从仿真到真机的完整路径。
动机:为什么需要一棵决策树¶
选型的难点不在"不知道有哪些工具"(§P0.D.1 的表已列全),而在"面对具体任务时,那么多维度怎么权衡"。决策树的价值是把"权衡"变成"顺序提问"——按固定顺序问几个关键问题,每个回答砍掉一批选项,最后收敛到 1-2 个候选。这比每次都把六个仿真器在七个维度上两两比较高效得多。
理论:无人机仿真器选型决策树¶
开始:你的核心任务是什么?
│
├─【飞控在环验证】(验证 PX4/ArduPilot 上的控制器/任务逻辑)
│ │
│ ├─ 要光真渲染(视觉感知也要验证)?
│ │ ├─ 是 → Pegasus(Isaac Sim):PX4 + 光真 PhysX
│ │ └─ 否 → Gazebo + PX4 SITL:物理保真闭环(§P0.D.4)★ 最常用
│ └─ 要复现一篇基于 RotorS 的老论文? → RotorS
│
├─【强化学习训练】(训练 RL 策略)
│ │
│ ├─ 状态策略(观测=位置/速度/姿态,不看图像)?
│ │ ├─ 要极致并行(百万级)/几何控制 → Aerial Gym(§P0.D.6)★
│ │ └─ 要 Isaac Lab 生态/多机/基准 → OmniDrones
│ │
│ └─ 视觉策略(观测含图像)?
│ ├─ 只用深度图(避障)→ Aerial Gym(GPU 光线投射深度)★
│ ├─ 要光真 RGB、合成场景够用 → OmniDrones / Flightmare
│ └─ 要部署到特定真实场景、极小渲染 gap → 3DGS(FiGS 系)
│
├─【入门 / 教学 / 快速原型】(学习、Crazyflie 原型、多机教学)
│ └─ gym-pybullet-drones:CPU 即可、多模式物理、教学友好(Quick Start)★
│
└─【VIO / 视觉竞速 / 人机交互研究】
└─ Flightmare:Unity 光真 + 多模态传感器 + VR(§P0.D.5)
怎么用这棵树:从顶端"核心任务"开始,每个分叉问一个 yes/no 或选项问题,沿着匹配的分支往下走,叶子节点就是推荐工具(★ 标的是该分支最常用的)。注意一个项目可能走多个分支——比如"训 RL 策略 + 上 PX4 真机",先走【RL 训练】选 Aerial Gym 训练,再走【飞控在环】用 Gazebo+PX4 做部署前验证。树不是"只能选一个",而是"每个子任务选一个"。
理论:从仿真到真机的完整工作流(七步)¶
选好仿真器后,下面是把一个算法从零做到真机的标准工作流,串起本章所有知识点:
| 步骤 | 做什么 | 用什么(本章对应节) | 验收标志 |
|---|---|---|---|
| 1 选型 | 按决策树定主训练仿真器 + 验证仿真器 | §P0.D.1/9 | 选定 1 个训练器 + 1 个验证器 |
| 2 接口统一 | 确定动作空间(首选 CTBR)+ 搭 Bridge 抽象 | §P0.D.3/8 | 算法只依赖统一 State/Command |
| 3 环境搭建 | 配训练环境(num_envs/dt/decimation/obs/reward) | §P0.D.6 | Quick Start 跑通最小训练 |
| 4 域随机化 | 配 DR(六类 gap 对应的随机化项) | §P0.D.7 | 训练含质量/电机/延迟/外扰随机化 |
| 5 训练 | 4096 并行 PPO,监控 FPS/reward/GPU 利用率 | §P0.D.6 | reward 收敛、GPU 利用率 90%+ |
| 6 sim2sim 验证 | 导出策略到验证仿真器跑 | §P0.D.7 | 策略在第二个仿真器也能工作 |
| 7 真机部署 | HITL → 真机小范围(带 Guard)→ 逐步放开 | §P0.D.4/7/8 | 真机稳定,Guard 未触发 |
多视角理解:选型的两种心态¶
双重解读:面对选型,有两种心态,都对但适用场景不同: - 心态1(任务驱动,本节决策树):先想清楚任务要什么,再去找匹配的工具。适合**有明确目标**的工程项目——你知道要做视觉避障 RL,决策树直接带你到 Aerial Gym。 - 心态2(能力驱动,探索期):先熟悉手头工具的能力边界,再看能做什么。适合**探索期**——你刚入门,先用 gym-pybullet-drones 把 RL 全流程跑通,建立直觉,再根据"还差什么能力"去升级工具(如发现要并行就上 Aerial Gym)。
成熟的做法是两者结合:探索期用心态2 建立工具直觉(先把 Quick Start 跑通),项目期用心态1 精准选型(用决策树)。本章的结构正是这样安排的——前面让你建立全景和直觉,这里给你精准选型的决策树。
配置详解:选型时容易忽略的"隐性成本"¶
决策树看的是"能力匹配",但实际选型还要算几笔"隐性账":
| 隐性成本 | 说明 | 怎么权衡 |
|---|---|---|
| 硬件门槛 | Isaac 系要 NVIDIA RTX + 大显存,不是人人有 | 没好显卡 → 入门先用 gym-pybullet-drones(CPU) |
| 安装复杂度 | Isaac Gym 要官网注册下载、Flightmare 要装 Unity | 评估团队能投入的搭环境时间 |
| 维护状态 | AirSim 已归档、Flightmare 停更(2021) | 新项目优先选活跃维护的(Aerial Gym/OmniDrones/Pegasus) |
| 社区与文档 | 用的人多 → 遇到坑好找答案 | 同等能力下,选社区活跃的 |
| 生态衔接 | 已在用 Isaac Lab 的足式项目 → OmniDrones 无缝 | 复用已有技术栈,减少学习成本 |
本质洞察(选型的终极原则):把能力匹配(决策树)和隐性成本(上表)合起来,选型的终极原则是——选"能完成任务的工具里,团队总成本最低的那个",而非"能力最强的那个"。一个能力稍弱但你团队已经熟悉、有好显卡、社区活跃的工具,往往比一个能力顶尖但要从零搭环境、显卡不够、文档稀缺的工具,能让项目跑得更快。这呼应了 §P0.D.1 的陷阱"星标数 ≠ 适配度"——最适合的工具,是任务能力和团队现实的交集,不是某个单一维度的最优。
⚠️ 常见陷阱¶
💡 概念误区:决策树走到一个叶子就认定"只能用这一个仿真器"
新手想法:"决策树带我到了 Aerial Gym,那我整个项目就只用它"
实际上:一个完整项目通常跨多个子任务,每个子任务可能走不同分支。典型的"训练
+部署"项目要同时用训练仿真器(Aerial Gym)和验证/部署仿真器(gym-
pybullet-drones 做 sim2sim、Gazebo+PX4 做飞控在环)
正确理解:决策树是"为每个子任务选工具",不是"为整个项目选一个工具"。专业团队
常态是手里 2-3 个仿真器按子任务切换——这正是 §P0.D.8 的 Bridge 抽象
让多仿真器切换变得低成本的原因
🔧 配置陷阱:选了 Isaac 系仿真器才发现硬件不达标,项目卡死
错误做法:看决策树推荐 Aerial Gym 就直接上,没先确认有没有 NVIDIA RTX 显卡
现象:装到一半发现没合适的 GPU,或显存太小跑不了像样的 num_envs,项目停滞
根本原因:只看了"能力匹配",没算"硬件门槛"这笔隐性账。Isaac 系强依赖 NVIDIA
GPU,这是硬约束
正确做法:选型时先过一遍隐性成本表,尤其硬件门槛。没有合适 GPU 时,要么先用
CPU 路线(gym-pybullet-drones)做原型、要么租云 GPU、要么调整方案。
硬件可行性应在选型第一步就确认,而非搭到一半才发现
练习¶
-
【选型练习·走决策树】 对下面四个真实项目,分别走一遍决策树,写出推荐的仿真器(可能不止一个)和理由:① 大学课程作业,教学生 RL 控制无人机悬停,机房只有普通 PC;② 实验室研究室内深度避障 RL,有 RTX 4090;③ 公司要在 PX4 上验证一个新的航点任务逻辑;④ 复现一篇 2017 年基于 Gazebo 的视觉伺服论文。验收标准:每个走到正确叶子且理由合理。预计 30 分钟。
-
【综合练习·完整工作流】 为"训练一个能上真机的室内悬停策略(有 RTX 显卡,最终上 Crazyflie)"项目,按七步工作流写出完整计划:每一步用什么工具、关键配置、验收标志。这是把全章串起来的综合练习。验收标准:七步齐全、每步对应到本章具体小节。预计 50 分钟。
-
【思考题·隐性成本权衡】 假设两个仿真器 A、B:A 能力略强但要从零搭环境(团队没人用过、要装 Unity)、B 能力够用且团队已熟悉。决策树倾向 A(能力匹配),但你最终选了 B。用"总成本最低"原则论证你的选择,并说明在什么情况下你会反过来选 A。预计 20 分钟。
本章常见误解汇总¶
下表汇总全章出现的核心误解,供快速回顾。每一条都是新手最容易建立的错误心智模型——读完本章后,你应该能对每一条给出"正确理解"的解释。
| # | 误解 | 正确理解 | 对应节 |
|---|---|---|---|
| 1 | 选星标最高/最流行的仿真器准没错 | 选型只看"任务-能力匹配度",星标是次要参考 | §P0.D.1 |
| 2 | 一个仿真器能覆盖入门到 RL 全流程 | 六大仿真器分属三条技术栈,依赖互斥,须分环境 | §P0.D.1 |
| 3 | 渲染质量高的仿真器物理也一定准 | 渲染和物理是解耦的两层,正交评估 | §P0.D.1/2 |
| 4 | 提高仿真保真度总是好的、越高越好 | 保真度按任务敏感维度匹配,盲目堆是"伪精确" | §P0.D.2 |
| 5 | 动作空间越底层(越接近电机)越精确越好 | sim-to-real 要鲁棒性不要仿真内精确性,CTBR 故意不底层 | §P0.D.3 |
| 6 | SITL 跑通 = 真机一定能飞 | SITL 验证软件层,物理 gap(六大来源)覆盖不到 | §P0.D.4/7 |
| 7 | 渲染保真路线必须放弃物理保真 | 渲染和物理正交,可选"两者都要"的工具(牺牲并行) | §P0.D.5 |
| 8 | 3DGS 重建的场景可直接做物理碰撞仿真 | 3DGS 是渲染表示,不含碰撞几何,要另配物理 | §P0.D.5 |
| 9 | 仿真中跑通 = 真机能用 | gap 来自六个独立来源,须 SysID+DR+sim2sim+渐进部署 | §P0.D.7 |
| 10 | 域随机化范围越大越鲁棒 | 范围过大致策略保守/不收敛,应"恰好包住"不确定区间 | §P0.D.7 |
| 11 | 有了 Bridge 抽象仿真和真机就完全一样 | Bridge 解决代码一致性,物理 gap 仍需 DR/SysID | §P0.D.8 |
| 12 | 决策树走到一个叶子就只用那一个仿真器 | 决策树是"为每个子任务选工具",项目常用多个 | §P0.D.9 |
本章小结¶
本章不教任何控制或规划算法,而是教"怎么搭好让算法跑起来的仿真台子,并让仿真里的东西能搬到真机"。一句话串起全章:仿真器是三层解耦(物理/渲染/控制接口)的,这三层在 GPU 资源上构成不可能三角,所以没有最好只有最匹配的仿真器;选对工具、用对动作空间(CTBR)、系统治理 sim-to-real gap 的六大来源、用 Bridge 抽象统一代码——这是从仿真走到真机不崩的完整方法论。
术语速查表¶
| 术语 | 中英对照 | 定义 | 首见 |
|---|---|---|---|
| SITL | 软件在环(Software-In-The-Loop) | 飞控固件运行在 PC 上、与仿真器闭环,验证软件逻辑 | §P0.D.4 |
| HITL | 硬件在环(Hardware-In-The-Loop) | 真机固件跑在真 Pixhawk 上、吃仿真传感器,验证硬件约束 | §P0.D.4 |
| CTBR | 集体推力+机体角速度(Collective Thrust & Body Rates) | 4D 中层动作空间(归一化推力 + 三轴角速度),sim-to-real 最鲁棒 | §P0.D.3 |
| SRT | 单旋翼推力(Single-Rotor Thrust) | 4D 底层动作空间(每个桨的推力),sim-to-real 难 | §P0.D.3 |
| RPM | 转速指令(Revolutions Per Minute) | 直接给电机目标转速,最底层动作空间 | §P0.D.3 |
| MAVLink | 微型飞行器通信链路协议 | 无人机领域事实标准的轻量二进制消息协议 | §P0.D.4 |
| offboard | 板外控制模式 | PX4 允许外部程序通过 setpoint 流接管控制的模式 | §P0.D.4 |
| lockstep | 锁步模式 | 仿真与飞控严格同步推进,保证确定性可复现 | §P0.D.4 |
| BEM | 叶素动量理论(Blade Element Momentum) | 高保真气动模型,用于高速飞行的气动残差建模 | §P0.D.5 |
| 3DGS | 三维高斯泼溅(3D Gaussian Splatting) | 从真实照片重建可渲染场景孪生的新型渲染表示 | §P0.D.5 |
num_envs |
并行环境数 | GPU 一次步进的独立环境数量,决定采样吞吐与显存 | §P0.D.6 |
decimation |
控制降采样因子 | 物理步与控制步的比值,解耦物理精度与决策频率 | §P0.D.6 |
| asymmetric actor-critic | 非对称演员-评论家 | critic 可用比 actor 更多的特权信息(如全局状态)训练 | §P0.D.6 |
| DR | 域随机化(Domain Randomization) | 训练时随机扰动物理参数,让策略对参数分布鲁棒 | §P0.D.7 |
| SysID | 系统辨识(System Identification) | 用真飞数据标定仿真参数,"对准中心" | §P0.D.7 |
| sim2sim | 仿真到仿真验证 | 把策略从训练仿真器迁到验证仿真器,真机 gap 的廉价代用品 | §P0.D.7 |
| Bridge/Adapter | 桥接/适配器模式 | 把"运行目标"收敛成可插拔适配器,一份代码跑仿真和真机 | §P0.D.8 |
| Guard | 守卫(独立安全管线) | 用独立估计器监控实验控制器,越界即接管,真机安全网 | §P0.D.8 |
知识点总表¶
| 编号 | 知识点 | 核心要点 | 对应节 | 难度 |
|---|---|---|---|---|
| K1 | 仿真器四代演进 | G1 Gazebo→G2 光真→G3 GPU 并行→G4 3DGS,每代补上代短板 | §P0.D.1 | ⭐ |
| K2 | 六大仿真器选型矩阵 | 物理/渲染/并行/接口/飞控五维对比,从"最适场景"反查 | §P0.D.1 | ⭐ |
| K3 | 三问法快速定位新仿真器 | 看渲染引擎/并行规模/控制接口三问,给它在地图上定位 | §P0.D.1 | ⭐ |
| K4 | 三层解耦架构 | 物理/渲染/控制接口三层可独立替换,约束你的代码组织 | §P0.D.2 | ⭐⭐ |
| K5 | 不可能三角 | 物理保真/渲染真实/并行规模抢同一份 GPU,最多选两个 | §P0.D.2 | ⭐⭐ |
| K6 | 按需关层 | 状态策略关渲染换速度,由最终部署形态倒推关哪层 | §P0.D.2 | ⭐⭐ |
| K7 | CTBR/SRT/RPM 三接口 | 策略接入控制链的三个高度,决定 sim-to-real 难度 | §P0.D.3 | ⭐⭐⭐ |
| K8 | CTBR 为什么赢 | 把难仿的快变量外包给固件 rate loop,策略只学仿得准的刚体层 | §P0.D.3 | ⭐⭐⭐ |
| K9 | 质量归一化推力 | CTBR 推力用 m/s² 而非牛顿,与质量解耦,换机型不失效 | §P0.D.3 | ⭐⭐⭐ |
| K10 | Gazebo+PX4 SITL 闭环 | 真飞控固件在环,验证 EKF2/commander/故障保护等软件逻辑 | §P0.D.4 | ⭐⭐ |
| K11 | SITL vs HITL | 数据注入点深浅不同:SITL 测软件,HITL 测硬件算力约束 | §P0.D.4 | ⭐⭐ |
| K12 | lockstep 模式 | 仿真按步走非按墙钟走,保证确定性可复现 | §P0.D.4 | ⭐⭐ |
| K13 | offboard 协议约束 | 切 offboard 前须先发 setpoint,频率须 >2Hz 否则触发保护 | §P0.D.4 | ⭐⭐ |
| K14 | Flightmare 双引擎解耦 | ZeroMQ 连接渲染(230Hz)和物理(200kHz),物理简单渲染光真 | §P0.D.5 | ⭐⭐⭐ |
| K15 | 渲染 gap 与视觉过拟合 | 视觉策略主战场在像素不在物理,低真渲染致"仿真捷径" | §P0.D.5 | ⭐⭐⭐ |
| K16 | 3DGS 范式反转 | 从"仿真逼近真实"反转为"从真实生成仿真",渲染 gap 天然小 | §P0.D.5 | ⭐⭐⭐ |
| K17 | GPU 并行采样吞吐 | 全程零拷贝留显存,4096 环境把周级训练压到分钟级 | §P0.D.6 | ⭐⭐⭐ |
| K18 | num_envs 取值 | GPU 显存与并行效率的甜点,与 PPO 批次配置耦合 | §P0.D.6 | ⭐⭐⭐ |
| K19 | sim_dt 与 decimation 联动 | 解耦物理精度与决策频率,改 dt 必须同步改 decimation | §P0.D.6 | ⭐⭐⭐ |
| K20 | 观测用旋转矩阵 | 9D 旋转矩阵无奇异无双覆盖,比欧拉角/四元数对网络友好 | §P0.D.6 | ⭐⭐⭐ |
| K21 | 奖励设计与动作平滑 | exp 形奖励+速度正则+动作平滑项,平滑项是 sim-to-real 桥 | §P0.D.6 | ⭐⭐⭐ |
| K22 | sim-to-real gap 六大来源 | 动力学/气动/电机/传感器/延迟/接触,逐抽屉诊断治理 | §P0.D.7 | ⭐⭐⭐⭐ |
| K23 | SysID + DR 黄金组合 | SysID 对准中心、DR 覆盖范围,缺一不可 | §P0.D.7 | ⭐⭐⭐⭐ |
| K24 | 域随机化的真正含义 | 不是让仿真逼真,而是让策略对参数分布鲁棒,覆盖真实而非复制 | §P0.D.7 | ⭐⭐⭐⭐ |
| K25 | sim2sim 验证 | 跨仿真器迁移是真机 gap 的廉价代用品,筛掉脆弱策略 | §P0.D.7 | ⭐⭐⭐⭐ |
| K26 | Bridge/Adapter 模式 | 算法只依赖统一 State/Command,目标由可插拔 Bridge 实现 | §P0.D.8 | ⭐⭐⭐ |
| K27 | 语义契约 | Bridge 统一数据结构还不够,必须统一坐标系/单位/方向语义 | §P0.D.8 | ⭐⭐⭐ |
| K28 | Guard 独立安全网 | 真机安全网不能依赖被测对象,用独立估计器独立运行 | §P0.D.8 | ⭐⭐⭐ |
| K29 | 选型决策树 | 按核心任务顺序提问,为每个子任务收敛到 1-2 个候选 | §P0.D.9 | ⭐⭐ |
| K30 | 选型隐性成本 | 能力匹配之外算硬件/安装/维护/社区/生态账,选总成本最低 | §P0.D.9 | ⭐⭐ |
累积项目:本章新增模块¶
无人机累积项目总览:本方向的累积项目是"从零搭一个完整的四旋翼自主飞行栈",每章加一个模块。前序章节已完成微分平坦控制(D1/D2)、轨迹生成(D3/D5/D6)、感知规划(D7/D8)、RL 敏捷飞行(D9)。本章(P0.D,作为 Part 0 的工程基座)为整个项目补上**仿真与 sim-to-real 基础设施**——这是所有上层算法的运行底座。
本章新增模块:sim_infra/——统一仿真基础设施
project_drone_stack/
├── ...(前序章节模块:control/, planning/, perception/, rl/)
└── sim_infra/ ← 本章新增
├── bridges/ ← §P0.D.8 Bridge 抽象
│ ├── bridge_base.py ← Bridge ABC + DroneState/CTBRCommand
│ ├── pybullet_bridge.py ← gym-pybullet-drones 适配(入门/sim2sim)
│ ├── mavros_bridge.py ← PX4 SITL/真机适配(§P0.D.4)
│ └── aerialgym_bridge.py ← Aerial Gym 适配(RL 训练)
├── envs/ ← §P0.D.6 RL 环境配置
│ ├── hover_task_config.py ← num_envs/dt/decimation/obs/reward 全配置
│ └── domain_randomization.py ← §P0.D.7 六类 gap 的 DR 配置
├── sim2sim/ ← §P0.D.7 跨仿真器验证
│ └── validate.py ← 加载策略到验证仿真器跑
└── sitl/ ← §P0.D.4 PX4 SITL
└── takeoff_offboard.py ← MAVSDK offboard 起飞悬停脚本
本章项目任务(按工作流逐步搭建):
- 搭 Bridge 抽象(§P0.D.8):实现
bridge_base.py的DroneState/CTBRCommand/BridgeABC,写明语义契约。实现pybullet_bridge.py并用 P 控制器验证悬停。 - 搭 RL 环境配置(§P0.D.6):把悬停任务的全套配置(num_envs/sim_dt/decimation/obs/reward/termination)写成
hover_task_config.py,每个配置项加注释说明取值依据。 - 搭域随机化(§P0.D.7):实现
domain_randomization.py,覆盖六类 gap 对应的随机化项。 - 搭 SITL 脚本(§P0.D.4):跑通
takeoff_offboard.py,让无人机在 PX4 SITL 里起飞悬停降落。 - 搭 sim2sim 验证(§P0.D.7):实现
validate.py,把(D9 章训的或本章训的)策略导出并在pybullet_bridge里跑,验证跨仿真器迁移。
项目里程碑:完成后,你的项目就有了一个"算法无关、目标可切换"的仿真底座——上层的控制器(D1/D2)、RL 策略(D9)都能通过同一套 Bridge 在 gym-pybullet-drones / PX4 SITL / Aerial Gym 之间零重写切换。这是后续所有真机实验的基础设施。
延伸阅读¶
论文(按主题分类,标注难度)¶
仿真器本身: - ⭐⭐⭐ Song et al., Flightmare: A Flexible Quadrotor Simulator, CoRL 2020 — 双引擎解耦架构的奠基论文,必读 - ⭐⭐⭐ Kulkarni et al., Aerial Gym Simulator: A Framework for Highly Parallelized Simulation of Aerial Robots, 2025(arXiv 2503.01471)— GPU 并行 + SE(3) 几何控制 - ⭐⭐⭐ Vivekanandan et al., Pegasus Simulator: An Isaac Sim Framework for Multiple Aerial Vehicles, 2023(arXiv 2307.05263)— Isaac Sim + PX4 集成 - ⭐⭐⭐ Xu et al., OmniDrones: An Efficient and Flexible Platform for RL in Drone Control, RA-L 2024 — Isaac Sim RL 基准
动作空间与 sim-to-real: - ⭐⭐⭐⭐ Kaufmann et al., A Benchmark Comparison of Learned Control Policies for Agile Quadrotor Flight, ICRA 2022 — CTBR vs SRT 的权威基准,理解动作空间选择的必读 - ⭐⭐⭐⭐ What Matters in Learning A Zero-Shot Sim-to-Real RL Policy for Quadrotor Control? 2024(arXiv 2412.11764,SimpleFlight)— sim-to-real 系统性消融研究 - ⭐⭐⭐⭐ Song et al., Reaching the Limit in Autonomous Racing: Optimal Control vs. RL, Science Robotics 2023 — 高速飞行的 sim-to-real
敏捷飞行平台(回顾 D8): - ⭐⭐⭐⭐ Foehn et al., Agilicious: Open-Source and Open-Hardware Agile Quadrotor for Vision-Based Flight, Science Robotics 2022 — Pilot/Bridge/Guard 架构
下一代渲染: - ⭐⭐⭐⭐ Low et al., SOUS VIDE: Cooking Visual Drone Navigation Policies in a Gaussian Splatting Vacuum, RA-L 2025(FiGS)— 3DGS 仿真渲染
仓库与文档¶
- ⭐ gym-pybullet-drones — 入门首选,
gym_pybullet_drones/envs/BaseAviary.py看_physics()步进 - ⭐⭐ PX4 Gazebo Simulation 官方文档 — SITL 搭建权威指南
- ⭐⭐⭐ Flightmare —
flightlib/include/flightlib/envs/vec_env.hpp看VecEnv<T>模板化向量环境 - ⭐⭐⭐ Aerial Gym Simulator — 配套文档详尽,RL 路线主推
- ⭐⭐⭐ OmniDrones —
omni_drones/envs/single/hover.py看 Isaac Lab 风格环境 - ⭐⭐⭐ Pegasus Simulator — Isaac Sim + PX4 集成文档
- ⭐⭐ RotorS — 经典论文复现,理解 Gazebo 插件式仿真
- ⭐⭐ PX4 HITL 文档 — 硬件在环搭建
项目精读清单(呼应骨架)¶
- gym-pybullet-drones:
gym_pybullet_drones/envs/BaseAviary.py— 理解 PyBullet 四旋翼的_physics()步进与多模式物理 - Flightmare:
flightlib/include/flightlib/envs/vec_env.hpp—VecEnv<T>模板化向量化环境 - OmniDrones:
omni_drones/envs/single/hover.py— Isaac Lab 风格环境定义 - Agilicious:
agilib/include/agilib/pilot/pilot.hpp— Pilot/Pipeline/Guard 三层架构的 C++17 实现(回顾 D8)
本章与后续章节的关系¶
| 后续章节 | 关系 | 本章铺垫的知识点 |
|---|---|---|
| D1/D2 微分平坦与几何控制 | 控制器是仿真要"装进去"的东西 | 仿真三层解耦中的"控制接口层";CTBR 与几何控制的关系(K7/K8) |
| D3/D5/D6 轨迹生成 | 生成的轨迹要在仿真里验证 | SITL 闭环验证轨迹跟踪(K10);sim2sim 验证(K25) |
| D7/D8 感知规划与敏捷平台 | 视觉感知依赖渲染保真,平台依赖 Bridge | 渲染保真路线(K14-16);Bridge/Guard 架构(K26-28) |
| D9 RL 敏捷飞行与 sim-to-real | 本章是 D9 的工程基座 | 并行 RL 工厂(K17-21);sim-to-real gap 六大来源(K22-25) |
| D10 集群协同规划 | 多机仿真依赖并行能力 | num_envs 并行(K18);多机仿真器选型(K2) |
本章的定位(Part 0 的工程基座):P0.D 是无人机方向 Part 0 的唯一章节,定位是"所有上层算法的运行底座"。后续 D1-D12 的每一个算法(控制、规划、感知、RL、集群),都要在本章搭的仿真台子上验证、都要面对本章讲的 sim-to-real gap。先有台子,才有戏唱——这就是把仿真章放在 Part 0 最前面的原因。
🔧 故障排查手册¶
下表覆盖五个高频故障场景,重点是环境配置和版本兼容问题(工程实践类型的排查重灾区)。遇到问题先按"症状"定位,再走"排查步骤"。
| 场景 | 症状 | 可能原因 | 排查步骤 | 相关节 |
|---|---|---|---|---|
| F1 Gazebo 启动失败 | make px4_sitl gz_x500 报 gz-sim 找不到/版本不兼容 |
① Ubuntu 20.04 装了 Harmonic(不支持);② Gazebo 版本与 PX4 不匹配;③ 环境变量未刷新 | 1) 确认 OS:lsb_release -a,20.04 须用 gazebo-classic_iris target;2) 22.04 确认 gz sim --version 是 Harmonic;3) 重跑 bash Tools/setup/ubuntu.sh 后**重启终端** |
§P0.D.4 |
| F2 offboard 切换即退出 | 飞机刚进 offboard 就 fallback 到 hold/land,日志 "Offboard ... stopped" | ① 切换前没先发 setpoint;② setpoint 频率 <2Hz;③ 未通过预飞检查(无定位) | 1) 确认 offboard.start() 前已发 \(\ge\)1 个 setpoint;2) 控制循环频率提到 20-50Hz;3) 等 health.is_global_position_ok 为真再 arm |
§P0.D.4 |
| F3 RL 训练异常慢 | GPU 训练 FPS 远低于预期,nvidia-smi 显示 GPU 利用率个位数 | ① 训练循环里有 .cpu()/.item()/print(tensor);② Python 端有阻塞瓶颈;③ num_envs 太小 |
1) 搜训练循环里所有 .cpu()/.numpy()/.item(),删掉或改 TensorBoard;2) 用 torch.profiler 看瓶颈;3) 加大 num_envs(在显存允许内) |
§P0.D.6 |
| F4 CUDA out of memory | 启动或跑几步后报 OOM | ① num_envs 太大;② 视觉任务相机分辨率太高;③ 显存被其他进程占 | 1) nvidia-smi 看显存占用和其他进程;2) num_envs 从小往大试(512→1024→…);3) 视觉任务降分辨率或降 num_envs(通常比状态任务小一量级) |
§P0.D.6 |
| F5 依赖冲突/import 崩溃 | pip install 报版本冲突,或 import isaacgym/omni_drones 崩 |
① 多个仿真器装进同一环境;② Isaac 版本与 Pegasus/OmniDrones 错配;③ PyTorch 与 CUDA 不匹配 | 1) 按用途切分 conda 环境,别一锅炖;2) 严格按版本兼容表配对(Isaac 5.1↔Pegasus v5.1);3) python -c "import torch; print(torch.cuda.is_available())" 验证 PyTorch-CUDA 匹配 |
§P0.D.1 |
排查总原则:环境配置类故障(F1/F5)占新手问题的大多数,根因几乎都是"版本错配"或"环境混装"。养成两个习惯能避开 80% 的坑——① 严格按版本兼容表配对、锁精确小版本;② 按用途切分独立环境(入门/SITL/RL 各一个),永不混装。运行时故障(F2/F3/F4)则要善用日志和监控工具(PX4 终端日志、TensorBoard、nvidia-smi)定位,而非盲改。
版本信息速查¶
本章涉及的所有工具/库/框架版本号汇总(撰写时的推荐配置,使用时请对照各项目最新文档):
| 工具/库 | 推荐版本 | 关键依赖约束 | 备注 |
|---|---|---|---|
| Ubuntu | 22.04 LTS | — | Gazebo Harmonic 与 Isaac 的共同要求;20.04 仅能跑 Gazebo Classic |
| Python | 3.10 | Isaac Gym 锁 3.8,Isaac Lab 主推 3.10 | 不同仿真器对 Python 版本要求不一,分环境隔离 |
| PyTorch | 2.1.0 | 须与 CUDA 严格匹配 | torch.cuda.is_available() 验证 |
| CUDA | 11.8 / 12.x | 与 PyTorch 和 NVIDIA 驱动匹配 | 见 v8 Ch36 |
| PX4-Autopilot | v1.15 | — | v1.14 起默认仿真器切到 Gazebo(gz) |
| Gazebo | Harmonic(gz-harmonic) | 仅 Ubuntu 22.04+ | 20.04 用 Gazebo Classic 11 |
| ROS 2 | Humble | Ubuntu 22.04 | 当前 LTS 主力;MAVROS 走 ROS 2 |
| MAVSDK-Python | 最新稳定版 | — | offboard 控制 SDK |
| QGroundControl | 最新稳定版 | — | 地面站,下载 AppImage |
| gym-pybullet-drones | 1.0.0 | pybullet>=3.2、gymnasium>=0.28、stable-baselines3>=2.0 |
入门首选 |
| Flightmare | master(2021) | Unity 2020.1.10f1、C++14 | 已停止维护 |
| Isaac Sim | 4.5 / 5.1 | — | Pegasus v5.1↔Isaac 5.1,v4.5.1↔Isaac 4.5 |
| Isaac Gym | Preview 4(1.0rc4) | Python 3.8 | Aerial Gym 依赖,NVIDIA 已停更 |
| Isaac Lab | 与 Isaac Sim 配对 | — | 旧称 Orbit,OmniDrones 依赖 |
| Aerial Gym Simulator | main(2025) | Isaac Gym Preview 4 + Python 3.8 | RL 路线主推 |
| OmniDrones | 0.1 / devel | Isaac Sim + Isaac Lab | RL 基准研究 |
| Pegasus Simulator | v5.1.0 / v4.5.1 | 对应 Isaac 5.1 / 4.5 | Isaac Sim + PX4 集成 |
| RotorS | master | Gazebo Classic + ROS 1/2 | 经典论文复现 |
| stable-baselines3 | 2.3.0 | gymnasium API | 入门 RL 算法库 |
版本锁定再强调:仿真器生态版本耦合极强,务必锁到精确小版本并按用途隔离环境。Isaac 生态尤其敏感——Isaac Sim 大版本间的 Python 扩展 API 有 breaking changes,Pegasus/OmniDrones 必须配对应版本,错配会在 import 阶段直接崩(见故障 F5)。建议用
pip freeze > requirements.lock固化每个环境,并在 README 记录"本项目在 Isaac X.Y + PyTorch A.B + CUDA C.D 上验证通过"。