😀 ORB-SLAM2 代码解读(二):可视化线程
文章目录
0. 可视化线程介绍
可视化线程用于显示 3D 地图绘制器和 2D 图像帧绘制器,还包括一些运行模式的开关,不涉及到具体的算法,只负责接受、传递和显示数据,不影响 SLAM 系统的运行,可以再初始化 SLAM 系统时选择开启或者关闭。但需要注意的是线程锁的问题,不严谨的数据共享可能会引起多线程的段错误。
1 创建可视化线程
- 可视化线程的创建和运行在初始化 SLAM 系统时进行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 创建帧绘制器和地图绘制器 mpFrameDrawer = new FrameDrawer(mpMap); mpMapDrawer = new MapDrawer(mpMap, strSettingsFile); // 创建并运行可视化线程 if(bUseViewer) { // 新建viewer mpViewer = new Viewer( this, mpFrameDrawer, //帧绘制器 mpMapDrawer, //地图绘制器 mpTracker, //追踪器 strSettingsFile); //配置文件的访问路径 // 运行viewer线程 mptViewer = new thread(&Viewer::Run, mpViewer); // 给运动追踪器设置其查看器 mpTracker->SetViewer(mpViewer); }
- 可视化线程构造函数 ==
Viewer::Viewer()
==- 用于初始化可视化线程,读取相机的配置参数,包括:
- 图像采集的帧率,计算出每帧的耗时
double mT;
- 读取图像的长款,默认值为 640 × 480;
- 可视化视角和相机的焦距。
- 图像采集的帧率,计算出每帧的耗时
- 用于初始化可视化线程,读取相机的配置参数,包括:
2 运行可视化线程
- 可视化线程与系统其他线程并行运行在 ==
Viewer::Run()
== 函数中 - 步骤一: 创建 pangolin 窗口并设置大小
1 2 3 4 5 6 7
pangolin::CreateWindowAndBind("ORB-SLAM2: Map Viewer",1024,768); // 启动深度测试,OpenGL只绘制最前面的一层,绘制时检查当前像素前面是否有别的像素,如果别的像素挡住了它,那它就不会绘制 glEnable(GL_DEPTH_TEST); // 在OpenGL中使用颜色混合 glEnable(GL_BLEND); // 选择混合选项 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- 步骤二: 创建按钮和选择框
1 2 3 4 5 6 7 8
// 新建按钮和选择框,第一个参数为按钮的名字,第二个为默认状态,第三个为是否有选择框 pangolin::CreatePanel("menu").SetBounds(0.0,1.0,0.0,pangolin::Attach::Pix(175)); pangolin::Var<bool> menuFollowCamera("menu.Follow Camera",true,true); pangolin::Var<bool> menuShowPoints("menu.Show Points",true,true); pangolin::Var<bool> menuShowKeyFrames("menu.Show KeyFrames",true,true); pangolin::Var<bool> menuShowGraph("menu.Show Graph",true,true); pangolin::Var<bool> menuLocalizationMode("menu.Localization Mode",false,true); pangolin::Var<bool> menuReset("menu.Reset",false,false);
- 步骤三: 设置 pangolin 相机的投影模型和观测方向
1 2 3 4 5 6 7 8
// 定义相机投影模型:ProjectionMatrix(w, h, fu, fv, u0, v0, zNear, zFar) // 定义观测方位向量:观测点位置:(mViewpointX mViewpointY mViewpointZ) // 观测目标位置:(0, 0, 0) // 观测的方位向量:(0.0,-1.0, 0.0) pangolin::OpenGlRenderState s_cam( pangolin::ProjectionMatrix(1024,768,mViewpointF,mViewpointF,512,389,0.1,1000), pangolin::ModelViewLookAt(mViewpointX,mViewpointY,mViewpointZ, 0,0,0,0.0,-1.0, 0.0) );
- 步骤四: 定义显示面板大小
1 2 3 4 5 6 7
// 定义显示面板大小,orbslam中有左右两个面板,左边显示一些按钮,右边显示图形 // 前两个参数(0.0, 1.0)表明宽度和面板纵向宽度和窗口大小相同 // 中间两个参数(pangolin::Attach::Pix(175), 1.0)表明右边所有部分用于显示图形 // 最后一个参数(-1024.0f/768.0f)为显示长宽比 pangolin::View& d_cam = pangolin::CreateDisplay() .SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f/768.0f) .SetHandler(new pangolin::Handler3D(s_cam));
- 步骤五:更新绘制内容(详见下一节)
3. 更新可视化内容
- 步骤一: 地图绘制器获取最新的相机位姿,在函数 ==
MapDrawer::GetCurrentOpenGLCameraMatrix(pangolin::OpenGlMatrix &M)
== 中实现;- 检查地图绘制器的
cv::Mat mCameraPose
是否为空,若不为空,则将其转换为cv::Mat
格式转换为pangolin::OpenGlMatrix &M
格式,否则则设置为单位矩阵; - 检查地图绘制器中的相机位姿
cv::Mat mCameraPose
是通过 ==MapDrawer::SetCurrentCameraPose(const cv::Mat &Tcw)
== 函数从跟踪线程传递过来的,该函数在跟踪线程中有两个地方会更新:- 创建初始化地图时
1
mpMapDrawer->SetCurrentCameraPose(pKFcur->GetPose());
- 跟踪线程每次跟踪成功之后
1 2
if(bOK) mpMapDrawer->SetCurrentCameraPose(mCurrentFrame.mTcw);
- 创建初始化地图时
- 检查地图绘制器的
- 步骤二: 根据可视化界面的按钮和相机位姿情况选择调整视角;
- 步骤三: 判断模式是否有边,定位模式还是 SLAM 模式;
- 步骤四: 绘制 3D 地图
- 步骤 1: 根据相机位姿在 pangolin 界面中绘制相机框,在函数 ==
MapDrawer::DrawCurrentCamera(pangolin::OpenGlMatrix &Twc)
== 中实现;- 该步骤需要的参数是相机位姿,在步骤一中已获取;
- 步骤 2: 在地图中绘制关键帧及其共视关系线,在函数 ==
MapDrawer::DrawKeyFrames(const bool bDrawKF, const bool bDrawGraph)
== 中实现;- 该步骤需要获取地图的所有关键帧,通过函数 ==
vector<KeyFrame*> Map::GetAllKeyFrames()
== 来获取关键帧mspKeyFrames
;1 2
// 取出所有的关键帧 const vector<KeyFrame*> vpKFs = mpMap->GetAllKeyFrames();
- 关键帧
mspKeyFrames
通过函数 ==Map::AddKeyFrame(KeyFrame *pKF)
== 来更新,该函数在三个地方被触发- ①:单目相机创建初始地图时;
- ②:双目和 RGBD 相机初始化时;
- ③:局部建图线程处理待处理关键帧列表时 ==
LocalMapping::ProcessNewKeyFrame()
==
- 该步骤需要获取地图的所有关键帧,通过函数 ==
- 步骤 3: 绘制地图点,在函数 ==
MapDrawer::DrawMapPoints()
== 中实现,局部地图点用红色表示,出局部地图点外的地图点用黑色表示;- 该步骤需要用到两种地图点:所有地图点和局部地图点
1 2 3 4
// 取出所有的地图点 const vector<MapPoint*> &vpMPs = mpMap->GetAllMapPoints(); // 取出mvpReferenceMapPoints,也即局部地图d点 const vector<MapPoint*> &vpRefMPs = mpMap->GetReferenceMapPoints();
- 所有地图点通过函数 ==
Map::GetAllMapPoints()
== 获取mspMapPoints
,mspMapPoints
的更新情况与步骤 2 的关键帧一样; - 局部地图点在地图绘制器中通过函数 ==
Map::GetReferenceMapPoints()
== 来获取,局部地图点在地图中通过 ==Map::SetReferenceMapPoints(const vector<MapPoint *> &vpMPs)
== 来更新,该函数在三个地方被触发- ①:单目相机创建初始地图时;
- ②:双目和 RGBD 相机初始化时;
- ③:==
Tracking::UpdateLocalMap()
== 函数中,也就是跟踪局部地图进行局部优化时;
- 该步骤需要用到两种地图点:所有地图点和局部地图点
- 步骤 1: 根据相机位姿在 pangolin 界面中绘制相机框,在函数 ==
- 步骤 四: 绘制带有检测结果的 2D 图像帧,通过函数 ==
FrameDrawer::DrawFrame()
== 来绘制- 绘制时需要用到的参数信息需要在进入该函数时加互斥锁先获取,后面才可以放心使用,否则容易出现段错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// step 1:将成员变量赋值给局部变量(包括图像、状态、其它的提示) //NOTICE 加互斥锁,避免与FrameDrawer::Update函数中图像拷贝发生冲突 { unique_lock<mutex> lock(mMutex); state=mState; if(mState==Tracking::SYSTEM_NOT_READY) mState=Tracking::NO_IMAGES_YET; // 这里使用copyTo进行深拷贝是因为后面会把单通道灰度图像转为3通道图像 mIm.copyTo(im); // 没有初始化的时候 if(mState==Tracking::NOT_INITIALIZED) { //获取当前帧\参考帧的特征点,并且得到他们的匹配关系 vCurrentKeys = mvCurrentKeys; // 当前帧的特征点. vIniKeys = mvIniKeys; // 参考帧的特征点. vMatches = mvIniMatches; // 之间的匹配关系. } // 初始化完成跟踪正常时 else if(mState==Tracking::OK) { vCurrentKeys = mvCurrentKeys; // 当前帧的特征点. vbVO = mvbVO; vbMap = mvbMap; } else if(mState==Tracking::LOST) { // 跟丢的时候就之获得当前帧的特征点就可以了 vCurrentKeys = mvCurrentKeys; } }
- 步骤 五: 响应复位停止等请求。
【Q】问题
2019.05.28
wuyanminmax@gmail.com