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>(),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 地图保存的更多相关文章
- orb slam2 双目摄像头
主要参考了http://blog.csdn.net/awww797877/article/details/51171099这篇文章,其中需要添加的是:export ROS_PACKAGE_PATH=$ ...
- ORB SLAM2在Ubuntu 16.04上的运行配置
http://www.mamicode.com/info-detail-1773781.html 安装依赖 安装OpenGL 1. 安装opengl Library$sudo apt-get inst ...
- 关于ORB SLAM2资源整理(持续更新)
ORB SLAM2源码讲解(吴博) https://www.youtube.com/watch?v=2GVE7FTW7AU 泡泡机器人视频整理: http://space.bilibili.com/3 ...
- 开源词袋模型DBow3原理&源码(二)ORB特征的保存和读取
util里提供了create_voc_step0用于批量生成features并保存,create_voc_step1读入features再生成聚类中心,比较适合大量语料库聚类中心的生成. 提取一张图的 ...
- ORB SLAM2 学习笔记
cd ~/Documents/demos/ORB_SLAM2 ./Examples/RGB-D/rgbd_tum Vocabulary/ORBvoc.txt Examples/RGB-D/TUM1.y ...
- Ubuntu14.04 使用本地摄像头跑ORB SLAM2(暂未完成)
嗯 这个方法我暂时弄不出来,用了另外一个方法:SLAM14讲 第一次课 使用摄像头或视频运行 ORB-SLAM2 前面的准备: Ubuntu14.04安装 ROS 安装步骤和问题总结 Ubuntu14 ...
- orb slam2
- 使用 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 ...
- Ubuntu16.04+Ros+Usb_Cam ORB SLAM2
转载自:https://www.jianshu.com/p/dbf39b9e4617亲测可用 1.其中编译ORB_SLAM2的 ./build.sh 和 ./build_ros.sh之前需要修改文 ...
随机推荐
- 【ECNU71】一个游戏(水题)
点此看题面 大致题意: \(n\)种元素,有若干组将\(x\)元素转化为\(y\)元素的操作.问对于所有可能的元素拥有状况,改变转化操作的顺序,是否会影响转化后最终的结果. 水题 虽说是这么水的题,但 ...
- 基于Docker的Consul服务发现集群搭建
在去年的.NET Core微服务系列文章中,初步学习了一下Consul服务发现,总结了两篇文章.本次基于Docker部署的方式,以一个Demo示例来搭建一个Consul的示例集群,最后给出一个HA的架 ...
- manage.py相关命令
python manage.py makemigrations <app_name> 在对应app下的migrations目录下,生成XXXX_initial.py文件,该XXXX_ini ...
- IT兄弟连 HTML5教程 HTML5的靠山 W3C、IETF是什么
无规矩不成方圆,软件开发当然不能例外.Web开发涉及的厂商和技术非常多,所以必须要有参考的标准,而且需要一系列的标准.Web程序都是通过浏览器来解析执行的,通过页面的展示内容与用户互动,所以Web标准 ...
- Numpy数值类型与数值运算-03
什么是NumPy? NumPy是Python中科学计算的基本软件包.它是一个Python库,提供多维数组对象,各种派生对象(例如蒙版数组和矩阵) 以及各种例程,用于对数组进行快速操作,包括数学,逻辑, ...
- 轻量级监控平台之java进程监控脚本
轻量级监控平台之java进程监控脚本 #!/bin/bash #进程监控脚本 #功能需求: 上报机器Java进程的进程ID,对应的端口号service tcp端口号,tomcat http 端口号,以 ...
- d03
回顾: 两个环境 工具环境:Jmeter的下载.启动.基本使用 项目环境:学生信息管理系统 两种接口: 天气预报:4个接口----GET方法 学生信息管理系统: 被测软件的学院信息接口: 4类: 查询 ...
- C++ delete 和 delete []的区别
转载自https://blog.csdn.net/cbNotes/article/details/38900799 1.我们通常从教科书上看到这样的说明:delete 释放new分配的单个对象指针指向 ...
- 记一次token安全认证的实践
阅读此文前请先阅读上一篇SpringBoot整合JWT实现用户认证了解JWT. 背景介绍: 因项目需求,有PC端 APP端和小程序端,但登陆接口是同一个,然而微服务也无法使用传统的session解决用 ...
- TreeMap源码分析,看了都说好
概述 TreeMap也是Map接口的实现类,它最大的特点是迭代有序,默认是按照key值升序迭代(当然也可以设置成降序).在前面的文章中讲过LinkedHashMap也是迭代有序的,不过是按插入顺序或访 ...