ORB-SLAM2的地图保存

“女生不适合弄硬件”,这句话真的深有体会,今天为了让Pioneer 3-AT给Kinect供电,真的是路途坎坷.首先机器人尘封很久,螺丝都是拧死的,LZ拿着螺丝头去拧,发现螺丝纹丝不动,好吧!需要师兄的协助.找到供电板的12V电源接口,因为操作问题…导致保险丝熔断,LZ的心情啊…默默的买了对应的保险丝,心里想着还是乖乖写代码吧…

回归正题,如文章题目所示,如何保存ORB-SLAM的地图?

如何保存地图?肯定是要先明白在ORB-SLAM中的地图到底有哪些变量?

在之前源码分析和一些跑ORB-SLAM的的显示图像,我们可以看到

ORB-SLAM2的地图保存_第1张图片

地图主要可见的有关键帧(包括相机的pose,相机的内参,ORB特征),3D的地图点( 空间中3D位置,法线方向,ORB的描述子),词袋向量,共视图等.可以看到如果要保存地图点,保存的数据还是相当大的,下面我们就按照http://www.cnblogs.com/mafuqiang/p/6972342.html这个博客的步骤一点点来实现地图的保存操作.

首先要明确一点的是一般SLAM对地图维护的操作均在Map.cc这个函数类中,最终按照博客的想法是把最终的地图保存成二进制的文件(filename.bin)这种类型,如果是大神请绕道,讲的有些确实很基础。

所以,首先要在Map.h中声明几个函数,因为后续添加了Converter.h中的类别,在Map.h的头文件中要添加Converter.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 "<//输出地图点的数目
     cerr << "The number of MapPoints is :"<//地图点的数目
     unsigned long int nMapPoints = mspMapPoints.size();
     f.write((char*)&nMapPoints, sizeof(nMapPoints) );
     //依次保存MapPoints
     for ( auto mp: mspMapPoints )
     //调用SaveMapPoint函数
         SaveMapPoint( f, mp );

     //获取每一个MapPoints的索引值,即从0开始计数,初始化了mmpnMapPointsIdx  
     GetMapPointsIdx(); 

    //输出关键帧的数量
     cerr <<"The number of KeyFrames:"<//关键帧的数目
     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!"<

可以看到,Save函数依次保存了地图点的数目、所有的地图点、关键帧的数目、所有关键帧、关键帧的生长树节点和关联关系;

  下面是SaveMapPoint函数的构成:

void Map::SaveMapPoint( ofstream& f, MapPoint* mp)
{   
     //保存当前MapPoint的ID和世界坐标值,x,y z
     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(i,j), sizeof(float));
 //              //cerr<<"Tcw.at("<(i,j)<
 //      }
 //    }

     //保存当前关键帧包含的ORB特征数目
     //cerr<<"kf->N:"<N<
     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函数的构成为,它的作用是初始化成员变量,在Map.h中添加成员变量:

protectedstd::mapunsigned long int> mmpnMapPointsIdx;
    void GetMapPointsIdx();

这个成员变量存储的是特征点对应的地图点的索引值。

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

另外,关于旋转矩阵的存储可以通过四元数或矩阵的形式存储,如果使用四元数需要自定义一个矩阵和四元数相互转换的函数,在Converter.cc类里面ORB_SLAM2已经定义好了,所以直接使用就行了。

在Converter.h里面加上如下函数定义

 static cv::Mat toCvMat( const std::vector<float>& v );

在Converter.cc中加入

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;
 }

在system.h和system.cc中分别添加声明和定义:

 void SaveMap(const string &filename);  

void System::SaveMap(const string &filename)  
{  
   mpMap->Save(filename);   
}  

以上就是地图保存的代码,LZ在ros下和rgbd的example下都经过测试,代码是可以保存的,最后再rgbd_tum.cc中加入

SLAM.SaveMap("MapPointandKeyFrame.bin")

在运行的路径下就会有存有地图的二进制文件了。哈哈,运行结果如下图所示O(∩_∩)O哈哈~

ORB-SLAM2的地图保存_第2张图片

你可能感兴趣的:(SLAM)