关键帧是很多SLAM框架都会用到的一个概念,在之前的代码流程里可以看到,Tracking线程向LocalMappingLoopClosing线程传递的就只有关键帧。关键帧相当于slam的骨架,是在局部一系列普通帧中选出一帧作为局部帧的代表,记录局部信息。关键帧的筛选、增加和剔除在ORB-SLAM2里都有着很严密的设计。但在KeyFrame类里,实现的更多的是关键帧自身的一些功能。
具体在函数里,在Tracking中进行关键帧判断,调用的是Tracking类的CreateNewKeyFrame函数,然后再调用LocalMapping线程的InsertKeyFrame函数插入到局部地图之中。

// Tracking.cc
if(NeedNewKeyFrame())CreateNewKeyFrame();
// CreateNewKeyFrame
// 关键帧插入到列表 mlNewKeyFrames中,等待local mapping线程临幸
mpLocalMapper->InsertKeyFrame(pKF);

然后在LocalMapping线程中,在Run函数中会直接把当前关键帧插入到LoopClosing线程中

mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);

目录

  • 成员变量
  • 成员函数
    • 构造函数
    • 关键帧类重要函数1 `UpdateConnections`
    • 关键帧类重要函数2 `SetBadFlag`
    • 取出关键帧的函数1 `GetBestCovisibilityKeyFrames`
    • 取出关键帧的函数2 `GetCovisiblesByWeight`

成员变量

关键帧的成员变量有很多和Frame类是相同的,像public的网格化用到的变量mnGridCols等,相机的参数,特征点的变量mvKeys等,词袋的mBowVecmFeatVec,尺度信息、图像边界信息,还有protected的相机位姿mTcw等,这里把一些不同的变量列出来

变量名 访问控制 简单解释
static long unsigned int nNextId public 上一帧的ID(用nLastId会不会好一些?)
long unsigned int mnId public 当前帧的ID(用上一帧的ID+1)
const long unsigned int mnFrameId public 记录当前关键帧是由哪个Frame初始得到的
const double mTimeStamp public 时间戳
long unsigned int mnTrackReferenceForFrame public 这个变量主要是用做一个记录的功能
long unsigned int mnFuseTargetForKF public 也是一个标记,在局部建图线程中标记和哪个关键帧融合了
long unsigned int mnBALocalForKF
long unsigned int mnBAFixedForKF
public 这两个都是在LocalMapping中的LocalBA中用到的,前面的是当前局部关键帧的ID,后边的是添加进优化中做约束条件但不参与优化的关键帧ID
long unsigned int mnLoopQuery public 在回环检测中使用,标记候选关键帧
int mnLoopWords public 当前关键帧和形成回环的候选关键帧中具有相同Word的个数
float mLoopScore public 和回环候选关键帧词袋匹配得分
long unsigned int mnRelocQuery public 重定位中,需要进行重定位的帧ID
int mnRelocWords public 和重定位帧的相同Word个数
float mRelocScore public 和重定位帧的词袋匹配得分
cv::Mat mTcwGBA
cv::Mat mTcwBefGBA
long unsigned int mnBAGlobalForKF
public 全局BA使用,第一个是全局BA后的位姿,第二个是记录的优化前的位姿,第三个是记录哪个帧触发的全局BA,主要是防止重复
cv::Mat mTcp public 相对于父关键帧的位姿,在删除关键帧连接关系的时候会用到
std::vector<MapPoint*> mvpMapPoints protected 和特征点联系起来的地图点
KeyFrameDatabase* mpKeyFrameDB protected 关键帧数据库
ORBVocabulary* mpORBvocabulary protected 词袋对象
std::vector
<std::vector<std::vector<size_t>>> mGrid
protected 加速匹配时用到的,把关键帧的特征点信息存储起来
std::map<KeyFrame*,int> mConnectedKeyFrameWeights protected 与该关键帧连接的关键帧与权重
std::vector<KeyFrame*> mvpOrderedConnectedKeyFrames protected 共视关键帧的排序,权重从大到小
std::vector<int> mvOrderedWeights protected 共视关键帧权重的排序,和上一个变量对应
bool mbFirstConnection
KeyFrame* mpParent
std::set<KeyFrame*> mspChildrens
std::set<KeyFrame*> mspLoopEdges
protected 生成树相关的一些变量
bool mbNotErase
bool mbToBeErased
bool mbBad
protected 一些标记
float mHalfBaseline protected 基线长的一半,只有在可视化中使用了
Map* mpMap protected 对应的地图
std::mutex mMutexPose
std::mutex mMutexConnections
std::mutex mMutexFeatures
protected 互斥锁

