讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的(01)ORB-SLAM2源码无死角解析链接如下(本文内容来自计算机视觉life ORB-SLAM2 课程课件): (01)ORB-SLAM2源码无死角解析-(00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/123092196 文末正下方中心提供了本人 联系方式, 点击本人照片即可显示 W X → 官方认证 {\color{blue}{文末正下方中心}提供了本人 \color{red} 联系方式,\color{blue}点击本人照片即可显示WX→官方认证} 文末正下方中心提供了本人联系方式,点击本人照片即可显示WX→官方认证
一、前言上一篇博客对 SearchInNeighbors() 函数进行了讲解,该函数的主要功能为检查并融合当前关键帧与相邻关键帧帧(两级相邻)中重复的地图点。接下来呢,原本是应该讲解一个重要的函数,那就是 Optimizer::LocalBundleAdjustment() 函数,其功能为局部优化。
但是其涉及的东西还是挺多的,所以就放在后面进行讲解了,另外,在前面的博客中,讲解了参考关键帧追踪TrackReferenceKeyFrame()、恒速模型跟踪当前普通帧TrackWithMotionModel()、重定位跟踪 Relocalization()、重定位跟踪 Relocalization()。这些函数都调用了 Optimizer::PoseOptimization(&mCurrentFrame),可想而知其是多么的重要。
Optimizer::PoseOptimization()与Optimizer::LocalBundleAdjustment() 他们有很多相似的地方,为了给大家一个系统的认知,打算在后面的文章中进行统一的讲解。这里我们进行简单的理解即可:
O p t i m i z e r : : P o s e O p t i m i z a t i o n ( ) : \color{blue}Optimizer::PoseOptimization(): Optimizer::PoseOptimization(): 当前帧仅位姿优化 O p t i m i z e r : : L o c a l B u n d l e A d j u s t m e n t ( ) : \color{blue} Optimizer::LocalBundleAdjustment() : Optimizer::LocalBundleAdjustment():对局部关键帧的进行位姿与地图点优化。
那么我们回到 src/LocalMapping.cc 文件中的 void LocalMapping::Run() 函数,暂且跳过 Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap); 函数,直接对 KeyFrameCulling(); 进行讲解了。
二、KeyFrameCulling()该函数位于 src/LocalMapping.cc 文件中,其主要功能为: 检测当前关键帧在共视图中的关键帧,根据地图点在共视图中的冗余程度剔除该共视关键帧,90%以上的地图点能被其他关键帧(至少3个)观测到。 注意: \color{red} 注意: 注意:剔除的不是当前关键帧,而是共视关键帧。
( 1 ) : \color{blue}{(1):} (1): 根据共视图提取当前关键帧mpCurrentKeyFrame的所有共视关键帧,存储于变量 vpLocalKeyFrames 之中。
( 2 ) : \color{blue}{(2):} (2): 对所有的共视关键帧进行遍历, 先判断共视关键帧是否为第一帧关键帧,如果为第一帧关键帧则跳过(第一帧关键帧不会被删除),然后提取共视关键帧的所有地图点存放于 vpMapPoints 之中。
( 3 ) : \color{blue}{(3):} (3): 对共视关键帧的所有地图 vpMapPoints 进行遍历,如果地图点 pMP 是坏的则跳过。然后统计地图点能够被多少关键帧观测到,如果超过 thObs=3,则 nRedundantObservations 进行 +1操作。
( 4 ) : \color{blue}{(4):} (4): 遍历完共视关键帧的所有地图之后会跳出小循环,然后判断 nRedundantObservations 的数目是否超过该共视关键帧地图数的90%。如果超过了,则通过 pKF->SetBadFlag() 告知系统该共视关键帧需要删除,这里需要注意,其并没有马上删除该关键帧→这个关键帧有可能正在回环检测或者计算sim3操作,这时候虽然这个关键帧冗余,但是却不能删除,仅设置mbNotErase为true,这时候调用setbadflag函数时,不会将这个关键帧删除,只会把mbTobeErase变成true,代表这个关键帧可以删除但不到时候,先记下来以后处理。在闭环线程里调用 SetErase()会根据mbToBeErased 来删除之前可以删除还没删除的帧。
代码注释如下:
/**
* @brief 检测当前关键帧在共视图中的关键帧,根据地图点在共视图中的冗余程度剔除该共视关键帧
* 冗余关键帧的判定:90%以上的地图点能被其他关键帧(至少3个)观测到
*/
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
// 该函数里变量层层深入,这里列一下:
// mpCurrentKeyFrame:当前关键帧,本程序就是判断它是否需要删除
// pKF: mpCurrentKeyFrame的某一个共视关键帧
// vpMapPoints:pKF对应的所有地图点
// pMP:vpMapPoints中的某个地图点
// observations:所有能观测到pMP的关键帧
// pKFi:observations中的某个关键帧
// scaleLeveli:pKFi的金字塔尺度
// scaleLevel:pKF的金字塔尺度
// Step 1:根据共视图提取当前关键帧的所有共视关键帧
vector vpLocalKeyFrames = mpCurrentKeyFrame->GetVectorCovisibleKeyFrames();
// 对所有的共视关键帧进行遍历
for(vector::iterator vit=vpLocalKeyFrames.begin(), vend=vpLocalKeyFrames.end(); vit!=vend; vit++)
{
KeyFrame* pKF = *vit;
// 第1个关键帧不能删除,跳过
if(pKF->mnId==0)
continue;
// Step 2:提取每个共视关键帧的地图点
const vector vpMapPoints = pKF->GetMapPointMatches();
// 记录某个点被观测次数,后面并未使用
int nObs = 3;
// 观测次数阈值,默认为3
const int thObs=nObs;
// 记录冗余观测点的数目
int nRedundantObservations=0;
int nMPs=0;
// Step 3:遍历该共视关键帧的所有地图点,其中能被其它至少3个关键帧观测到的地图点为冗余地图点
for(size_t i=0, iend=vpMapPoints.size(); iisBad())
{
if(!mbMonocular)
{
// 对于双目或RGB-D,仅考虑近处(不超过基线的40倍 )的地图点
if(pKF->mvDepth[i]>pKF->mThDepth || pKF->mvDepth[i]Observations() 是观测到该地图点的相机总数目(单目1,双目2)
if(pMP->Observations()>thObs)
{
const int &scaleLevel = pKF->mvKeysUn[i].octave;
// Observation存储的是可以看到该地图点的所有关键帧的集合
const map observations = pMP->GetObservations();
int nObs=0;
// 遍历观测到该地图点的关键帧
for(map::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;
// 尺度约束:为什么pKF 尺度+1 要大于等于 pKFi 尺度?
// 回答:因为同样或更低金字塔层级的地图点更准确
if(scaleLeveli=thObs)
break;
}
}
// 地图点至少被3个关键帧观测到,就记录为冗余点,更新冗余点计数数目
if(nObs>=thObs)
{
nRedundantObservations++;
}
}
}
}
}
// Step 4:如果该关键帧90%以上的有效地图点被判断为冗余的,则认为该关键帧是冗余的,需要删除该关键帧
if(nRedundantObservations>0.9*nMPs)
pKF->SetBadFlag();
}
}
三、补充
执行 KeyFrameCulling() 函数之后,LocalMapping::Run() 调用了 mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame),将当前帧加入到闭环检测队列中。然后做了查看是否有复位线程的请求,如果当前线程已经结束了就跳出主循环等操作。在 LocalMapping 的初始化函数中可以看到很多标志位,主要用于线程之间的交互的,如下:
// 构造函数
LocalMapping::LocalMapping(Map *pMap, const float bMonocular):
mbMonocular(bMonocular), mbResetRequested(false), mbFinishRequested(false), mbFinished(true), mpMap(pMap),
mbAbortBA(false), mbStopped(false), mbStopRequested(false), mbNotStop(false), mbAcceptKeyFrames(true)
{
/*
* mbStopRequested: 外部线程调用,为true,表示外部线程请求停止 local mapping
* mbStopped: 为true表示可以并终止localmapping 线程
* mbNotStop: true,表示不要停止 localmapping 线程,因为要插入关键帧了。需要和 mbStopped 结合使用
* mbAcceptKeyFrames: true,允许接受关键帧。tracking 和local mapping 之间的关键帧调度
* mbAbortBA: 是否流产BA优化的标志位
* mbFinishRequested: 请求终止当前线程的标志。注意只是请求,不一定终止。终止要看 mbFinished
* mbResetRequested: 请求当前线程复位的标志。true,表示一直请求复位,但复位还未完成;表示复位完成为false
* mbFinished: 判断最终LocalMapping::Run() 是否完成的标志。
*/
}
四、结语
通过一系列博客的讲解,已经对 void LocalMapping::Run() 函数做了很细致的分析,那么这里再贴一下整体的代码,这样局部建图线程我们就全部讲解完成了(除了Optimizer::LocalBundleAdjustment() ),代码注释如下:
// 线程主函数
void LocalMapping::Run()
{
// 标记状态,表示当前run函数正在运行,尚未结束
mbFinished = false;
// 主循环
while(1)
{
// Tracking will see that Local Mapping is busy
// Step 1 告诉Tracking,LocalMapping正处于繁忙状态,请不要给我发送关键帧打扰我
// LocalMapping线程处理的关键帧都是Tracking线程发来的
SetAcceptKeyFrames(false);
// Check if there are keyframes in the queue
// 等待处理的关键帧列表不为空
if(CheckNewKeyFrames())
{
// BoW conversion and insertion in Map
// Step 2 处理列表中的关键帧,包括计算BoW、更新观测、描述子、共视图,插入到地图等
ProcessNewKeyFrame();
// Check recent MapPoints
// Step 3 根据地图点的观测情况剔除质量不好的地图点
MapPointCulling();
// Triangulate new MapPoints
// Step 4 当前关键帧与相邻关键帧通过三角化产生新的地图点,使得跟踪更稳
CreateNewMapPoints();
// 已经处理完队列中的最后的一个关键帧
if(!CheckNewKeyFrames())
{
// Find more matches in neighbor keyframes and fuse point duplications
// Step 5 检查并融合当前关键帧与相邻关键帧帧(两级相邻)中重复的地图点
SearchInNeighbors();
}
// 终止BA的标志
mbAbortBA = false;
// 已经处理完队列中的最后的一个关键帧,并且闭环检测没有请求停止LocalMapping
if(!CheckNewKeyFrames() && !stopRequested())
{
// Local BA
// Step 6 当局部地图中的关键帧大于2个的时候进行局部地图的BA
if(mpMap->KeyFramesInMap()>2)
// 注意这里的第二个参数是按地址传递的,当这里的 mbAbortBA 状态发生变化时,能够及时执行/停止BA
Optimizer::LocalBundleAdjustment(mpCurrentKeyFrame,&mbAbortBA, mpMap);
// Check redundant local Keyframes
// Step 7 检测并剔除当前帧相邻的关键帧中冗余的关键帧
// 冗余的判定:该关键帧的90%的地图点可以被其它关键帧观测到
KeyFrameCulling();
}
// Step 8 将当前帧加入到闭环检测队列中
// 注意这里的关键帧被设置成为了bad的情况,这个需要注意
mpLoopCloser->InsertKeyFrame(mpCurrentKeyFrame);
}
else if(Stop()) // 当要终止当前线程的时候
{
// Safe area to stop
while(isStopped() && !CheckFinish())
{
// 如果还没有结束利索,那么等
// usleep(3000);
std::this_thread::sleep_for(std::chrono::milliseconds(3));
}
// 然后确定终止了就跳出这个线程的主循环
if(CheckFinish())
break;
}
// 查看是否有复位线程的请求
ResetIfRequested();
// Tracking will see that Local Mapping is not busy
SetAcceptKeyFrames(true);
// 如果当前线程已经结束了就跳出主循环
if(CheckFinish())
break;
//usleep(3000);
std::this_thread::sleep_for(std::chrono::milliseconds(3));
}
// 设置线程已经终止
SetFinish();
}
本文内容来自计算机视觉life ORB-SLAM2 课程课件