ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ORB-SLAM2 编译避坑、数据集评估与真机调试全流程

ORB-SLAM2 编译避坑、数据集评估与真机调试全流程 ORB-SLAM2 这个仓库我已经不记得 clone 过多少遍了。第一次是在一台 4G 内存的虚拟机上照着 README 一路敲最后卡在 Pangolin 链接报错上整整一个下午后来带实习生同样的问题又见了五六回几乎每次都是同一个套路build.sh跑到 80% 红一片回去翻日志发现是 OpenCV 版本或者-marchnative在作祟。所以当有人问我ORB-SLAM2 到底怎么装、怎么测的时候我不太想再复述一遍官方 README——那东西只告诉你敲什么命令不告诉你为什么。这篇东西想干的事就一件把 ORB-SLAM2 从这个算法在干什么讲到你的相机跑起来不丢中间所有版本兼容、编译选项、参数含义、数据集组织的细节都摊开说。刚入门的同学可以照着命令一步步走通已经跑通过一遍但轨迹飘得厉害、或者想接真实相机的同学重点看依赖版本和标定那两节。1. ORB-SLAM 到底在算什么先把系统结构摸清楚在动手装之前如果不知道这个系统由哪几块拼起来后面出问题只能靠猜。ORB-SLAM2 的代码结构其实相当清晰它的骨架是一套并行的多线程架构而不是把所有逻辑塞在一个循环里。1.1 四个线程各干各的活系统启动后会拉起三个常驻线程再加一个按需唤醒的第四个Tracking跟踪线程对每一帧图像提取 ORB 特征和上一帧或者局部地图做匹配估计当前帧位姿。如果匹配失败会尝试重定位也就是用词袋模型在历史关键帧里找这地方我好像来过。跟踪线程还要判断当前帧值不值得升级成关键帧判断依据通常是跟踪质量、距离上一个关键帧的间隔、以及共视关系。Local Mapping局部建图线程接住跟踪线程丢过来的关键帧做三角化生成新的地图点剔除质量差的地图点然后跑局部 BABundle Adjustment把当前关键帧窗口内所有位姿和地图点一起优化。这个线程决定了地图长得准不准。Loop Closing回环检测线程拿新关键帧去和数据库里的老关键帧比看是不是回到了曾经走过的位置。一旦确认回环先做位姿图优化把漂移摊平然后触发一个独立的全局 BA 线程把整个地图重新优化一遍。Place Recognition 线程本质上和回环检测共用一套词袋数据库但它做的是更宽泛的我在哪查询用在重定位和回环候选筛选上。理解这四个线程的分工对调试很有帮助。比如你发现地图里同一面墙被建了两遍问题大概率出在回环检测这一环而不是跟踪如果每一帧位姿抖得厉害重点看跟踪线程的 BA 和局部建图的质量控制。1.2 为什么是 ORB 特征而不是 SIFT 或 SURF这是我最常被问到的设计问题。SIFT 精度好、鲁棒性强但它的计算量在没有 GPU 加速的情况下根本撑不住 30fpsSURF 稍快一些但依然是浮点描述子为主。ORB 用的是 FAST 角点加 BRIEF 描述子描述子是二进制的匹配时用汉明距离硬件上一条指令就能异或出一堆位速度优势非常明显。代价是旋转和大尺度变化下的鲁棒性弱于 SIFT但 SLAM 场景里相邻帧之间的视角变化本来就小这个代价是可以接受的。ORB-SLAM2 还在标准 ORB 上做了两层工程优化一是八层图像金字塔每层分配特征点数量按面积开方衰减保证不同尺度下都有足够特征二是四叉树均匀化把画面切成格子逐层细分避免特征点全挤在纹理丰富的角落。这两点直接决定了跟踪的稳定性ORBextractor.nFeatures这个参数改的只是总数均匀化策略是写死在代码里的。1.3 ORB-SLAM2 支持哪些传感器配置模式输入尺度典型应用单目单张图像序列不可观测需要 Sim3 对齐教学、低成本验证双目左右图像 基线真实尺度车载、机器人RGB-D彩色 深度图真实尺度室内、手持扫描单目那条要特别强调单目 ORB-SLAM2 估计出来的轨迹是没有绝对尺度的你评估的时候如果直接用evo_ape不加重尺度对齐会得到一个看着很难看的误差值然后误以为自己代码写错了。这个问题后面评估那节还会细说。2. 依赖版本比命令更重要装之前先对齐版本ORB-SLAM2 是 2016-2017 年的代码那个年代的主流环境是 Ubuntu 16.04 ROS Kinetic OpenCV 3.2。现在大家手里多是 Ubuntu 20.04 甚至 22.04OpenCV 默认装的是 4.x直接照 README 走必然翻车。所以第一步不是敲命令是先决定我要不要把 OpenCV 降到 3.x。2.1 依赖清单与版本对应关系依赖最低要求推荐版本备注编译器支持 C11gcc 7 以上建议直接 gcc 9 / 11CMake2.83.10 以上新版 CMake 会警告旧语法一般无害Eigen33.1.03.3.x必须装libeigen3-devOpenCV2.4.33.4.x 最省事用 4.x 需要改代码Pangolin任意v0.5 或 v0.6版本不合会链接报错DBoW2 / g2o仓库自带用自带在Thirdparty/下ROS可选Kinetic 以上Melodic / Noetic只有跑 ROS 节点才需要我的经验是如果只是为了学习和评估精度踏踏实实用 OpenCV 3.4。Ubuntu 18.04 有现成的libopencv-dev3.2 包20.04 及以上就得自己源码编译一份 3.4或者接受打补丁。自己编译 OpenCV 3.4 大概要 20 分钟到 40 分钟比后面一路改代码省心得多。2.2 用 OpenCV 4 要改哪些地方如果你坚持用系统自带的 OpenCV 4至少有两类改动躲不掉。第一类是枚举名变更。OpenCV 4 删掉了CV_LOAD_IMAGE_UNCHANGED、CV_LOAD_IMAGE_GRAYSCALE这些老宏改成cv::IMREAD_UNCHANGED、cv::IMREAD_GRAYSCALE。ORB-SLAM2 的几个 demo 文件里用了老宏编译时会直接报未定义grep -rl CV_LOAD_IMAGE . | xargs sed -i s/CV_LOAD_IMAGE_UNCHANGED/cv::IMREAD_UNCHANGED/g grep -rl CV_LOAD_IMAGE_GRAYSCALE . | xargs sed -i s/CV_LOAD_IMAGE_GRAYSCALE/cv::IMREAD_GRAYSCALE/g第二类是CMake 里的版本号。根目录CMakeLists.txt里写的是find_package(OpenCV 3.0 QUIET)ROS 示例目录下还有一个同样的写法都要改成 4。另外 OpenCV 4 用到CV_RGB2GRAY这类老转换码的地方也要替换成cv::COLOR_RGB2GRAY虽然 ORB-SLAM2 主体代码里这块用得不多但第三方示例里偶尔会撞上。提示改完记得把build/整个删掉重新 cmake。因为 CMake 会把OpenCV_DIR缓存进CMakeCache.txt不清理的话改了也没用这种现象特别容易让人误判我明明改了为什么不生效。2.3 Pangolin 和 OpenGL 那一堆链接错误比 OpenCV 更烦的是 Pangolin。新版 Pangolinv0.6 之后内部 API 和头文件组织都改过ORB-SLAM2 里对pangolin::View、pangolin::GlTexture的用法会和它对不上链接阶段报一片undefined reference to pangolin::...。稳妥做法是切到老版本git clone https://github.com/stevenlovegrove/Pangolin.git cd Pangolin git checkout v0.5 mkdir build cd build cmake .. make -j4 sudo make install编译 Pangolin 之前系统得先有 GL 相关的开发库sudo apt install libglew-dev libgl1-mesa-dev libepoxy-dev。少一个 GLEWPangolin 自己就编不过去而它编不过的时候报错信息往往指向别的地方容易带偏方向。还有一个环境层面的坑如果你在虚拟机里跑一定要在虚拟机设置里打开 3D 加速。否则 Pangolin 创建 OpenGL 上下文时会直接失败表现是程序启动后立刻退出或者报一句很含糊的Unable to create OpenGL context。这个跟代码一点关系都没有纯粹是虚拟化图形支持没开。2.4 CMake 里那个-marchnative是颗定时炸弹ORB-SLAM2 的CMakeLists.txt在 Release 模式下追加了-O3 -marchnative。在自己机器上编译、自己机器上跑这没问题但如果你在 A 机器上编译、把产物拷到 B 机器上跑或者用 Docker 构建、宿主机执行就会出现经典的Illegal instruction (core dumped)——编译器针对构建机的指令集做了优化目标机不支持。处理方式很简单把-marchnative换成具体的架构或者干脆去掉set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} -O3)顺带提一句build.sh里写死了make -j4。在 2G / 4G 内存的机器上编Thirdparty/g2o的时候极容易 OOM 被系统杀掉日志上看不出错误就是突然中断。内存紧张就手动改成make -j2。3. 编译全流程从 clone 到 exe 落地环境对齐之后编译本身反而不难关键是搞清楚每一步在干什么出问题才知道往哪查。3.1 源码结构与三方库clone 下来之后顶层目录大致是这样ORB_SLAM2/ ├── Examples/ 各种传感器模式的 demo 与配置文件 │ ├── Monocular/ │ ├── Stereo/ │ ├── RGB-D/ │ ├── ROS/ ROS 节点源码 │ └── AR/ 一个小型的 AR 演示 ├── include/ 头文件 ├── src/ 核心实现 ├── Thirdparty/ │ ├── DBoW2/ 词袋模型的实现 │ └── g2o/ 图优化库 ├── Vocabulary/ ORBvoc.txt压缩包里约 145MB ├── build.sh └── build_ros.shVocabulary/ORBvoc.txt.tar.gz需要解压build.sh会自动处理。这个文件是训练好的 ORB 词袋字典系统启动时要把它读进内存并构建树结构加载过程通常要几秒到十几秒占用的内存在几百 MB 量级。如果你在内存吃紧的设备上跑这会是第一道坎。3.2build.sh逐行解释官方脚本做的事情就三步理解之后你完全可以手动执行cd Thirdparty/DBoW2 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j2 cd ../../../Thirdparty/g2o mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j2 cd ../../../ tar -xf Vocabulary/ORBvoc.txt.tar.gz -C Vocabulary/ mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make -j2顺序很重要DBoW2 和 g2o 必须先编因为主工程的CMakeLists.txt里是直接引用Thirdparty/DBoW2/lib和Thirdparty/g2o/lib下的产物。如果你先编主工程会看到一堆找不到libDBoW2.so的链接错误这时候不用慌按顺序补编一遍就行。编译产物落在lib/libORB_SLAM2.so和各Examples/*/目录下的可执行文件里。build.sh最后会输出Examples/Monocular/mono_tum之类的可执行文件看到它们就说明主体通过了。3.3 ROS 节点单独编ROS 那部分走build_ros.sh但跑之前必须先告诉 catkin 去哪找包export ROS_PACKAGE_PATH${ROS_PACKAGE_PATH}:/home/yourname/ORB_SLAM2/Examples/ROS ./build_ros.sh这里有个高频失误点ROS_PACKAGE_PATH只在当前终端有效换了终端窗口再编就找不到包。要么写进~/.bashrc要么养成编 ROS 之前先 export的习惯。另外在 Ubuntu 20.04 Noetic 环境下ROS 示例目录里的CMakeLists.txt也用了find_package(OpenCV 3.0 QUIET)同样要改成 4。3.4 编译期报错速查报错关键词根因处理undefined reference to pangolin::Pangolin 版本过新降到 v0.5清理 build 重编CV_LOAD_IMAGE_UNCHANGED was not declaredOpenCV 4 删除老宏全局替换为cv::IMREAD_*Could NOT find OpenCVCMake 里版本号写死 3.0改成 4删CMakeCache.txtc: internal compiler error: Killed内存不足make -j1或加 swapIllegal instruction运行时报-marchnative跨机去掉该编译选项fatal error: Eigen/Core: No such fileEigen 头文件路径装libeigen3-dev并确认 include 路径4. 数据集测试先证明它能跑再谈它准不准装完之后我建议先别急着接自己的相机。用公开数据集跑通一遍你才有一个基准才能区分是我相机标定错了还是系统本身就这样。4.1 选哪个数据集三个数据集的特点很分明TUM RGB-D室内手持序列短几十秒到几分钟文件组织简单适合第一次跑通。fr1_xyz这种序列只有几百帧跑完不到一分钟。KITTI车载双目室外大场景序列长、运动快适合验证双目模式在高速运动下的表现也方便和别人的公开结果对比。EuRoC MAV无人机室内双目加 IMU运动剧烈是检验鲁棒性的好材料。第一遍我推荐 TUM 的fr1_xyz因为它自带 RGB-D 和单目两种输入一份数据能测两种模式。4.2 RGB-D 模式为什么要先做 associationsTUM 数据集里彩色图和深度图是两条独立的列表rgb.txt和depth.txt时间戳不是严格一一对应的深度图采集频率还更低。ORB-SLAM2 的 RGB-D 示例需要一个associations.txt每一行是彩色图路径 时间戳 深度图路径 时间戳把时间差在阈值内的两张图配对起来。TUM 官方提供了associate.py。如果你的 Python 环境版本较新脚本可能因为语法兼容问题跑不起来改一下或者找社区维护的 Python 3 版本即可python associate.py rgb.txt depth.txt associations.txt注意生成的路径要是相对路径形如rgb/1305031102.175304.png因为示例程序会把数据集根目录和这个相对路径拼起来。如果你在数据集目录外面运行脚本生成了绝对路径程序会拼出一个不存在的路径然后静默跳过所有帧——表现就是程序秒退什么都没输出。4.3 三条命令跑三种模式假设你已经cd到ORB_SLAM2根目录# 单目输入是数据集目录内部要能找到 rgb.txt ./Examples/Monocular/mono_tum \ Vocabulary/ORBvoc.txt \ Examples/Monocular/TUM1.yaml \ /data/rgbd_dataset_freiburg1_xyz # RGB-D需要额外的 associations 文件 ./Examples/RGB-D/rgbd_tum \ Vocabulary/ORBvoc.txt \ Examples/RGB-D/TUM1.yaml \ /data/rgbd_dataset_freiburg1_xyz \ Examples/RGB-D/associations/fr1_xyz.txt # 双目KITTI 序列 ./Examples/Stereo/stereo_kitti \ Vocabulary/ORBvoc.txt \ Examples/Stereo/KITTI00-02.yaml \ /data/dataset/sequences/00跑起来之后会弹出一个 Pangolin 窗口左边显示当前帧和提取到的特征点右边是实时构建的稀疏点云和相机轨迹。三块窗口分别对应跟踪状态、地图点和关键帧连接关系。一个必须知道的事实这些 demo 是离线跑的图片读一张处理一张能跑多快跑多快不是按原始帧率回放。所以你在 TUM 上看到轨迹平滑、没什么问题不代表实时接相机的效果一样好——真实相机有运动模糊、有曝光变化、有丢帧这些在数据集里都被洗过了。4.4 用 evo 评估轨迹精度程序退出时会在当前工作目录生成CameraTrajectory.txt和KeyFrameTrajectory.txt。前者是每帧位姿后者只保留关键帧。评估一般用前者。我更习惯用evo它比仓库自带脚本顺手得多pip install evo --upgrade # 单目轨迹必须用 -as 做相似变换对齐否则误差没有意义 evo_ape tum groundtruth.txt CameraTrajectory.txt -as -va --plot --plot_mode xy # 只看轨迹形状对比 evo_traj tum CameraTrajectory.txt --ref groundtruth.txt -as -p --plot_mode xyzTUM 的 ground truth 文件格式是时间戳 tx ty tz qx qy qz qw正好对应evo_ape tum。KITTI 的 ground truth 是 12 个数字一行的位姿矩阵需要用 evo 里带的转换脚本转一下再配evo_ape kitti使用。关于那个-as再啰嗦一句单目系统估出来的轨迹和真值之间差一个任意的尺度因子和一个刚体变换-as表示同时对齐尺度和位姿。不加这个参数你会看到一个几米甚至几十米的 ATE然后陷入我是不是装错了版本的自我怀疑。4.5 结果怎么读才算合理TUMfr1_xyz这个序列RGB-D 模式下跑出来的 ATE 通常在几厘米量级单目模式通常在几厘米到十几厘米之间浮动对齐之后。如果你的单目结果只有几厘米别高兴太早先检查是不是把-as去掉后依然很小——那说明你可能用了真值初始化评估没意义。真正值得关注的是轨迹末端有没有明显跳变。点云图视角下如果你看到轨迹在某个位置突然拐了一个和实际路径不符的弯多半是回环检测误触发或者那个位置纹理太弱导致跟踪丢失后重定位到错误位置。这种情况把KeyFrameTrajectory.txt单独画一遍会更容易定位到出问题的帧段。5. 接真实相机标定和话题这两件事最容易出错数据集跑通之后接自己的相机是下一个关卡也是踩坑最密集的地方。5.1 标定参数怎么写进 yamlORB-SLAM2 的配置文件是 YAML单目、双目、RGB-D 三种模板都在Examples/*/下。参数数量不少但真正决定成败的就那几个Camera.fx: 535.4 Camera.fy: 539.2 Camera.cx: 320.1 Camera.cy: 247.6 Camera.k1: 0.0 Camera.k2: 0.0 Camera.p1: 0.0 Camera.p2: 0.0 Camera.k3: 0.0 Camera.width: 640 Camera.height: 480 Camera.fps: 30.0 Camera.bf: 40.0 ThDepth: 40.0 DepthMapFactor: 5000.0fx/fy/cx/cy是内参标定方法两种用 ROS 的camera_calibration包配合棋盘格或者用 OpenCV 的calibrateCamera自己写十几行代码。内参标错一点点轨迹就会以看得见的速度漂尤其是fx和fy偏差超过 2% 的时候。所以标完一定要看重投影误差一般要压到 0.3 像素以内才算合格。Camera.bf是双目基线和焦距的乘积单目模式下这个值用不上但格式上要留着。DepthMapFactor是深度图的缩放因子RGB-D 相机给出来的深度值往往是整数需要除以这个因子换成米。RealSense 系列常见的是 1000Kinect v1 是 5000TUM 数据集用的也是 5000。这个值填错整张地图的尺度就整体错了但轨迹形状看着还挺像特别有迷惑性。ThDepth决定多远之外的点只靠视差判断、不参与深度约束室内小场景一般给 35 到 40室外大场景可以给到 60。5.2 ROS 节点的编译与话题对齐ROS 节点跑起来的命令形式是这样的rosrun ORB_SLAM2 Mono /path/to/Vocabulary/ORBvoc.txt /path/to/MyCamera.yaml它订阅的话题名是写死在源码里的不同模式不一样节点订阅话题Mono/camera/image_rawRGB-D/camera/rgb/image_raw/camera/depth_registered/image_rawStereo/camera/left/image_raw/camera/right/image_raw你自己的相机驱动发出来的话题名大概率不是这个尤其是 RGB-D 深度图那条很多驱动发的是/camera/depth/image_raw而不是/camera/depth_registered/image_raw。两个办法在 launch 文件里用remap把话题重映射过去或者直接改ros_rgbd.cc里的订阅名重新编译。前者更干净后者更快。调试时可以先用rostopic hz /camera/image_raw确认帧率稳定再用rqt_image_view看一眼图是不是正常。别小看这一步我遇到过好几次SLAM 跑不起来最后发现是相机驱动根本没出图纯粹白折腾。5.3 实时跑起来之后调什么实时运行和离线跑数据集最大的差别是时间预算。系统需要在 33ms 内完成一帧的所有处理才能跟上 30fps。几个可以调的地方ORBextractor.nFeatures默认 1000降低到 800 能省不少时间但弱纹理环境下更容易跟丢。ORBextractor.nLevels默认 8一般不动。ThDepth和DepthMapFactor不影响速度影响精度。局部建图线程的频率代码里控制了关键帧插入的最小间隔太密会导致 BA 堆积CPU 一路飙满。如果 CPU 长期跑满还是掉帧先看是哪个线程吃满的。跟踪线程吃满说明特征提取太慢降nFeatures局部建图吃满说明 BA 规模太大可以考虑限制局部地图的关键帧窗口大小。6. 我踩过的那些坑从段错误到轨迹乱飘这一节是我这些年攒下来的排查链路按症状—可能原因—验证方式来组织。遇到问题的时候最怕的不是解决不了而是不知道从哪下手。6.1 启动就段错误日志还什么都没有这是最让人抓狂的一类。程序一跑就Segmentation fault连第一行日志都没打出来。按这个顺序排查基本能定位先确认词袋文件加载成功。在System构造函数里加一行打印或者直接把ORBvoc.txt的路径换成绝对路径试一次。路径写错时程序可能在加载阶段就崩。确认 yaml 文件能被解析。ORB-SLAM2 用 OpenCV 的FileStorage读 yaml格式稍微不对比如多了个 tab或者缩进层次乱了就会读到空值然后在后续计算出问题。可以写个小程序单独cv::FileStorage fs(path, cv::FileStorage::READ)验证一下能不能读出Camera.fx。确认 Pangolin 能创建窗口。如果你在远程 SSH 或者没有 X11 的环境里跑Pangolin 会直接失败。这种情况下用xvfb-run包一层或者临时把 viewer 相关代码注释掉mpViewer和mViewer跑纯计算模式。看是不是 OpenGL 驱动问题。虚拟机没开 3D 加速就是这个症状改虚拟机设置比重编代码快得多。6.2 跑着跑着跟丢了再也不回来跟踪丢失有两种表现一种是短暂丢失后成功重定位地图上会多出一堆重复的关键帧另一种是彻底回不来Pangolin 窗口里点云不再增长。第一种情况通常是纹理太弱或者运动太快。看特征点数量是不是经常掉到 100 以下如果是考虑把nFeatures提上去或者降低相机运动速度、改善光照。硬要治本的话加 IMU 做预积分那是 ORB-SLAM3 的事了比调参数有用。第二种情况先查重定位为什么失败。重定位靠的是词袋数据库如果数据库里压根没有相似场景那是数据的问题不是参数的问题。还有一个隐蔽的原因是时间戳单调性被破坏——有些驱动在丢帧之后会重发旧帧时间戳回退会让跟踪线程的状态机乱掉。这种情况得在驱动层解决。6.3 轨迹整体是对的但尺度不对这个症状很典型轨迹形状和真实路径几乎重合但整体偏大或偏小一个固定比例。RGB-D 和双目模式下出现这种情况九成是DepthMapFactor或者Camera.bf填错了。用一个已知长度的物体放在相机前面量一下估算深度比对着参数表猜要快得多。单目模式没有这个问题因为单目本来就没有绝对尺度反过来如果你在单目模式下发现轨迹形状都歪了那才是真有问题多半是内参标定不准或者关键帧选取策略不合适。6.4 关于内存和长时间运行ORB-SLAM2 跑长时间序列会持续吃内存地图点和关键帧都在累积。我在一台 8G 内存的机器上跑 KITTI 长序列跑到后面明显感觉到卡顿用top一看进程占了 5G 多。几个缓解思路一是定期清理长期没被观测到的地图点需要改代码二是把关键帧的 BA 窗口限制得更小三是接受跑一段重启一次这种土办法反正对精度评估来说切段评估本来也是常规做法。说点个人体会从第一次被 Pangolin 卡住到现在我对 ORB-SLAM2 的感情挺复杂的它不是最新最快的方案工程细节也不算现代但它把视觉 SLAM 应该由哪些模块组成、这些模块怎么协作这件事讲得足够清楚。很多人后来去看 ORB-SLAM3 或者别的方案之所以上手快本质上是因为在 ORB-SLAM2 上已经把数据流和调试路径建立起来了。如果你现在卡在某一步我的建议是别急着换环境、换版本、换数据集先把当前这一步的错误日志完整读一遍。这个系统大部分所谓的玄学问题最后都能落到版本不匹配、路径不对、时间戳不连续这三类上。另外编译成功只是起点真正的门槛在于你能看懂轨迹为什么漂、以及在什么条件下它会漂——这件事只能靠一次次跑数据、对比真值慢慢积累没有捷径。按上面的顺序走——先搞清楚结构再对齐版本编过之后用 TUM 跑通一版接着用 evo 量一遍误差最后才接真实相机——基本就绕开了我当年踩的那几个大坑。剩下的细节交给你们的日志去找。
返回列表