从零理解 ORB-SLAM3(二):系统全景、执行上下文与核心数据结构

本卷建立源码地图。先知道对象是谁、数据向哪里走、谁在哪个执行上下文中修改数据,再进入几千行 Tracking 和 Optimizer 才不会迷路。

1.ORB-SLAM3 的四项关键扩展

  1. 视觉惯性 MAP 系统:把视觉重投影与 IMU 约束紧耦合,惯性初始化也采用概率一致的优化;
  2. 高召回地点识别:候选首先做几何验证,再检查局部共视一致性,减少等待连续三帧词袋一致性造成的漏检;
  3. Atlas 多地图:跟踪长时间失败时可创建新地图,以后发现公共区域再融合;
  4. 抽象相机模型:Tracking 和优化器通过投影/反投影/Jacobian 接口支持针孔和鱼眼。

论文还用短期、中期、长期数据关联解释精度来源。源码中的连续帧匹配、局部地图投影匹配、重定位/闭环/融合分别对应这三个层次。

*图:ORB-SLAM3 总体架构。Atlas 是多地图状态中心,三条算法通道围绕它协作。*

2.仓库与六种Sensor模式

主库重点目录:

目录 作用
include/ 类声明、公开接口、成员变量
src/ 主流程和算法实现
include/CameraModels/ GeometricCamera、Pinhole、KannalaBrandt8
Examples/ 数据集和相机入口、时间戳读取、输入同步
Thirdparty/DBoW2 词袋地点识别
Thirdparty/g2o 图优化基础框架
Thirdparty/Sophus 李群表示和运算

System::eSensor 定义六种输入组合:

复制代码
MONOCULAR, STEREO, RGBD,
IMU_MONOCULAR, IMU_STEREO, IMU_RGBD

Sensor 决定帧构造方式、是否接收 IMU、尺度是否固定、是否执行惯性初始化等。相机模型则决定像点投影方式;鱼眼不是第七种 Sensor。

3. 输入接口与配置

System 对外提供:

  • TrackMonocular(image, timestamp, imuMeasurements, filename);
  • TrackStereo(left, right, timestamp, imuMeasurements, filename);
  • TrackRGBD(image, depth, timestamp, imuMeasurements, filename)。

调用者负责按时间顺序送入图像。视觉惯性示例还需要从 IMU 流中取出落在相邻图像时间区间内的测量,并传给相应 Track*()。双目图像必须同步;RGB-D 深度必须与彩色图注册。

配置读取涉及两条兼容路径:Settings 解析新格式;部分旧配置仍通过 cv::FileStorage 直接读取。配置不只是内参,还包括帧率、颜色顺序、ORB 参数、双目基线/深度阈值、IMU 噪声/频率、相机---IMU 外参、Viewer 参数和可选 loopClosing。

配置错误的影响链

  • 焦距/主点错误 → 投影和重投影雅可比错误;
  • 双目基线或深度尺度错误 → 地图整体尺度错误;
  • Tbc 方向错误 → 视觉与惯性预测互相冲突;
  • IMU 噪声单位错误 → 信息矩阵权重错误;
  • 时间戳不同步 → 预积分覆盖错误时间段。

4. System 构造和模块连接

构造函数完整签名包含词典、配置、Sensor、Viewer 开关、初始帧编号和序列名。核心顺序是:

  1. 保存 Sensor,读取配置;
  2. 加载 ORB 词典;
  3. 创建 KeyFrameDatabase;
  4. 创建 Atlas,必要时加载已保存 Atlas;
  5. 向 Atlas 标记是否包含惯性传感器;
  6. 创建 FrameDrawer、MapDrawer;
  7. 在调用线程中创建 Tracking;
  8. 创建 LocalMapping 并启动其 Run() 线程;
  9. 创建 LoopClosing 并启动其 Run() 线程;
  10. 互相注入 Tracker、LocalMapper、LoopCloser 指针;
  11. 可选地创建 Viewer 线程。

重要事实:源码注释明确说明 Tracking "live in the main thread of execution"。所以"Tracking 线程"是算法语境中的执行通道,不是 System 内部新建的 std::thread。

*图:Tracking、Local Mapping、Loop Closing、Viewer 和 Global BA 的执行关系。*

复制代码
应用/Example 调用线程
  └─ System::Track*()
       └─ Tracking::GrabImage*()
            └─ Tracking::Track()

后台线程 A: LocalMapping::Run()
后台线程 B: LoopClosing::Run()
可选线程 C: Viewer::Run()
条件线程 D: LoopClosing::RunGlobalBundleAdjustment()

5. Track*() 到 Tracking::Track()

以 TrackMonocular() 为例,接口首先检查 Sensor 是否兼容,再响应以下控制请求:

  • 激活/关闭纯定位模式;
  • 重置当前活跃地图或全部系统。

随后若有 IMU 测量,调用 GrabImuData() 放入 Tracking 队列;再调用 GrabImageMonocular()。后者处理图像缩放/灰度、构造 Frame,最终进入 Track()。

Track() 是状态机中枢。主要状态包括:

  • NO_IMAGES_YET:尚无图像;
  • NOT_INITIALIZED:等待建立初始地图;
  • OK:正常跟踪;
  • RECENTLY_LOST:短时丢失,惯性模式可能继续预测并尝试恢复;
  • LOST:恢复窗口耗尽,需要重定位、重置或创建新地图。

状态并非只由"匹配点数量"决定,还受地图规模、初始化阶段、IMU 是否完成初始化、距上次重定位帧数和时间戳连续性影响。

*图:输入经过 Frame 和 Tracking 形成关键帧/地图点,最终输出轨迹和稀疏地图。*

6. 为什么要并行

