1. 从一堆乱飘的轨迹说起:为什么单目必须拉上IMU
如果你只用单目跑过ORB-SLAM3,大概率见过这种场景:相机端着不动,轨迹却像喝多了似的慢慢往一边漂;或者快速转个弯,画面一糊,跟踪直接丢失,地图点炸成一团。这不是代码写错了,而是单目视觉的先天缺陷——尺度不可观、快速运动易丢、纯旋转无法三角化。单目能算出“相对形状”,但算不出“真实距离”,一个杯子在画面里占多大,取决于它离你一米还是两米,单张图像根本无法区分。
IMU(惯性测量单元)恰好补上这块短板。它输出三轴线加速度和三轴角速度,角速度积分能得到旋转,加速度二次积分能得到位移,虽然单独用会随时间疯狂漂移,但它在短时间窗口内极其精准,而且天然带尺度。把两者放一起,视觉负责长期稳定和闭环,IMU负责短时高频和尺度恢复,这就是视觉惯性里程计(VIO)的核心思路。ORB-SLAM3的贡献在于把这套融合做进了完整的SLAM系统里,支持单目+IMU、双目+IMU、RGB-D+IMU多种模式,还能做多地图合并和重定位。
这篇内容面向的是已经跑通过纯视觉ORB-SLAM3、想进一步吃透单目+IMU完整链路的同学。我会从数据加载、时间戳对齐、初始化、跟踪、局部建图一路讲到位姿优化,把每个环节“为什么这么设计”“参数怎么调”“哪里最容易翻车”讲清楚。整套流程我在实际项目里反复跑过,踩的坑足够写满一页纸,下面按真实操作顺序展开。
2. 数据加载:别小看这一步,八成问题出在这
2.1 单目+IMU的数据到底长什么样
ORB-SLAM3对单目+IMU的输入要求很明确:一路图像序列,一路IMU序列,两者各自带时间戳。图像就是常规的灰度或彩色图,IMU则是六轴数据——三轴角速度(rad/s)加三轴线加速度(m/s²)。很多人第一次配数据就懵了:为什么我的IMU读出来加速度是9.8左右?因为静止时它测的是重力反作用力,模长约等于重力加速度,这是正常的,不是传感器坏了。
数据组织上,ORB-SLAM3的示例数据集(比如EuRoC)通常是一个cam0文件夹放图像,一个imu0文件夹放data.csv,外加一个config.yaml描述内外参。你自己采的数据如果来自ZED、D435i这类带IMU的相机,导出时要注意:图像和IMU必须是两个独立的时间戳序列,不能混在一个文件里按行交错。我见过有人把IMU塞进图像帧的元数据里,结果加载器直接读崩。
2.2 时间戳对齐:VIO的生死线
这是整个流程里最容易被低估、也最致命的一环。视觉和IMU是两个独立的时钟源,如果时间戳没对齐,融合出来的轨迹会系统性偏移,甚至初始化直接失败。ORB-SLAM3内部会做时间戳的线性插值和偏移估计,但它能容忍的偏差有限,通常要求在毫秒级。
实操中我建议这样做:先确认图像时间戳和IMU时间戳用的是同一个时钟基准。如果是两个设备分别采集,务必做一次硬件同步或者软件时间对齐。EuRoC数据集之所以好用,就是因为它的图像和IMU是硬件同步的,时间戳天然对齐。你自己采数据时,如果相机和IMU来自不同模组,强烈建议先跑一遍时间偏移标定,把偏移量写进配置。
提示:ORB-SLAM3的配置文件里有
IMU.T_b_c1(IMU到相机的外参)和IMU.InsertKFsWithIMU等参数,时间偏移不在配置里直接给,而是靠初始化阶段估计。所以前期数据质量越干净,初始化越稳。
2.3 配置文件里那几个必须改对的字段
配置文件是新手翻车重灾区。以单目+IMU为例,几个关键字段必须逐字核对:
| 字段 | 含义 | 常见错误 |
|---|---|---|
Camera.type | 相机模型 | 单目要写PinHole,别写成Rectified |
Camera.fx/fy/cx/cy | 内参 | 直接抄错一位小数,轨迹就歪 |
Camera.bf | 基线乘焦距 | 单目用不到,但别乱填 |
IMU.Frequency | IMU频率 | 填成图像频率,初始化必挂 |
IMU.NoiseGyro/NoiseAcc | 噪声密度 | 用默认值往往偏大,影响优化权重 |
IMU.T_b_c1 | IMU到相机外参 | 旋转矩阵顺序搞反,轨迹直接镜像 |
IMU.Frequency这一项我特别想强调。EuRoC的IMU是200Hz,图像是20Hz,比例10:1。如果你填错,系统预积分的时间步长就错了,融合结果会莫名其妙地抖。外参T_b_c1是个4x4变换矩阵,表示从相机坐标系到IMU坐标系的变换,旋转部分一定要用正确的旋转矩阵,平移部分单位是米。这个矩阵通常来自相机-IMU联合标定,没标定过的话,用厂家给的标称值也比瞎填强。
3. 初始化:VIO最玄学也最关键的一步
3.1 为什么单目+IMU不能直接开始跟踪
纯视觉SLAM可以第一帧就建图,因为图像本身提供了足够的几何约束。但VIO不行,因为IMU的加速度计测的是“比力”,里面混着重力和运动加速度,你没法从一帧数据里区分“相机在加速”还是“相机倾斜着静止”。所以ORB-SLAM3必须先做视觉-惯性初始化,估计出重力方向、速度、尺度、IMU零偏这些量,才能进入正常跟踪。
这个过程大致分两步:先用纯视觉跑一小段,建立初始地图和相机位姿;再拿这段视觉轨迹去对齐IMU预积分,解出惯性参数。听起来简单,实际很挑数据。初始化期间相机必须有足够的平移运动,纯旋转或者站着不动,尺度根本估不出来。
3.2 初始化失败的几种典型表现和排查
我整理了几种最常见的初始化失败场景,基本覆盖了九成问题:
- 一直卡在“Initializing”不动:多半是平移不够。拿着相机走个“8”字或者前后移动一两米,给它足够的视差。
- 初始化后轨迹尺度明显不对:检查IMU噪声参数,噪声填太大,优化会把IMU权重压低,尺度就飘。
- 初始化成功但马上丢失:外参
T_b_c1大概率错了,尤其是旋转方向。可以试着把旋转矩阵转置一下再跑。 - 重力方向估计歪了:加速度计数据里混了太多运动加速度,初始化时动作太剧烈。慢一点,稳一点。
注意:ORB-SLAM3的初始化对IMU零偏也有估计,如果你的IMU零偏很大(廉价IMU常见),初始化阶段可能估不准,导致后续漂移。这种情况可以考虑先做一次静态零偏标定,把结果作为初值。
3.3 初始化窗口大小的取舍
ORB-SLAM3内部有个初始化窗口的概念,大致是攒够一定数量的关键帧和IMU测量再做联合优化。窗口太小,约束不够,解不稳定;窗口太大,初始化慢,而且期间的运动畸变会累积。默认参数在多数场景下够用,但如果你用的是低帧率相机(比如10fps),可以适当放宽窗口,让IMU预积分积累更多信息。
这里有个经验:初始化阶段尽量让相机做“有加速度的平移”,而不是匀速平移。因为匀速时加速度计只测到重力,运动信息几乎为零,对速度估计没帮助。带点加减速,IMU的激励更充分,初始化质量明显更高。
4. 跟踪与局部建图:融合真正开始发力
4.1 帧间跟踪里IMU在干什么
初始化完成后,系统进入正常跟踪。每一帧图像进来,ORB-SLAM3会先用IMU预积分预测一个位姿初值,再拿这个初值去引导特征匹配和位姿优化。这一步的意义在于:IMU提供的先验让匹配搜索范围大大缩小,快速运动时尤其明显。纯视觉在快速旋转时容易跟丢,因为上一帧的特征点位置和这一帧差太远,描述子匹配不上;有了IMU预测,搜索窗口能跟着运动走,鲁棒性提升一个档次。
预积分的本质是把两帧之间的所有IMU测量“打包”成一个相对运动约束,避免在优化里逐个积分。这个约束包含旋转、速度、位置的变化量,以及对应的协方差。协方差很重要,它告诉优化器“这个约束有多可信”,噪声大的IMU协方差就大,优化时权重自然降低。
4.2 关键帧选取策略的调整
单目+IMU模式下,关键帧的选取比纯视觉更谨慎。因为每个关键帧都要参与IMU预积分和后续的局部BA,选太多会拖慢优化,选太少约束不够。ORB-SLAM3的判据大致是:跟踪质量下降、距离上一关键帧足够远、或者IMU预积分时间够长。
实际调参时,IMU.InsertKFsWithIMU这个参数控制是否在插入关键帧时同时插入IMU约束。默认是开的,别关。另外,如果发现地图点增长过快、内存吃紧,可以适当提高关键帧的插入阈值,但别太激进,否则IMU约束会变稀疏,轨迹平滑性下降。
4.3 局部BA里视觉和惯性的联合优化
局部建图阶段,ORB-SLAM3会做一个局部光束法平差(Local BA),同时优化最近若干关键帧的位姿、地图点位置,以及IMU的状态量(速度、零偏)。这是一个联合优化问题,视觉重投影误差和IMU预积分误差一起进目标函数。
这里的关键是信息矩阵的平衡。视觉误差和IMU误差的量纲不同,如果权重没调好,优化会偏向一边。ORB-SLAM3通过协方差矩阵自动处理这个平衡,但前提是你的噪声参数填得合理。我见过有人把IMU噪声填得极小,结果优化完全信IMU,视觉约束形同虚设,轨迹跟着IMU漂。噪声参数不是随便填的,最好根据IMU数据手册或者实测Allan方差来定。
5. 位姿优化:从局部到全局的精度打磨
5.1 局部优化解决的是“眼前准”
局部BA优化的是最近一段时间的轨迹,目标是让这段轨迹在视觉和惯性约束下最一致。它解决的是“局部精度”,比如你绕一个房间走一圈,局部优化能保证这一圈内部没有明显跳变。但它管不了长期漂移,因为局部窗口之外的历史约束没参与。
局部优化的频率和窗口大小直接影响实时性。窗口越大,精度越高但越慢。在嵌入式平台上跑,通常要限制窗口内关键帧数量,牺牲一点精度换帧率。这个取舍没有标准答案,取决于你的应用场景——如果是无人机悬停,局部精度优先;如果是大范围建图,得靠回环和全局优化兜底。
5.2 回环检测与全局优化:把漂移拉回来
ORB-SLAM3的回环检测用的是词袋模型(DBoW2),识别到曾经来过的地方后,会触发全局优化。单目+IMU模式下,回环不仅能修正视觉漂移,还能修正IMU累积的零偏误差。因为回环提供了一个“绝对”的位置约束,优化器会重新分配误差,把IMU零偏也一并调整。
全局优化里有个细节值得说:IMU零偏在优化中被建模为随机游走,也就是说它允许缓慢变化,但不是无限制乱变。这个建模符合真实IMU的特性——零偏确实会随温度和时间缓慢漂移。如果你的IMU零偏特别不稳定,全局优化后轨迹可能还是会有残余漂移,这时候要考虑是不是IMU本身质量太差,或者标定没做好。
5.3 位姿输出的坐标系和尺度问题
优化完成后,ORB-SLAM3输出的位姿是在它自己的世界坐标系下的,尺度由初始化阶段确定。如果你需要和外部系统(比如激光雷达、轮式里程计)对齐,得做一次坐标系变换和尺度对齐。单目+IMU的尺度虽然可观测,但精度受初始化质量影响,大范围场景下仍可能有百分之几的误差。
实操中我通常这样做:跑完一段轨迹后,拿起点和终点的已知距离去校验尺度,如果偏差超过5%,回头检查初始化数据和IMU噪声参数。另外,ORB-SLAM3支持保存轨迹为TUM格式,方便用evo等工具做精度评估,这个习惯强烈建议养成,比肉眼看轨迹靠谱得多。
6. 那些文档里不会写的实操心得
6.1 IMU标定不是可选项
很多人拿到带IMU的相机就直接跑,结果轨迹抖得没法看。IMU出厂标定通常只保证“能用”,不保证“好用”。零偏、尺度因子、轴间非正交这些误差,不标定的话会直接进融合结果。我建议至少做两件事:静态零偏标定(放着不动采几分钟,取均值)和相机-IMU外参标定(用kalibr之类的工具)。这两步做完,轨迹质量通常有肉眼可见的提升。
6.2 图像和IMU的频率匹配有讲究
IMU频率远高于图像频率是常态,但比例不是越大越好。比例太大,预积分计算量增加,而且高频IMU里的噪声也会被积进去。一般IMU在100-200Hz、图像在20-30Hz是比较舒服的区间。如果你的IMU是1000Hz,可以考虑降采样到200Hz再用,既省算力又不损失太多信息。
6.3 光照和纹理对VIO的影响被低估了
VIO虽然比纯视觉鲁棒,但视觉部分仍然是基础。光照剧烈变化、纹理缺失(白墙、地面)时,特征点提取不出来,视觉约束就废了,系统会退化成纯IMU积分,漂移飞快。实际部署时,尽量保证场景有足够纹理和稳定光照。如果场景实在恶劣,可以考虑加辅助传感器,但那是另一个话题了。
6.4 实时性调优的几个抓手
在资源受限的平台上跑ORB-SLAM3单目+IMU,实时性往往是瓶颈。几个有效的调优方向:降低图像分辨率(但别低于640x480,否则特征太少)、限制局部BA窗口大小、减少每帧提取的特征点数量、关闭不必要的可视化。这些调整都会影响精度,需要根据实际需求权衡。我的经验是,先把分辨率降到能接受的下限,再调BA窗口,最后才动特征点数量,因为特征点直接关系到跟踪鲁棒性。
6.5 评估不能只看轨迹图
最后说个评估的坑。很多人跑完一看轨迹图挺漂亮就以为成了,实际上轨迹图在尺度归一化后可能掩盖了真实误差。正确的做法是用evo之类的工具算ATE(绝对轨迹误差)和RPE(相对位姿误差),并且和真值对比。没有真值的话,至少做闭环一致性检查——回到起点时,估计位置和起点位置差多少。这个差值才是系统真实漂移的体现。
整套流程跑下来,我的体会是:ORB-SLAM3的单目+IMU模式本身很成熟,但它的表现高度依赖输入数据的质量和参数配置的合理性。算法再强,也救不了时间戳错位的数据和瞎填的噪声参数。把数据加载、时间对齐、初始化这三步做扎实,后面的跟踪和优化基本就是水到渠成的事。