主体框架
输入:有三种模式选择,单目模式,双目模式和RGB-D模式。
跟踪:初始化成功后首先会选择参考关键帧跟踪,然后大部分时间是恒速模型跟踪,当跟踪丢失的时候启动重定位跟踪,,在经过以上跟踪后可以估计初步的位姿,然后经过局部地图对位姿进行进一步优化,同时会根据条件判断是否要将当前关键帧新建为关键帧。
局部建图:输入的关键帧来自跟踪重新建的关键帧,为了增加局部地图点数目,局部地图里关键帧之间会重新进行特征匹配,生成新的地图点,局部BA会同时优化共视图里关键帧位姿和地图点,优化后也不会删除不准确的地图点和冗余的关键帧。
闭环:通过词袋来查询数据集检测是否闭环,计算当前关键帧和闭环候选关键帧之间的Sim3位姿,仅在单目时考虑尺度,双目或RGB-D模式下尺度固定为1,然后执行闭环融合和本质图优化,使得所有关键帧位姿更加准确。
全局BA:优化所有的关键帧及其地图点。
位置识别:需要导入离线训练好的字典,这个字典是由视觉词袋模型构建的,新输入的图像帧需要先在线转化成词袋向量,主要应用于特征匹配,重定位,闭环。
地图:地图主要由地图点和关键帧组成,关键帧之间根据共视地图点数目组成了共视图,根据父子关系组成了生成树。
orb_slam2总共由三个线程:Tracking,Local Mapping,Loop Closing 线程,Tracking主要负责定位相机以及相机是否插入一个新的关键帧,将输入的每一个帧进行orb fearures,并且以此估计相机位姿。local mapping 线程主要负责处理新的关键帧以及执行局部BA以此来优化相机位姿,此外,负责新的map point的创建和剔除掉冗余的keyframes。loop closing线程主要负责回环检测和全局地图优化,减少位姿累计误差漂移,将误差均匀化,使得总体误差减小。
其中Tracking线程位于主线程之中,Local Mapping 线程和Loop Closing线程位于主线程中的系统初始化中。
命名规则:
了解一个工程项目的命名规则有助于学习源码。
以小写字母m(member)开头的变量表示类的成员变量,如:
int mSensor;
int mTrackingState;
std::mutex mMutexMode;
mp(member point)开头的变量表示指针型类成员变量,如:
Tracking* mpTracker;
LocalMapping* mpLocalMapper;
LoopClosing* mpLoopCloser;
Viewer* mpViewer;
mb(member bool)开头的变量表示布尔型类成员变量,如:
bool mbOnlyTracking;
mv(member vector)开头的变量表示向量型类成员变量。如:
std::vector<int> mvIniLastMatches;
std::vector<cv::Point3f> mvInip3D;
mpt(member point thread)开头的变量表示指针型类成员变量,并且是一个线程,如:
std::thread* mptLocalMapping;
std::thread* mptLoopClosing;
std::thread* mptViewer;
ml(member list)开头的变量表示列表型成员变量,
mlp(menber list pointer)开头的变量表示列表型类成员变量,并且元素类型是指针
mlb(member list bool)开头的变量表示列表型类成员变量,并且他的元素类型是布尔型
list<double> mlFrameTimes;
list<bool> mlbLost;
list<cv::Mat> mlRelativeFramePoses;
list<KeyFrame*> mlpReferences;
接下来介绍main()函数入口
首先加载图片,将rgb.txt文档中的timestamp-filename序列存到vstrImageFilenames和vTimestamps中。
LoadImages(strFile, vstrImageFilenames, vTimestamps);
初始化,创建一个slam系统
ORB_SLAM2::System SLAM(argv[1],argv[2],ORB_SLAM2::System::MONOCULAR,true);
调用TrackMonocular函数,进行跟踪图片,并记录每张图片的开销时间
for(int ni=0; ni<nImages; ni++)
{
// Read image from file
im = cv::imread(string(argv[3])+"/"+vstrImageFilenames[ni],CV_LOAD_IMAGE_UNCHANGED);
double tframe = vTimestamps[ni];
if(im.empty())
{
cerr << endl << "Failed to load image at: "
<< string(argv[3]) << "/" << vstrImageFilenames[ni] << endl;
return 1;
}
#ifdef COMPILEDWITHC11
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
#else
std::chrono::monotonic_clock::time_point t1 = std::chrono::monotonic_clock::now();
#endif
// Pass the image to the SLAM system
SLAM.TrackMonocular(im,tframe);
#ifdef COMPILEDWITHC11
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
#else
std::chrono::monotonic_clock::time_point t2 = std::chrono::monotonic_clock::now();
#endif
double ttrack= std::chrono::duration_cast<std::chrono::duration<double> >(t2 - t1).count();
vTimesTrack[ni]=ttrack;
// Wait to load the next frame
double T=0;
if(ni<nImages-1)
T = vTimestamps[ni+1]-tframe;
else if(ni>0)
T = tframe-vTimestamps[ni-1];
if(ttrack<T)
usleep((T-ttrack)*1e6);
}
关闭slam系统
SLAM.Shutdown();
计算Track图片时间的平均值和中间值
sort(vTimesTrack.begin(),vTimesTrack.end());
float totaltime = 0;
for(int ni=0; ni<nImages; ni++)
{
totaltime+=vTimesTrack[ni];
}
保存相机轨迹为KeyFrameTrajectory.txt
这里只保存了关键帧位姿,不包含全部帧的轨迹
SLAM.SaveKeyFrameTrajectoryTUM("KeyFrameTrajectory.txt");
ps:笔者水平有限,如有错误,联系邮箱2806762759@qq.com,欢迎批评指正