ORB-SLAM3 关键帧相似度计算

float si = mpVoc->score(F->mBowVec, pKFi->mBowVec); 这行代码的核心作用,是计算当前帧与某个候选关键帧在视觉上的相似度得分,得分越高,两者在视觉上就越相似。

这个得分是后续筛选和聚合候选关键帧的基础,也是ORB-SLAM3重定位流程中"粗匹配"的关键一环。

下面将从得分计算方式和算法实现两个层面来详细解释:

📊 得分计算方式:基于词袋向量的相似度

mpVoc->scoreDBoW2库提供的接口,其输入是两个词袋向量(BowVector),输出是一个浮点数,代表它们的相似度。

  • 输入是什么? F->mBowVec 是当前帧的词袋向量,pKFi->mBowVec 是候选关键帧的词袋向量。每个向量都是一个 std::map<WordId, WordValue>,记录了该帧图像包含的"视觉单词"及其对应的TF-IDF权重。

  • 如何计算相似度? DBoW2库提供了多种评分方法,例如L1范数、L2范数、卡方距离、点积等。ORB-SLAM3通常使用L1范数评分(L1Scoring) ,其本质是计算两个词袋向量的加权曼哈顿距离。得分越高,代表两帧共享的视觉单词越多、权重越大,它们在视觉内容上就越相似。

⚙️ 算法实现:为什么这一步是关键?

理解了得分计算方式后,我们再把这个步骤放回DetectRelocalizationCandidates函数的上下文中,就能更清楚地看到它的重要性:

  1. 从"粗筛"到"精筛" :在这行代码之前,系统通过倒排索引mvInvertedFile)快速找出了所有与当前帧有共视单词的关键帧,这是第一步的"粗筛"。

  2. 计算精确得分score()函数就是第二步的"精筛"。它基于TF-IDF权重,为每一个通过粗筛的关键帧计算一个精确的相似度得分。TF-IDF确保了那些罕见但更具区分度的视觉单词(高区分度)对得分的贡献更大,而普遍出现的单词(如天空、墙壁)的贡献被削弱。

  3. 指导后续筛选:这个得分会被用于后续的筛选逻辑。例如,代码中常见的策略是:只保留得分大于最高分75%的候选帧,然后将这些高得分帧的得分累加到它们共视图中的邻居帧上,形成"累积得分",从而找到视觉上最相似的"区域",而非单个孤立的帧。

💎 总结

mpVoc->score(...) 这行代码,本质上是利用DBoW2库提供的相似度计算接口,将两个词袋向量转换为一个量化的视觉相似度得分。这个得分是重定位候选帧筛选流程的核心依据,它让系统能够快速地从成百上千个候选帧中,精确定位到与当前帧视觉上最相似的几个关键帧。

补充:

mpVoc->score(F->mBowVec, pKFi->mBowVec) 这行代码,其核心是调用DBoW2库来计算两个词袋向量的相似度得分。在ORB-SLAM3中,默认使用的是 L1范数评分(L1 Scoring) 方法。

下面,将从代码结构、计算原理和具体例子三个方面来拆解这个过程。


🧱 1. 代码结构:DBoW2的评分家族

在DBoW2库中,score 是一个定义在基类 GeneralScoring 中的纯虚函数。它通过一个宏 __SCORING_CLASS 来定义不同的评分方法。

cpp

复制代码
// Thirdparty/DBoW2/DBoW2/ScoringObject.h[reference:6]

namespace DBoW2 {
    /// Base class of scoring functions
    class GeneralScoring {
    public:
        // 纯虚函数:计算两个向量的得分
        virtual double score(const BowVector &v, const BowVector &w) const = 0;
        // ...
    };
}

ORB-SLAM3支持多种评分方法,并通过宏来声明:

评分方法 宏定义 是否需要归一化
L1范数评分 (L1 Scoring) __SCORING_CLASS(L1Scoring, true, L1)
L2范数评分 (L2 Scoring) __SCORING_CLASS(L2Scoring, true, L2)
卡方评分 (ChiSquare Scoring) __SCORING_CLASS(ChiSquareScoring, true, L1)
KL散度评分 (KL Scoring) __SCORING_CLASS(KLScoring, true, L1)
巴氏距离评分 (Bhattacharyya Scoring) __SCORING_CLASS(BhattacharyyaScoring, true, L1)
点积评分 (Dot Product Scoring) __SCORING_CLASS(DotProductScoring, false, L1)

