float si = mpVoc->score(F->mBowVec, pKFi->mBowVec); 这行代码的核心作用,是计算当前帧与某个候选关键帧在视觉上的相似度得分,得分越高,两者在视觉上就越相似。
这个得分是后续筛选和聚合候选关键帧的基础,也是ORB-SLAM3重定位流程中"粗匹配"的关键一环。
下面将从得分计算方式和算法实现两个层面来详细解释:
📊 得分计算方式:基于词袋向量的相似度
mpVoc->score 是DBoW2库提供的接口,其输入是两个词袋向量(BowVector),输出是一个浮点数,代表它们的相似度。
-
输入是什么?
F->mBowVec是当前帧的词袋向量,pKFi->mBowVec是候选关键帧的词袋向量。每个向量都是一个std::map<WordId, WordValue>,记录了该帧图像包含的"视觉单词"及其对应的TF-IDF权重。 -
如何计算相似度? DBoW2库提供了多种评分方法,例如L1范数、L2范数、卡方距离、点积等。ORB-SLAM3通常使用L1范数评分(L1Scoring) ,其本质是计算两个词袋向量的加权曼哈顿距离。得分越高,代表两帧共享的视觉单词越多、权重越大,它们在视觉内容上就越相似。
⚙️ 算法实现:为什么这一步是关键?
理解了得分计算方式后,我们再把这个步骤放回DetectRelocalizationCandidates函数的上下文中,就能更清楚地看到它的重要性:
-
从"粗筛"到"精筛" :在这行代码之前,系统通过倒排索引 (
mvInvertedFile)快速找出了所有与当前帧有共视单词的关键帧,这是第一步的"粗筛"。 -
计算精确得分 :
score()函数就是第二步的"精筛"。它基于TF-IDF权重,为每一个通过粗筛的关键帧计算一个精确的相似度得分。TF-IDF确保了那些罕见但更具区分度的视觉单词(高区分度)对得分的贡献更大,而普遍出现的单词(如天空、墙壁)的贡献被削弱。 -
指导后续筛选:这个得分会被用于后续的筛选逻辑。例如,代码中常见的策略是:只保留得分大于最高分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)
在进行相似度计算前,需要对词袋向量 v 和 w 进行 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);
-
mpVoc是一个指向DBoW2::Vocabulary类的指针,它内部维护着一个DBoW2::L1Scoring类型的评分对象。 -
score()函数接收两个BowVector作为输入。 -
函数内部首先对两个向量进行 L1归一化。
-
然后计算它们归一化后权重的 交集和 ,得到一个
[0, 1]范围内的浮点数。 -
这个数值就是两帧图像在视觉上的相似度得分,分数越高,视觉上越相似。
关于:
累加的结果绝不会大于 1 ,最大值就是 1。原因其实很简单:
📐 数学证明
假设有两个经过 L1 归一化 的非负向量 v 和 w,它们的所有元素和都等于 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] 之间,非常合理。