概述
本博客記錄我使用ORB-SLAM2+立體匹配的算法實現一個簡單的三維重構系統的過程。
我的思路是這樣:從一組雙目序列中,得到一條軌跡,在軌跡上對一幀中的兩張圖作三維重構,並畫在幀所在的座標上,這樣就能得到一組數據三維重構的結果。因此,可以用ORB-SLAM2來得到這條軌跡;三維重構需要用到立體匹配的算法,我打算採用opencv封裝好的SGBM,但不失擴展性我會採取一些方法保證算法可以隨時變更。由於系統在以後有可能變成實時的,因此圖像序列來源可以來自真實相機,因此我打算對相機也做一層抽象。最終呈現的效果將會是一幅點圖,我打算採用pangolin進行繪圖。
綜上,我的配置過程和本文章的組織順序是:
- ORB-SLAM2的封裝
- 相機類的抽象
- 立體匹配算法的封裝
- 三維重構系統的搭建
開始闡述我的過程之前,首先明確我們的平臺和軟件工具是:
- window 10
- 64位程序
- vs 2017
- C++
ORB-SLAM2的封裝
我們首先來封裝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 <string>
#include <opencv2/opencv.hpp>
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 <iostream>
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 <SLAM.h>
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函數,返回值沒有賦值。已修正。