ORB-SLAM3默认使用的是 L1Scoring。这类评分通常要求向量先进行归一化(mustNormalize 返回 true)。

⚙️ 2. 核心原理:L1范数评分如何计算?

L1Scoring 的计算分为两步:向量归一化计算相似度

步骤一:向量归一化 (Normalization)

在进行相似度计算前,需要对词袋向量 vw 进行 L1归一化

对于一个词袋向量 v,它包含了一系列 (单词ID, 权重) 对。L1归一化就是让向量中所有权重的绝对值之和等于1

公式

v_norm = v / ||v||₁

其中,||v||₁ = Σ |v_i| 是向量中所有元素绝对值之和。

步骤二:计算L1相似度

归一化后,两个向量的L1相似度通过以下交集(Intersection) 方法计算:

公式

score(v, w) = Σᵢ min(v_i, w_i)

这个公式的含义是:遍历两个向量中所有共同的单词,将其权重中较小的那个累加起来。

注意 :由于L1归一化保证了向量元素和为1,这个交集得分天然地落在 0, 1 区间内。得分越高,表示两个向量共享的视觉单词越多,图像越相似。


💡 3. 实例演示:从公式到数字

让我们通过一个具体的例子来理解这个过程。

假设我们有两个极度简化的词袋向量,词汇表里只有 单词A、B、C 三个视觉单词。

  • 图像1 (当前帧):包含单词A(权重2) 和 单词B(权重3)。

    • 向量 v = {A: 2, B: 3}
  • 图像2 (候选关键帧):包含单词B(权重4) 和 单词C(权重1)。

    • 向量 w = {B: 4, C: 1}
步骤一:L1归一化
  • ||v||₁ = 2 + 3 = 5

  • v_norm = {A: 2/5, B: 3/5} = {A: 0.4, B: 0.6}

  • ||w||₁ = 4 + 1 = 5

  • w_norm = {B: 4/5, C: 1/5} = {B: 0.8, C: 0.2}

步骤二:计算L1相似度

找到两个向量共有的单词(即交集 ),这里是 单词B

  • 对于单词B:min(v_norm(B), w_norm(B)) = min(0.6, 0.8) = 0.6

  • 最终的相似度得分:score = 0.6

这个 0.6 就是 mpVoc->score() 函数返回的 float si 值。

💎 4. 总结

float si = mpVoc->score(F->mBowVec, pKFi->mBowVec);

  1. mpVoc 是一个指向 DBoW2::Vocabulary 类的指针,它内部维护着一个 DBoW2::L1Scoring 类型的评分对象。

  2. score() 函数接收两个 BowVector 作为输入。

  3. 函数内部首先对两个向量进行 L1归一化

  4. 然后计算它们归一化后权重的 交集和 ,得到一个 [0, 1] 范围内的浮点数。

  5. 这个数值就是两帧图像在视觉上的相似度得分,分数越高,视觉上越相似。

关于:

累加的结果绝不会大于 1 ,最大值就是 1。原因其实很简单:

📐 数学证明

假设有两个经过 L1 归一化 的非负向量 vw,它们的所有元素和都等于 1:

∑ivi=1,∑iwi=1i∑​vi​=1,i∑​wi​=1

对于每个共同的单词 i,我们取 min(v_i, w_i)。由于 min(v_i, w_i) ≤ v_i(也 ≤ w_i),所以对整个向量求和:

∑imin⁡(vi,wi)≤∑ivi=1i∑​min(vi​,wi​)≤i∑​vi​=1

同理,它也 ≤ 1。因此累加和 ≤ 1 ,并且只有当两个向量完全相等(非零元素完全相同且权重一致)时,才能达到 1

💡 直观理解

你可以把 L1 归一化后的向量看作一个"概率分布",每个单词的权重就是它出现的相对频率。两个分布的交集(共同单词中较小的频率)的总和,不可能超过整个分布的总概率(即 1)。这就像两个馅饼,你只能取它们重叠的那部分,最多也只能取到整个馅饼(1)。

所以,mpVoc->score() 返回的相似度得分始终在 [0, 1] 之间,非常合理。