3. 工程结构解析:源码目录树分析、CMakeLists.txt构建系统、核心类依赖关系图、数据流架构

好,咱们今天来聊聊ORB-SLAM3的工程结构。说实话,我第一次打开这个源码的时候,也被它的目录结构吓了一跳——文件不少,类也不少。但别慌,我带你一层层剥开它。

3.1 源码目录树:从根到叶

先看看根目录下都有什么。我个人习惯,拿到一个新项目,第一件事就是看目录结构。这就像你去一个陌生的城市,先看地图一样。

ORB-SLAM3/
├── CMakeLists.txt          # 主构建文件
├── Thirdparty/             # 第三方库
│   ├── DBow2/              # 词袋模型
│   ├── g2o/                # 图优化库
│   └── Sophus/             # 李代数库
├── Examples/               # 示例程序
│   ├── Monocular/          # 单目
│   ├── Stereo/             # 双目
│   ├── RGB-D/              # RGB-D
│   └── Monocular-Inertial/ # 单目+IMU
├── include/                # 头文件
│   └── System.h            # 系统核心
├── src/                    # 源文件
│   ├── System.cpp
│   ├── Tracking.cpp
│   ├── LocalMapping.cpp
│   ├── LoopClosing.cpp
│   └── ...
├── Vocabulary/             # 离线词袋
└── config/                 # 配置文件

嗯,这里要注意:Thirdparty 里的三个库,是ORB-SLAM3的基石。DBow2做回环检测,g2o做后端优化,Sophus处理位姿。我在项目中遇到过,有人把g2o版本搞错了,编译直接报错——所以版本一定要对齐。

3.2 CMakeLists.txt构建系统:别小看它

很多初学者觉得CMakeLists.txt就是复制粘贴,其实不然。ORB-SLAM3的构建系统设计得挺讲究的。咱们看看核心部分:

cmake_minimum_required(VERSION 2.8)
project(ORB_SLAM3)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O3")

# 找依赖
find_package(OpenCV 4.4 REQUIRED)
find_package(Eigen3 3.1.0 REQUIRED)
find_package(Pangolin REQUIRED)

# 第三方库
add_subdirectory(Thirdparty/g2o)
add_subdirectory(Thirdparty/DBow2)
add_subdirectory(Thirdparty/Sophus)

# 主库
add_library(${PROJECT_NAME} SHARED
    src/System.cpp
    src/Tracking.cpp
    src/LocalMapping.cpp
    src/LoopClosing.cpp
    src/Atlas.cpp
    src/KeyFrameDatabase.cpp
    ...
)

target_link_libraries(${PROJECT_NAME}
    ${OpenCV_LIBS}
    ${Eigen3_LIBS}
    g2o
    DBow2
    Sophus::Sophus
    Pangolin
)

你看,它把第三方库作为子模块编译,而不是用系统安装的。为什么?说白了,就是为了版本控制。我曾经因为系统g2o版本不对,折腾了两天——后来学乖了,直接用源码里的。

小技巧: 如果你要移植到嵌入式平台,记得把 -O3 改成 -O2,有些ARM编译器对 -O3 支持不太好。

3.3 核心类依赖关系图

这是整个课程里最核心的一张图。我建议你多看几遍,把类之间的关系刻在脑子里。

ORB-SLAM3 核心类依赖关系 System Tracking LocalMapping LoopClosing Atlas Map KeyFrame MapPoint 图例: System(入口) Tracking(前端) LocalMapping(后端) LoopClosing(回环) Atlas(地图集)

你看,System 是总入口,它创建并管理三个核心线程:TrackingLocalMappingLoopClosing。这三个线程又共享 Atlas(地图集),而 Atlas 管理着多个 Map,每个 Map 包含 KeyFrameMapPoint

核心要点: ORB-SLAM3 的多地图(Multi-Map)能力,就体现在 Atlas 这个类上。它不像ORB-SLAM2只有一个地图,而是可以同时维护多个子地图。这在视觉+IMU模式下特别有用——当你丢失跟踪时,系统会创建一个新地图,而不是直接崩溃。

3.4 数据流架构:图像进来,位姿出去

好了,类的关系搞清楚了,咱们看看数据是怎么流动的。说白了,就是图像帧进来,经过一系列处理,最后输出相机位姿和地图。

步骤 模块 输入 输出 关键操作
1 Tracking 图像帧 初始位姿 特征提取、匹配、PnP
2 LocalMapping 关键帧 局部地图 三角化、BA优化
3 LoopClosing 关键帧 回环校正 词袋检索、Sim3求解
4 Atlas 地图数据 全局地图 地图合并、管理

我举个例子,你想想看:当摄像头采集到一帧图像后,Tracking 线程会先提取ORB特征点,然后和上一帧或者地图点匹配,用PnP算法算出一个初始位姿。如果匹配质量好,就生成关键帧,丢给 LocalMapping 线程。

LocalMapping 拿到关键帧后,会做两件事:一是三角化新的地图点,二是做局部BA优化。嗯,这里要注意,BA优化很耗时,所以它只优化当前关键帧附近的帧和点,不是全局的。

最后,LoopClosing 线程会定期检查是否形成了回环。怎么检查?用词袋模型。如果检测到回环,就计算Sim3变换,然后做全局BA,把累积误差消除掉。

避坑指南: 我曾经在移植到嵌入式平台时,发现回环检测特别慢。后来一查,是词袋加载太慢了——Vocabulary文件有几百MB。解决办法是:在初始化时异步加载词袋,或者用压缩版的词袋文件。

3.5 多线程架构:三个线程的协作

ORB-SLAM3 用了三个线程并行工作。它们之间怎么通信?说白了,就是通过队列和互斥锁。

// Tracking 向 LocalMapping 发送关键帧
mlNewKeyFrames.push_back(pKF);  // 队列
// LocalMapping 处理
while(!mlNewKeyFrames.empty()) {
    // 加锁
    unique_lock<mutex> lock(mMutexNewKFs);
    KeyFrame* pKF = mlNewKeyFrames.front();
    mlNewKeyFrames.pop_front();
    lock.unlock();
    // 处理...
}

你看,Tracking 把关键帧塞进队列,LocalMapping 从队列里取出来处理。这种生产者-消费者模式,在多线程编程里很常见。我建议你仔细看看 Tracking.cpp 里的 GrabImageMonocular() 函数,它是整个数据流的起点。

好了,工程结构就讲到这里。记住这张依赖关系图,后面咱们逐行解析代码时,你会经常回来参考它。


专注资料整理