一、简介

  在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>(),sizeof(float));
f.write((char*)& mpWorldPos.at<float>(),sizeof(float));
f.write((char*)& mpWorldPos.at<float>(),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 = ; i < ; i ++ )
f.write((char*)&Quat[i],sizeof(float));
//保存平移矩阵
for ( int i = ; i < ; i ++ )
f.write((char*)&Tcw.at<float>(i,),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 = ; 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 = ; 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 = ;
for ( auto mp: mspMapPoints )
{
mmpnMapPointsIdx[mp] = i;
i += ;
}
}

另外,关于旋转矩阵的存储可以通过四元数或矩阵的形式存储,如果使用四元数需要自定义一个矩阵和四元数相互转换的函数,在Converter.cc类里面:

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

三、总结

  上面就是地图保存部分的代码,经过测试针对TUM的视频是有效的。但是需要在System中设置保存函数并在主函数中调用,尤其是针对无ROS依赖并从摄像头读取图像的时候,这个最后再说。后面继续分享地图的加载部分。

ORB-SLAM2 地图保存的更多相关文章

  1. orb slam2 双目摄像头

    主要参考了http://blog.csdn.net/awww797877/article/details/51171099这篇文章,其中需要添加的是:export ROS_PACKAGE_PATH=$ ...

  2. ORB SLAM2在Ubuntu 16.04上的运行配置

    http://www.mamicode.com/info-detail-1773781.html 安装依赖 安装OpenGL 1. 安装opengl Library$sudo apt-get inst ...

  3. 关于ORB SLAM2资源整理(持续更新)

    ORB SLAM2源码讲解(吴博) https://www.youtube.com/watch?v=2GVE7FTW7AU 泡泡机器人视频整理: http://space.bilibili.com/3 ...

  4. 开源词袋模型DBow3原理&源码(二)ORB特征的保存和读取

    util里提供了create_voc_step0用于批量生成features并保存,create_voc_step1读入features再生成聚类中心,比较适合大量语料库聚类中心的生成. 提取一张图的 ...

  5. ORB SLAM2 学习笔记

    cd ~/Documents/demos/ORB_SLAM2 ./Examples/RGB-D/rgbd_tum Vocabulary/ORBvoc.txt Examples/RGB-D/TUM1.y ...

  6. Ubuntu14.04 使用本地摄像头跑ORB SLAM2(暂未完成)

    嗯 这个方法我暂时弄不出来,用了另外一个方法:SLAM14讲 第一次课 使用摄像头或视频运行 ORB-SLAM2 前面的准备: Ubuntu14.04安装 ROS 安装步骤和问题总结 Ubuntu14 ...

  7. orb slam2

  8. 使用 evo 工具评测 VI ORB SLAM2 在 EuRoC 上的结果

    http://www.liuxiao.org/2017/11/%E4%BD%BF%E7%94%A8-evo-%E5%B7%A5%E5%85%B7%E8%AF%84%E6%B5%8B-vi-orb-sl ...

  9. Ubuntu16.04+Ros+Usb_Cam ORB SLAM2

    转载自:https://www.jianshu.com/p/dbf39b9e4617亲测可用 1.其中编译ORB_SLAM2的   ./build.sh 和 ./build_ros.sh之前需要修改文 ...

随机推荐

  1. 【oracle】表和索引建立在不用表空间原因

    磁盘I/O竞争,要放在[真]的不同的磁盘上. Oracle强烈建议,任何一个应用程序的库表至少需要创建两个表空间,其中之一用于存储表数据,而另一个用于存储表索引数据.因为如果将表数据和索引数据放在一起 ...

  2. CF750G New Year and Binary Tree Paths(DP)

    神仙题.为啥我第一眼看上去以为是个普及题 路径有两种,第一种是从 LCA 一边下去的,第二种是从 LCA 两边都下去了的. 先考虑第一种. 先枚举路径长度 \(h\). 当 LCA 编号是 \(x\) ...

  3. nacos 的服务注册与发现

    nacos的服务注册于发现. 这个要求服务统一注册到注册中心,然后调用的时候就不需要通过ip来调用,直接通过服务名即可. 服务提供者 pom.xml配置,需要spring-cloud-starter- ...

  4. python面试题及答案 2019

    利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法. 正解1: def trim(s): while s[:1] == ' ': s = s[1:] ...

  5. 添加Chrome插件时出现“程序包无效”等问题的解决办法

    相较之各大浏览器,我最喜欢的便是Chrome了,不只因为Chrome搜索,也因为Google Chrome强大的插件功能. 而这一切的东风,就是"谷歌访问助手". 谷歌访问助手的下 ...

  6. python接口自动化5-session关联

    前言 我们不难发现浏览器中存在着cookie缓存等,但我们在python中如果像浏览器这样的缓存,我们就很难的需要关联cookie或会话了. 但python的requests库,就封装了Session ...

  7. activiti5初识

    因工作需要,接手新的项目,其中用到了activiti实现的工作流,特意去大致学习下,特此记录下. 1.acticiti5框架说明及表结构介绍 Activiti5工作流引擎框架: 它实际上是一个java ...

  8. Think in Speed (关于速度的一点思考)

    天下武功,无坚不摧,唯快不破!所以我们重视速度没毛病! 老话说:不要过早优化.赞同! 我们在写代码过程中,有时可能就是为了追求所谓的性能,然后,就给自己挖坑了. 关于开发速度,我有以下几点思考: 1. ...

  9. Java电商项目-1.构建数据库,搭建项目环境

    目录 到Github获取源码请点击此处 一. 数据库还原 二. Mybatis逆向生成工具的使用 三. 搭建项目环境 四. 在linux虚拟机上部署zookeeper, 搭建Dubbo服务. linu ...

  10. jackson json转实体对象 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException

    Jackson反序列化错误:com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field的解 ...