1. 从KeyFrame类看ORB-SLAM2的设计骨架做SLAM的同学应该都有这种感觉读ORB-SLAM2源码前几遍最容易卡住的地方往往是数据结构和数据流而不是某个具体算法。因为算法再复杂它终归是线性的逻辑链条顺着函数调用往下看总能理清楚。但数据结构不一样它定义的是整个系统里“什么东西存什么、谁跟谁怎么建立联系、信息在哪个环节被汇总”一旦理不清KeyFrame、MapPoint、Map这三者之间的关系后面读LoopClosing和LocalMapping就会一头雾水。这篇就专门拆关键帧KeyFrame这一个类。为啥单拎它出来讲因为在ORB-SLAM2里关键帧几乎是所有核心模块的“交叉路口”——Tracking负责生成它LocalMapping负责筛它、优化它LoopClosing负责认它、修它最终地图的规模和精度也直接由它的选取策略决定。可以说理解了KeyFrame就理解了ORB-SLAM2一半的数据流。这篇主要面向三类人第一类是刚把ORB-SLAM2跑通、准备深入源码的入门者第二类是想在ORB-SLAM2基础上做二次开发比如改关键帧选取策略、做语义SLAM、做多传感器融合的研究者第三类是面试前突击SLAM内核知识点的求职者。我会尽量用“先看数据结构、再讲为什么这么设计、最后讲实际代码里怎么用”的顺序来拆代码版本基于官方开源版类名和函数名都和源码保持一致方便你自己翻源码对照。先说一个看源码的小建议不要从头到尾把KeyFrame.h的成员变量背一遍而是带着问题去读。核心问题就三个——关键帧要表达什么关键帧跟谁有联系这些联系怎么被维护和更新把这三个问题用代码回答出来这个类你就算吃透了。2. KeyFrame的核心数据结构从位姿到图结构2.1 关键帧的基本身份信息怎么给一帧画像打开KeyFrame.h先看到一堆public成员第一个是两个IDmnId和mnFrameId。很多刚开始读源码的人会疑惑——一个关键帧为什么要记两个IDmnId是KeyFrame自己的全局递增ID每创建一个关键帧就加1它是关键帧在系统里的唯一身份标识。mnFrameId则是这个关键帧在被创建时对应的原始Frame的ID。Frame的ID也是全局递增的但Frame的数量远大于KeyFrameTracking每处理一帧图像就产生一个Frame但只有很小一部分被提升为关键帧。所以这两个ID记录的其实是两个维度的信息关键帧在全图关键帧序列里的位置以及它对应原始帧序列里的位置。调试的时候很有用——看到mnFrameId相差300多你就能估算出这中间经过了大约多少普通帧对判断关键帧密度是个很直观的参考。mTimeStamp是时间戳double类型存的是从系统启动到这一帧被创建所经过的时间。别小看这个字段后面做匀速模型预测、IMU融合虽然原版没有IMU但很多扩展版本会用到、回环检测时端到端的轨迹时间计算全都要靠它。接着是图像相关的成员。mIm是灰度图像注意是灰度图彩色图在进入Tracking之前就已经被转成灰度了。关键帧保存整张图像的目的是为了在LoopClosing里做回环候选帧的Reprojection匹配以及在显示优化结果时方便调试。mImgSize是图像尺寸存的是金字塔第0层的宽高。然后就是一大堆相机参数mK是内参矩阵mfx、mfy是焦距像素单位mcx、mcy是主点坐标mbf是基线长度乘以焦距双目和RGB-D用mb是双目基线长度单位是米mThDepth是深度阈值。mThDepth这个参数值得单独说——在ORB-SLAM2里它对单目相机是普通内参用但对双目和RGB-D它是区分近处点和远处点的分界线。深度值小于mThDepth的点被认为是近处点它们携带的深度信息可靠参与尺度准确的局部优化深度值大于mThDepth的点被视为远景点默认深度已经不太靠谱了在优化里的权重和处理逻辑会不一样。这个阈值在System构造函数里根据图像分辨率和内参自动计算一般取基线倍数的几十倍。2.2 特征数据关键帧拿什么去跟别人比较每个关键帧都保存着一大堆特征相关的数据这部分是后面做特征匹配、词袋检索、建图的原料。mvKeys是原始关键点二维坐标带金字塔层级和方向mvKeysUn是去畸变后的关键点两者之差就是畸变校正量。mDescriptors是描述子矩阵每一行是一个特征点的ORB描述子注意是CV_8UC1类型每行32个字节256位。mBowVec是词袋向量用于回环检测时的快速比较mFeatVec是一个逆序索引——记录每个词汇节点下有哪些特征点。这个数据结构在寻找候选匹配点时特别有用它能直接告诉你在某个词汇节点下当前帧有哪些特征点候选帧又有哪些特征点然后在这两个集合之间取交集做几何校验比自己遍历所有特征点再逐个计算描述子距离要快几个数量级。mvScaleFactor、mvLevelSigma2、mvInvLevelSigma2这三兄弟是金字塔相关的缩放因子和方差用于特征匹配时的尺度一致性检查。mnScaleLevels是金字塔层数。还有一个容易被忽略的点mvKeysUn里存的是去畸变后的坐标所有后续的几何计算三角化、重投影、优化用的都是去畸变坐标。但画图调试的时候如果你想在原图上画特征点就要画回mvKeys的位置。这个细节不注意画出来的点会跟实际特征位置偏一截排查半天还以为是特征提取得有问题。2.3 位姿相关Tcw、Twc、Ow三件套位姿这部分ORB-SLAM2的做法是用三个量共同表达一个变换关系——mTcw、mTwc、mOw。mTcw是世界坐标系到相机坐标系的变换矩阵也就是通常说的“相机位姿”mTwc是它的逆矩阵mOw是相机光心在世界坐标系下的坐标——mTwc的平移部分也就是矩阵的第四列前三行。为什么不直接用mTcw完事儿因为系统里不同的模块需要不同的表达方式。追踪阶段用mTcw做投影和匹配回环检测阶段用Sim3变换和mTwc而设置地图点观测方向时要频繁用到mOw。虽然这三个量可以互相转换但每次用的时候现算既费时又容易因为局部更新出bug。ORB-SLAM2的做法是在SetPose()里一次性把三者都算好后续读取时各取所需。这种“同一数据冗余存储、但写的时候保证一致性”的思路在工程上很常见——它是一种以空间换便利、以一次性更新避免重复计算的经典手法。SetPose()的输入是一个四乘四的变换矩阵带旋转和平移里面会调用RotationMatrixToQuaternion(...)把旋转矩阵转成四元数同时更新mTcw、mTwc、mOw三者。UpdatePoseMatrices()是内部辅助函数在SetPose里被调用但也会被其他函数单独调用来“刷新”冗余量。这种更新逻辑如果你要改关键帧的位姿表示方式比如加SE3、加Sim3这个函数是必经之地。3. KeyFrame跟Map和MapPoint的数据关系3.1 KeyFrame和MapPoint的互相引用在ORB-SLAM2里MapPoint和KeyFrame是双向关联的。KeyFrame保存了mvpMapPoints成员——一个长度等于特征点数目的vector里面每个元素要么是一个MapPoint指针要么是nullptr。如果某个特征点成功三角化成了地图点这个位置就指向那个MapPoint如果这个特征点没有被三角化单目模式下很常见很多点因为视角太窄、深度不确定性太大而被放弃就是nullptr。反过来每个MapPoint内部也保存了mObservations——一个mapKeyFrame*, int记录“哪些关键帧观测到了我以及我在那个关键帧里的特征索引是多少”。所以从KeyFrame出发可以找到它关联的所有MapPoint从MapPoint出发也可以找到所有观测到它的关键帧。这种双向关联是整个SLAM地图数据结构的核心骨架。代码里更新这种双向关系的地方在Replace()和Erase()。Replace(newPF)是这样一个场景两个地图点被判定为同一个物理点比如回环检测时发现两个点重合于是用一个点替换另一个点。被替换的旧点会把它在旧关键帧里的那些观测全部转移到新点上。这个方向对了但很多人会漏掉一件事——旧点的mObservations被清空之后它自己的成员还被多个关键帧的mvpMapPoints引用着如果直接delete旧点那些指针就变成野指针。所以Replace里还有一步关键操作遍历旧点观测到的所有关键帧把那些关键帧的mvpMapPoints里指向旧点的位置统一改成新点。两个方向都更新干净了才敢把旧点从地图里删除。Erase()是删除地图点的入口。它先从自己的mObservations里取出所有关键帧然后对每个关键帧的mvpMapPoints做“该位置置空”操作。这里有个判断如果该点正在被局部建图线程处理不能直接删否则优化过程中会访问到已删除的内存造成崩溃。代码里用mbTrackInView追踪可见等标志位来判断并配合SetErase()标记“待删除”等当前处理完再真正删除。这个逻辑的谨慎程度体现了一个工程系统处理并发和安全内存释放的成熟做法。3.2 关键帧里的“坏点”和其他标记每个MapPoint在关键帧里还有一个“坏点”概念。ORB-SLAM2不直接删除那些观测质量差的地图点而是通过mbBad标志位标记。然后在Optimizer优化时如果一个MapPoint满足“被坏点替换”“观测数过少”“重投影误差过大”等条件就先被标记为坏点最后在合适时机从地图中剔除。为什么要搞“标记-延迟删除”这套机制而不是发现坏点就马上删因为在多线程环境下Tracking和LocalMapping并行运行一个MapPoint可能正被某个线程的优化器计算着如果被另一个线程物理删除轻则程序逻辑错误严重时直接段错误。所以只给一个“我坏了”的标记让后续模块处理时自行判断并跳过等所有处理都结束了再清理。这种思路在SLAM系统里特别普遍你不仅会在MapPoint里看到在很多线程安全的队列和任务系统里也能见到。3.3 关键帧保存相机中心为了快速计算观测方向继续看KeyFrame的一些成员函数SetVelocity和GetVelocity处理的是帧间速度简单说就是追踪阶段用来做运动模型预测的那部分TrackView相关的函数则保存了“该关键帧被多少个点观测到”这类信息但这些相对外围。真正核心的是GetCameraCenter()和GetPose()这两个极简函数。前者返回mOw后者返回mTcw。它们的调用频率极高——每次地图点投影、每次局部BA、每次回环候选的sim3变换都要用到。所以它们必须是inline级别的高效函数这也是为什么ORB-SLAM2里大量函数直接在头文件里实现而不是扔到cpp里。另一个值得留意的是ComputeSceneDepth()它计算这个关键帧的“场景平均深度”也就是它观测到的三维点距离相机平面的平均距离。Tracking阶段判定关键帧的前后关系时会用到类似信息来筛选关键帧防止原地不动时不断插入功能重复的关键帧。LocalMapping在筛选新关键帧时也用SceneDepth的信息——如果新关键帧和已有关键帧在位置上太接近深度又差不多说明信息冗余会被剔除掉。4. 关键帧之间的“社交网络”共视图和生成树4.1 共视图结构怎么知道谁跟谁是“邻居”共视图Covisibility Graph是ORB-SLAM2的一个核心图结构它刻画了关键帧之间“共同观测到多少个地图点”的关系。具体来说如果两个关键帧共同观测到一定数量的MapPoint就认为它们之间存在一条“连接”连接边上的权重就是共同观测到的地图点数量。KeyFrame类里对应的是mConnectedKeyFrameGraphWeights一个mapKeyFrame*, int和mvpOrderedConnectedKeyFrames一个排序后的vector。每次更新连接关系时算法会统计“当前关键帧”和“其他每个关键帧”共同观测到的地图点数量过滤掉数量少于阈值的边缘关系然后按权重从大到小排序存储。为什么要维护这个图因为SLAM里的绝大多数优化局部BA、回环修正、全局BA都需要一个“局部约束”的信息来划定优化范围。你不能每次优化都把全地图所有关键帧拉进来那样复杂度太高。ORB-SLAM2的做法是在局部建图里只优化当前关键帧和它的一级、二级共视关键帧以及这些帧观测到的地图点在回环检测里先找共视图的局部窗口来做Sim3变换验证。可以说共视图决定了优化问题的稀疏结构直接影响求解规模。UpdateConnections()就是维护这个图的函数。它接收一个新的关键帧进来先统计它和现有所有关键帧的共视点数筛选出权重超过阈值的邻居然后对每个邻居也反向把当前关键帧加进它的邻居列表里。这个过程保证了图的对称性A认为B是邻居B也认为A是邻居。Graph边添加完成后调用UpdateCovisibilityGraph来计算排序后的有序邻居列表。在代码里还有一个范围判断值得注意——它只在和当前关键帧的mMinDistance、mMaxDistance范围内的关键帧之间建边。mMinDistance和mMaxDistance是当前关键帧到它观测到的MapPoint最近和最远的观测距离。超出这个距离范围的关键帧即使共观点数量达标也不会被建边因为距离太远意味着它们很可能在不同的场景区域共视关系也偏弱。这个设计在工程上是很有意义的它让共视图在空间上保持局部性避免出现跨区域的“长边”扰乱后面的优化。4.2 生成树保证全球连通性的备用图生成树Spanning Graph是ORB-SLAM2里的第二张图。它比共视图更稀疏但覆盖了所有关键帧保证任意两个关键帧之间都存在一条路径相连。KeyFrame里的成员包括mSpanningParent指向生成树父节点的一个指针和mSpanningChildren一个setKeyFrame*保存所有孩子节点。生成树的建立和维护比较简单每次插入新关键帧时它会认当前处理的参考关键帧为父节点然后把自己加进父节点的孩子集合里。如果父节点后来被删除了需要从它的孩子里选择一个继续当父节点这是通过mSpanningChildren的更新来完成的。生成树的主要用途是在全局优化比如闭环后的位姿图优化里为那些“没有共视关系、但为了保持连通性必须参与到优化里”的关键帧提供约束边。想象一下一个很长的走廊场景两头的关键帧可能完全不存在共视关系但通过中间一串关键帧的生成树连接整个位姿图还是连通的。这样优化方程能正常求解不会因为图不连通而失败。4.3 关键帧之间的“亲戚关系”Essential Graph回环检测和全局优化的过程中ORB-SLAM2还用到了Essential Graph本质图。它比共视图稀疏但比生成树稠密大致包含生成树的所有边以及共视图上权重较高的边。这样一方面保证了优化时的全局连通性生成树部分另一方面又保留了足够的约束来让优化结果更准确。从代码实现角度看Essential Graph并不是一个独立的数据结构而是从共视图和生成树的信息中临时构建出来的主要在LoopClosing和全局BA阶段使用。你在KeyFrame类里看到的mConnectedKeyFrameGraph、mSpanningParent等都是为构建这种图服务的底层素材。5. 高层运作流程关键帧从创建到删除的一生5.1 从普通帧到关键帧谁负责举手“我可以成为关键帧”Tracking线程负责判断“当前帧能不能成为关键帧”这个判断逻辑主要写在了NeedNewKeyFrame()里。不是说每处理一帧图像都要创建关键帧创建得太频繁会导致地图臃肿、优化规模爆炸、轨迹抖动创建得太稀疏则会导致追踪中断、地图点三角化失败。NeedNewKeyFrame()的核心判断条件有几个。第一当前帧距离上次关键帧插入已经过了足够多的普通帧数最少间隔防止密集插入第二当前帧中能可靠追踪到的特征点数量下降到一个阈值以下说明当前地图可能快丢了需要补充关键帧来稳定追踪第三局部建图线程不忙也就是它能及时处理你插入的新关键帧如果局部建图忙不过来你也别硬塞否则队列堆积会造成帧延迟和内存膨胀第四当前帧观测到的地图点中有一部分在最近的关键帧里观测不到说明有新的、未被充分观测的场景内容。这些条件组合起来的整体倾向是“宁可多插几个也别丢追踪”——但要加一个“局部建图线程空闲”的前提。实际调参的时候如果发现地图很大、很密但轨迹有抖动多半是第一个条件的最小间隔设得太小了如果你发现追踪经常丢但地图并不密那多半又是因为第三个条件限制太强、局部建图一忙就停插帧了。5.2 关键帧的插入它怎么加入系统当Tracking判定当前帧需要成为关键帧就会调用KeyFrame的构造函数用当前Frame的信息构造一个KeyFrame对象。构造函数里会从Frame拷贝图像、关键点、描述子、词袋向量、位姿等所有必要数据同时初始化关键的图关系。注意一点这个“图关系”在构造时还没有真正建立只是先把关于自身的基本数据填好。紧接着Tracking会调用LocalMapping的InsertKeyFrame()把这个新关键帧放进局部建图的待处理队列。然后LocalMapping线程从队列里取出它执行MapPoint的创建对没被三角化的特征点做三角化、关键帧之间的共视图更新、局部BA优化等流程。也就是说从“决定创建”到“真正成为地图的一部分”之间还有一个异步的缓冲队列这就是为什么关键帧创建时不能直接用它去做共视图和局部优化——那些工作是LocalMapping线程的事。这个异步模型理解起来有点绕但它的优势很明显Tracking线程不会被建图拖慢保证了前端实时性而LocalMapping线程有足够的时间做高质量的三角化和局部优化。需要在你的二次开发工作中留意这个数据流——如果你想让关键帧创建后立刻被前端使用比如做视觉惯性融合时的立即更新那这个异步缓冲就必须额外处理。5.3 关键帧的剔除LocalMapping里的删帧策略关键帧不是只增不减的ORB-SLAM2在建图过程中会定期剔除那些“冗余”的关键帧避免局部地图膨胀、优化负荷过大。LocalMapping线程里的KeyFrameCulling()函数就是这么干的。它遍历当前局部地图里的所有关键帧对于每个关键帧K找出所有和它共同观测到的地图点并且这些观测也都是被其他90%以上的关键帧共同看到的——如果K能看到的点都被别人看到了那说明K本身存在的意义不大就把它从地图里删掉。这个策略的直觉解释是一个关键帧只有在它提供了别人没有提供过的信息时才值得存在。如果它看到的地图点其他关键帧也都能看到它就是冗余的尽早剔除可以控制地图规模。相反如果它独有的地图点很多说明它代表了新的场景视角即使与别人重合度高也会保留。在代码实现里这个“冗余”判断是通过统计当前关键帧观测到的所有地图点中有多少点的“其他观测关键帧数”大于等于当前帧观测数的90%来进行的。如果这样的点占所有点的比例超过了90%就把这个关键帧剔除。5.4 关键帧在回环检测里的角色LoopClosing线程处理回环检测时第一步就是用词袋向量检索“和当前关键帧最相似的历史关键帧”。这一步本质上就是一个关键帧之间的快速比较。通过词袋向量的距离计算选出若干候选关键帧再通过共视图做进一步的几何验证最终确认是否真有回环。确认回环之后系统要计算当前关键帧和回环帧之间的Sim3变换这里用到的是关键帧的位姿、共视图里的局部窗口地图点以及特征匹配信息。Sim3优化成功后系统会通过Essential Graph对全局位姿做一次大范围的位姿图优化。整个过程从头到尾都在操作关键帧和它们之间的边——位姿优化更新的主要是关键帧的位姿MapPoint的坐标则是通过关键帧位姿做辅助修正。5.5 关键帧数据的内存管理每个关键帧保存了图像、金字塔所有层级的特征点、描述子矩阵、词袋向量等内存开销不小。如果地图规模大关键帧数量可能几千到几万内存占用非常可观。ORB-SLAM2对KeyFrame的删除是谨慎的。它先把该关键帧从所有关键帧的共视图里摘出去UpdateConnections会调用EraseConnection再把它的MapPoint观测关系清空每个MapPoint的mObservations里删掉对当前关键帧的引用最后再把关键帧本身从Map的关键帧列表里移除。这些操作每一步都要保持MapPoint和关键帧的双向关系一致性否则会留下悬空指针程序跑着跑着就崩了。实际跑比较大的数据集时我建议你在System里加一个统计关键帧数和地图点数的日志输出每几十帧打印一次。你会看到LocalMapping在持续做关键帧剔除如果发现关键帧数目增长得很慢甚至下降说明你的关键帧选取策略偏保守了地图容易丢如果增长得非常快说明有点密了优化负担会变大。6. 实战经验读代码时必然遇到的几个坑和排查技巧6.1 追踪线程和建图线程之间的数据竞争因为Tracking和LocalMapping异步运行关键帧可能在一个线程里被创建、同时被另一个线程做共视图更新或剔除这里就存在数据竞争。ORB-SLAM2大量使用mutex锁来保护关键帧的图结构成员。比如UpdateConnections和EraseConnection在修改共视图会对mMutexConnections加锁而遍历共视图的代码也会在同一把锁内读取。如果你在二次开发中随意添加遍历共视图的代码而不加锁大概率会偶发崩溃且崩溃时机飘忽不定极难复现。排查这类问题有个土办法在怀疑有竞争的代码段里临时添加一个计数器记录“读取到不一致数据”的次数跑几个数据集看是否非零。如果非零基本可以锁定是锁缺失或锁粒度不一致。6.2 关键帧数量增长但地图质量不升的排查思路很多人在自己的数据集上测试时会出现关键帧数量涨到很多、但建图效果还是很差的情况。这个时候先别怀疑算法有问题先检查你的关键帧是否“过密”。从KeyFrame的共视图和生成树信息中可以很快判断一个正常SLAM的局部地图里关键帧的分布应该呈现“稀疏但足够”的状态共视图的邻接表里每帧的邻居数通常在10~20之间。如果你发现大部分关键帧的邻居数都超过50甚至上百那说明关键帧插入太密局部BA每次要优化接近全局规模的帧数速度和精度都会恶化。解决办法是调整Tracking的NeedNewKeyFrame里的最小间隔以及LocalMapping里KeyFrameCulling的冗余比例阈值。一般把最小间隔从默认的1调到2或3冗余比例阈值保持默认就能明显看到关键帧数量下降建图质量反而更稳定。6.3 修改关键帧数据结构后忘记同步更新的连环错做二次开发时如果往KeyFrame类里加了新的成员比如为语义信息加的Label经常会出现“添加成员容易、维护一致性难”的问题。比如你新增了一个记录“语义标签”的vector但在关键帧被剔除时没有清理对应的标签或者在共视图更新时没有同步更新语义相关的索引就会导致后面处理语义信息的线程访问到过期数据。这类问题的排查没有捷径但有个习惯能有效降低出错率每次新增关键帧成员务必把“构造时初始化、更新时同步、删除时清理”三个点都检查一遍。特别是在Erase和Replace这类函数里要把新成员纳入“一致性维护”的范畴。6.4 关键帧位姿被多模块修改时的混乱ORB-SLAM2里多个模块都会修改关键帧位姿。Tracking在每一帧都会更新当前关键帧的位姿LocalMapping在局部BA后会把优化结果写回关键帧LoopClosing在做Sim3优化和全局位姿图优化后也会修改关键帧位姿。如果这些模块之间的修改时序没理清就会出现“位姿来回跳”的现象。调试时可以用关键帧的mnId作为主键每个模块修改位姿后都把关键帧ID、时间戳和位姿矩阵打印到日志。多跑几次数据集分析同一关键帧在各模块更新前后的位姿差值。如果同一个关键帧在局部BA和回环优化中都被大尺度修改很可能是图结构连接不对而不是优化算法本身的问题。6.5 单目模式下关键帧深度信息的不稳定性如果是单目相机关键帧的尺度是无法直接从图像里得到的深度只能通过三角化和多帧运动观察来估计。因此单目模式下关键帧的SceneDepth经常不太稳定尤其是一开始的时候。如果你发现Tracking频繁判定“当前帧质量过低”并触发重定位一个常见原因是场景深度变化太快关键帧的深度范围被错误估计。这种情况下可以适当调大NeedNewKeyFrame里关于“局部地图跟踪到的点数”的阈值让它更愿意创建新关键帧减少对旧关键帧依赖从而缓解深度估计失当带来的问题。7. 善用关键帧让二次开发事半功倍我在实际项目里对ORB-SLAM2做过几次改造最深的一点体会是关键帧是系统的“信息枢纽”你加的任何新功能几乎都要从关键帧接入。比如你想做语义SLAM语义标签往往挂在关键帧的图像和地图点上关键帧类的图像缓存就给你提供了天然的语义信息附着点。你想做动态物体过滤关键帧里保存的特征点和描述子可以直接用于动态区域检测不需要额外再存原始图像。你想做多传感器融合关键帧的时间戳和位姿矩阵正好提供了时间对齐和空间对齐的基准。所以与其把KeyFrame当成一个被动存数据的类不如把它理解成“SLAM系统所有模块之间交换信息的走廊”。理解了它的成员、关系和更新逻辑你就掌握了整个ORB-SLAM2数据流的命门。最后再分享一个小技巧。读这类源码时不要只盯着类定义建议用一个简单的数据集哪怕是TUM的几张小图把代码里所有调用关键帧成员函数的地方都打印一条日志日志格式简单点帧ID、函数名、更新前后关键数据的变化。跑完看完整日志整个数据流其实就是一条线Tracking插入→LocalMapping优化→LoopClosing修正→新的Tracking再插入……循环往复周而复始。你把这条线看通了再看任何SLAM系统的源码都有一种“原来如此”的顺畅感。