ORB_SLAM2关键帧数据库

关键帧数据库

  注意区分词典和关键帧数据库的区别。
词典:事先训练好的,用来计算关键帧的词袋信息。
关键帧数据库:根据关键帧的词袋生成,在回环检测、重定位的时候使用,寻找与当前帧最相似的候选关键帧。
  在局部建图(LocalMapping)线程的ProcessNewKeyFrame函数中,计算了每个关键帧的词袋信息:

mpCurrentKeyFrame->ComputeBoW();

ORB_SLAM2关键帧数据库

关键帧数据库中存放着所有关键帧的单词信息。形式为:
单词1:关键帧(1)(2)(3)
单词2:关键帧(3)(4)(5)
单词3:关键帧(5)(6)(7)

其作用为:在回环检测、重定位的时候,搜索与当前帧最相似的候选关键帧集合。

std::vector<list<KeyFrame*> > mvInvertedFile; 

关键帧数据库。mvInvertedFile[i]表示含有第i个单词的关键帧的id组成的链表。

DBoW2::BowVector mBowVec; 

mBowVec 内部实际存储的是std::map<WordId, WordValue>
WordId 和 WordValue 表示Word在叶子中的id 和权重

构造函数

KeyFrameDatabase::KeyFrameDatabase (const ORBVocabulary &voc):
    mpVoc(&voc)
{
    // 数据库的主要内容了
    mvInvertedFile.resize(voc.size()); // number of words
}

其中mvInvertedFile的数据类型为:

std::vector<list<KeyFrame*> > mvInvertedFile; 

mvInvertedFile[i]表示含有第i个单词的关键帧组成的链表。

关键帧插入关键帧数据库中

// 根据关键帧的BoW,更新数据库的倒排索引
void KeyFrameDatabase::add(KeyFrame *pKF)
{
    unique_lock<mutex> 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);
}


关键帧数据库中删除关键帧

通过mBowVec找到该关键帧包含哪些单词,然后在mvInvertedFile中将相应的关键帧删掉。

// 关键帧被删除后,更新数据库的倒排索引
void KeyFrameDatabase::erase(KeyFrame* pKF)
{
    unique_lock<mutex> lock(mMutex);

    // Erase elements in the Inverse File for the entry
    // 每一个KeyFrame包含多个words,遍历mvInvertedFile中的这些words,然后在word中删除该KeyFrame
    for(DBoW2::BowVector::const_iterator vit=pKF->mBowVec.begin(), vend=pKF->mBowVec.end(); vit!=vend; vit++)
    {
        // List of keyframes that share the word

        list<KeyFrame*> &lKFs = mvInvertedFile[vit->first];
        // 这个效率有点低啊
        for(list<KeyFrame*>::iterator lit=lKFs.begin(), lend= lKFs.end(); lit!=lend; lit++)
        {
            if(pKF==*lit)
            {
                lKFs.erase(lit);
                break;
            }
        }
    }
}


清空关键帧数据库

void KeyFrameDatabase::clear()
{
    mvInvertedFile.clear();// mvInvertedFile[i]表示包含了第i个word id的所有关键帧
    mvInvertedFile.resize(mpVoc->size());// mpVoc:预先训练好的词典
}


获得回环检测时的候选关键帧

vector<KeyFrame*> KeyFrameDatabase::DetectLoopCandidates(KeyFrame* pKF, float minScore)

  寻找可能与当前帧构成回环的关键帧。
(1)找出与当前帧具有相同单词的关键帧。
(2)设置最多的相同单词的880%为阈值,根据词典计算当前帧与候选帧的相似度。
(3)计算上述候选帧对应的共视关键帧组的总得分,只取最高组得分75%以上的组。
(4)得到上述组中分数最高的关键帧作为闭环候选关键帧。

找出与当前帧的共视点大于15的关键帧

set<KeyFrame*> spConnectedKeyFrames = pKF->GetConnectedKeyFrames();

寻找与当前帧有相同单词的关键帧

 &emps;寻找与当前帧有相同单词的关键帧,并将其放入到链表list<KeyFrame*> lKFsSharingWords;中。其中变量mnLoopWords记录了当前帧与候选帧具有的相同的单词个数。

    // 用于保存可能与当前关键帧形成闭环的候选帧(只要有相同的word,且不属于局部相连(共视)帧)
    list<KeyFrame*> lKFsSharingWords;

    // Search all keyframes that share a word with current keyframes
    // Discard keyframes connected to the query keyframe
    // Step 1:找出和当前帧具有公共单词的所有关键帧,不包括与当前帧连接(也就是共视)的关键帧
    {
        unique_lock<mutex> lock(mMutex);

        // words是检测图像是否匹配的枢纽,遍历该pKF的每一个word
        // mBowVec 内部实际存储的是std::map<WordId, WordValue>
        // WordId 和 WordValue 表示Word在叶子中的id 和权重
        for(DBoW2::BowVector::const_iterator vit=pKF->mBowVec.begin(), vend=pKF->mBowVec.end(); vit != vend; vit++)
        {
            // 提取所有包含该word的KeyFrame
            list<KeyFrame*> &lKFs =   mvInvertedFile[vit->first];
            // 然后对这些关键帧展开遍历
            for(list<KeyFrame*>::iterator lit=lKFs.begin(), lend= lKFs.end(); lit!=lend; lit++)
            {
                KeyFrame* pKFi=*lit;
                
                if(pKFi->mnLoopQuery!=pKF->mnId)    
                {
                    // 还没有标记为pKF的闭环候选帧
                    pKFi->mnLoopWords=0;
                    // 和当前关键帧共视的话不作为闭环候选帧
                    if(!spConnectedKeyFrames.count(pKFi))
                    {
                        // 没有共视就标记作为闭环候选关键帧,放到lKFsSharingWords里
                        pKFi->mnLoopQuery=pKF->mnId;
                        lKFsSharingWords.push_back(pKFi);
                    }
                }
                pKFi->mnLoopWords++;// 记录pKFi与pKF具有相同word的个数
            }
        }
    }