Tracking 必须尽快给出每帧位姿,预算接近相机帧间隔。Local Mapping 的三角化和 Local BA 更耗时;Loop Closing 的地点识别和图优化发生频率更低但可能更昂贵。拆分执行上下文可以让相机输入继续前进。

并行也带来一致性问题:

  • Tracking 读局部地图时,Local Mapping 可能添加或删除点;
  • Loop Closing 校正整张地图时,Local Mapping 必须停止;
  • Global BA 运行期间若出现新闭环,旧优化结果可能失效;
  • 系统退出时各线程要先收到 finish 请求,再等待其确认。

ORB-SLAM3 使用互斥锁、停止/完成标志和队列保护实现协作。不要把 mutex 当作算法细节;它决定一条调用链是否可以安全修改地图。

7. Frame、KeyFrame 与观测

Frame

表示一次实时输入,包含:

  • 时间戳、图像特征、描述子和金字塔尺度信息;
  • 相机模型、内参、畸变和双目/RGB-D 深度;
  • 当前位姿、速度和 IMU bias;
  • 与特征对应的 MapPoint*;
  • BoW 向量;
  • 前一帧与 IMU 预积分对象。

Frame 生命周期较短,但用于恢复完整轨迹的相对位姿、参考关键帧和丢失标记会由 Tracking 另存。

KeyFrame

关键帧由选中的 Frame 构造,是地图的长期节点。它保存特征和观测,还维护:

  • 与其他关键帧的共视权重;
  • 生成树的父子关系;
  • 回环边/融合边;
  • 前后惯性关键帧;
  • 线程安全的位姿和连接更新。

不是"图像清晰就成为关键帧"。NeedNewKeyFrame() 同时考虑帧间隔、局部建图是否繁忙、跟踪内点、近点数量和 Sensor 模式。

8. MapPoint 与地图图结构

MapPoint 保存世界坐标、代表性描述子、法向/可见距离、观测集合,以及被看到/被匹配统计。一个地图点必须经历:

  1. 由双目深度或多视图三角化创建;
  2. 在邻域关键帧中投影搜索;
  3. 计算代表性描述子和法向;
  4. 根据找到率、观测数和年龄筛选;
  5. 若与已有点重复则融合或替换。

共视图的边权通常与共同观测的地图点数量相关。Tracking 通过共视邻居构造局部地图,Local Mapping 用它选择三角化和 BA 邻域。生成树提供始终连通的稀疏骨架;闭环边补充长期约束;Essential Graph 用较稀疏的图完成全局校正。

9. Map、Atlas 与地点数据库

Map 持有一组 KeyFrame 和 MapPoint,并记录初始关键帧、参考点、变化索引和 IMU 初始化状态。

Atlas 持有多张可能断开的 Map:

  • CreateNewMap():创建并激活新地图;
  • ChangeMap(map):切换活跃地图;
  • GetCurrentMap():供 Tracking/Mapping 获取活跃地图;
  • 保存相机模型,并支持序列化。

KeyFrameDatabase 跨 Atlas 使用统一 ORB 词袋数据库。查询结果可能用于重定位、同地图闭环或跨地图融合;最终类别要看候选关键帧所属 Map 及几何验证结果。

10. 输出、持久化和关闭

Track*() 返回当前相机位姿;System 还复制 Tracking 状态、当前跟踪地图点和关键点,供应用查询。轨迹保存函数根据 TUM、EuRoC 或 KITTI 格式输出位姿。因为普通 Frame 位姿常以参考 KeyFrame 的相对变换保存,导出时要沿关键帧关系恢复到统一坐标。

V1.0 支持 Atlas 保存/加载。Boost 序列化恢复对象后还需要重建部分运行时指针和数据库关系;序列化不是简单地把内存地址写入文件。

安全关闭顺序是:

  1. System::Shutdown() 向后台模块请求结束;
  2. 等待 Local Mapping、Loop Closing 和 Viewer 确认完成;
  3. 若 Global BA 仍在运行,等待或终止对应流程;
  4. 再保存轨迹/地图或销毁对象。

删除模块会怎样:

  • 删除 Local Mapping:地图不再增长和局部优化,最终无法稳定跟踪新区域;
  • 删除 Loop Closing:短/中期跟踪仍能运行,但长程漂移无法校正,地图也不能融合;
  • 删除 Atlas:跟踪彻底失败后只能重置或结束,不能保留旧地图等待以后复用;
  • 删除 KeyFrameDatabase:重定位、闭环和地图融合都会失去高效候选检索。

卷二建立了对象和执行上下文。

卷三将沿纯视觉输入逐分支走完初始化、正常 Tracking、Local Mapping 和同地图闭环。

相关推荐
Doraemomo3 小时前
数据结构-二叉树
数据结构
爱跳舞的烤冷面3 小时前
自学嵌入式第20天(数据结构篇——队列)
数据结构
旖旎夜光3 小时前
LeetCode 202:快乐数(双指针问题) —— 题解
数据结构·c++·算法·leetcode·双指针
cpp_25014 小时前
P1113 [USACO02FEB] 杂务
数据结构·c++·算法·动态规划·图论·拓扑排序·洛谷题解
Doraemomo4 小时前
数据结构-哈希表
数据结构·算法·散列表
君君思密达4 小时前
Leetcode Hot 100 题目详解-哈希表
数据结构·算法·leetcode
Doraemomo6 小时前
数据结构-双向链表与多文件工程整合之makefile基础
数据结构·链表
爱跳舞的烤冷面6 小时前
自学嵌入式第21天(数据结构——树)
数据结构·算法·链表
神明不懂浪漫7 小时前
【第五章】队列
开发语言·数据结构·经验分享·笔记·算法