
1. 从一次地图漂移说起为什么这四个概念必须放在一起理解去年帮一个做室内巡检机器人的团队排查定位问题时遇到一个很典型的现象机器人在走廊里跑前两百米轨迹很干净一进到会议室那种纹理重复、光照变化大的区域轨迹就开始缓慢地“拧麻花”回环之后地图整体被拉歪。当时他们已经在用ORB-SLAM2参数也调了不少但问题始终在。后来把问题定位到局部建图和回环校正的衔接上本质上是没搞清楚关键帧KeyFrame、共视图Covisibility Graph、扩展树Spanning Tree、**本质图Essential Graph**这四个东西各自管什么、怎么互相喂数据。这四个概念是ORB-SLAM2后端优化的骨架。单独看每一个都不难难的是它们之间的数据流和职责边界。很多刚接触这套系统的朋友会把共视图和本质图混为一谈或者以为扩展树只是共视图的一棵普通生成树结果在调参、改代码、做二次开发时到处踩坑。这篇就把这四个概念从“是什么”到“怎么用”完整梳理一遍重点讲清楚它们之间的联系以及在实际工程里怎么利用这些联系去定位问题。内容适合已经跑通过ORB-SLAM2、想深入理解后端结构的开发者也适合正在做SLAM二次开发、需要改动局部建图或回环模块的工程师。如果你还在纠结前端特征匹配这篇可以先收藏等跑通demo再回来看。2. 四个概念各自是什么先把角色分清楚2.1 关键帧不是普通帧是地图的“骨架节点”ORB-SLAM2里每一帧图像都会做特征提取但并不是每一帧都会变成关键帧。普通帧用完就丢关键帧会被永久保留在地图里成为后续优化和重定位的锚点。判断一帧是否升级为关键帧系统里有一套条件常见的有距离上一次关键帧已经过了足够多的帧数避免关键帧太密当前帧跟踪到的地图点数量低于某个阈值说明跟踪质量下降需要新关键帧补充信息当前帧相对参考关键帧的平移或旋转超过一定量这三个条件不是随便设的。关键帧太密后端优化规模爆炸实时性保不住关键帧太稀地图细节丢失回环检测和重定位都会变差。实际调参时室内场景通常把帧数间隔调小一点因为室内运动慢、纹理重复多需要更密的关键帧来约束室外大场景可以适当放宽。关键帧里存了什么除了位姿和特征点它还维护了几个重要的连接关系和它共视地图点最多的若干关键帧共视连接、它在扩展树里的父节点和子节点、以及它参与的本质图边。这些连接关系就是后面三个概念的基础。注意关键帧的位姿不是一成不变的。局部BA和全局BA都会修改关键帧位姿所以任何依赖关键帧位姿的计算都要考虑优化后的更新。2.2 共视图谁和谁“看得见同一片地方”共视图的节点是所有关键帧边表示两个关键帧之间存在共视关系。具体来说如果关键帧A和关键帧B同时观测到至少N个相同的地图点它们之间就有一条边边的权重就是共视地图点的数量。ORB-SLAM2里这个N通常取15但代码里实际用的是最小共视点数阈值低于这个值不建边。共视图的核心作用是局部建图。当一个新的关键帧插入后局部BA不会优化整张地图而是只优化这个新关键帧、它的共视邻居、以及这些邻居的共视邻居通常限制在两跳以内。这样既保证了局部一致性又控制了计算量。共视图的边权重越大说明两个关键帧观测到的共同地图点越多它们在优化时的相互约束就越强。这里有个容易误解的点共视图的边是动态更新的。当某个地图点被观测它的关键帧剔除后相关边的权重会下降低于阈值时边会被删除。所以共视图不是建好就不动的它随着地图点的增删在实时变化。2.3 扩展树共视图的一棵“最小连通骨架”扩展树是从共视图中提取出来的一棵生成树。具体做法是每个关键帧选择共视权重最大的那个邻居作为父节点如果权重相同就选关键帧ID更小的。这样整张共视图就被压缩成了一棵树树上的边保留了最强的共视连接。扩展树的作用是快速遍历和回环候选筛选。在回环检测阶段系统需要快速找到当前关键帧可能形成回环的历史关键帧。如果直接在共视图上做广度优先搜索边太多搜索空间大。扩展树把搜索空间压缩到树结构上先沿着树找到一定范围内的候选再在共视图里做精细匹配。另外扩展树还用于地图点的可见性传播和关键帧的剔除策略。扩展树和共视图的关系可以这样理解共视图是“完整关系网”扩展树是“骨干网络”。扩展树上的边一定是共视图的边但共视图的边不一定是扩展树的边。扩展树保证了任意两个关键帧之间有一条路径但这条路径不一定是最短共视路径。2.4 本质图回环校正时的“轻量级全局约束”本质图是ORB-SLAM2里专门为回环校正和全局优化设计的一个图结构。它的节点也是所有关键帧但边只保留两类扩展树上的边保证图的连通性共视权重超过某个较高阈值的边通常取100远高于共视图的建边阈值本质图的核心思想是回环校正时如果直接优化整张共视图边太多计算量太大如果只优化扩展树约束又太弱校正结果不准确。本质图取了一个折中只保留最强的共视约束既保证了优化规模可控又保证了校正精度。本质图在回环校正中的具体用法是当检测到回环后系统会在本质图上做位姿图优化Pose Graph Optimization把回环误差均匀分配到整张图上。优化完成后再根据优化后的位姿更新所有关键帧和地图点。这个过程不是一次性的回环校正后局部建图会继续运行新的关键帧和边会继续加入本质图。3. 它们之间的联系数据流和职责边界3.1 从关键帧到共视图共视关系的建立和更新关键帧插入后系统会计算它和已有共视邻居的共视关系。具体流程是新关键帧提取ORB特征和前一关键帧做特征匹配得到初始位姿遍历新关键帧观测到的地图点找到这些地图点的其他观测关键帧统计每个其他关键帧和新关键帧的共视地图点数量如果数量超过阈值通常15建立共视边权重为共视点数同时更新新关键帧的扩展树父节点选共视权重最大的邻居这个过程是增量的每插入一个关键帧就更新一次。共视图的边权重会随着地图点的增删而变化但边的删除不是实时的通常在地图点被剔除后相关边的权重会在下一次更新时重新计算。实操心得如果你在做二次开发时需要手动干预共视图比如强制合并两个关键帧一定要同步更新扩展树和本质图否则回环校正时会出现约束不一致导致地图扭曲。3.2 从共视图到扩展树生成树的构建和维护扩展树的构建发生在关键帧插入时。每个新关键帧选择共视权重最大的邻居作为父节点如果权重相同就选ID更小的。这个选择策略保证了扩展树上的边是共视关系最强的边。扩展树的维护比共视图简单因为树结构一旦建立只有在关键帧被剔除时才需要更新。关键帧剔除时它的子节点会重新选择父节点通常选择原父节点的父节点或者共视权重最大的其他邻居。这个重连过程保证了扩展树的连通性。扩展树和共视图的另一个联系是扩展树上的边一定是共视图的边但共视图的边不一定是扩展树的边。这意味着扩展树是共视图的一个子图而且是保留最强连接的子图。3.3 从共视图和扩展树到本质图回环校正的图结构选择本质图的构建发生在回环校正阶段。系统会遍历所有关键帧把扩展树上的边和共视权重超过高阈值的边加入本质图。这个高阈值通常取100远高于共视图的建边阈值15。为什么选100这是ORB-SLAM2作者在论文里给出的经验值。阈值太低本质图边太多优化计算量大阈值太高本质图边太少校正约束不足。100这个值在大多数场景下能取得平衡但在纹理特别丰富的场景下可以适当调高在纹理稀疏的场景下可以适当调低。本质图和共视图的另一个区别是本质图的边是静态的一旦回环校正开始本质图就固定了优化过程中不会新增或删除边。而共视图是动态的局部建图过程中边会不断更新。这个区别很重要因为回环校正需要稳定的图结构来保证优化收敛。3.4 四者之间的数据流总结把四个概念的数据流串起来关键帧插入 → 更新共视图边 → 更新扩展树父节点局部建图 → 在共视图上做局部BA → 更新关键帧位姿和地图点回环检测 → 在扩展树上搜索候选 → 在共视图上做精细匹配回环校正 → 在本质图上做位姿图优化 → 更新所有关键帧位姿这个数据流是ORB-SLAM2后端的主循环。理解了这个循环就能理解为什么改一个参数会影响整个系统。4. 实操中怎么用调参、排查、二次开发4.1 关键帧策略调参密度和质量的权衡关键帧策略直接影响共视图的规模和扩展树的形状。ORB-SLAM2里关键帧的插入条件在Tracking::NeedNewKeyFrame()里主要有三个mnFramesSinceLastKeyFrame距离上一次关键帧的帧数mnTrackedMapPoints当前帧跟踪到的地图点数量mnTrackedMapPoints / mnMatchesInliers跟踪质量实际调参时室内场景通常把帧数间隔从默认的20调到10到15因为室内运动慢需要更密的关键帧来约束。室外场景可以保持默认或者调到25到30因为室外运动快关键帧太密会导致共视图规模爆炸。注意关键帧太密不仅影响实时性还会导致扩展树退化。因为新关键帧和前一关键帧的共视权重可能很高扩展树会变成一条长链回环候选搜索效率下降。4.2 共视图阈值调参局部BA的规模和精度共视图的建边阈值在KeyFrame::UpdateConnections()里默认是15。这个阈值决定了局部BA的优化规模。阈值太低共视图边太多局部BA计算量大阈值太高共视图边太少局部BA约束不足轨迹容易漂移。实际调参时纹理丰富的场景可以适当调高到20到25因为共视点数量普遍较多阈值太低会导致边太多。纹理稀疏的场景可以适当调低到10到12因为共视点数量普遍较少阈值太高会导致边太少。4.3 本质图阈值调参回环校正的精度和速度本质图的建边阈值在LoopClosing::ComputeSim3()和LoopClosing::CorrectLoop()里默认是100。这个阈值决定了回环校正的优化规模。阈值太低本质图边太多优化计算量大阈值太高本质图边太少校正精度下降。实际调参时如果回环校正后地图仍然有明显扭曲可以适当调低到80到90增加约束。如果回环校正耗时太长影响实时性可以适当调高到120到150减少约束。4.4 常见问题排查从现象反推是哪个环节出了问题现象可能原因排查方向轨迹缓慢漂移回环后仍不闭合共视图边太少局部BA约束不足检查共视图建边阈值适当调低回环校正后地图局部扭曲本质图边太少校正约束不足检查本质图建边阈值适当调低回环检测耗时太长扩展树搜索范围太大检查扩展树深度限制搜索范围局部BA耗时太长共视图边太多优化规模太大检查共视图建边阈值适当调高关键帧数量增长过快关键帧插入条件太宽松检查关键帧插入条件适当收紧这个表是实际排查时最常用的。遇到问题先看现象再反推是哪个环节然后针对性调参。4.5 二次开发注意事项改图结构时要同步更新如果你在做二次开发比如要手动合并关键帧、删除关键帧、或者修改共视关系一定要同步更新共视图、扩展树和本质图。这三个图结构是相互依赖的改一个不改另一个会导致约束不一致。具体来说删除关键帧时要删除它在共视图里的所有边更新扩展树里它的子节点的父节点从本质图里删除它的所有边合并关键帧时要合并它们的共视边更新扩展树结构合并本质图边修改共视关系时要同步更新扩展树和本质图实操心得我试过在回环校正后手动调整关键帧位姿结果因为没更新本质图下一次回环校正时约束冲突地图直接崩了。后来学乖了任何手动干预都要走完整的更新流程。5. 一个完整的回环校正案例从检测到优化5.1 回环检测扩展树搜索加共视图匹配假设机器人在走廊里跑了一圈回到起点当前关键帧K_current和起点附近的关键帧K_old形成了回环。回环检测的流程是在扩展树上从K_current出发搜索一定深度范围内的候选关键帧对每个候选关键帧在共视图上做精细匹配计算相似度如果相似度超过阈值认为检测到回环这里扩展树的作用是快速缩小搜索范围共视图的作用是精细匹配。如果直接在共视图上搜索边太多搜索空间大如果只在扩展树上搜索约束太弱容易漏检。5.2 回环校正本质图上的位姿图优化检测到回环后系统会在本质图上做位姿图优化。具体流程是构建本质图节点是所有关键帧边是扩展树边和共视权重超过100的边在本质图上做位姿图优化把回环误差均匀分配到整张图上根据优化后的位姿更新所有关键帧和地图点更新共视图和扩展树因为关键帧位姿变了共视关系可能变化这个过程中本质图的边是固定的优化过程中不会新增或删除边。这保证了优化收敛的稳定性。5.3 校正后的地图更新共视图和扩展树的同步回环校正后关键帧位姿变了共视关系可能变化。系统会重新计算共视图边权重更新扩展树父节点。这个过程是增量的只更新受影响的关键帧。注意回环校正后局部建图会继续运行新的关键帧和边会继续加入共视图和扩展树。所以回环校正不是一次性的而是一个持续的过程。6. 几个容易踩的坑和我的实操体会第一个坑是把共视图和本质图混为一谈。共视图是局部BA用的边多、动态更新本质图是回环校正用的边少、静态。两者阈值不同、用途不同不能互换。第二个坑是忽略扩展树的维护。扩展树在关键帧剔除时需要重连如果重连策略不对扩展树会退化成长链回环候选搜索效率下降。第三个坑是回环校正后不更新共视图。关键帧位姿变了共视关系可能变化如果不更新下一次局部BA会用到过时的共视关系导致轨迹漂移。我个人的体会是这四个概念不是孤立的它们是一个数据流上的四个环节。理解数据流比理解单个概念更重要。实际排查问题时先看现象再反推是哪个环节出了问题然后针对性调参或改代码。调参时不要一次改多个参数一次改一个观察效果逐步逼近最优。最后分享一个小技巧如果你不确定某个参数该调多少可以先在小数据集上跑几组对比实验记录轨迹误差和耗时画成曲线找拐点。这个方法比盲目试参数高效得多。