设置最少单词阈值

    // Only compare against those keyframes that share enough words
    // Step 2:统计上述所有闭环候选帧中与当前帧具有共同单词最多的单词数,用来决定相对阈值 
    int maxCommonWords=0;
    for(list<KeyFrame*>::iterator lit=lKFsSharingWords.begin(), lend= lKFsSharingWords.end(); lit!=lend; lit++)
    {
        if((*lit)->mnLoopWords>maxCommonWords)
            maxCommonWords=(*lit)->mnLoopWords;
    }

    // 确定最小公共单词数为最大公共单词数目的0.8倍
    int minCommonWords = maxCommonWords*0.8f;

挑选候选关键帧

list<pair<float,KeyFrame*> > lScoreAndMatch;存放所有满足条件的候选关键帧。其中float变量是计算出来的两帧的词典相似度,KeyFrame*为候选关键帧。

    // Compute similarity score. Retain the matches whose score is higher than minScore
    // Step 3:遍历上述所有闭环候选帧,挑选出共有单词数大于minCommonWords且单词匹配度大于minScore存入lScoreAndMatch
    for(list<KeyFrame*>::iterator lit=lKFsSharingWords.begin(), lend= lKFsSharingWords.end(); lit!=lend; lit++)
    {
        KeyFrame* pKFi = *lit;

        // pKF只和具有共同单词较多(大于minCommonWords)的关键帧进行比较
        if(pKFi->mnLoopWords>minCommonWords)
        {
            nscores++;// 这个变量后面没有用到

            // 用mBowVec来计算两者的相似度得分
            float si = mpVoc->score(pKF->mBowVec,pKFi->mBowVec);

            pKFi->mLoopScore = si;
            if(si>=minScore)
                lScoreAndMatch.push_back(make_pair(si,pKFi));
        }
    }

计算候选关键帧的共视关键帧与当前帧的得分

  单单计算当前帧和某一候选关键帧的相似性是不够的,这里将与候选关键帧相连(权值最高,共视程度最高)的前十个关键帧归为一组,计算累计得分。在这十个关键帧中选择一个最好的插入到lAccScoreAndMatch中。

    for(list<pair<float,KeyFrame*> >::iterator it=lScoreAndMatch.begin(), itend=lScoreAndMatch.end(); it!=itend; it++)
    {
        KeyFrame* pKFi = it->second;
        vector<KeyFrame*> vpNeighs = pKFi->GetBestCovisibilityKeyFrames(10);

        float bestScore = it->first; // 该组最高分数
        float accScore = it->first;  // 该组累计得分
        KeyFrame* pBestKF = pKFi;    // 该组最高分数对应的关键帧
        // 遍历共视关键帧,累计得分 
        for(vector<KeyFrame*>::iterator vit=vpNeighs.begin(), vend=vpNeighs.end(); vit!=vend; vit++)
        {
            KeyFrame* pKF2 = *vit;
            // 只有pKF2也在闭环候选帧中,且公共单词数超过最小要求,才能贡献分数
            if(pKF2->mnLoopQuery==pKF->mnId && pKF2->mnLoopWords>minCommonWords)
            {
                accScore+=pKF2->mLoopScore;
                // 统计得到组里分数最高的关键帧
                if(pKF2->mLoopScore>bestScore)
                {
                    pBestKF=pKF2;
                    bestScore = pKF2->mLoopScore;
                }
            }
        }

        lAccScoreAndMatch.push_back(make_pair(accScore,pBestKF));
        // 记录所有组中组得分最高的组,用于确定相对阈值
        if(accScore>bestAccScore)
            bestAccScore=accScore;
    }

获得闭环候选关键帧

将lAccScoreAndMatch中的得分大于阈值的关键帧插入vpLoopCandidates中并返回。

    for(list<pair<float,KeyFrame*> >::iterator it=lAccScoreAndMatch.begin(), itend=lAccScoreAndMatch.end(); it!=itend; it++)
    {
        if(it->first>minScoreToRetain)
        {
            KeyFrame* pKFi = it->second;
            // spAlreadyAddedKF 是为了防止重复添加
            if(!spAlreadyAddedKF.count(pKFi))
            {
                vpLoopCandidates.push_back(pKFi);
                spAlreadyAddedKF.insert(pKFi);
            }
        }
    }

ORB_SLAM2关键帧数据库


获得重定位时的候选关键帧

  过程与回环检测时的类似,但没有设置最小得分。
(1)找到与当前帧具有相同的单词的所有关键帧。
(2)根据阈值进行筛选,使用词典计算当前帧和重定位候选关键帧的相似度得分。
(3)将与重定位候选关键帧相连的十个关键帧看为一组,计算总得分,并选出其中得分最高的关键帧
(4)返回累计得分最高的几个组中的最好的关键帧

vector<KeyFrame*> KeyFrameDatabase::DetectRelocalizationCandidates(Frame *F)
上一篇:2021-06-18


下一篇:ORB-SLAM2之LocalMapping线程——代码篇