ORB-SLAM2里使用词袋模型来做重定向与回环检测,使用的是第三方库DBoW2。
首先,有一个离线词袋ORBvoc.txt文件(ORB-SLAM2作者提供了一个他们训练好的词典),一般这个称之为词典,为了快速查询,以空间换时间建立了一个d层的K叉树来保存词典,树的叶子节点包含了每个单词及在词典里的权重。
词典的加载
//在System对象的初始化中
mpVocabulary = new ORBVocabulary();
if (has_suffix(strVocFile, ".txt"))
bVocLoad = mpVocabulary->loadFromTextFile(strVocFile);
else if(has_suffix(strVocFile, ".bin"))
bVocLoad = mpVocabulary->loadFromBinaryFile(strVocFile);
计算单张图像的词包
/**
* @brief Bag of Words Representation
*
* 计算词包mBowVec和mFeatVec,其中mFeatVec记录了属于第i个node(在第4层)的ni个描述子
* @see CreateInitialMapMonocular() TrackReferenceKeyFrame() Relocalization()
*/
void Frame::ComputeBoW()
{
if(mBowVec.empty())
{
vector vCurrentDesc = Converter::toDescriptorVector(mDescriptors);
mpORBvocabulary->transform(vCurrentDesc,mBowVec,mFeatVec,4);
}
}
词包mBowVec和mFeatVec
//KeyFrame.h中有
//BoW
DBoW2::BowVector mBowVec; ///< Vector of words to represent images
DBoW2::FeatureVector mFeatVec; ///< Vector of nodes with indexes of local features
其中BowVector很好理解——就是用来表示图像的向量(同描述子类似)具体形式为[[在词典特征索引,权重],[在词典特征索引,权重],。。。],计算两图像的相似度最本质的就是计算这个向量两两间的距离。
而FeatureVector可以看类的定义
class EXPORT FeatureVector:
public std::map >
即这个向量是个map,其中以一张图片的每个特征点在词典某一层节点下为条件进行分组,用来加速图形特征匹配——两两图像特征匹配只需要对相同NodeId下的特征点进行匹配就好。
以下是利用FeatureVector加速匹配的代码:
//int ORBmatcher::SearchByBoW(KeyFrame *pKF1, KeyFrame *pKF2, vector &vpMatches12)
DBoW2::FeatureVector::const_iterator f1it = vFeatVec1.begin();
DBoW2::FeatureVector::const_iterator f2it = vFeatVec2.begin();
DBoW2::FeatureVector::const_iterator f1end = vFeatVec1.end();
DBoW2::FeatureVector::const_iterator f2end = vFeatVec2.end();
while(f1it != f1end && f2it != f2end)
{
if(f1it->first == f2it->first)//步骤1:分别取出属于同一node的ORB特征点(只有属于同一node,才有可能是匹配点)
{
// 步骤2:遍历KF中属于该node的特征点
for(size_t i1=0, iend1=f1it->second.size(); i1second[i1];
MapPoint* pMP1 = vpMapPoints1[idx1];
if(!pMP1)
continue;
if(pMP1->isBad())
continue;
const cv::Mat &d1 = Descriptors1.row(idx1);
int bestDist1=256;
int bestIdx2 =-1 ;
int bestDist2=256;
// 步骤3:遍历F中属于该node的特征点,找到了最佳匹配点
for(size_t i2=0, iend2=f2it->second.size(); i2second[i2];
MapPoint* pMP2 = vpMapPoints2[idx2];
if(vbMatched2[idx2] || !pMP2)
continue;
if(pMP2->isBad())
continue;
const cv::Mat &d2 = Descriptors2.row(idx2);
int dist = DescriptorDistance(d1,d2);
if(dist
构建倒排索引KeyFrameDatabase
//在system对象初始化时进行初始化
mpKeyFrameDatabase = new KeyFrameDatabase(*mpVocabulary);
// 对象内包含的数据结构
std::vector > mvInvertedFile; ///< 倒排索引,mvInvertedFile[i]表示包含了第i个word id的所有关键帧
/**
* @brief 根据关键帧的词包,更新数据库的倒排索引
* @param pKF 关键帧
*/
void KeyFrameDatabase::add(KeyFrame *pKF)
{
unique_lock lock(mMutex);
// 为每一个word添加该KeyFrame
for(DBoW2::BowVector::const_iterator vit= pKF->mBowVec.begin(), vend=pKF->mBowVec.end(); vit!=vend; vit++)
mvInvertedFile[vit->first].push_back(pKF);
}
可以看到这个对象的实际作用是,可以通过图像上的特征点找到其他也包含相似特征点的图像
这样进行重定位和回环检测的时候就不用在所有的图像中进行词袋向量的相似度查找了:
// words是检测图像是否匹配的枢纽,遍历该pKF的每一个word
for(DBoW2::BowVector::const_iterator vit=F->mBowVec.begin(), vend=F->mBowVec.end(); vit != vend; vit++)
{
// 提取所有包含该word的KeyFrame
list &lKFs = mvInvertedFile[vit->first];
for(list::iterator lit=lKFs.begin(), lend= lKFs.end(); lit!=lend; lit++)
{
KeyFrame* pKFi=*lit;
if(pKFi->mnRelocQuery!=F->mnId)// pKFi还没有标记为pKF的候选帧
{
pKFi->mnRelocWords=0;
pKFi->mnRelocQuery=F->mnId;
lKFsSharingWords.push_back(pKFi);
}
pKFi->mnRelocWords++;
}
}