参考原项目:https://github.com/raulmur/ORB_SLAM2#2-prerequisites
ORB_SLAM2 项目需要使用 Pangolin 0.5 版本,直接从仓库 clone 将得到最新版本,使用时会有冲突。可以通过以下方法下载特定版本:
git clone --recursive https://github.com/stevenlovegrove/Pangolin.git -b v0.5或者在 release 界面手动下载:https://github.com/stevenlovegrove/Pangolin/releases/tag/v0.5
之后正常编译安装即可。
ORB_SLAM2 只在 OpenCV 2.4.11 和 OpenCV 3.2 版本上经过测试。为使用 OpenCV 的新特性,本版本采用了 OpenCV 4.2.0 版本,后续会对相应函数接口进行修改。
原始版本的 ORB_SLAM2 编译方法如下:
git clone https://github.com/raulmur/ORB_SLAM2.git ORB_SLAM2
cd ORB_SLAM2
chmod +x build.sh
./build.sh- 若使用了不同版本的 OpenCV,需要将 CMakeLists.txt 中的
find_package(OpenCV $OPENCV_VERSION QUIET)修改为对应版本。 - 经测试,使用
-march=native指令优化编译,会导致重复编译后运行时出现错误。目前解决方案是将主目录和 Thirdparty/ 下的 CMakeLists.txt 中的对应指令注释掉。 - 该版本只对 Monocular 模式进行测试,使用的是 KITTI 数据集,因此可注释掉除了
Examples/Monocular/mono_kitti.cc之外的测试用例的编译。
在编译时可能会出现 static assertion failed 错误,需要将 include/LoopClosing.h 中的
typedef map<KeyFrame*,g2o::Sim3,std::less<KeyFrame*>,
Eigen::aligned_allocator<std::pair<const KeyFrame*, g2o::Sim3> > > KeyFrameAndPose;修改为:
typedef map<KeyFrame*,g2o::Sim3,std::less<KeyFrame*>,
Eigen::aligned_allocator<std::pair<KeyFrame *const, g2o::Sim3> > > KeyFrameAndPose;OpenCV 的部分函数命名和路径相比旧版本有改动,需要进行以下修改:
- 在
include/FrameDrawer.h中添加头文件:#include <opencv2/imgproc/types_c.h> - 将 Examples/ 下需要编译的文件中的
CV_LOAD_IMAGE_UNCHANGED更换成cv::IMREAD_UNCHANGED
可能与编译时的 C++ 版本相关,需要在调用了 usleep() 函数的文件中添加头文件:#include <unistd.h>
数据文件目录如下:
dataset
│ times.txt
│
└───image_02
│ │ 000000.png
│ │ 000001.png
│ │ ...
│
└───seg_result
│ 000000.png
│ 000001.png
│ ...语义分割模型使用 da-sac,对 image_02/ 中的图片进行语义分割处理后,将带 Label 信息的图片存入 seg_result/ 文件夹中。times.txt 文件中的每一行对应图片拍摄时间,单位为秒。
可采用自定义数据集,或者下载 KITTI 数据集,参考:https://github.com/raulmur/ORB_SLAM2#kitti-dataset
以 Monocular 为例,对代码进行修改,注意保持与头文件的匹配:
-
将
src/System.cc文件中的cv::Mat Tcw = mpTracker->GrabImageMonocular(im,timestamp);修改为:
cv::Mat Tcw = mpTracker->GrabImageMonocular(im, timestamp, mImseg);
其中 mImseg 为传入的语义信息图片。
-
修改
src/Tracking.cc中的GrabImageMonocular()函数定义,形参列表修改为:cv::Mat Tracking::GrabImageMonocular(const cv::Mat &im, const double ×tamp, const cv::Mat &imSeg)
同时在
Track()函数后添加语义信息处理:if (mState == OK) UpdateSemantic_cor(mImSeg);
其中,新增的
UpdateSemantic_cor()函数定义如下:void Tracking::UpdateSemantic_cor(const cv::Mat &imSeg) { cv::Mat im; im = imSeg; const int N=mCurrentFrame.N; for(int i=0; i<N; i++) { MapPoint* pMP = mCurrentFrame.mvpMapPoints[i]; if(pMP) { int nKpX = (int)(mCurrentFrame.mvKeysUn[i].pt.x); int nKpY = (int)(mCurrentFrame.mvKeysUn[i].pt.y); int ucSemanticLabel = int(mImSeg.at<uchar >(nKpY, nKpX)); pMP->UpdateSemantic(ucSemanticLabel); } } }
-
在
src/MapPoint.cc中新增UpdateSemantic()与GetSemanticLabel()函数:void MapPoint::UpdateSemantic(const unsigned int nSemanticLabel) { if(nSemanticLabel==0||nSemanticLabel==13||nSemanticLabel==2||nSemanticLabel==3||nSemanticLabel==4||nSemanticLabel==8) mvSemanticLabels[nSemanticLabel]++; else mvSemanticLabels[nSemanticLabel]=0; vector<long unsigned int>::iterator vitMaxLabel = max_element(mvSemanticLabels.begin(), mvSemanticLabels.end()); if (*vitMaxLabel == 0) { mnLabel = -1; } else if (mnLabel == -1 || *vitMaxLabel > mvSemanticLabels[mnLabel]) { mnLabel = vitMaxLabel - mvSemanticLabels.begin(); cout << "Label: " << mnLabel << endl; } } int MapPoint::GetSemanticLabel() { return mnLabel; }
-
在
src/MapDrawer.cc中,修改DrawMapPoints()函数为: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; int nLabel = vpMPs[i]->GetSemanticLabel(); if (nLabel == -1) // no label { glColor3f(0., 0., 0.); } else { glColor3f((float)mvColorMap[nLabel][0]/255., (float)mvColorMap[nLabel][1]/255., (float)mvColorMap[nLabel][2]/255.); } cv::Mat pos = vpMPs[i]->GetWorldPos(); glVertex3f(pos.at<float>(0),pos.at<float>(1),pos.at<float>(2)); } glEnd(); }
其中,
mvColorMap的定义为:vector<vector<int> > MapDrawer::mvColorMap { {128, 64,128}, // 0. road {244, 35,232}, // 1. sidewalk { 70, 70, 70}, // 2. building {102, 102,156}, // 3. wall {190, 153,153}, // 4. fence {153, 153,153}, // 5. pole {250, 170, 30}, // 6. traffic light {220, 220, 0}, // 7. traffic sign {107, 142, 35}, // 8. vegetation {152, 251,152}, // 9. terrain { 70, 130,180}, // 10. sky {220, 20, 60}, // 11. person {255, 0, 0}, // 12. rider { 0, 0, 142}, // 13. car { 0, 0, 70}, // 14. truck { 0, 60, 100}, // 15. bus { 0, 80, 100}, // 16. train { 0, 0, 230}, // 17. motorcycle {119, 11, 32} // 18. bicycle };
-
在
Examples/Monocular/mono_kitti.cc中,在main()中添加声明:vector<string> vstrSemanticImage;
并修改图像加载语句为:
LoadImages(string(argv[3]), vstrImageFilenames, vstrSemanticImage, vTimestamps);
修改 SLAM 运行的语句为:
SLAM.TrackMonocular(im, tframe, imSeg);其中,
LoadImages()的定义修改为:void LoadImages(const string &strPathToSequence, vector<string> &vstrImageFilenames, vector<string> &vstrSemanticImage, vector<double> &vTimestamps) { ifstream fTimes; string strPathTimeFile = strPathToSequence + "/times.txt"; fTimes.open(strPathTimeFile.c_str()); while(!fTimes.eof()) { string s; getline(fTimes,s); if(!s.empty()) { stringstream ss; ss << s; double t; ss >> t; vTimestamps.push_back(t); } } string strPrefixLeft = strPathToSequence + "/image_2/"; string strSemantic=strPathToSequence + "/seg_result/"; const int nTimes = vTimestamps.size(); vstrImageFilenames.resize(nTimes); vstrSemanticImage.resize(nTimes); for(int i=0; i<nTimes; i++) { stringstream ss; ss << setfill('0') << setw(6) << i; vstrImageFilenames[i] = strPrefixLeft + ss.str() + ".png"; vstrSemanticImage[i] = strSemantic + ss.str() + ".png"; } }
运行方法为:
./Examples/Monocular/mono_kitti \
./Vocabulary/ORBvoc.txt \
./Examples/Monocular/KITTI00-02.yaml \
./dataset若采用了自定义数据集,则要使用包含对应参数的 yaml 文件。 运行结果如下,左边为包含了语义信息的点云,右边为原始图像和关键帧。