成员函数

KeyFrame类和Frame类也有很多相同的函数,相同的函数主要是为了后边把图像网格化做特征提取与匹配用,关键帧自身的特殊操作可能就在于连接关系,里面有很多添加、更新或删除连接关系的操作。同时关键帧在后边两个线程中是处理的主要对象,所以具体的实现与应用在三大线程中看得会更加直接。

构造函数

构造函数的参数变量有三个,分别是Frame &F,也就是初始化成关键帧的那一帧,Map *pMap,这个是和当前关键帧可能产生联系的地图,然后就是KeyFrameDatabase *pKFDB,也就是关键帧数据库,这个是在跟踪线程初始化的,在关键帧中被不断的操作。
KeyFrame构造函数使用了列表初始化的方法,把F的一些变量直接赋值给了当前关键帧,同时也对位姿进行了一个初始化。

关键帧类重要函数1 UpdateConnections

UpdateConnections在三个线程中都有使用,主要的作用就是更新关键帧之间的连接关系。

// Tracking线程中在初始化中使用到
// CreateInitialMapmonocular
pKFini->UpdateConnections();
// LocalMapping线程中在处理关键帧队列和融合当前帧与相邻关键帧地图点时使用
mpCurrentKeyFrame->UpdateConnections();
// LoopClosing线程中在回环矫正中使用
mpCurrent->UpdateConnections();
pKFi->UpdateConnections();

先是定义了一个很重要的变量,map<KeyFrame*,int> KFcounter,这个变量代表的就是其他关键帧和当前关键帧的共视程度,再把所有地图点取出来(用了mutex防止地图点被其他地方改变),放在定义的vector<MapPoint*> vpMp中。

  1. 先通统计关键帧之间的共视程度,就是看地图点有没有被当前帧和其他关键帧同时看到
for(vector<MapPoint*>::iterator vit=vpMP.begin(), vend=vpMP.end(); vit!=vend; vit++)
{MapPoint* pMP = *vit;if(!pMP)continue;if(pMP->isBad())continue;// 对于每一个地图点,observations记录了可以观测到该地图点的所有关键帧map<KeyFrame*,size_t> observations = pMP->GetObservations();for(map<KeyFrame*,size_t>::iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++){// 除去自身,自己与自己不算共视if(mit->first->mnId==mnId)continue;KFcounter[mit->first]++;
}
}
  1. 之后共视程度最高的关键帧,这里涉及到一个新变量vector<pair<int,KeyFrame*>> vPairs,它记录的是共视帧数大于阈值(th=15)的关键帧,这里用到了另一个函数AddConnections
if(mit->second>=th)
{// 对应权重需要大于阈值,对这些关键帧建立连接vPairs.push_back(make_pair(mit->second,mit->first));// 对方关键帧也要添加这个信息// 更新KFcounter中该关键帧的mConnectedKeyFrameWeights// 更新其它KeyFrame的mConnectedKeyFrameWeights,更新其它关键帧与当前帧的连接权重(mit->first)->AddConnection(this,mit->second);
}
  1. 当没有超过阈值的权重时,就与权重最大的关键帧建立连接
if(vPairs.empty())
{vPairs.push_back(make_pair(nmax,pKFmax));pKFmax->AddConnection(this,nmax);
}
  1. 对满足共视程度的关键帧对更新连接关系和权重(从大到小排列)
    排序直接用了C++ 的sort函数,默认的是升序排列因此后边用了push_front把整个序列调了过来,就实现从大到小排列了。
  2. 又用了一个mutex,把上边得到的结果都赋值给对应的成员变量,再更新生成树的连接
 if(mbFirstConnection && mnId!=0)
{// 初始化该关键帧的父关键帧为共视程度最高的那个关键帧mpParent = mvpOrderedConnectedKeyFrames.front();// 建立双向连接关系,将当前关键帧作为其子关键帧mpParent->AddChild(this);mbFirstConnection = false;
}

这里涉及到了父关键帧和子关键帧两个概念,父关键帧只有一个,这里选择了与当前帧共视程度最高的关键帧,子关键帧则是只要有共视关系即可,子关键帧的添加就是把当前关键帧添加到父关键帧的子关键帧集里

关键帧类重要函数2 SetBadFlag

