ORB系统总共有3个线程分别为 Tracking,LocalMapping,LoopClosing。
其中Tracking运行在主线程,由 Tracking::GrabImageMonocular() 进入。
LocalMapping 线程由 LocalMapping::Run() 进入;
LoopClosing 线程由 LoopClosing::Run() 进入;
我将由这个3个线程讲述ORBslam2的单目模式是如何运作的。
系统将图片交给 Tracking::GrabImageMonocular() 后,先将图片转化为灰度图,然后使用图片构建了一个 Frame。
注意系统在初始化的时候使用了不同的 ORBextractor 来构建 Frame,是因为在初始化阶段的帧需要跟多的特征点。
然后将进入了Track(),Tracking的大部分功能将在这里实现:
Tracking执行了4个任务:
1、单目初始化;
2、通过上一帧获得初始位姿估计或者重定位。也就是求出当前帧在世界坐标系下的位姿 T2;
3、跟踪局部地图(TrackLocalMap()) 求得位姿估计 T3。这个步骤回答当前帧的特征点和map中的哪些mappoint匹配。
4、判断是否需要给localmapping插入关键帧;
初始化成功后,后面的帧所面对的情况是一样的。所以我们不从第3帧开始,我们从 第i开始。
假设系统给 tracking传过来了第i个Frame,我先来阐述下我们现在有什么。我们在map中有许多关键帧keyframe,以及mappoint。
那么来自灵魂的拷问来了,1. 请问你如何快速求得当前帧Frame的相对世界坐标系的位姿呢?
好的,陈独秀同学已经举手示意了,他说可以像初始化那样,通过2D-2D的方式计算当前帧和上一个帧的F和H,进而得到它们之间的位姿关系。好的陈独秀同学,您可以坐下了。想法虽好,但是不可行,因为这样太耗时间了。其实还有一种方式是通过将当前帧的特征点与map中的mappoint进行匹配,进而得到当前帧在世界坐标系中的位姿。这样这个问题就变成了高翔的slam十四讲第七章的3D-2D的PnP问题。
2.那如何将手中已经当前帧的特征点和map中的mappoint进行匹配呢?
一个一个暴力匹配?不可能的,这辈子都是不可能滴,信不信我tracking分分钟卡死给你看!
orbslam2提供了3种方式回到上述我提的问题,分别为 TrackWithMotionModel(),TrackReferenceKeyFrame(),Relocalization()。
具体如何实施呢?当上一帧跟踪正常的时候,我们会生成一个速度模型 mVelocity,使得mVelocity不为空,于是就我们就会进入 TrackWithMotionModel()。
速度模型mVelocity就是上一帧和上上帧的位姿变化。由于在短时间内,速度模型不会变化太大,这样我们可以认为上一帧的速度模型(即上一帧和上上帧的位姿变化)等于当前帧的速度模型(即当前帧和上帧的位姿变化)。根据上一帧的求得的速度模型,以及上一帧在世界坐标系的位姿得到当前帧在世界坐标系中的初始位姿 T0。
我们将上一帧匹配的mappoint,通过 T0投影到当前帧上,然后在投影位置附近搜索当前帧的特征点以加速上一帧mappoint和当前帧的特征点匹配。也就是说,我们以上一帧为跳板,将当前帧的特征点与空间中mappoint进行匹配。有了当前帧的初始位姿,以及当前帧的特征点与空间中mappoint进行匹配关系。我们就可以在通过 bundle adjustment方法 优化得到当前帧的较为精确的位姿 T1.
得到当前帧较为精确的位姿后,当前帧的有些特征点和mappoint匹配通过这个 T1将不成立,形成误匹配于是将它标记为outliner后取消它们之间的匹配。
当 速度模型 mVelocity为空时,会进入 TrackReferenceKeyFrame() 通过参考关键帧获得初始位姿估计(很少使用)
首先,我们通过和tracking的参考关键帧用bow进行特征匹配,进而匹配点参考关键帧的mappoint。
那么参考关键帧怎么得来的呢?如果上一帧被用来转化为了关键帧,那么参考关键帧就为上一帧转化的关键帧。
如果上一帧没有被转化为参考关键帧,那么参考关键帧就为在后面 TrackLocalMap() 中找到的,和上一帧共视程度最高的那个关键帧;
然后,我们再进行BA位姿优化。为了加速BA,我们将当前帧在世界坐标系中的位姿的初始值 T0设置为以上一帧的位姿在世界坐标系中的位姿;
BA位姿优化后,得到当前帧在世界坐标系中的位姿 T1。当前帧的特征点和mappoint的匹配有些会变为误匹配,将它剔除。
当mState=LOST时,我们会进入 Relocalization()。当速度模型不能用,参考关键帧也没有的时候,就意味着tracking已经跟丢了。
这时候,我们先将当前帧转化为BOW,然后将代表 当前帧的BOW交给KeyFrameDatabase,它就会返回一个和当前帧很相似的集合作为候选帧。
接着,我们对当前帧和候选关键帧进行通过BOW加速的特征点匹配。并剔除特征匹配数少于15的候选帧。
对于剩下的候选关键帧我们通过epnp结合RANSAC算法进行筛选,并算出当前帧在世界坐标系中的位姿 T2。
此时,我们通过 TrackWithMotionModel(),或者TrackReferenceKeyFrame()或者Relocalization()得到了当前帧在世界坐标系中的位姿 T2,以及当前帧的特征点和一些mappoint的匹配。
那么我不禁想问,当前帧的特征点和当前我们在map中存储的mappoint的匹配就只有这些了吗?
当然不是,我们还需要再找更多的当前帧的特征点与mappoint的匹配。最直接的方法当然是将map中所有还未匹配的mappoint一个个和当前帧还为匹配的特征点进行匹配啦,不过这样做效率太低啦。最直观的想法,当然是缩小mappoint的搜索方法啦,于是 UpdateLocalMap() 局部地图应运而生啦!通过这函数,我们缩小了搜索mappoint的范围。
然后在刚才UpdateLocalMap()得到的mappoint范围内,通过位姿投影的方法在SearchLocalPoints()和当前帧的特征点进行匹配。于是我们就得到了更多的当前帧的特征点与mappoint的匹配啦。
因为有更多的mappoint匹配的加入,于是我们就需要进行一次位姿BA,将这些新加入的mappoint匹配的影响也考虑进去,于是就得到了新的当前帧的位姿 T3。
记住,每次位姿BA后都会有一些本来匹配的特征点与mappoint的匹配变得不匹配了。剔除这些不匹配的点对后,我们就掰起我们的小手指数一数当前帧还能和多少mappoint匹配,并根据这个数量判断当前帧的这个跟踪是否成功。
给localmapping插入关键帧,必须满足以下所有条件:
LocalMapping线程由 LocalMapping::Run() 进入;
它主要做了以下几件事:
- 向map中插入关键帧;
- 新近插入的mappoint的剔除;
- 新建mappoint;
- 局部捆集调整(BA);
- 剔除冗余关键帧;
1、 计算了插入的关键帧的mBowVec,mFeatVec;
2、 遍历和关键帧匹配的mappoint
- 让mappoint知道自己可以被哪些keyframe看到;
- 更新此mappoint参考帧光心到mappoint平均观测方向以及观测距离范围
- 在此mappoint能被看到的特征点中找出最能代表此mappoint的描述子
3、 更新共视图Covisibility graph,spanningtree;
4、 将该关键帧插入到地图map中;
新近添加入map的mappoint必须经它被创建之后的3帧的检验,以保证它是可追踪以及由于错误的匹配导致的被错误的三角化。因此mappoint必须满足以下两个条件:
当LocalMapping已经处理完队列中的最后的一个关键帧,并且闭环检测此时没有请求停止LocalMapping;也就是说现在LocalMapping有空,那么就来一次local BA吧!
检测并剔除当前帧相邻的关键帧中冗余的关键帧
剔除的标准是:该关键帧的90%的MapPoints可以被其它至少3个关键帧观测到
交给LocalMapping的关键帧几乎都会给LoopClosing。取出一个 LoopClosing队列里的关键帧为 Ki
在这一步进一步确定关键帧 Ki和哪一个候选帧形成闭环检测;
并通过计算sim3确定关键帧 Ki和闭环检测帧之间的位姿;