
这篇运行记录拖了大半个月终于有空整理出来。起因是项目需要一套能工作在常规室内环境的轻量级定位方案手上只有一个普通USB相机和一块老IMU模块所以最后定下了ORBSLAM3的单目IMU这条路线。ORBSLAM3是西班牙萨拉戈萨大学开源的视觉惯性SLAM系统支持单目、双目、RGB-D三种传感器并且能在这些基础上接入IMU官方提供的多地图Atlas机制对丢帧重定位场景也比较友好。我从Ubuntu 20.04编译开始到标定、跑EuRoC数据集验证再到接上ZED实时跑中间踩了一堆坑特别是yaw方向慢漂这个问题折腾了很久。这篇文章把整个过程复盘一遍包含完整的部署命令、标定参数解释、配置文件写法以及我在真实运行中遇到的异常现象和排查思路给想用单目IMU跑ORBSLAM3的同学做个参考。1. 整体设计与方案选型1.1 为什么是单目IMU而不是其他组合做定位方案选型的时候我对比过三种路线纯单目、单目IMU、直接用深度相机或双目。纯单目的问题很明确系统初始化时必须做带平移的运动不然三角化不出深度而且单目SLAM估计出的轨迹没有绝对尺度跑出来的轨迹虽然形状对但实际距离完全不知道是多少。这在很多工程场景里是硬伤。双目或RGB-D能在一定程度上解决尺度问题但代价是传感器体积、标定工作量、以及对硬件同步的依赖都上去了。尤其是我手头这套设备一个USB单目相机加一块IMU模块如果非得上双目整个硬件方案都得推翻。单目IMU在这个场景里是性价比最高的选择。IMU里的加速度计能感知重力方向和真实的线加速度配合视觉特征系统可以在初始化阶段把尺度、重力方向、速度这些量一起估计出来。相比纯单目单目IMU的初始化对运动姿态的要求宽松很多甚至在静止状态下先完成IMU内部对齐再靠小幅运动触发视觉初始化这在室内弱纹理环境也很实用。1.2 整体处理链路是怎么设计的从零到跑通的完整链路我拆成了下面几块编译部署Ubuntu 20.04下装好Pangolin、OpenCV、Eigen编译ORBSLAM3本体。传感器标定相机内参、IMU噪声参数、相机到IMU的外参和时间偏移这一步决定系统能不能收敛。数据验证先用EuRoC数据集把单目IMU模式跑通确认代码和配置流程没问题。实时运行用ZED的左目摄像头和内置IMU数据通过ROS节点喂给ORBSLAM3观察实际运行效果。问题调优针对yaw慢漂、初始化失败、掉帧等问题做针对性排查。这条路看着简单实际每一步都能卡人。编译卡在版本兼容标定卡在外参方向运行卡在时间戳对齐调试过程大量时间耗在区分是代码问题还是标定问题还是配置问题。后面我把每个环节的关键判断依据都写出来照着走能省很多事。2. 编译部署全过程2.1 基础依赖安装ORBSLAM3源码里自带DBoW2、g2o、Sophus这几个模块不需要单独安装但它依赖的外部库需要自己装好。我用的系统是Ubuntu 20.04编译器是gcc/g 9CMake 3.16。依赖清单如下sudo apt update sudo apt install -y cmake build-essential git vim sudo apt install -y libeigen3-dev libopencv-dev sudo apt install -y libgl1-mesa-dev libglew-dev libpython3-dev python3-pipEigen3和OpenCV是核心。Eigen3主要是矩阵运算ORBSLAM3的图优化和IMU预积分到处都在用OpenCV负责图像读取、特征提取和畸变矫正Ubuntu 20.04默认源提供的是OpenCV 4.2够用。Pangolin需要从源码编译我建议直接指定v0.6这个版本tag后续编译不容易出幺蛾子git clone https://github.com/stevenlovegrove/Pangolin.git cd Pangolin git checkout v0.6 mkdir build cd build cmake .. make -j$(nproc) sudo make install如果机器内存不大make那里建议用-j4而不是-j8Pangolin编译到后面内存占用挺高我一开始图快直接-j8结果让系统卡死过一次后来老老实实降并发。2.2 ORBSLAM3编译与报错排除依赖装好之后拉源码编译就很顺了git clone https://github.com/UZ-SLAMLab/ORB_SLAM3.git cd ORB_SLAM3 chmod x build.sh ./build.sh整个编译过程大概十几分钟取决于机器性能。这里重点说几个我实际遇到过的编译问题。第一个是OpenCV头文件路径问题。Ubuntu 20.04的libopencv-dev会把头文件放到/usr/include/opencv4/opencv2/下面而ORBSLAM3源码里很多文件写的是#include opencv2/opencv.hpp。理论上系统能找到这个路径但个别版本下CMake的include路径拼接有问题会报找不到opencv2/xxx.hpp。我的处理方法比较粗暴直接建了一个软链sudo ln -s /usr/include/opencv4/opencv2 /usr/include/opencv2第二个是Eigen3版本太新导致的接口报错。Eigen3的3.3和3.4在某些头文件上有差异ORBSLAM3源码比较早对新版Eigen的兼容不是特别好。如果编译时报Eigen/src/Core/Matrix.h: no matching function这类模板错误一个常见处理方式是检查是不是安装了多个Eigen版本尽量保留apt源里的那个手动编译安装的版本容易覆盖系统路径。第三个坑是Pangolin版本。直接用main分支最新代码编ORBSLAM3大概率会报错因为Pangolin的新版API变动不小。我切到v0.6之后一次通过建议别在这方面省事。编译通过后建议先跑一下自带的单目示例比如monocular_tum确认整个链路没问题再进入标定环节。3. 相机与IMU联合标定3.1 标定参数对系统的影响有多大很多同学忽略标定觉得差不多就行但视觉惯性SLAM对参数极其敏感。我实测的结果是相机内参标不准特征三角化误差增大地图点发散轨迹会出现缓慢漂移IMU噪声参数给错优化器对IMU约束的信任程度就不对轻则轨迹抖重则初始化失败相机到IMU的外参如果几度几厘米的误差系统跑十几秒就飞了。所以这一步值得花时间做扎实。后面所有调参工作都建立在标定数据可靠的假设上。3.2 相机内参标定相机内参包括焦距fx、fy主点cx、cy以及畸变系数。我用的方法是张正友棋盘格标定法最省事的工具是Kalibr。为什么要用Kalibr而不是OpenCV脚本因为后面做相机-IMU外参标定也是在同一套工具链里直接用Kalibr的标定板和流程中间少一次坐标系和格式转换的麻烦。标定过程要点打印一张Aprilgrid标定板贴在平整硬板上注意别用反光材料。用待标定相机对着标定板从不同角度拍摄覆盖画面边缘和中心记录一段视频或者图片序列。调用Kalibr的kalibr_calibrate_cameras命令得到相机内参和畸变模型。拿结果和官方标称参数对比如果差别很大检查标定板尺寸是否填对。对于普通USB相机标定出来的fx/fy误差在零点几像素以内就够用了。如果是变焦镜头记得把镜头固定在最常用的焦段标定期间不要碰变焦环。3.3 IMU噪声参数标定IMU噪声参数在ORBSLAM3配置里有四个陀螺仪噪声密度NoiseGyro、加速度计噪声密度NoiseAcc、陀螺仪随机游走GyroWalk、加速度计随机游走AccWalk。这些参数决定了IMU预积分协方差的大小进而决定优化时IMU约束和视觉约束之间的权重。获取这些参数的标准做法是Allan方差分析。把IMU静止放在桌上连续采集两个小时以上的数据然后做Allan方差曲线拟合。在双对数坐标下斜率为-1/2的部分对应角度随机游走噪声密度斜率为1/2的部分对应速度随机游走偏置不稳定性。Kalibr的imu_covariance工具可以自动做这件事也可以自己用python脚本实现。没有条件采集长时间数据的时候可以先参考IMU芯片手册里的典型值比如MPU6050系列的NoiseGyro在1e-4量级NoiseAcc在1e-2量级然后用实际运行效果微调。但注意芯片手册值通常偏理想真实板子上的电源噪声、温漂、震动都会让实际噪声更大。3.4 相机到IMU外参标定这部分是整个标定流程里最容易出问题的地方。我用Kalibr的kalibr_calibrate_imu_camera命令做联合标定大致流程把IMU和相机固定在一起确保相对位姿在标定过程中不变化。将相机画面和IMU数据录成一个rosbag时间戳同步问题在采集阶段就要注意。标定板保持在视野内运动时让设备做各方向的平移和旋转但要避免设备一直朝一个方向转那样角速度激励不充分。运行联合标定命令得到相机内参、IMU噪声、以及相机到IMU的旋转和平移。这里要提醒一下采集数据时标定板要尽量占画面面积的四分之一以上运动速度不要太快以免图像模糊同时又要保证IMU有足够激励。我采集了大概两分钟的数据覆盖各个方向效果还不错。3.5 ORBSLAM3配置文件编写要点标定完成之后把参数写进yaml配置文件ORBSLAM3的单目IMU模式才能启动。关键配置项如下Camera.fx: 610.234 Camera.fy: 610.567 Camera.cx: 313.45 Camera.cy: 243.21 Camera.k1: -0.286 Camera.k2: 0.089 Camera.p1: 0.0 Camera.p2: 0.0 Camera.width: 640 Camera.height: 480 Camera.fps: 30 Camera.RGB: 1 IMU.NoiseGyro: 0.00016 IMU.NoiseAcc: 0.0028 IMU.GyroWalk: 0.000022 IMU.AccWalk: 0.0004 IMU.Frequency: 200.0 Tbc: !!opencv-matrix rows: 4 cols: 4 dt: f data: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]这里特别强调Tbc矩阵方向问题。Kalibr联合标定输出的一般是相机坐标系到IMU坐标系的变换但ORBSLAM3源码里读Tbc后用在了把相机坐标点变换到IMU坐标系的位姿链路上所以要确认你填进去的4x4矩阵方向是正确的。我的经验是先把标定结果用python的numpy矩阵求逆验证一下把变换应用到一组已知点上看看坐标系是否符合直觉再填进yaml。填反了的典型症状是系统能启动但不稳定几十帧之后就发散轨迹直接飞到天上去。如果标定过程中还估计了时间偏移time offsetORBSLAM3在配置里没有直接的字段需要在代码里修改或者在数据采集时通过硬件/时间同步尽量让图像和IMU时间戳对齐。实战中时间偏移小于半帧周期通常可以忽略但偏移过大就会导致IMU预积分和视觉特征帧之间错位严重时初始化就失败。4. 运行过程与参数微调4.1 用EuRoC数据集验证系统标定配置做完先别急着接真实相机用EuRoC数据集把整条链路跑通更稳妥。EuRoC数据集是视觉惯性SLAM的标准测试集包含MH系列工厂环境和V1/V2系列室内场景同时提供双目图像和IMU数据单目IMU模式只需要取左目图像。运行命令./Examples/Monocular-Inertial/mono_inertial_euroc \ Vocabulary/ORBvoc.txt \ Examples/Monocular-Inertial/EuRoC_imu.yaml \ ~/dataset/EuRoC/MH_01 \ Examples/Monocular-Inertial/EuRoC_TimeStamps/MH_01.txtEuRoC_imu.yaml是官方给的配置里面IMU频率等参数和数据集对齐不需要改。跑起来之后Pangolin窗口会显示当前相机位姿、地图点和轨迹。第一次跑通时那种兴奋感相信做SLAM的都懂。如果你拿到了自己相机录制的bag包也想离线测试可以把bag包转换成Euroc格式或者直接用后面说的ROS实时节点。4.2 接ZED相机做实时运行实时运行的硬件方案我选的是ZED一代相机。ZED的优势是左右目各有一个感光芯片内置IMUSDK能直接输出左目图像和IMU数据省掉了自己接IMU模块和做时间同步的麻烦。单目IMU模式下只用左目图像加IMU。实时运行有两种方式。一种是自己写采集线程直接用ZED SDK读取数据后喂给ORBSLAM3的Monocular-Inertial接口另一种是使用ORBSLAM3仓库里的ROS节点把数据发布成topic让节点订阅。我用的ROS方式流程是安装ZED SDK和zed-ros-wrapper。把左目图像发布到/camera/left/image_rawIMU发布到/imu/data。编译ORBSLAM3的ROS示例。运行rosrun挂载节点指定自己的yaml配置。相关命令就不详细写了不同版本的ZED SDK和ROS发行版接口有差异重点说几个运行时的坑。一是IMU频率问题。ZED内置IMU输出频率一般在200Hz左右ORBSLAM3配置里的IMU.Frequency必须和实际一致否则预积分的时间戳计算会错位。二是相机帧率ZED默认输出可能是30Hz或60Hz如果yaml里写的是30但实际是60时间戳也会有问题。三是图像分辨率ORBSLAM3对图像尺寸没有强约束但焦距、主点要和内参标定时一致用SDK自带的分辨率参数时要确认配置和标定时是同一分辨率。4.3 关键参数微调心得配置文件的参数不是标定完就一劳永逸的实际运行中经常要做微调。我整理了几个影响最大的参数。首先是IMU噪声参数。前面标定出来的是一个理论值实际运行中我发现NoiseGyro和NoiseAcc稍微调大一点轨迹会更平滑因为优化器不会过度信任单帧IMU读数但如果调得太大IMU约束几乎失效又会出现单目尺度漂移。这个平衡点要靠实验找我通常在标定值上下浮动2倍范围内试。其次是特征点数量。如果场景纹理稀疏可以适当提高特征提取上限比如从默认的1000提到2000但代价是每帧计算时间变长实时性会下降。如果跑的是纹理丰富的场景2000个特征并不会让精度线性提升反而启动时初始化耗时更长需要做取舍。最后是初始化相关参数。ORBSLAM3在视觉惯性初始化阶段会要求一定视差和IMU激励如果现场环境空间很小、平移受限可以适当降低最小视差阈值。注意的是阈值太低会导致初始地图质量差后面容易丢跟踪最好保持默认值先跑几次确定是初始化不起还是跟踪容易丢再决定改不改。5. 常见问题与排查技巧5.1 yaw慢漂为什么防不住也压不住这是我在实时运行中遇到的最顽固的问题。具体现象是轨迹在平面上的形状基本正确整体尺度也没问题但运行时间长了之后整个轨迹绕着重力方向慢慢旋转也就是yaw角缓慢漂移。在纯视觉惯性SLAM里yaw方向本质上不可观重力方向只能约束roll和pitchyaw的自由度残留了下来。ORBSLAM3不融合磁力计所以没有外部信息做绝对yaw参考这个漂移只能靠视觉信息间接抑制。我实测下来的缓解手段有几个。一是确保标定正确尤其是陀螺仪零偏和噪声。零偏没估计准角速度积分会持续累积错误yaw漂移会明显加速。ORBSLAM3初始化阶段会估计陀螺仪零偏所以运行前设备要先保持静止几秒。二是运行轨迹尽量制造回环。回环检测一旦成功全局优化会大幅修正累积的yaw误差。在长走廊、办公室这种往返路径多的环境里yaw漂移能被压到可以接受的范围。如果一直是长直线无回环yaw越到后面越偏就是正常现象。三是如果应用对绝对朝向有硬要求单靠ORBSLAM3不够需要在系统外面再加一层修正比如和磁力计做松耦合或者定期用已知路标做方位校正。当时我还试过一种取巧的做法把IMU的NoiseGyro调到比标定值小一个数量级希望让角速度积分更准。结果适得其反优化器把IMU角速度当成了几乎完全可信的约束视觉反而没法纠正yaw误差最后这条路径直接弃用。所以参数调整一定要盯着整体误差看不能只看单方向。5.2 初始化一直失败怎么办单目IMU初始化失败在日志里通常表现为卡在某个状态持续不出初始地图。我遇到过的原因有三类。第一类IMU激励不足。系统启动后要保持相机和IMU有运动最好是缓慢地晃动加平移让加速度计和陀螺仪读数明显变化这样初始化才能估计出可靠的零偏和重力方向。如果一直静止或者匀速直线IMU给的约束太弱初始化就会卡住。解决方法很简单启动后让设备做几下8字或者画圆运动但要避免剧烈甩动导致图像模糊。第二类特征点丢失。在光线暗或纯白墙环境ORBSLAM3提不到足够特征点初始化无从谈起。这时候要么补光、要么换有纹理的场景或者调整ORB特征提取的阈值参数。第三类时间戳错乱。我用ZED实时跑的时候一开始IMU和图像时间戳不同步初始化反复失败但也看不出明显报错。后来把IMU回调里的时间戳打印出来和图像时间戳对比才发现两者差了接近100毫秒。改成用同一个时钟源给两种数据打时间戳后初始化一次就过。5.3 其他坑汇总速查把我在整个过程中遇到的其他问题和对应处理方式整理成一张表方便排查时对照。现象可能原因处理方法编译报OpenCV头文件找不到OpenCV头文件路径没被正确包含建立/usr/include/opencv2软链编译报Eigen相关模板错误Eigen版本和源码不兼容只保留系统libeigen3-dev安装的版本程序启动直接段错误词汇表路径不对或yaml矩阵格式错误检查Vocabulary/ORBvoc.txt路径和Tbc矩阵格式运行几秒轨迹发散Tbc方向反了或外参误差大用numpy验证变换方向重标定轨迹尺度越跑越大/小IMU噪声参数不合理在标定值附近微调NoiseAcc/NoiseGyro跟踪丢失后重定位失败场景纹理差或运动过快降速、增加特征数、避免长时遮挡实时运行延迟大特征点太多或图像分辨率过高降低分辨率、下调特征数上限6. 一点项目经验总结整趟下来最大的体会是单目IMU的SLAM系统标定和配置的优先级远高于调参技巧。环境编译能靠搜索引擎解决调参也有大量经验帖可以参考但传感器标定一旦糊弄过去后面所有现象都失去解释依据。yaw漂移也好、初始化失败也好很多时候追根溯源都是标定数据不扎实。另外一件事跑通demo只是起点。数据集和实时运行完全是两个世界数据集的图像干净、时间戳整齐实时相机则要考虑自动曝光、白平衡、震动、不同分辨率、USB带宽这些乱七八糟的因素。从EuRoC到ZED我花在工程适配上的时间比跑通算法本身还多。还有一个小建议每次改配置之前先把当前版本的可执行文件和yaml配置备份一下。我因为连续调参忘了备份回头找一组之前跑得还行的参数花了整整一个下午。如果后面要继续扩展可以在ORBSLAM3输出位姿的接缝处加一层滤波或者和里程计做融合把单目IMU的轨迹用在更完整的导航框架里。那又是另一个故事了这篇先写到这里。