成员函数中还有一个SetErase函数,那个函数的作用是在LoopClosing函数中删除当前关键帧,表示不进行回环检测的工作。而SetBadFlag函数也会在SetErease中被调用,是真正执行删除关键帧操作的函数。主要是在局部建图线程中,当当前关键帧的90%以上地图点被认为是冗余时,就会删除当前关键帧。

if(nRedundantObservations>0.9*nMPs)pKF->SetBadFlag();
  1. 首先处理无法删除的情况
    不允许删除的情况分为两种,第一种就是当前关键帧是第一帧,那么作为整个系统的基,是不可以被删除的;第二种是mbNotErase变量被其他地方设置为true
if(mnId==0)return;
else if(mbNotErase)
{// mbNotErase表示不应该删除,于是把mbToBeErased置为true,假装已经删除,其实没有删除mbToBeErased = true;return;
}
  1. 遍历当前关键帧的所有相连关键帧,因为之前添加的连接关系都是双向的,所以这里要把连接关系再删掉,调用EraseConnection函数就可以了。
  2. 遍历当前关键帧的地图点,把地图点和关键帧的连接关系也删掉,EraseObservation
  3. 更新生成树,主要是处理好父子关键帧的关系
    先把自己和其他关键帧的关系清空
set<KeyFrame*> sParentCandidates;
// 将当前帧的父关键帧放入候选父关键帧
sParentCandidates.insert(mpParent);

遍历每一个子关键帧,为其选择新的父关键帧


for(set<KeyFrame*>::iterator sit=mspChildrens.begin(), send=mspChildrens.end(); sit!=send; sit++)
{KeyFrame* pKF = *sit;// 跳过无效的子关键帧if(pKF->isBad())    continue;// Check if a parent candidate is connected to the keyframe   vector<KeyFrame*> vpConnected = pKF->GetVectorCovisibleKeyFrames();for(size_t i=0, iend=vpConnected.size(); i<iend; i++){// sParentCandidates 中刚开始存的是这里子关键帧的“爷爷”,也是当前关键帧的候选父关键帧for(set<KeyFrame*>::iterator spcit=sParentCandidates.begin(), spcend=sParentCandidates.end(); spcit!=spcend; spcit++){if(vpConnected[i]->mnId == (*spcit)->mnId){int w = pKF->GetWeight(vpConnected[i]);// 寻找并更新权值最大的那个共视关系if(w>max){pC = pKF;                   //子关键帧pP = vpConnected[i];        //目前和子关键帧具有最大权值的关键帧(将来的父关键帧) max = w;                    //这个最大的权值bContinue = true;           //说明子节点找到了可以作为其新父关键帧的帧}}}}
}

如果找到了就更新子节点的父关键帧信息,如果找不到新的,就把当前关键帧(也就是要删除的这一帧)的父关键帧直接设为子节点的新关键帧,最后把这一帧分别在地图和关键帧数据库中删除,就彻底完成了关键帧的删除工作。

取出关键帧的函数1 GetBestCovisibilityKeyFrames

这个函数非常简单,但在后边用到的也非常多,函数输入的参数是设定要取出的关键帧的数目,因为之前得到的集合都是按共视程度从大到小排列好的,因此这里直接就取出了共视程度最高的前N个共视关键帧。

取出关键帧的函数2 GetCovisiblesByWeight

和上一个函数一样,这个函数的作用也是把关键帧取出来,不过这里输入的参数就相当于是一个阈值,利用了C++中的upper_bound函数(官方解释),把权重大于阈值w的关键帧取了出来。

