基于 PPO 的无人机自主导航与避障(uav_ppo_nav)
本模块用 PPO(近端策略优化,Proximal Policy Optimization) 训练一个神经网络策略, 让无人机自主飞向目标点并避开障碍物。训练在轻量质点环境完成(快),部署在 CarlaAir/AirSim 仿真器(真实),两侧观测/动作接口完全一致,策略可直接迁移。
1. 运行架构
/lidar/points ─┐
├─► uav_ppo_nav ──/uav/cmd_vel──► carlair_ros_bridge ──► CarlaAir/AirSim
/uav/odom ─────┘ (策略推理) (坐标换算+执行)
2. 观测与动作(全部使用世界系 ENU)
为什么用世界系而不是机体系? 实测 CarlaAir 里 SimpleFlight 的偏航保持不生效: 即使命令
yaw_rate = 0,机头仍以约 9°/s 漂移并 ±25° 摆动。若用机体系速度控制, 前进方向会随漂移的机头一起转,轨迹变成绕圈(实测半径 ≈ |v|/ω ≈ 0.8 m)。 改用世界系后,机头怎么转都不影响轨迹,策略才稳定收敛。
2.1 观测(22 维)
| 部分 | 维度 | 说明 |
|---|---|---|
| 激光雷达扇形直方图 | 16 | 360° 均分 16 个扇形(22.5°/个,扇形 0 = 正东,逆时针),每扇取最近障碍距离,归一化 [0,1](1=无遮挡)。垂直方向只统计无人机高度 ±1.5 m 内的点,避免地面误报 |
| 目标相对位置 | 3 | 目标点相对无人机的世界系偏移(东/北/天),除以 8 m 归一化 |
| 世界系速度 | 3 | 东/北/天速度,分别除以 3 / 3 / 1.5 m/s 归一化 |
扇形直方图公式(以无人机为原点,世界系点 (x, y) 为东/北):
r = sqrt(x² + y²)
θ = atan2(y, x) # -π..π,0 = 正东
i = ⌊(θ + Δ/2) / Δ⌋ mod 16 # Δ = 2π/16
h[i] = min over sector i of clip(r / R_max, 0, 1) # R_max = 12 m
2.2 动作(4 维,[-1,1])
vx = a[0] · 3.0 # 世界系东向速度 (m/s)
vy = a[1] · 3.0 # 世界系北向速度 (m/s)
vz = a[2] · 1.5 # 世界系天向速度 (m/s)
ω = a[3] · 1.0 # 偏航角速度 (rad/s,不影响轨迹)
映射到 geometry_msgs/Twist 的 linear.x/y/z 与 angular.z。
因为是世界系速度,桥接必须以 body_frame:=false 启动(否则会被当成机体系):
roslaunch carlair_ros_bridge main.launch publish_image:=false body_frame:=false
节点启动时会自动校验桥接的
control/body_frame参数:读到true会打印logerr明确报错,读不到会logwarn提示,避免出现"无人机会飞但方向不对、 日志里却什么都没有"的隐蔽故障。桥接的
control/cmd_duration(单条速度指令持续时长)默认 0.1 s,与本模块训练 环境的dt = 0.1 s对齐;若设成 0.2 s,指令作用时间翻倍,会造成过冲震荡。
3. 训练
训练环境 scripts/env.py 是质点 + 速度环一阶滞后模型:
vel += (cmd_vel − vel) · dt / τ # τ = 0.45 s,模拟真实速度环滞后
pos += vel · dt
随机摆放圆柱障碍物与随机目标,用射线-圆求交生成合成激光雷达直方图。 奖励 = 「向目标靠近的进度 − 动作平滑 − 时间惩罚」,到达 +20、撞障碍/越界 −20。
为什么要建模速度滞后? 真实飞行器的速度环不是瞬时到达指令速度。早期版本 用瞬时质点模型训练,策略在 CarlaAir 里会因惯量过冲而来回震荡;加入一阶滞后后 明显改善。
pip install "stable-baselines3" "gymnasium" numpy
cd src/air/uav_ppo_nav/scripts
python3 main.py --train --total 400000 --obstacles 16 --out ../models
# 产出 models/policy_weights.npz(部署用)与 models/best_model.zip
注意
--out ../models:训练脚本默认输出到当前目录下的models,而部署时读的是$(find uav_ppo_nav)/models/policy_weights.npz,所以要用--out ../models指回包内目录。
环境规模(贴近 CarlaAir 城镇尺度):场地 ±10 m、目标距离 2~8 m、16 个半径 0.6 m 的障碍、
控制周期 dt = 0.1 s。训练评估见 §5。
PPO 超参:n_steps=2048, batch_size=256, lr=3e-4, gamma=0.99, gae_lambda=0.95,
ent_coef=0.005, net_arch=[64,64]。
4. 部署
部署节点 scripts/ppo_nav_node.py 用训练导出的 MLP 权重做纯 numpy 前向,
因此虚拟机不需要安装 torch / stable-baselines3:
# 终端 1:桥接(必须 body_frame:=false,因为策略输出世界系速度)
source ~/bridge_ws/devel/setup.bash
roslaunch carlair_ros_bridge main.launch publish_image:=false body_frame:=false
# 终端 2:PPO 导航
source ~/bridge_ws/devel/setup.bash
roslaunch uav_ppo_nav main.launch goal_x:=16.6 goal_y:=5.4 goal_z:=-10.0
推理前向(MLP [64,64]):
h0 = tanh(W0·x + b0)
h1 = tanh(W1·h0 + b1)
a = clip(W2·h1 + b2, -1, 1)
5. 验证
5.1 本地桩测试(无需 ROS / 仿真器)
python3 tests/test_ppo_nav_local.py # 20 项:直方图/世界系变换/动作映射/环境/策略
python3 tests/test_ppo_nav_node_local.py # 20 项:部署节点(mock 掉 rospy 也能测)
项数是不同断言的个数。所有断言都写在循环外——写进循环会靠重复执行把计数灌水 (早期版本就是这样把 19 条断言报成「68 项」的,已修正)。
其中「终止判定」是一条可证伪的确定性断言:悬停不动 +
max_steps=20时, 必须在正好第 20 步返回truncated=True, terminated=False(实测max_steps取 5/20/30 均精确命中,写成n == max_steps - 1会全部失败)。
另外可以校验「部署侧纯 numpy 推理」与训练框架(SB3)完全一致:
from policy import MlpPolicy; from stable_baselines3 import PPO
# 同一观测下 MlpPolicy.forward 与 model.predict 的最大绝对误差 < 1e-5
# (两者都是 float32,实测量级 1e-8~1e-6,属正常舍入误差)
5.2 训练环境评估
main.py --eval --episodes 100 --obstacles 16:
| 指标 | 数值 |
|---|---|
| 成功率 | 78/100(78.0%) |
| 失败(碰撞/越界) | 17 |
| 超时 | 5 |
| 平均步数 | 38.4 |
| 平均奖励 | 16.5 |
必须显式带
--obstacles 16:--eval的--obstacles默认值是 6,不传会得到 不同的成功率,容易误判。训练共 40 万步,但
train.py故意导出的是评估集最优检查点best_model.zip(约 30 万步处);已逐层比对确认policy_weights.npz与 best 权重完全一致 (与 final 差 1.04e-1)。只有policy_weights.npz入库,两个.zip训练产物不入库。
5.3 CarlaAir 真机联调(同一策略、同一观测代码)
把训练好的策略直接部署到 CarlaAir(Town01),从悬停点给出目标点,记录全程:
| 目标距离 | 飞行用时 | 最终误差 | 说明 |
|---|---|---|---|
| 6.71 m | 3.4 s | 0.59 m | 开阔空域 |
| 8.94 m | 4.2 s | 0.38 m | 楼群高度(z ≈ −10) |
| 16.12 m | 6.4 s | 0.22 m | 楼群高度,激光雷达 2567~3228 点 |
| 11.00 m | 5.0 s | 0.35 m | 楼群高度 |
结论:训练环境(纯 numpy 质点模型)中学到的策略,可直接迁移到 CarlaAir 的 真实六自由度无人机上完成自主导航,位置误差均在 0.6 m 以内。
5.4 工程经验(sim-to-real 差距)
联调过程中定位到三个真实的落差来源,均已修正:
- 偏航不可控 → 改用世界系控制。CarlaAir 的偏航保持不生效(漂移+摆动), 机体系控制会导致绕圈飞行。
- 速度环滞后 → 训练环境建模一阶滞后。否则策略在真机上因惯量过冲震荡。
- 控制周期必须一致。训练
dt = 0.1 s,部署也须用 0.1 s(用 0.25 s 会震荡)。
另外,CarlaAir 的仿真器在高密度几何 + 大点云场景下(楼群中激光雷达 9000+ 点) 单步 RPC 可达数秒,属平台性能上限,与策略无关。
该限制已通过传感器配置缓解:原先 settings.json 的雷达没设 Range 且
PointsPerSecond: 100000,桥接又把滤波上限放到 60 m,而策略实际只用到 12 m ——
等于每帧都传输并解包大量用不上的远点。现改为 Range: 15.0、PointsPerSecond: 40000,
并把 lidar/max_range 收到 15 m,显著降低每帧射线投射与传输开销。
若在楼群中仍偏慢,可继续下调这两项。
6. 效果图

上图:PPO 策略驱动 CarlaAir 无人机自主飞向目标点(第一视角,前视相机)。