ORB-SLAM2 地圖保存


一、簡介

  在ORB-SLAM2的System.h文件中,有這樣一句話:// TODO: Save/Load functions,讓讀者自己實現地圖的保存與加載功能。其實在應用過程中很多場合同樣需要先保存當前場景的地圖,然后下次啟動時直接進行跟蹤,這樣避免了初始化和建圖,減小相機跟蹤過程中計算機負載,還有就是進行全場的定位。今天暫時描述一下如何進行地圖的保存,其實網上已經有地圖保存的代碼了(http://recherche.enac.fr/~drouin/slam/orbslam2/poine_orbslam2_04_07_16.tgz,不保證有效),有時間我上傳兩份(其實是一份)網上代碼,但是由於只有代碼,所以小菜給配一個教程。

二、地圖元素分析

  所謂地圖保存,就是保存地圖“Map”中的各個元素,以及它們之間的關系,凡是跟蹤過程中需要用到的東西自然也就是需要保存的對象,上一節曾經說過地圖主要包含關鍵幀、3D地圖點、BoW向量、共視圖、生長樹等,在跟蹤過程中有三種跟蹤模型和局部地圖跟蹤等過程,局部地圖跟蹤需要用到3D地圖點、共視關系等元素,參考幀模型需要用到關鍵幀的BoW向量,重定位需要用到BoW向量、3D點等(具體哪里用到了需要翻看代碼),所以基本上述元素都需要保存。

  另一方面,關鍵幀也是一個抽象的概念(一個類),我們看看具體包含什么(其實都在關鍵幀類里面了),關鍵幀是從普通幀來的,所以來了視頻幀首先需要做的就是檢測特征點,計算描述符,還有當前幀的相機位姿,作為關鍵幀之后需要有對應的ID編號,以及特征點進行三角化之后的3D地圖點等。

  關於3D地圖點需要保存的就只有世界坐標了,至於其它的關聯關系可以重關鍵幀獲得。需要單獨說的是在關鍵幀類中包含了特征點和描述符,所以BoW向量是不需要保存的(也沒辦法保存),只需要在加載了關鍵幀之后利用特征描述符重新計算即可。

  所以現在需要保存的東西包括關鍵幀、3D地圖點、共視圖、生長樹。

三、地圖保存代碼實例

  需要明確的是一般SLAM系統對地圖的維護均在Map.cc這個函數類中,最終把地圖保存成二進制文件,所以現在Map.h中聲明幾個函數吧:

public:
    void Save( const string &filename );
protected:
    void SaveMapPoint( ofstream &f, MapPoint* mp );
    void SaveKeyFrame( ofstream &f, KeyFrame* kf );

 

   下面關於Save函數的構成:

