ORB-SLAM2應用練習:三維重建系統搭建 (1)

概述


本博客記錄我使用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,現在點擊編譯運行,出現以下畫面就沒問題了:
封裝ORB-SLAM2

不想一下子寫太多,本文就先到此爲止了。


發現以上代碼中有誤:封裝SLAM的track函數,返回值沒有賦值。已修正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章