ORB-SLAM3代码详解 – 第 02 篇 · 编译、运行与代码工程结构
第 01 篇给出了系统的全局结构视图。本篇完成环境搭建与运行验证:从源码编译、
跑通 EuRoC 数据集、对照实际数据理解可视化窗口,到借 build.sh 与 CMakeLists.txt
厘清"依赖关系与构建产物",为后续逐模块的源码分析提供可运行、可调试的实验环境。
本篇命令、行号基于仓库 commit 4452a3c。环境以 Ubuntu 20.04 + OpenCV 4.x 为例。
1. 先看清依赖关系:四类依赖
ORB-SLAM3 的依赖可分为四类,区分清楚是顺利编译的前提:
┌─────────────────────────────────────────┐
│ ORB_SLAM3 (主库) │
│ lib/libORB_SLAM3.so │
└───────────────┬─────────────────────────┘
│
┌───────────────┬─────────┴────────┬──────────────────┐
▼ ▼ ▼ ▼
系统级第三方库 随仓库自带 头文件依赖 可选
(自己装) (Thirdparty/,build.sh编) (header-only)
───────────── ────────────────── ────────────── ──────────
OpenCV ≥4.4 DBoW2 (词袋) Eigen3 ≥3.1.0 realsense2
Pangolin g2o (图优化) Sophus(也在 (RealSense
Boost(序列化) Sophus(李群,头文件) Thirdparty/) 相机, 可不装)
(crypto/ssl)
1.1 为什么 DBoW2 / g2o / Sophus 要"自带"
这三个库在 Thirdparty/ 里,是作者修改过的特定版本,不能用系统 apt install 的版本替代:
- DBoW2:作者裁剪了词袋库,专门适配 ORB 的二进制描述子(汉明距离)。
- g2o:图优化框架,作者固定在某个版本并打了补丁,新版 g2o API 不兼容。
- Sophus:李群(SE3/Sim3)库,header-only(只有头文件),但仓库里仍给它建了 build 目录(生成测试/示例,主库其实只用头文件)。
关键结论:应使用 Thirdparty/ 内的版本。若系统中另装有其它版本的 g2o,需避免其 include 路径污染本工程的编译。
1.2 系统级依赖与版本红线
| OpenCV | find_package(OpenCV 4.4),找不到 >4.4 直接 FATAL_ERROR | 图像 I/O、特征、矩阵 |
| Eigen3 | find_package(Eigen3 3.1.0 REQUIRED) | 线性代数(贯穿全系统) |
| Pangolin | REQUIRED | 3D 可视化窗口 |
| Boost | 链接 -lboost_serialization | 地图序列化(存/读地图) |
| OpenSSL | 链接 -lcrypto | 计算地图校验/哈希 |
| realsense2 | find_package(realsense2)(不 REQUIRED) | Intel RealSense 实机示例,没有就跳过 |
需注意:CMakeLists 第 32 行固定为 find_package(OpenCV 4.4)。若环境中为
OpenCV 3.x,将直接报错 OpenCV > 4.4 not found。解决方式是改用 4.x,或将该行
修改为对应版本号(如 find_package(OpenCV 3.2))并自行处理 API 差异。
2. 逐行读懂 build.sh
仓库根目录的 build.sh 就是"一键编译"脚本。它做的事其实非常朴素——按依赖顺序,逐个 cmake + make:
# 1) 编译三个第三方库(注意都是 Release)
cd Thirdparty/DBoW2 && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release && make -j # 词袋
cd ../../g2o && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release && make -j # 图优化
cd ../../Sophus && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release && make -j # 李群
# 2) 解压预训练词袋(100 多 MB 的文本词典)
cd Vocabulary && tar -xf ORBvoc.txt.tar.gz
# 3) 编译主库 + 所有 Examples
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release && make -j4
三个要点:
运行:
chmod +x build.sh
./build.sh
3. 拆解 CMakeLists.txt:产物从哪来
3.1 编译选项(性能的来源)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O3")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=native")
IF(NOT CMAKE_BUILD_TYPE)
SET(CMAKE_BUILD_TYPE Release) # 默认 Release
ENDIF()
- -O3:最高级别优化。
- -march=native:针对当前 CPU 指令集编译(用上 AVX 等)。这是把双刃剑:
- 好处:特征匹配、矩阵运算明显加速;
- 代价:编译产物不可跨 CPU 架构拷贝。在较新 CPU 上编译、在较旧 CPU 上运行会触发 Illegal instruction 崩溃,容器与集群分发场景需特别注意。
- 语言标准为 C++11(CHECK_CXX_COMPILER_FLAG("-std=c++11" …),并 add_definitions(-DCOMPILEDWITHC11))。源码中以 #ifdef COMPILEDWITHC11 切换 std::chrono 计时 API 的写法即源于此。
3.2 主库 = 一个共享库
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
add_library(${PROJECT_NAME} SHARED
src/System.cc src/Tracking.cc src/LocalMapping.cc src/LoopClosing.cc
src/ORBextractor.cc src/ORBmatcher.cc src/Optimizer.cc …
src/CameraModels/Pinhole.cpp src/CameraModels/KannalaBrandt8.cpp …)
所有 src/*.cc 编进一个 lib/libORB_SLAM3.so。这就是我们整个专栏要逐个剖析的对象。
3.3 链接清单
add_subdirectory(Thirdparty/g2o) # 把 g2o 作为子项目一起构建
target_link_libraries(${PROJECT_NAME}
${OpenCV_LIBS} ${EIGEN3_LIBS} ${Pangolin_LIBRARIES}
${PROJECT_SOURCE_DIR}/Thirdparty/DBoW2/lib/libDBoW2.so
${PROJECT_SOURCE_DIR}/Thirdparty/g2o/lib/libg2o.so
-lboost_serialization
-lcrypto)
注意 include_directories 里把 Thirdparty/Sophus 也加了进来——这印证了 §1.1:Sophus 是以头文件形式被主库使用的,并不链接它的 .so。
3.4 可执行文件 = 各传感器示例
CMakeLists 里有 46 个 add_executable(部分被 if(realsense2_FOUND) 包住)。它们按传感器分组,输出到对应的 Examples/<类型>/ 目录:
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/Examples/Monocular)
add_executable(mono_euroc Examples/Monocular/mono_euroc.cc)
target_link_libraries(mono_euroc ${PROJECT_NAME}) # 每个示例都只链主库
| Examples/Monocular | mono_euroc mono_kitti mono_tum | EuRoC / KITTI / TUM |
| Examples/Stereo | stereo_euroc stereo_kitti | EuRoC / KITTI |
| Examples/RGB-D | rgbd_tum | TUM RGB-D |
| Examples/Monocular-Inertial | mono_inertial_euroc | EuRoC(含 IMU) |
| Examples/Stereo-Inertial | stereo_inertial_euroc | EuRoC(含 IMU) |
| *_realsense_* | 需 RealSense SDK | 实机相机 |
每个示例都只链 libORB_SLAM3.so。这告诉你:示例程序本身很薄,真正的逻辑全在主库里。第 5 节我们就读一个示例,看它有多薄。
4. 跑通第一个数据集:EuRoC 单目
4.1 下载数据
EuRoC MAV 数据集(苏黎世联邦理工无人机数据集)是 ORB-SLAM3 官方主力测试集。下载任意一个序列(如 MH_01_easy)的 ASL 格式,解压后结构:
MH_01_easy/
└── mav0/
├── cam0/ # 左目相机
│ ├── data/ # 一堆 <时间戳ns>.png
│ └── data.csv
├── cam1/ # 右目
├── imu0/ # IMU 数据
└── …
仓库 Examples/Monocular/EuRoC_TimeStamps/MH01.txt 提供了配套的时间戳文件(每行一个纳秒时间戳,对应一张图)。
4.2 启动命令
cd ORB_SLAM3
./Examples/Monocular/mono_euroc \\
./Vocabulary/ORBvoc.txt \\
./Examples/Monocular/EuRoC.yaml \\
/path/to/MH_01_easy \\
./Examples/Monocular/EuRoC_TimeStamps/MH01.txt \\
dataset-MH01_mono # 可选:轨迹输出文件名
四个必需参数对应 mono_euroc.cc 第 37 行的 Usage:
./mono_euroc path_to_vocabulary path_to_settings
path_to_sequence_folder_1 path_to_times_file_1
(… 更多序列 …) (trajectory_file_name)
多序列拼接:参数以 (序列文件夹 + 时间戳) 成对出现,可一次传入多对。
mono_euroc.cc 第 41 行 num_seq = (argc-3)/2 即据此统计序列段数。连续输入多段序列,
是观察 Atlas 多地图行为(第 18 篇)的实验入口——系统会在丢失处新建子地图、在重叠处融合。
4.3 看懂 Pangolin 可视化窗口
跑起来会弹出两个窗口:
- Current Frame:当前图像叠加绿色小方框(成功跟踪到的特征点)。绿框数量反映跟踪质量,骤减通常预示即将丢失。
- Map Viewer(3D):
- 黑点:地图点(稀疏点云);
- 蓝色小相机框:关键帧;
- 绿色相机框:当前帧位姿;
- 绿色连线:共视图(Covisibility Graph,第 12 篇)——两个关键帧看到足够多共同点就连一条线;
- 左侧按钮:Follow Camera、Show Points、Show KeyFrames、Show Graph、Localization Mode(纯定位)等开关。
需注意:mono_euroc.cc 第 83 行构造 System(…, MONOCULAR, false),末位参数 false
即 useViewer,表示该示例默认不启用内置 Viewer。如需可视化,应将其改为 true
重新编译,或选用已启用 Viewer 的示例程序。
5. 读懂示例入口:mono_euroc.cc 到底做了什么
示例程序薄到只有 228 行,核心就三步。读它能直观感受第 01 篇说的"用户只和 System 打交道":
// ① 加载图像路径与时间戳(注意时间戳单位换算)
LoadImages(seqPath + "/mav0/cam0/data", timesFile, vstrImageFilenames[seq], vTimestampsCam[seq]);
// ② 构造系统(加载词袋、建 Atlas、起三线程——见第 01 篇)
ORB_SLAM3::System SLAM(argv[1], argv[2], ORB_SLAM3::System::MONOCULAR, false);
float imageScale = SLAM.GetImageScale();
// ③ 主循环:每帧喂给 TrackMonocular
for (int ni = 0; ni < nImages[seq]; ni++) {
cv::Mat im = cv::imread(vstrImageFilenames[seq][ni], cv::IMREAD_UNCHANGED);
double tframe = vTimestampsCam[seq][ni];
if (imageScale != 1.f) { // 可选降采样(见下)
int width = im.cols * imageScale;
int height = im.rows * imageScale;
cv::resize(im, im, cv::Size(width, height));
}
SLAM.TrackMonocular(im, tframe); // ← 一切发生在这里
}
// ④ 收尾:关闭线程 + 保存轨迹
SLAM.Shutdown();
SLAM.SaveTrajectoryEuRoC("CameraTrajectory.txt");
SLAM.SaveKeyFrameTrajectoryEuRoC("KeyFrameTrajectory.txt");
两个容易忽略的工程细节:
5.1 轨迹输出格式
SaveKeyFrameTrajectoryEuRoC 输出 TUM 格式:每行 timestamp tx ty tz qx qy qz qw(平移 + 四元数)。配合 evaluation/ 目录的脚本和数据集真值,就能算 ATE(绝对轨迹误差)/ RPE(相对位姿误差)——这是评测 SLAM 精度的标准指标。
# 示例:用 evo 工具对比真值
evo_ape euroc MH01_groundtruth.csv KeyFrameTrajectory.txt -a -s –plot
# -a 对齐 -s 估计尺度(单目无尺度,必须加 -s)
单目记得加 -s(Sim3 对齐,允许缩放),否则因尺度不可观,ATE 会大得离谱——这不是算法烂,是单目的固有性质(第 01 篇、第 07 篇)。
6. 读懂配置文件 EuRoC.yaml
配置文件是你最常改的东西。新版采用结构化字段(File.version: "1.0"),分四块:
%YAML:1.0
File.version: "1.0"
# ── 相机 ──
Camera.type: "PinHole" # 针孔;鱼眼填 "KannalaBrandt8"(第 05 篇)
Camera1.fx: 458.654 # 内参:焦距/主点
Camera1.fy: 457.296
Camera1.cx: 367.215
Camera1.cy: 248.375
Camera1.k1: -0.28340811 # 畸变系数 k1 k2 p1 p2
Camera1.k2: 0.07395907
Camera.width: 752 # 原始分辨率
Camera.height: 480
Camera.newWidth: 600 # ← 降采样目标分辨率(对应 §5 imageScale)
Camera.newHeight: 350
Camera.fps: 20 # 帧率(影响关键帧插入节奏、IMU 预积分窗口)
Camera.RGB: 1 # 0=BGR 1=RGB,灰度图忽略
# ── ORB 特征(第 03 篇)──
ORBextractor.nFeatures: 1000 # 每帧提多少特征:太少易跟丢,太多拖慢
ORBextractor.scaleFactor: 1.2 # 金字塔层间缩放比
ORBextractor.nLevels: 8 # 金字塔层数
ORBextractor.iniThFAST: 20 # FAST 初始阈值(高)
ORBextractor.minThFAST: 7 # 提不到点时的降级阈值(低对比度场景调它)
# ── 可视化 ──
Viewer.KeyFrameSize: 0.05
Viewer.GraphLineWidth: 0.9
# …
几个高频调参点(这些会在对应章节深入):
- nFeatures:低纹理/室内调大(1500~2000),算力紧张调小。
- iniThFAST/minThFAST:图像偏暗、对比度低时调小 minThFAST,能多提点。
- newWidth/newHeight:删掉(注释)这两行就用原分辨率,精度↑帧率↓。
- 顶部被注释的 System.LoadAtlasFromFile / SaveAtlasToFile:取消注释即可保存/加载地图,做"预建图 + 纯定位"(第 18 篇)。
旧版本配置采用扁平字段 Camera.fx,新版本为 Camera1.fx 并由 Settings.cc(第 18 篇)统一解析。两种写法并存属版本差异,含义一致。
7. 工程提示
以下为编译、运行、调试三个阶段的常见问题及处理方式。
编译期
- OpenCV > 4.4 not found:环境为 OpenCV 3.x 或 CMake 未定位到。安装 4.x,或修改 find_package(OpenCV …) 版本号。
- Pangolin 链接或编译报错:多由 Pangolin 版本过新、API 变更导致。应安装 Dependencies.md 中推荐的版本。
- make 阶段内存耗尽(OOM):大文件模板展开占用内存高,将 make -j4 降为 make -j2。
- 段错误且栈帧集中于 Eigen:通常为 Eigen 内存对齐问题,需确认各编译单元的编译选项一致(避免混用不同 -march)。
运行期
- 卡在加载词袋或找不到 ORBvoc.txt:未执行 tar -xf ORBvoc.txt.tar.gz,或路径配置错误。
- Illegal instruction (core dumped):-march=native 编译的二进制在不同 CPU 架构上运行所致,需在目标机重新编译。
- 无可视化窗口:检查 System(…) 末位 useViewer 是否为 false(参见 §4.3)。
- 无显示器服务器批量评测:关闭 Viewer(useViewer=false),避免 Pangolin 因无法连接 X11 而崩溃。
调试
- Debug 编译:cmake .. -DCMAKE_BUILD_TYPE=Debug 重新编译后方可在 gdb 中完整查看变量。Debug 构建较 Release 慢数倍,无法满足实时性,仅用于单步分析。
- 段错误定位:gdb –args ./Examples/Monocular/mono_euroc …,崩溃后执行 bt 查看调用栈。
- 内存越界或未初始化:用 valgrind 检测(运行极慢,宜选用短序列)。
- 日志策略:如第 01 篇所述,日志优先加入 LocalMapping/LoopClosing 等后台线程,避免在 Tracking 主循环中引入 I/O 拖慢帧率。
8. 本篇小结 & 下一篇
本篇要点:
第 03 篇进入第一个算法模块 ORBextractor,分析图像金字塔的构建、FAST 角点提取,以及四叉树均匀化如何控制特征点的数量与空间分布。



