神经视觉惯性里程计与三后端 SLAM 对比

对应作业任务(2)传感器感知与任务(3)建图导航 + SLAM 同步仿真。 对比实验①:同一模型、不同 SLAM 算法。

前端是 CNN + GRU 的神经视觉惯性里程计,纯 numpy 手写前向与反向;三个后端 共用同一前端、同一数据集,只换后端,对比定位精度、地图一致性与 GPS 拒止下的退化。


1. 模块定位

GPS 拒止环境(室内、隧道、城市峡谷)下,位姿只能靠机载传感器推。本模块用 深度相机 + IMU 做神经 VIO,再在同一前端上比较三种 SLAM 后端:

后端 观测量 地图表示 复杂度
EKF-SLAM 立柱的 (距离, 方位) 单高斯,位姿 + 路标联合
EKF-SLAM + 神经观测模型 同上,噪声由网络给 同上
FastSLAM 1.0 同上 粒子云 + 逐粒子路标 EKF

课程内已有的 octree_uav_3d_pathfinding 直接用仿真器给的真值位姿建图, 没有状态估计;carla_slam_gmapping 是车、且 gmapping 是黑箱,做不了"同前端 换后端"的受控对比。课程内还没有把状态估计与建图分开评测的模块。


2. 架构

深度图 160×128 ─┐
                ├─ CNN(3 层, stride 2) ─┐
IMU 100 Hz ─────┘                       ├─ GRU(64) ─┬─ head_p → Δp_b (3)
                                        │            ├─ head_r → Δrpy_b (3)
                                        │            └─ head_s → log σ² (6)
                                        │
                                        └─→ ┬ EKF-SLAM
                                            ├ EKF-SLAM + 神经观测模型
                                            └ FastSLAM 1.0
                                                     │
                                                     └→ 位姿 + 路标 → 占用栅格

前端与后端之间只有 (Δp_b, Δrpy_b, σ) 一条接口,换后端不动前端一行。

/camera/depth 与 IMU 由底座 uav_mujoco_sim 提供;激光雷达的立柱观测由 landmarks.py 从射线距离里检测。


3. 神经前端

3.1 网络结构

层 形状 参数
Conv1 2×48×64 → 8×22×30,5×5 /2 2 通道输入(深度 + 有效掩码)
Conv2 8×22×30 → 16×10×14,3×3 /2
Conv3 16×10×14 → 16×4×6,3×3 /2
IMU 分支 10×6 → 64 → 64 全连接 + ReLU
融合 64 + 64 → GRU(64)
head_p / head_r 64 → 3 位移 / 姿态增量
head_s 64 → 6 log σ²,限幅 [−8, 4]

深度输入两通道:归一化深度 + 有效掩码。无回波像素也记 0,掩码把"无回波" 与"距离 0"分开。

3.2 损失

位移与姿态增量都用异方差高斯负对数似然:

网络同时预测 :给后端提供逐帧不确定度,残差大的帧在损失里自动降权。 限幅在 ;不加限幅时训练会把 推向 0 换 loss (实测 20 轮内单帧精度退到 32 mm)。

3.3 训练

按定长片段(32 帧)切:片段内 GRU 隐状态连续、片段间清零。梯度只回传一步, 隐状态相当于额外输入特征,与在线推理时只有历史信息一致。优化器 Adam (,梯度范数限幅 1.0),25 轮,验证集为第 3 集。

3.4 前端精度

指标 值
逐帧位移增量 RMSE 2.8 mm
逐帧姿态增量 RMSE 0.11°
位移不确定度标定比 6.6(偏保守)

标定比偏大:网络的高斯假设覆盖不了偶发的无效深度帧,后端因此偏保守。


4. 三个后端

4.1 EKF-SLAM

状态 , 协方差 。

预测只推进位姿,雅可比 传递位姿与路标的互协方差:

均值走非线性转移, 只用于协方差;用 乘 是一阶近似,整条轨迹会累积系统偏差。

观测更新用完整的联合协方差:

新路标初始化传播位姿不确定度,补上与已有状态的互协方差:

只写对角块时,回环处路标被当作真值,位姿无法修正。

4.2 学习式观测噪声模型

经典 EKF 用固定 ,但实测测距噪声 强烈依赖距离:

距离段 真实 σ_r 常数模型 学习模型
0.8–2 m 0.017 m 0.100 m(悲观 5.8×) 0.027 m
2–4 m 0.025 m 0.100 m 0.028 m
4–6 m 0.028 m 0.100 m 0.032 m
6–8 m 0.047 m 0.100 m 0.043 m
8–10.5 m 0.052 m 0.100 m 0.049 m

