1. 为什么选择双目ORB-SLAM2与EuRoC数据集这个组合
双目视觉SLAM一直是机器人、无人机、AR/VR领域里绕不开的一块硬骨头。单目SLAM存在尺度不确定和初始化漂移的问题,RGB-D虽然能直接拿到深度,但受限于红外结构光的测量距离和室外强光干扰,真正在户外飞行的平台上,双目方案才是更稳妥的选择。ORB-SLAM2作为视觉SLAM里被引用最多的开源框架之一,它的双目模式在精度、鲁棒性和回环检测上都有比较成熟的表现,而EuRoC MAV数据集则是苏黎世联邦理工发布的微型飞行器室内数据集,自带双目灰度图、IMU数据以及运动捕捉系统提供的真值轨迹,是验证双目SLAM算法最常用的基准之一。
把这两者组合起来跑通,意义在于:你能在一个有真值参考的环境下,完整地观察ORB-SLAM2的双目前端如何提取ORB特征、如何做双目立体匹配、如何通过特征点三角化恢复尺度,以及后端如何做局部BA和回环优化。对于刚接触SLAM的开发者来说,这是从理论走向工程实践的一条捷径;对于已经做过单目SLAM的人来说,切换到双目模式能帮你理解尺度恢复和立体匹配这两个关键环节的差异。
这篇文章面向的是有一定C++和Linux基础、想动手跑通双目SLAM的开发者。我会从环境准备、代码修改、参数配置、运行调试到结果评估,把整个流程拆开讲清楚,同时把我在实际操作中踩过的坑和总结的技巧一并分享出来。整个流程基于常见的工程实践补充细节,你照着做基本能复现。
2. 环境准备与依赖安装的完整流程
2.1 系统环境与基础依赖
我用的环境是Ubuntu 18.04,这是ORB-SLAM2社区里兼容性最好的版本之一。Ubuntu 20.04也能跑,但Pangolin和OpenCV的版本匹配会稍微麻烦一点。如果你用的是更新的系统,建议直接用Docker起一个18.04的容器,省去大量依赖冲突的排查时间。
基础依赖包括编译工具链、CMake、Git这些常规工具,以及ORB-SLAM2强依赖的几个库:OpenCV、Eigen3、Pangolin、DBoW2和g2o。后两个其实已经包含在ORB-SLAM2的第三方目录里,不需要单独安装,但OpenCV、Eigen3和Pangolin必须提前装好。
sudo apt update sudo apt install -y build-essential cmake git libgtk2.0-dev pkg-config \ libavcodec-dev libavformat-dev libswscale-dev libtbb2 libtbb-dev \ libjpeg-dev libpng-dev libtiff-dev libdc1394-22-dev上面这串是OpenCV编译前需要的基础库。很多人装OpenCV时只装主包,结果编译到一半报缺少某个codec,回头再补很浪费时间。
2.2 OpenCV版本选择与安装
ORB-SLAM2官方推荐OpenCV 3.4.x系列,我用的是3.4.16。为什么不建议用OpenCV 4.x?因为ORB-SLAM2源码里有一些API在4.x里被移除了,比如CV_LOAD_IMAGE_UNCHANGED这种常量,虽然可以手动改,但改动点比较分散,容易漏。如果你非要用4.x,记得把源码里所有旧常量替换成新的枚举名。
编译OpenCV的时候,有几个CMake选项值得注意:
cmake -D CMAKE_BUILD_TYPE=Release \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D WITH_CUDA=OFF \ -D BUILD_EXAMPLES=OFF \ -D BUILD_opencv_python2=OFF \ -D BUILD_opencv_python3=OFF \ ..关掉CUDA是因为ORB-SLAM2本身不用GPU加速,开了反而增加编译时间和依赖复杂度。关掉Python绑定和示例能省不少编译时间。make -j4根据你机器的核数调整,内存小于8G的话建议用-j2,否则容易在链接阶段被OOM killer干掉。
注意:OpenCV安装完成后一定要执行
sudo ldconfig刷新动态链接库缓存,否则编译ORB-SLAM2时可能报找不到so文件。
2.3 Eigen3与Pangolin的安装要点
Eigen3直接用apt装就行,版本3.3以上都可以:
sudo apt install -y libeigen3-devPangolin是ORB-SLAM2的可视化依赖,负责显示特征点、轨迹和地图点。它依赖GLEW、GLU和Python。安装Pangolin时最容易出问题的是它自带的libpng和系统库冲突,如果编译报错,可以在CMake里加上-DBUILD_EXAMPLES=OFF跳过示例编译。
Pangolin的版本也有讲究。v0.5和v0.6在API上有细微差别,ORB-SLAM2的源码对v0.5兼容性更好。如果你用v0.6,可能会遇到glGenVertexArrays相关的编译错误,需要在CMakeLists里显式链接GL库。
2.4 ORB-SLAM2源码编译与常见报错
从官方仓库克隆源码后,先别急着./build.sh。我建议手动分步编译,这样出错时能定位到具体模块:
cd ORB_SLAM2 chmod +x build.sh ./build.shbuild.sh会依次编译DBoW2、g2o和ORB-SLAM2主库。常见的报错有三类:一是找不到Eigen,通常是/usr/include/eigen3路径没被CMake识别,可以在CMakeLists里加include_directories("/usr/include/eigen3");二是Pangolin链接失败,检查/usr/local/lib下是否有libpango*.so;三是C++标准问题,ORB-SLAM2用的是C++11,如果你的编译器默认C++14以上,某些旧写法会报警告但不影响编译。
编译完成后,lib/libORB_SLAM2.so和各个可执行文件应该都生成了。这时候先别跑EuRoC,用TUM数据集快速验证一下环境是否正常,能省去后面排查环境问题的时间。
3. EuRoC数据集的结构与使用方式
3.1 数据集目录结构解析
EuRoC数据集每个序列(比如MH_01_easy、V1_01_easy)下载解压后,目录结构是这样的:
MH_01_easy/ ├── cam0/ │ └── data/ │ ├── 1403636579763555584.png │ └── ... ├── cam1/ │ └── data/ │ └── ... ├── imu0/ │ └── data.csv ├── state_groundtruth_estimate0/ │ └── data.csv └── sensor.yamlcam0和cam1分别是左右目相机的图像序列,文件名是纳秒级时间戳。imu0里是200Hz的IMU数据,state_groundtruth_estimate0里是运动捕捉系统给出的真值位姿。sensor.yaml里记录了相机内参、畸变系数、双目基线、IMU噪声参数等关键信息。
ORB-SLAM2的双目模式需要的是左右目图像的时间戳对齐。EuRoC的cam0和cam1图像是硬件同步的,时间戳基本一致,但偶尔会有几纳秒的偏差。ORB-SLAM2内部用时间戳做关联,如果偏差太大可能导致某帧匹配不上,所以建议在读取时做一次时间戳对齐检查。
3.2 相机参数提取与配置文件编写
ORB-SLAM2的双目配置文件格式是YAML,需要包含相机内参、畸变系数、双目基线、图像尺寸等信息。EuRoC的sensor.yaml里给的是原始参数,但ORB-SLAM2需要的格式不太一样,需要手动转换。
以MH_01_easy为例,cam0的内参是:
Camera.fx: 458.654 Camera.fy: 457.296 Camera.cx: 367.215 Camera.cy: 248.375 Camera.k1: -0.28340811 Camera.k2: 0.07395907 Camera.p1: 0.00019359 Camera.p2: 1.76187114e-05 Camera.width: 752 Camera.height: 480 Camera.fps: 20.0 Camera.bf: 47.90639384423901这里的Camera.bf是基线乘以焦距,EuRoC的基线大约是0.11米,焦距458.654,乘起来约50.45。但sensor.yaml里给的bf是47.9,这是因为实际标定时的焦距略有差异。建议直接用sensor.yaml里的值,不要自己算。
提示:
Camera.fps在双目模式下其实不影响运行,ORB-SLAM2是按时间戳处理帧的,但填20.0是EuRoC的实际采集频率,保持一致性比较好。
3.3 图像序列的读取与时间戳处理
ORB-SLAM2的双目示例程序stereo_euroc已经写好了EuRoC的读取逻辑,但它的读取方式比较粗暴:把cam0和cam1的图像路径分别存成两个vector,然后按索引一一对应。这种方式假设左右目图像数量完全一致且时间戳严格对齐。实际上EuRoC大部分序列满足这个条件,但V2_03这种序列偶尔会有丢帧。
如果你要自己写读取逻辑,建议用时间戳做关联而不是索引。具体做法是:先读cam0的所有时间戳,再读cam1的所有时间戳,然后用双指针法找最近邻匹配。匹配阈值设成1毫秒以内,超过这个阈值的帧直接丢弃。这样能避免因为丢帧导致的左右目错位。
另外,EuRoC的图像是灰度图,ORB-SLAM2的双目模式默认就是处理灰度图,所以不需要做颜色空间转换。如果你用的是其他数据集,记得在读取时用cv::IMREAD_GRAYSCALE。
4. 双目ORB-SLAM2的核心配置与代码修改
4.1 配置文件的关键参数详解
ORB-SLAM2的双目配置文件里,除了相机内参,还有几个影响性能的关键参数:
| 参数名 | 含义 | 推荐值 | 调整影响 |
|---|---|---|---|
| ORBextractor.nFeatures | 每帧提取的特征点数 | 1200 | 增大提升精度但降低帧率 |
| ORBextractor.scaleFactor | 金字塔缩放因子 | 1.2 | 减小增加尺度覆盖但增加计算量 |
| ORBextractor.nLevels | 金字塔层数 | 8 | 增大提升大尺度变化鲁棒性 |
| ORBextractor.iniThFAST | 初始FAST阈值 | 20 | 降低增加特征点但引入噪声 |
| ORBextractor.minThFAST | 最小FAST阈值 | 7 | 同上,用于补充特征 |
| Viewer.KeyFrameSize | 关键帧显示尺寸 | 0.05 | 仅影响可视化 |
| Viewer.ViewpointX/Y/Z | 视角位置 | 根据场景调整 | 仅影响可视化 |
对于EuRoC这种室内场景,纹理比较丰富但运动比较剧烈,我建议把nFeatures设成1500,nLevels保持8,iniThFAST设成20。如果发现跟踪丢失频繁,可以把iniThFAST降到15,但要注意特征点多了之后后端优化会变慢。
4.2 双目匹配与深度计算的关键代码
ORB-SLAM2的双目匹配在Frame.cc里的ComputeStereoMatches函数中实现。它的核心逻辑是:对左目每个特征点,在右目图像上沿极线搜索匹配点,然后用三角化计算深度。
关键步骤包括:
- 对左目特征点按行分组,因为双目校正后极线是水平的,同一行的特征点才可能匹配。
- 对每个左目特征点,在右目对应行的
[minU, maxU]范围内搜索ORB描述子最相似的点。 - 用SAD(Sum of Absolute Differences)做粗匹配,再用描述子距离做精匹配。
- 对匹配成功的点做抛物线拟合,得到亚像素级的视差。
- 用
z = fx * bf / disparity计算深度,其中bf是基线乘焦距。
这里有个细节:minU和maxU的计算依赖于bf和最小/最大深度阈值。ORB-SLAM2默认的最小深度是0,最大深度是bf(即视差为1时的深度)。对于EuRoC,基线0.11米,焦距458,最大深度约50米,室内场景完全够用。
如果你发现深度图里有很多噪点,可以检查ComputeStereoMatches里的thOrbDist阈值。默认是0.7,调小到0.6能过滤掉一些误匹配,但会减少有效深度点数量。
4.3 需要修改的源码点与编译选项
跑EuRoC双目,官方示例stereo_euroc基本可以直接用,但有几个地方建议改一下:
第一,Examples/Stereo/stereo_euroc.cc里的LoadImages函数用的是cv::imread,默认读彩色图。EuRoC是灰度图,虽然读彩色再转灰度也能跑,但多了一次转换。改成cv::IMREAD_GRAYSCALE能省一点时间。
第二,示例里的时间戳读取用的是std::stod,对于EuRoC的纳秒时间戳,double精度可能不够。建议改成std::stoll读成long long,再除以1e9转成秒。不过ORB-SLAM2内部用的是double存时间戳,所以这个改动主要是为了避免读取时的精度损失。
第三,如果你要跑所有EuRoC序列做批量测试,建议把示例程序改成接受命令行参数指定序列路径,而不是硬编码。这样能省去反复改代码的麻烦。
编译时,确保CMakeLists.txt里链接了正确的OpenCV版本。如果系统里有多个OpenCV,用find_package(OpenCV 3.4 REQUIRED)指定版本号。
5. 完整运行流程与实操记录
5.1 单序列运行的完整命令
假设你已经编译好了ORB-SLAM2,EuRoC数据集放在~/datasets/EuRoC/MH_01_easy,配置文件放在ORB_SLAM2/Examples/Stereo/EuRoC.yaml,词袋文件用ORBvoc.txt,运行命令是:
./Examples/Stereo/stereo_euroc \ Vocabulary/ORBvoc.txt \ Examples/Stereo/EuRoC.yaml \ ~/datasets/EuRoC/MH_01_easy \ Examples/Stereo/EuRoC_TimeStamps/MH01.txt最后一个参数是时间戳文件,ORB-SLAM2用它来关联左右目图像。这个文件需要自己生成,格式是每行一个时间戳(秒为单位),对应cam0的图像文件名。生成脚本可以用Python写:
import os import glob cam0_path = os.path.expanduser('~/datasets/EuRoC/MH_01_easy/cam0/data') timestamps = sorted([os.path.splitext(f)[0] for f in os.listdir(cam0_path)]) with open('MH01.txt', 'w') as f: for ts in timestamps: f.write(f'{int(ts)/1e9:.9f}\n')注意时间戳要转成秒并保留足够的小数位,否则关联时会丢精度。
5.2 运行过程中的可视化观察
程序启动后,Pangolin窗口会显示几个视图:左上角是当前帧的特征点跟踪情况,右上角是地图点和关键帧,下方是轨迹。双目模式下,你还能看到左右目的匹配点连线。
运行MH_01_easy时,我观察到几个现象:前几帧初始化时,地图点比较少,轨迹会有轻微抖动;随着关键帧增加,地图点迅速增多,轨迹逐渐平滑;回环检测触发时,整个地图会做一次全局优化,轨迹会有明显修正。
如果Pangolin窗口卡顿,可以在配置文件里把Viewer.KeyFrameSize调小,或者直接关掉可视化(把Viewer相关的参数注释掉),用SaveTrajectoryEuRoC保存轨迹后离线分析。
5.3 轨迹保存与精度评估方法
ORB-SLAM2运行结束后会保存两个轨迹文件:CameraTrajectory.txt和KeyFrameTrajectory.txt。前者是每帧的位姿,后者是关键帧的位姿。评估精度时,通常用evo工具对比真值轨迹。
pip install evo evo_traj tum CameraTrajectory.txt --ref=MH01_groundtruth.txt -p --plot_mode=xyz evo_ape tum MH01_groundtruth.txt CameraTrajectory.txt -va --plotevo_ape会输出ATE(Absolute Trajectory Error)的RMSE、均值、中位数等指标。MH_01_easy上,调好参数的ORB-SLAM2双目模式ATE通常在0.05米左右,V1_01_easy因为运动更剧烈,ATE会稍大一些。
注意:EuRoC的真值轨迹和ORB-SLAM2的坐标系可能不一致,评估前需要做对齐。
evo的--align选项可以自动做Sim3对齐,但如果你要严格评估尺度,应该用--align --correct_scale。
6. 常见问题与排查技巧实录
6.1 跟踪丢失与初始化失败
跟踪丢失是双目ORB-SLAM2最常见的问题。表现是Pangolin窗口里特征点突然变少,轨迹断裂,终端输出Track lost。原因通常有三类:一是图像纹理不足,比如对着白墙;二是运动过快导致帧间重叠区域太小;三是双目匹配失败,深度点太少。
排查时先看终端输出的特征点数量。如果每帧特征点少于100,说明ORB提取有问题,检查iniThFAST和nFeatures。如果特征点够但深度点少,检查双目匹配的thOrbDist和bf参数。如果是运动过快,可以降低图像播放速率,或者用IMU数据做预积分辅助(需要额外改代码)。
6.2 轨迹漂移与尺度不一致
双目SLAM理论上尺度是确定的,因为基线提供了绝对尺度。但如果标定参数不准,尺度会有偏差。EuRoC的标定参数比较准,一般不会出问题。如果你发现轨迹整体缩放不对,检查Camera.bf是否填错。bf是基线乘焦距,单位是米乘像素,填成厘米或毫米会导致尺度差几个数量级。
漂移则主要来自累积误差。ORB-SLAM2有回环检测和全局BA,能修正大部分漂移。如果漂移仍然明显,检查回环检测是否触发。可以在配置文件里把LoopClosing相关的参数调激进一些,比如降低minScore阈值。
6.3 编译与运行时的典型报错速查
| 报错信息 | 可能原因 | 解决方法 |
|---|---|---|
undefined reference to cv::... | OpenCV版本不匹配 | 检查CMakeLists里的OpenCV版本 |
cannot find -lpango | Pangolin未安装或路径不对 | 重新安装Pangolin并ldconfig |
Segmentation faultat startup | 配置文件路径错误 | 检查YAML文件路径和格式 |
Track lostimmediately | 时间戳文件格式错误 | 检查时间戳是否为秒且精度足够 |
Killedduring build | 内存不足 | 减少make的-j参数 |
No images found | 数据集路径错误 | 检查路径是否指向序列根目录 |
6.4 提升运行效率的实用技巧
EuRoC一个序列有几千帧,跑完一遍要十几分钟。如果想快速验证,可以只跑前500帧,在时间戳文件里截取前500行即可。另外,关掉Pangolin可视化能提升约20%的速度,因为可视化渲染占了不少CPU。
如果要做批量测试,建议写一个shell脚本遍历所有序列,每个序列跑完后自动用evo评估并记录结果。这样能省去大量手动操作。
7. 参数调优与性能提升的进阶思路
7.1 特征点数量与跟踪精度的平衡
nFeatures是最直接影响性能的参数。设成1000时,帧率能到30fps以上,但跟踪精度一般;设成2000时,精度提升但帧率降到15fps左右。对于EuRoC这种20Hz采集的数据集,帧率只要不低于20fps就不会丢帧。我建议设成1500,在精度和速度之间取平衡。
另外,scaleFactor和nLevels影响的是尺度不变性。EuRoC的飞行器运动比较剧烈,尺度变化大,nLevels设成8是合理的。如果你跑的是手持设备的数据,运动平缓,可以降到6。
7.2 双目匹配阈值对深度图的影响
thOrbDist控制双目匹配的严格程度。默认0.7,调低到0.6能减少误匹配,但深度点会变少。调高到0.8会增加深度点,但可能引入错误深度。我的经验是:纹理丰富的场景用0.7,纹理弱的场景用0.6。
还有一个隐藏参数是minDepth和maxDepth,在Frame.cc里硬编码。默认minDepth=0,maxDepth=bf。对于EuRoC,maxDepth约50米,室内场景够用。如果你跑室外数据,需要把maxDepth调大,否则远处点会被丢弃。
7.3 回环检测与全局优化的触发条件
ORB-SLAM2的回环检测基于DBoW2词袋模型,触发条件是当前关键帧和候选关键帧的相似度超过阈值。EuRoC的室内场景回环比较多,MH_01_easy里飞行器绕了一圈回到起点,回环检测能明显修正轨迹。
如果回环没触发,检查LoopClosing里的minScore。默认是0.015,可以降到0.01。但降太低会引入误回环,导致地图错乱。另一个方法是增加关键帧的密度,让回环候选更多。
8. 我在实际调试中的几点体会
跑通EuRoC双目ORB-SLAM2这件事,说难不难,说简单也不简单。环境配置是最耗时的部分,尤其是OpenCV和Pangolin的版本匹配,我前后重装了三次才找到稳定的组合。建议新手直接用Docker,能省掉大量依赖排查的时间。
参数调优方面,不要一上来就改代码。先把默认参数跑一遍,看看基线效果,再针对具体问题调整。我见过有人把nFeatures设成5000,结果帧率掉到5fps,反而更容易丢跟踪。
最后分享一个小技巧:如果你要对比不同参数的效果,建议用同一个序列的前500帧做快速测试,不要每次都跑完整序列。这样一轮测试只要两三分钟,调参效率能提升好几倍。等参数稳定了,再跑完整序列做最终评估。