vector<int>::iterator it = upper_bound( mvOrderedWeights.begin(),   //起点mvOrderedWeights.end(),     //终点w,                          //目标阈值KeyFrame::weightComp);      //比较函数从大到小排序// 如果没有找到,说明最大的权重也比给定的阈值小,返回空
if(it==mvOrderedWeights.end() && *mvOrderedWeights.rbegin()<w)
return vector<KeyFrame*>();
else
{// 如果存在,返回满足要求的关键帧int n = it-mvOrderedWeights.begin();return vector<KeyFrame*>(mvpOrderedConnectedKeyFrames.begin(), mvpOrderedConnectedKeyFrames.begin()+n);
}

试图学会ORB-SLAM2(4)——KeyFrame类相关推荐

  1. ORB SLAM2源码解读(三):Frame类

    文章目录 前言 构造函数 双目相机 RGBD相机 单目相机 ExtractORB:提取特征点 ComputeBoW:计算词袋数据 SetPose:设置相机外参 isInFrustum:判断一个MapP ...

  2. ORB-SLAM2的源码阅读(七):KeyFrame类

    话不多说,直接上代码了... #ifndef KEYFRAME_H #define KEYFRAME_H#include "MapPoint.h" #include "T ...

  3. orbslam2可视化_[Ubuntu] ORB SLAM2 编译调试

    ORB SLAM2 是 2015年比较受到关注的一篇文章,它的主要思想是借助 ORB 描述子改进了 Sparse SLAM 的性能,使得其在稳定性和速度上都达到了比较好的程度.从创新性上来讲,它的主要 ...

  4. Ubuntu下使用单目相机运行ORB SLAM2

    环境:Ubuntu16.04+ROS Kinetic+USB单目摄像头 虽然ORB SLAM2的官方说明中表示没有ROS也可以编译运行,但要实时的跑ORB SLAM2还是需要ROS平台的,所以之前没有 ...

  5. ORBSLAM2源码学习(5) KeyFrame类

    先上代码,再做总结 #ifndef KEYFRAME_H #define KEYFRAME_H#include "MapPoint.h" #include "Thirdp ...

  6. 小白白也能学会的 PyQt 教程 —— 图像类及图像相关基础类介绍

    文章目录 〇.前言 一.PyQt 中的图像类 1.图像类简介 2.图像类转换 ① 常用类转换(QPixmap.QImage.QIcon) ② QBitmap.QBrush.QPen 转换为 QPixm ...

  7. ORB-SLAM3 KeyFrame类源码分析(一)

    2021SC@SDUSC KeyFrame中有一个很重要的概念共视图 1.首先解释一下什么是共视图(mConnectedKeyFrameWeights):能看到同一地图点的两关键帧之间存在共视关系,共 ...

  8. Ubuntu 16.04~ORB SLAM2~Kinect v1

    额 Ubuntu16.04 ORB-SLAM2实现(kinect V1/ROS) ----------------------------------------------------------- ...

  9. Ubuntu14.04 使用本地摄像头跑ORB SLAM2

    嗯 这个方法我暂时弄不出来,用了另外一个方法:SLAM14讲 第一次课 使用摄像头或视频运行 ORB-SLAM2 前面的准备: Ubuntu14.04安装 ROS 安装步骤和问题总结 Ubuntu14 ...

最新文章

  1. JQuery+CSS3实现封装弹出登录框效果
  2. ES内存持续上升问题定位
  3. MS SQL中的returnoutput的學習
  4. 信息抽取(二)花了一个星期走了无数条弯路终于用TF复现了苏神的《Bert三元关系抽取模型》,我到底悟到了什么?
  5. TCP/IP协议学习
  6. 【牛客 - 157B】凤凰(树上并查集,dfs)
  7. android opengl es 雾化效果实例
  8. MiniFlow,帮助理解TensorFlow关键概念--图
  9. 基于CUDA的三维VTI介质逆时偏移与ADCIGs提取
  10. Codeforces Gym 100187D D. Holidays 排列组合
  11. (转载) linux安装JDK
  12. Ubuntu过去十年的10个关键时刻
  13. MDK5软件入门之新建工程项目模板
  14. 基于JAVA-公益劳动招募管理系统-计算机毕业设计源码+系统+mysql数据库+lw文档+部署
  15. JAVA计算机毕业设计劳务外包管理系统(附源码、数据库)
  16. Java 生成条形码 订单条形码
  17. 《MLB美职棒大联盟》:世界大赛最有价值球员奖
  18. 一个合格数字IC设计工程师的知识结构
  19. ssm码农论坛毕业设计源码231126
  20. Android开发过程中的一些基本常识

热门文章

  1. 升级版剪刀石头布(表格的处理方法)
  2. 数据可视化界面UI设计大屏展示
  3. 8 8点阵显示原理c语言,8X8 LED点阵显示原理与编程技术
  4. [linux]platform总线机制与wtd驱动开发
  5. 正则表达式 regex_正则表达式初学者指南(Regex)
  6. CSS3通过3D变换立体旋转相册
  7. Android6.0动态申请SD卡读写的权限
  8. 如何提高或者修改WiFi发射功率
  9. DAX:用自定义变量代替EARLIER函数
  10. Flutter 报错Mapping values are not allowed here. Did you miss a colon earlier?