欢迎浏览我的SLAM专栏,一起加油淦穿SLAM!
2015年,西班牙萨拉戈萨大学机器人感知与实时研究组开源了ORB-SLAM第一个版本,由于其出色的效果受到广泛关注。该团队分别在
2016年和2020年开源了第二个版本ORB-SLAM2和第三个版本ORB-SLAM3。
ORB-SLAM2有如下优点:
ORB-SLAM2作者也指出了该框架的不足之处:
这是ORB-SLAM2文章中的系统线程和模块框架:
汉化一下如图:
上图中,单目的初始化比较复杂,双目比较容易,后几章会讲。地图初始化后就相当于以及生成了一些地图点了,就可以用这些地图点开始跟踪。跟踪(初步的粗糙跟踪)分为三种:恒速模型跟踪、参考关键帧跟踪、重定位跟踪。地图初始化后首先会根据参考关键帧来跟踪,初始化成功的那一帧就是参考关键帧。参考关键帧跟踪成功后,后续就是恒速模型跟踪,恒速意思是帧与帧之间在极短的时间内的速度是恒定的,可以做粗糙的预测,根据前边的速度预测后边帧的位置,大部分时间都是恒速模型跟踪。若跟丢了就启用重定位跟踪,比较复杂。第二阶段是局部地图跟踪,能保证跟踪更加精细。跟踪过程中有很多冗余的地图点需要踢掉,再关键帧之间做一些匹配生成新的地图点。经过优化后的关键帧进入闭环线程,首先通过词袋来查询,词袋就是图中的视觉字典,查询是否数据集是否闭环。视觉字典是作者做的,自己也能做,但不一定有他的全。当前关键帧和闭环后关键帧的SIM3计算(similar相似,3是三维空间)。单目下优化考虑尺度,双目有绝对尺度。闭环识别就是检测是否已经闭环,确定闭环后进行矫正。本质图优化是对当前地图的所有关键帧做一个位姿优化,比较快。后面在做一个全局的BA是整个地图的位姿和地图点都会优化,比较慢。
通过框架图可以总结如下:
为了兼容不同相机(双目相机与RGBD相机),需要对输入数据进行预处理,使得交给后期处理的数据格式一致,具体流程如下:
如上图,单目不用管,双目的话要做rectified stereo即矫正,然后左右点提取ORB特征点,做立体匹配,然后去矫正。RGB-D需要做对齐,把rgb的depth和rgb做一个对齐的操作,这个需要我们提前标定好内参。开源的数据集都是已经处理好的。
详见:
slam的环境配置大全–保姆教学
ORB_SLAM2代码的简介安装运行
关于associate.py
注意:只能在Python2 环境下运行
Python下运行
python associate.py rgb.txt depth.txt > associate.txt
python associate.py associate.txt groundtruth.txt > associate_with_groundtruth.txt
注意:
直接association后出问题,生成的结果
./Examples/Monocular/mono_tum Vocabulary/ORBvoc.txt Examples/Monocular/TUM1.yaml
Data/rgbd_dataset_freburg1_desk
associate.txt 1641行
associate_with_groundtruth.txt 1637行
也就是说,associate的不一定有groundtruth,所以要以associate_with_groundtruth.txt的关联结果为准
运行的中间结果截图如下所示,对重点信息进行了标注
不同颜色地图点的含义解析
红色点表示参考地图点,其实就是tracking里的local mappoints
void Tracking::UpdateLocalMap()
{
// This is for visualization
mpMap->SetReferenceMapPoints(mvpLocalMapPoints);
// Update
UpdateLocalKeyFrames();
UpdateLocalPoints();
}
黑色点表示所有地图点,红色点属于黑色点的一部分
void MapDrawer::DrawMapPoints()
{
const vector<MapPoint*> &vpMPs = mpMap->GetAllMapPoints();
const vector<MapPoint*> &vpRefMPs = mpMap->GetReferenceMapPoints();
set<MapPoint*> spRefMPs(vpRefMPs.begin(), vpRefMPs.end());
if(vpMPs.empty())
return;
glPointSize(mPointSize);
glBegin(GL_POINTS);
glColor3f(0.0,0.0,0.0);
for(size_t i=0, iend=vpMPs.size(); i<iend;i++)
{
if(vpMPs[i]->isBad() || spRefMPs.count(vpMPs[i]))
continue;
cv::Mat pos = vpMPs[i]->GetWorldPos();
glVertex3f(pos.at<float>(0),pos.at<float>(1),pos.at<float>(2));
}
glEnd();
glPointSize(mPointSize);
glBegin(GL_POINTS);
glColor3f(1.0,0.0,0.0);
for(set<MapPoint*>::iterator sit=spRefMPs.begin(), send=spRefMPs.end(); sit!=send; sit++)
{
if((*sit)->isBad())
continue;
cv::Mat pos = (*sit)->GetWorldPos();
glVertex3f(pos.at<float>(0),pos.at<float>(1),pos.at<float>(2));
}
glEnd();
}
后面几章会有大量的源码详解,在介绍之前,我们有必要先了解一下在ORB-SLAM2中变量的命名规则,这对我们学习代码非常有用。
以小写字母m(member的首字母)开头的变量表示类的成员变量。比如:
int mSensor;
int mTrackingState;
std::mutex mMutexMode;
对于某些复杂的数据类型,第2个甚至第3个字母也有一定的意义,比如:
mp开头的变量表示指针(pointer)型类成员变量:
Tracking* mpTracker;
LocalMapping* mpLocalMapper;
LoopClosing* mpLoopCloser;
Viewer* mpViewer;
mb开头的变量表示布尔(bool)型类成员变量:
bool mbOnlyTracking;
mv开头的变量表示向量(vector)型类成员变量:
std::vector<int> mvIniLastMatches;
std::vector<cv::Point3f> mvIniP3D;
mpt开头的变量表示指针(pointer)型类成员变量,并且它是一个线程(thread):
std::thread* mptLocalMapping;
std::thread* mptLoopClosing;
std::thread* mptViewer;
ml开头的变量表示列表(list)型类成员变量;
mlp开头的变量表示列表(list)型类成员变量,并且它的元素类型是指针(pointer);
mlb开头的变量表示列表(list)型类成员变量,并且它的元素类型是布尔型(bool):
list<double> mlFrameTimes;
list<bool> mlbLost;
list<cv::Mat> mlRelativeFramePoses;
list<KeyFrame*> mlpReferences;
总结一下:
以小写字母m(member的首字母)开头的变量表示类的成员变量;
对于某些复杂的数据类型,第2个甚至第3个字母也有一定的意义;
mp开头的变量表示指针(pointer)型类成员变量;
mb开头的变量表示布尔(bool)型类成员变量;
mv开头的变量表示向量(vector)型类成员变量;
mpt开头的变量表示指针(pointer)型类成员变量,并且它是一个线程(thread)。