关键帧是ORB-SLAM2地图里面重要的组成部分之一。关键帧中要重视的两部分:共视图和生成树。共视图和生成树都是来描述关键帧权重关系的,不同之处在于共视图更为稠密,而生成树更为稀疏。
ORB-SLAM2论文中的描述图,蓝色表示关键帧,黑色和红色部分表示特征点,绿色的实际上是一条条线,每条线表示附近的两个关键帧的共视关系
共视图仅作为参考用,在做优化的时候不会选择共视图。优化共视图是耗时严重的线程,而且优化共视图的时候要给其它变量加锁,其它线程无法工作,所以进一步导致了优化共视图是一个很长、很复杂的过程。
生成树
就能很好的简化这个工作
mConnectedKeyFrameWeights是共视图变量,key是KeyFrame关键帧,value值是int,是当前关键帧的与这个关键帧的权重,无序的
mvpOrderedConnectedKeyFrames、vOrderedWeights变量是所有共视关键帧,实际上就是mConnectedKeyFrameWeights变量的keys和vlaues的两个列表,是有序的
UpdateConnections()函数,是基于当前关键帧对地图点的观测构造共视图,是调用非常多,非常重要的函数
AddConnection(KeyFrame* pKF, int &weight)函数、EraseConnection(KeyFrame* pKF)函数、UpdateBestCovisibles()函数都是内部函数
成员函数/变量 | 访问控制 | 意义 |
---|---|---|
std::map |
protected | 当前关键帧的共视关键帧及权重 |
std::vector |
protected | 所有共视关键帧,按权重从大到小排序 |
std::vector mvOrderedWeights | protected | 所有共视权重,按从大到小排序 |
void UpdateConnections() | public | 基于当前关键帧对地图点的观测构造共视图 |
void AddConnection(KeyFrame* pKF, int &weight) | public应为private | 添加共视关键帧 |
void EraseConnection(KeyFrame* pKF) | public应为private | 删除共视关键帧 |
void UpdateBestCovisibles() | public应为private | 基于共视图信息修改对应变量 |
std::set |
public | get方法 |
std::vector |
public | get方法 |
std::vector |
public | get方法 |
std::vector |
public | get方法 |
int GetWeight(KeyFrame* pKF) | public | get方法 |
共视图结构由3个成员变量维护
(1)mConnectedKeyFrameWeights是一个字典,无序地保存当前关键帧的共视关键帧及权重
(2)mvOrderedWeights按权重降序,保存当前关键帧的共视关键帧列表
(3)mvpOrderedConnectedKeyFrame按权重降序,分别保存当前关键帧的权重列表
共视图构造的关系,这个函数是基于当前所有看到的地图点来构造共视关键帧,首先得到当前地图的所有地图点,然后基于这些地图点找共视关键帧,构建KFCounter
,每次找到一个共视关键帧的权重,就是value权重加一,找到之后把所有的keys和values按照降序排列,得到mvpOrderedConnectedKeyFrames和mvOrderedWeights变量,这样就更新了三个变量(mConnectedKeyFrameWeights、mvOrderedWeights和mvpOrderedConnectedKeyFrame),
这个函数中也会更新生成树的信息,第一次加入生成树的关键帧,设一个父关键帧
void KeyFrame::UpdateConnections() {
// 1. 通过遍历当前帧地图点获取其与其它关键帧的共视程度,存入变量KFcounter中
vector<MapPoint *> vpMP;
{
unique_lock<mutex> lockMPs(mMutexFeatures);
vpMP = mvpMapPoints;
}
map<KeyFrame *, int> KFcounter;
for (MapPoint *pMP : vpMP) {
map<KeyFrame *, size_t> observations = pMP->GetObservations();
for (map<KeyFrame *, size_t>::iterator mit = observations.begin(); mit != observations.end(); mit++) {
if (mit->first->mnId == mnId) // 与当前关键帧本身不算共视
continue;
KFcounter[mit->first]++;
}
}
// step2. 找到与当前关键帧共视程度超过15的关键帧,存入变量vPairs中
vector<pair<int, KeyFrame *> > vPairs;
int th = 15;
int nmax = 0;
KeyFrame *pKFmax = NULL;
for (map<KeyFrame *, int>::iterator mit = KFcounter.begin(), mend = KFcounter.end(); mit != mend; mit++) {
if (mit->second > nmax) {
nmax = mit->second;
pKFmax = mit->first;
}
if (mit->second >= th) {
vPairs.push_back(make_pair(mit->second, mit->first));
(mit->first)->AddConnection(this, mit->second); // 对超过阈值的共视边建立连接
}
}
// step3. 对关键帧按照共视权重降序排序,存入变量mvpOrderedConnectedKeyFrames和mvOrderedWeights中
sort(vPairs.begin(), vPairs.end());
list<KeyFrame *> lKFs;
list<int> lWs;
for (size_t i = 0; i < vPairs.size(); i++) {
lKFs.push_front(vPairs[i].second);
lWs.push_front(vPairs[i].first);
}
{
unique_lock<mutex> lockCon(mMutexConnections);
mConnectedKeyFrameWeights = KFcounter;
mvpOrderedConnectedKeyFrames = vector<KeyFrame *>(lKFs.begin(), lKFs.end());
mvOrderedWeights = vector<int>(lWs.begin(), lWs.end());
// step4. 对于第一次加入生成树的关键帧,取共视程度最高的关键帧为父关键帧
if (mbFirstConnection && mnId != 0) {
mpParent = mvpOrderedConnectedKeyFrames.front();
mpParent->AddChild(this);
mbFirstConnection = false;
}
}
}
调用的时机是非常重要的,这是一条暗线,这个调用错误的话后面的优化会出现问题,关键帧与地图点间的连接关系发生变化,调用UpdateConnections()函数进行共视图的更新,变化分为两种情况:
(1)关键帧创建
(2)地图点重新匹配关键帧特征点
更新共视图应该为RebulidConnections重新构造共视图,更新共视图的时候直接把原来的三个变量(mConnectedKeyFrameWeights、mvOrderedWeights和mvpOrderedConnectedKeyFrame)清空了,直接根据当前的观测点构造的新的共视图,和原来的共视图无关系,所以这是一个重操作,UpdateConnections()函数在程序里很多次调用,这个可能也是一个优化的地方