本博客记录我使用ORB-SLAM2+立体匹配的算法实现一个简单的三维重构系统的过程。
我的思路是这样:从一组双目序列中,得到一条轨迹,在轨迹上对一帧中的两张图作三维重构,并画在帧所在的坐标上,这样就能得到一组数据三维重构的结果。因此,可以用ORB-SLAM2来得到这条轨迹;三维重构需要用到立体匹配的算法,我打算采用opencv封装好的SGBM,但不失扩展性我会采取一些方法保证算法可以随时变更。由于系统在以后有可能变成实时的,因此图像序列来源可以来自真实相机,因此我打算对相机也做一层抽象。最终呈现的效果将会是一幅点图,我打算采用pangolin进行绘图。
综上,我的配置过程和本文章的组织顺序是:
开始阐述我的过程之前,首先明确我们的平台和软件工具是:
我们首先来封装ORB-SLAM2。在这里,我得假设诸位已经成功在目标平台上,成功运行了ORB-SLAM2原始版本的程序。事实上,这篇博客是从[我的上一个系列博客]继承来的,因此我现在的状态是,已经为ORB-SLAM2配置好了各个依赖库,以及以ORB-SLAM2为启动项目,在vs下成功运行了。现在我要做的事情是,将ORB-SLAM2导出为dll,供后面三维重构系统使用。
在vs下配置过导出库的同学都知道,我们需要为被调用到的类加上API宣言,但ORB-SLAM2并不是一个库,它有很多烦人的头文件我们也不会用到,因此我更倾向于将它视为一种服务,我不会在ORB-SLAM2的源码上做修改,而是在ORB-SLAM2的vs工程添加以下两个文件装门用于封装导出:
// SLAM.h
#pragma once
#ifdef SLAM_EXPORTS
#define SLAM_API __declspec(dllexport)
#else
#define SLAM_API __declspec(dllimport)
#endif
#include
#include
using std::string;
using cv::Mat;
class SLAM_API SLAM
{
public:
enum eSensor {
MONOCULAR = 0,
STEREO = 1,
RGBD = 2
};
public:
void init(const string &strVocFile, const string &strSettingsFile, const eSensor sensor, const bool bUseViewer = true);
Mat track(const Mat & im1, const Mat & im2 = Mat());
static SLAM * createSingleObject();
private:
SLAM();
SLAM(const SLAM & slam) = delete;
~SLAM();
};
extern SLAM_API SLAM & slam;
SLAM类即是对ORB-SLAM2的导出封装,我采用的是单例模式来设计这个类,同时导出该类唯一已被定义的对象的引用。我对ORB-SLAM2中的System类的接口做一下简化:即将单目和双目的接口统一在一个接口track下(RGBD的选项暂时不被我考虑在内)。对ORB-SLAM2系统的初始化,在SLAM的init接口中完成,因此要求用户需要手动调用init函数。
从该头文件中,看不到有关ORB-SLAM2的痕迹,这样就成功封装了ORB-SLAM2。该类的实现如下文件所示:
// SLAM.cpp
#include "SLAM.h"
#include "System.h"
#include
using namespace ORB_SLAM2;
System * slamobj = nullptr;
SLAM & slam = *SLAM::createSingleObject();
SLAM::SLAM()
{
}
SLAM::~SLAM()
{
slamobj->Shutdown();
delete slamobj;
slamobj = nullptr;
}
void SLAM::init(const string & strVocFile, const string & strSettingsFile, const eSensor sensor, const bool bUseViewer)
{
if (slamobj != nullptr)
{
slamobj->Shutdown();
delete slamobj;
}
slamobj = new System(strVocFile, strSettingsFile, ORB_SLAM2::System::eSensor(sensor), bUseViewer);
}
Mat SLAM::track(const Mat & im1, const Mat & im2)
{
static double timestamp = 0;
Mat r;
if (im2.data == nullptr)
r = slamobj->TrackMonocular(im1, timestamp);
else r = slamobj->TrackStereo(im1, im2, timestamp);
timestamp += 0.01;
return r;
}
SLAM * SLAM::createSingleObject()
{
static bool firstTime = true;
if (firstTime)
{
firstTime = false;
return new SLAM();
}
else return &slam;
}
现在我们需要配置一下vs工程,让其生成dll:右键项目->属性->常规->配置类型,从exe改成动态库/静态库,同时配置编译模式为Release x64,至此,准备工作已经结束,点击编译,不出意外,在项目文件夹中已经成功生成ORB-SLAM2的库文件。
我们需要测试一下是否真的导出成功,新建一个工程,就起名为“3DRestruct”吧,在该工程项目目录下新建一个文件夹“3rd”,用于放ORB-SLAM2(这个不太正式的依赖库),将SLAM.h放在3rd/include下,将ORB-SLAM2生成的库文件(.lib, .dll)放在3rd/lib下。为该工程添加ORB-SLAM2依赖的那些库的属性表到Release x64文件夹下,新建一个main.cpp文件,添加以下测试代码:
#include
int main()
{
slam.init("../Run/Vocabulary/ORBvoc.txt", "../Run/Setting/KITTI03.yaml", SLAM::eSensor::STEREO);
system("pause");
return 0;
}
注意到,为了让其编译成功,还需要配置一下项目的包含目录和库目录以及库文件,使其能够找到我们的ORB-SLAM2:右键项目->属性->VC++目录->包含目录,添加./3rd/include
(目录仅供参考),另外,我直接在当前对话窗口->链接器->输入->附加依赖项,添加./3rd/lib/ORB-SLAM2.lib
(目录与名称仅供参考),为库阐明位置,也就不用指定库目录了。
将编译模式调为Release x64,并为该工程配置运行环境:右键项目->属性->调试->环境,输入值:
path=D:/Packages/DBoW2/lib/;D:/Packages/g2o_orbslam/lib/;D:/Packages/Pangolin/lib/;D:/Packages/OpenCV3_1/opencv/build/x64/vc14/bin/;$(ProjectDir)/3rd/lib/;
该环境中的路径仅供参考,要强调的一点是,别忘了配置ORB-SLAM2动态库的路径。OK,现在点击编译运行,出现以下画面就没问题了:
不想一下子写太多,本文就先到此为止了。
发现以上代码中有误:封装SLAM的track函数,返回值没有赋值。已修正。