void Map::Save ( const string& filename )
 {
     cerr<<"Map Saving to "<<filename <<endl;
     ofstream f;
     f.open(filename.c_str(), ios_base::out|ios::binary);
     cerr << "The number of MapPoints is :"<<mspMapPoints.size()<<endl;
 
     //地圖點的數目
     unsigned long int nMapPoints = mspMapPoints.size();
     f.write((char*)&nMapPoints, sizeof(nMapPoints) );
     //依次保存MapPoints
     for ( auto mp: mspMapPoints )
         SaveMapPoint( f, mp );
//獲取每一個MapPoints的索引值,即從0開始計數,初始化了mmpnMapPointsIdx
GetMapPointsIdx();
cerr <<"The number of KeyFrames:"<<mspKeyFrames.size()<<endl; //關鍵幀的數目 unsigned long int nKeyFrames = mspKeyFrames.size(); f.write((char*)&nKeyFrames, sizeof(nKeyFrames)); //依次保存關鍵幀KeyFrames for ( auto kf: mspKeyFrames ) SaveKeyFrame( f, kf ); for (auto kf:mspKeyFrames ) { //獲得當前關鍵幀的父節點,並保存父節點的ID KeyFrame* parent = kf->GetParent(); unsigned long int parent_id = ULONG_MAX; if ( parent ) parent_id = parent->mnId; f.write((char*)&parent_id, sizeof(parent_id)); //獲得當前關鍵幀的關聯關鍵幀的大小,並依次保存每一個關聯關鍵幀的ID和weight; unsigned long int nb_con = kf->GetConnectedKeyFrames().size(); f.write((char*)&nb_con, sizeof(nb_con)); for ( auto ckf: kf->GetConnectedKeyFrames()) { int weight = kf->GetWeight(ckf); f.write((char*)&ckf->mnId, sizeof(ckf->mnId)); f.write((char*)&weight, sizeof(weight)); } } f.close(); cerr<<"Map Saving Finished!"<<endl; }

  可以看到,Save函數依次保存了地圖點的數目、所有的地圖點、關鍵幀的數目、所有關鍵幀、關鍵幀的生長樹節點和關聯關系;

  下面是SaveMapPoint函數的構成:

void Map::SaveMapPoint( ofstream& f, MapPoint* mp)
{   
     //保存當前MapPoint的ID和世界坐標值
     f.write((char*)&mp->mnId, sizeof(mp->mnId));
     cv::Mat mpWorldPos = mp->GetWorldPos();
     f.write((char*)& mpWorldPos.at<float>(0),sizeof(float));
     f.write((char*)& mpWorldPos.at<float>(1),sizeof(float));
     f.write((char*)& mpWorldPos.at<float>(2),sizeof(float));
}

  其實主要就是通過MapPoint類的GetWorldPos()函數獲取了地圖點的坐標值並保存下來;

  下面是SaveKeyFrame函數的構成:

void Map::SaveKeyFrame( ofstream &f, KeyFrame* kf )
 {
//保存當前關鍵幀的ID和時間戳
     f.write((char*)&kf->mnId, sizeof(kf->mnId));
     f.write((char*)&kf->mTimeStamp, sizeof(kf->mTimeStamp));
     //保存當前關鍵幀的位姿矩陣
     cv::Mat Tcw = kf->GetPose();
     //通過四元數保存旋轉矩陣
     std::vector<float> Quat = Converter::toQuaternion(Tcw);
     for ( int i = 0; i < 4; i ++ )
         f.write((char*)&Quat[i],sizeof(float));
     //保存平移矩陣
     for ( int i = 0; i < 3; i ++ )
         f.write((char*)&Tcw.at<float>(i,3),sizeof(float));
 
 
     //直接保存旋轉矩陣
 //  for ( int i = 0; i < Tcw.rows; i ++ )
 //  {
 //      for ( int j = 0; j < Tcw.cols; j ++ )
 //      {
 //              f.write((char*)&Tcw.at<float>(i,j), sizeof(float));
 //              //cerr<<"Tcw.at<float>("<<i<<","<<j<<"):"<<Tcw.at<float>(i,j)<<endl;
 //      }
 //    }
 
     //保存當前關鍵幀包含的ORB特征數目
     //cerr<<"kf->N:"<<kf->N<<endl;
     f.write((char*)&kf->N, sizeof(kf->N));
     //保存每一個ORB特征點
     for( int i = 0; i < kf->N; i ++ )
     {
         cv::KeyPoint kp = kf->mvKeys[i];
         f.write((char*)&kp.pt.x, sizeof(kp.pt.x));
         f.write((char*)&kp.pt.y, sizeof(kp.pt.y));
         f.write((char*)&kp.size, sizeof(kp.size));
         f.write((char*)&kp.angle,sizeof(kp.angle));
         f.write((char*)&kp.response, sizeof(kp.response));
         f.write((char*)&kp.octave, sizeof(kp.octave));
 
         //保存當前特征點的描述符
         for (int j = 0; j < kf->mDescriptors.cols; j ++ )
                 f.write((char*)&kf->mDescriptors.at<unsigned char>(i,j), sizeof(char));
 
         //保存當前ORB特征對應的MapPoints的索引值
         unsigned long int mnIdx;
         MapPoint* mp = kf->GetMapPoint(i);
         if (mp == NULL  )
                 mnIdx = ULONG_MAX;
         else
                 mnIdx = mmpnMapPointsIdx[mp];
 
         f.write((char*)&mnIdx, sizeof(mnIdx));
     }
 }

  保存關鍵幀的函數稍微復雜一點,首先需要明白一幅關鍵幀包含特征點,描述符,以及哪些特征點通過三角化成為了地圖點。

  其中在Save函數中的GetMapPointsIdx函數的構成為,它的作用是初始化成員變量:

std::map<MapPoint*, unsigned long int> mmpnMapPointsIdx;

這個成員變量存儲的是特征點對應的地圖點的索引值。

  void Map::GetMapPointsIdx()
  {
          unique_lock<mutex> lock(mMutexMap);
          unsigned long int i = 0;
          for ( auto mp: mspMapPoints )
          {
                  mmpnMapPointsIdx[mp] = i;
                  i += 1;
          }
  }

 另外,關於旋轉矩陣的存儲可以通過四元數或矩陣的形式存儲,如果使用四元數需要自定義一個矩陣和四元數相互轉換的函數,在Converter.cc類里面:

 std::vector<float> Converter::toQuaternion(const cv::Mat &M)
 {   
     Eigen::Matrix<double,3,3> eigMat = toMatrix3d(M);
     Eigen::Quaterniond q(eigMat);
     
     std::vector<float> v(4);
     v[0] = q.x();
     v[1] = q.y();
     v[2] = q.z();
     v[3] = q.w();
     
     return v;
 }
 cv::Mat Converter::toCvMat( const std::vector<float>& v )
 {
         Eigen::Quaterniond q;
         q.x()  = v[0];
         q.y()  = v[1];
         q.z()  = v[2];
         q.w()  = v[3];
         Eigen::Matrix<double,3,3>eigMat(q);
         cv::Mat M = toCvMat(eigMat);
         return M;
 }

三、總結

  上面就是地圖保存部分的代碼,經過測試針對TUM的視頻是有效的。但是需要在System中設置保存函數並在主函數中調用,尤其是針對無ROS依賴並從攝像頭讀取圖像的時候,這個最后再說。后面繼續分享地圖的加載部分。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM