文章目录
- 一、函数意义
- 二、函数讲解
- 三、函数源码
- 四、总结
一、函数意义
本函数是局部建图线程的最后一个关键函数,该函数的作用是剔除冗余关键帧,这是对关键帧的一次筛选,发到局部建图的关键帧是当时最新的一个关键帧,很多关键帧在当时是很优质的,但是和很多地图点都能在后续的关键帧中被观测到,那么该关键帧就是冗余的,完全可以删除掉。
二、函数讲解
前面讲到函数的作用是剔除冗余关键帧。那么就有这么几个关键问题:
- 从哪些关键帧中找出需要剔除的冗余关键帧?
在本函数中答案是,当前关键帧的所有邻接关键帧都需要接受筛选。我们注意到GetVectorCovisibleKeyFrames();这个函数在前面也经常用到,但是一般都会传入一个int类型的参数,代表邻接关键帧的个数,这里括号内没有参数,是默认找出所有的邻接关键帧。第一个for循环就是在遍历这些关键帧。- 什么样的关键帧是冗余的,需要被删除?
本函数使用的是地图点观测的方法,这里第二个for循环遍历了该关键帧能观测到的所有地图点,首先排除不合格的点。然后找出每个能观测到该地图点的关键帧,进入第三个循环,遍历这些关键帧统计合格的观测,如果观测大于等于3循环就停止,标记该点。如果被标记的地图点的个数大于合格地图点个数的0.9倍,该关键帧就应该被删除。下图中的蓝色方框代表关键帧,红点代表地图点,可以很明显的看出来有关键帧的删除(红色箭头标注的关键帧就被删除了)。
三、函数源码
// 剔除冗余关键帧
void LocalMapping::KeyFrameCulling()
{// Check redundant keyframes (only local keyframes)// A keyframe is considered redundant if the 90% of the MapPoints it sees, are seen// in at least other 3 keyframes (in the same or finer scale)// We only consider close stereo points// 检查冗余关键帧(仅本地关键帧)// 如果一个关键帧所看到的90%的地图点,在至少其他3个关键帧中也能看到(在相同或更精细的尺度下),// 则认为该关键帧是冗余的。// 我们只考虑近距离立体点。// 获取局部关键帧vector<KeyFrame*> vpLocalKeyFrames = mpCurrentKeyFrame->GetVectorCovisibleKeyFrames();// 遍历参与局部见图的关键帧for(vector<KeyFrame*>::iterator vit=vpLocalKeyFrames.begin(), vend=vpLocalKeyFrames.end(); vit!=vend; vit++){ // 获取关键帧的指针KeyFrame* pKF = *vit;// 跳过第一个关键帧,通常,第一个关键帧被视为基础帧,因此不考虑将其标记为冗余if(pKF->mnId==0)continue;// 获取该关键帧中的地图点const vector<MapPoint*> vpMapPoints = pKF->GetMapPointMatches();// 初始化观察阈值和计数器int nObs = 3;const int thObs=nObs;// 用于统计冗余关键帧的数量int nRedundantObservations=0;// 用于统计地图点的总数int nMPs=0;// 遍历该关键帧中的所有地图点for(size_t i=0, iend=vpMapPoints.size(); i<iend; i++){// 获取地图点指针MapPoint* pMP = vpMapPoints[i];// 排除不合格的点if(pMP){if(!pMP->isBad()){// 深度有效性检查if(!mbMonocular){if(pKF->mvDepth[i]>pKF->mThDepth || pKF->mvDepth[i]<0)continue;}// 合格的地图点加1nMPs++;// 统计有效地图点和冗余观察if(pMP->Observations()>thObs){// 获取对应的特征点的金字塔层级const int &scaleLevel = pKF->mvKeysUn[i].octave;// 获取地图点的所有观察const map<KeyFrame*, size_t> observations = pMP->GetObservations();// 初始化有效观察计数器int nObs=0;// 遍历所有观察到该地图点的关键帧for(map<KeyFrame*, size_t>::const_iterator mit=observations.begin(), mend=observations.end(); mit!=mend; mit++){// 获取关键帧指针KeyFrame* pKFi = mit->first;// 跳过当前关键帧if(pKFi==pKF)continue;// 获取其他关键帧中观察到的特征点的尺度层级const int &scaleLeveli = pKFi->mvKeysUn[mit->second].octave;// 检查其他关键帧的尺度层级是否在当前关键帧的层级范围内(允许的范围是当前尺度及其上一级)if(scaleLeveli<=scaleLevel+1){// 如果观察满足条件,则增加有效观察计数nObs++;// 果有效观察数量达到预定阈值 thObs(3),则提前退出循环以优化性能if(nObs>=thObs)break;}}// 更新冗余观察计数if(nObs>=thObs){nRedundantObservations++;}}}}} // 如果一个关键帧所观察的地图点中,90%以上的地图点都是冗余的,// 那么该关键帧将被标记为无效。if(nRedundantObservations>0.9*nMPs)pKF->SetBadFlag();}
}
四、总结
本函数是局部建图线程的最后一个函数,目的是剔除冗余关键帧。本函数中需要合格的地图点有超过百分之90被观测三次才被标记为冗余关键帧,看起来是一个很难达成的条件,但实际情况中关键帧的剔除是很常见的。造成这种结果的原因是,相机在运动的过程中时间间隔是很短的(帧间隔很短),相机的相对位姿是很小的,所以出现冗余关键帧是很常见的。到此局部建图线程就全部结束了,下面将进入到回环检测线程的。如果大家有什么问题,欢迎沟通交流。