模型在真值上训练:用真值位姿与真值路标配对,残差就是纯传感器误差。 网络拟合的是 ,换算成 要补一个偏置:

写成 (欧拉常数)会少补 0.693, 系统性低估 倍——实测标定比 0.69 而不是 1.0,就是这一项。

方位残差重尾(1 m 处散布 2.3°,多数样本接近 0); 拟合的是典型误差而非标准差,训练后补一次标量方差标定 。

4.3 FastSLAM 1.0

每个粒子带一条轨迹假设与自己的一套路标 EKF。位姿表示不同:EKF 单高斯(单峰), FastSLAM 粒子集(多峰)。粒子权重按观测似然连乘:

两个工程细节:数据关联跨粒子共享(用加权平均地图做最近邻), 路标更新用采样出的位姿当精确值(FastSLAM 1.0 的固有近似)。

4.4 关联门限与观测噪声解耦

χ² 门限的协方差 依赖 :噪声模型变准后 变小, 变小,同一创新的马氏距离变大,更多真实观测被拒,每条被拒观测都生成一根 假柱子。

实测(第 0 集,3591 条观测):

观测噪声 关联成功 新建路标 真值 30 个 接受时的 χ² 中位 ATE
常数 R 3517 74 44 个虚警 0.55 0.45 m
学习 R(门限耦合) 3375 216 186 个虚警 1.21 0.94 m

门限改用固定几何尺度( m, rad)后,关联行为与噪声模型无关,两种后端之间 只剩观测噪声模型这一个差异;即固定搜索半径。


5. 建图

位姿 + 激光雷达射线 → 2D 对数几率占用栅格。整帧向量化:所有射线的采样点 一次性算出来,用 np.bincount 把同一格的命中累加,每帧 4~6 ms。

地图一致性用 IoU 评价,同时给两个分辨率:

  • 0.10 m 原生:墙只有一格宽,位姿差 0.4 m 就整体错开,量的是位姿误差
  • 0.50 m 降采样:块内取最大(占据优先),才是"地图画得对不对"

6. 评测指标

指标 定义
ATE 原始 估计轨迹与真值轨迹逐点误差的 RMSE,不做任何对齐
ATE 对齐 先做 SE(2) 最优对齐(Umeyama)再算,排除整体漂移自由度
RPE 隔 s 比较相对运动:
路标 RMSE 估计路标与真值路标的最近邻配对误差
形状误差 配对点对做 SE(2) 对齐后的残差,只反映地图形状
地图 IoU 占用栅格与真值栅格的交并比

估计地图与真值地图要换到同一第一帧坐标系。只换轨迹不换路标会差一个首帧偏航 旋转(本数据集 yaw=0.965 rad,10 m 处错开 9 m),最近邻配对退化为随机—— 实测 rmse 0.71 m ≈ 1 m 配对半径下随机配对的期望 。修正后 0.30 m, 形状误差 0.148 m。


7. 实测结果

平台:仿真侧 Windows 11 + Intel Core 5 220H(核显);ROS 侧 Ubuntu 20.04 + Humble 虚拟机(colcon build 通过)。 数据:5 集八字闭环航线,每集 80 s @10 Hz,每帧约 4.5 条立柱观测,30 根真值立柱。

7.1 GPS 正常(每 1 s 一次,σ=0.5 m)

后端 ATE 原始 ATE 对齐 RPE 路标 RMSE 地图 IoU(0.5 m) 耗时
纯里程计(前端) 10.936 m 5.475 m 0.377 m — 0.08 0 s
EKF-SLAM 0.470 m 0.271 m 0.165 m 0.254 m 0.44 6 s
EKF-SLAM + 神经观测模型 0.342 m 0.146 m 0.093 m 0.249 m 0.55 5 s
FastSLAM 1.0 3.827 m 2.945 m 0.380 m 0.638 m 0.18 1 s
  • 神经观测模型相对常数 R:ATE −27%,RPE −44%,地图 IoU +25%, 5 集里赢 4 集(第 2 集 0.603 vs 0.291 落后)
  • EKF-SLAM 相对纯里程计:ATE 降低 23 倍
  • FastSLAM 明显落后,且耗时只有 1 s(EKF 是 ,FastSLAM 是 )

(轨迹图与地图图为第 0 集,标题里的 ATE / IoU 是该集数值;上表与指标图为 5 集平均。)

7.2 GPS 中断退化

中断时长 纯里程计 EKF-SLAM EKF+神经观测 FastSLAM
0 s 10.936 0.470 0.342 3.827
5 s 10.936 0.487 0.372 5.005
15 s 10.936 0.514 0.479 6.940
30 s 10.936 0.568 0.534 7.758

EKF 两个后端对 30 s 中断只退化 21%/56%(前端的位移增量精度本就到毫米级, 时间足够长时靠路标观测就能维住);FastSLAM 退化 103%,对绝对观测最敏感。

7.3 FastSLAM 为什么落后

诊断数据(第 0 集,160 粒子):

量 值
粒子位姿散布 σ ≈ 0.03–0.08 m
需要覆盖的不确定度 0.4 m 量级
重采样次数 / 800 帧 432
有效粒子数 118/160(权重接近均匀)

粒子云宽 3~8 cm,需要覆盖的位姿不确定度是 0.4 m 量级。云宽由“过程噪声 × √(重采样间隔)”决定:每帧 0.018 m、约每 2 帧重采样一次,锁在 3 cm 左右。 GPS 似然在整个云上几乎相同(0.5 m 观测噪声对 3 cm 云宽),权重区分不出来, 接近满值。

过程噪声、粒子数(160→2500)、重采样比都试过,ATE 在 1.0~2.8 m 波动、无单调 改善:FastSLAM 1.0 用里程计提议分布的结构性局限,非调参问题。


8. 源码解析

uav_neural_vio_slam/core/
├── nn.py           手写 Conv2d / GRUCell / Linear / Adam
├── network.py      VioNet:前向、反向、高斯 NLL、限幅
├── world.py        SLAM 基准世界(30 立柱)+ 八字闭环航线
├── landmarks.py    激光雷达 → (距离, 方位) 观测
├── dataset.py      数据采集与存取
├── training.py     训练与在线推理
├── ekf_slam.py     EKF-SLAM
├── fastslam.py     FastSLAM 1.0
├── obs_model.py    学习式观测噪声模型
├── mapping.py      占用栅格
├── metrics.py      ATE / RPE / 路标误差 / 地图 IoU
└── experiment.py   四后端对比与 GPS 中断

8.1 landmarks.py:从射线距离里挑出立柱

判据是"细":立柱在方位向只占几个波束,墙面、地面、大方柱连成一片;再加速度 一致性(同一立柱在 ±15° 两层距离接近,地面不同)。不需要高度与姿态,可在线使用。

两个实现细节:

水平距离。detect 输出乘过 cos 俯仰角的水平距离,与二维观测模型 一致;−15° 层三维距离大 , 8 m 处 0.28 m 系统偏差。

整组生长。逐格判孤立会丢掉柱子中部、只剩两侧边缘(实测 1 m 处拆成 1.01 m/+9° 与 1.02 m/+1°);按候选簇整组生长后再判。

8.2 nn.py:im2col 的维度顺序

as_strided 的维度顺序与 reshape 的目标一致。顺序不一致时 reshape 按内存序 重新分组,布局被打乱;此时前向与反向仍互为转置、数值梯度校验通过(层相当于 "核权重被置换过的卷积"),只有伴随恒等式

能发现(实测 8.11 vs 6.94)。--selftest 含这一项。

8.3 metrics.py:形状误差的迭代最近点

两图之间差一个刚体变换,直接按原坐标做最近邻会因整体平移/旋转配错。 landmark_shape_rmse 三步:点数相同时先 SE(2) 粗对齐 → 最近邻配对 → 用配对点估变换再作用于全部点,迭代 3 次。扣掉整体漂移,剩下的是形状误差。

8.4 ekf_slam.py:轨迹记录不含初始位姿

reset() 里写 traj = [mu.copy()] 会使估计轨迹整体错开一帧(1.4 m/s 下 0.14 m 系统滞后);只在 record() 里追加,与帧数一一对应。


9. 复现步骤

# 仿真侧(Python ≥3.10)
python main.py --collect --out data/vio_dataset.npz
python main.py --train   --data data/vio_dataset.npz --out data/

# 任意机器(只需 numpy)
python main.py --selftest
python main.py --evaluate --data data/vio_dataset.npz --model data/ \
               --out results.json --outage 5,15,30
python main.py --figures  --data data/vio_dataset.npz --model data/ \
               --outdir ../docs/img/air/uav_neural_vio_slam --outage 5,15,30

ROS2 在线运行见包内 README.md。


10. 依赖