從零學習SLAM位姿(二)

覺SLAM位姿估計(總結)

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

轉載鏈接:https://blog.csdn.net/u011178262/article/details/89685134

文章目錄

相關代碼:

Features Based Method

2D-2D: Epipolar Geometry

2D-2D 對極幾何 主要涉及到基礎矩陣、本質矩陣和單應性矩陣的求解,並從中恢復出旋轉矩陣 RRR 和平移向量 ttt。

同時,還要根據匹配的特徵點和計算出的相對位姿進行三角化,恢復出 3D空間點

在單目視覺SLAM中,以上過程主要用於SLAM的初始化:計算第二關鍵幀的相對位姿(假設第一幀位姿爲 [I0][I \quad 0][I0]),並計算初始Map。

OpenCV 中相關函數:

  • findFundamentalMat
  • findEssentialMat
  • findHomography
  • recoverPose
  • decomposeEssentialMat
  • triangulatePoints

上文是特徵,下文是位姿求取方法。

理解圖優化,一步步帶你看懂g2o框架

小白:師兄師兄,最近我在看SLAM的優化算法,有種方法叫“圖優化”。

師兄:圖優化的英文是 graph optimization 或者 graph-based optimization,你看,它的“圖”其實是數據結構中的graph。而凸優化的英文是 convex optimization,這裏的“凸”其實是凸函數的意思,所以單從英文就能區分開它們。

 

 

先了解g2o 框架

師兄:前面我們簡單介紹了圖優化,你也看到了它的神通廣大,那如何編程實現呢?

小白:對啊,有沒有現成的庫啊,我還只是個“調包俠”。。

師兄:這個必須有啊!在SLAM領域,基於圖優化的一個用的非常廣泛的庫就是g2o,它是General Graphic Optimization 的簡稱,是一個用來優化非線性誤差函數的c++框架。這個庫可以滿足你調包俠的夢想~

 

師兄:我先說安裝吧,其實g2o安裝很簡單,參考GitHub上官網:

https://github.com/RainerKuemmerle/g2o

按照步驟來安裝就行了。需要注意的是安裝之前確保電腦上已經安裝好了第三方依賴。

小白:好的,這個看起來很好裝。不過問題是,我看相關的代碼,感覺很複雜啊,不知如何下手啊

師兄:別急,第一次接觸g2o,確實有這種感覺,而且官網文檔寫的也比較“不通俗不易懂”,不過如果你能捋順了它的框架,再去看代碼,應該很快能夠入手了

小白:是的,先對框架了然於胸纔行,不然即使能湊合看懂別人代碼,自己也不會寫啊!

師兄:嗯嗯,其實g2o幫助我們實現了很多內部的算法,只是在進行構造的時候,需要遵循一些規則,在我看來這是可以接受的,畢竟一個程序不可能滿足所有的要求,因此在以後g2o的使用中還是應該多看多記,這樣才能更好的使用這個庫。

小白:記住了。養成記筆記的好習慣,還要多練習。

師兄:好,那我們首先看一下下面這個圖,是g2o的基本框架結構。如果你查資料的話,你會在很多地方都能看到。看圖的時候要注意箭頭類型

1、圖的核心

小白:師兄,這個圖該從哪裏開始看?感覺好多東西。。

師兄:如果你想要知道這個圖中哪個最重要,就去看看箭頭源頭在哪裏

小白:我看看。。。好像是最左側的SparseOptimizer?

師兄:對的,SparseOptimizer是整個圖的核心,我們注意右上角的 is-a 實心箭頭,這個SparseOptimizer它是一個Optimizable Graph,從而也是一個超圖(HyperGraph)。

小白:我去,師兄,怎麼突然冒出來這麼多奇怪的術語,都啥意思啊?

師兄:這個你不需要一個個弄懂,不然可能黃花菜都涼了。你先暫時只需要瞭解一下它們的名字,有些以後用不到,有些以後用到了再回看。目前如果遇到重要的我會具體解釋。

小白:好。那下一步看哪裏?

2、頂點和邊

師兄:我們先來看上面的結構吧。注意看 has-many 箭頭,你看這個超圖包含了許多頂點(HyperGraph::Vertex)和邊(HyperGraph::Edge)。而這些頂點頂點繼承自 Base Vertex,也就是OptimizableGraph::Vertex,而邊可以繼承自 BaseUnaryEdge(單邊), BaseBinaryEdge(雙邊)或BaseMultiEdge(多邊),它們都叫做OptimizableGraph::Edge

小白:頭有點暈了,師兄

師兄:哈哈,不用一個個記,現階段瞭解這些就行。頂點和邊在編程中很重要的,關於頂點和邊的定義我們以後會詳細說的。下面我們來看底部的結構。

小白:嗯嗯,知道啦!

3、配置SparseOptimizer的優化算法和求解器

師兄:你看下面,整個圖的核心SparseOptimizer 包含一個優化算法(OptimizationAlgorithm)的對象。OptimizationAlgorithm是通過OptimizationWithHessian 來實現的。其中迭代策略可以從Gauss-Newton(高斯牛頓法,簡稱GN), Levernberg-Marquardt(簡稱LM法), Powell's dogleg 三者中間選擇一個(我們常用的是GN和LM)

小白:GN和LM就是我們以前講過的非線性優化方法中常用的兩種吧
師兄:是的,如果不瞭解的話具體看《從零開始學習「張氏相機標定法」(四)優化算法前傳》《從零開始學習「張氏相機標定法」(五)優化算法正傳》這兩篇文章。

這裏寫圖片描述

4、如何求解

師兄:那麼如何求解呢?OptimizationWithHessian 內部包含一個求解器(Solver),這個Solver實際是由一個BlockSolver組成的。這個BlockSolver有兩個部分,一個是SparseBlockMatrix ,用於計算稀疏的雅可比和Hessian矩陣;一個是線性方程的求解器(LinearSolver),它用於計算迭代過程中最關鍵的一步HΔx=−b,LinearSolver有幾種方法可以選擇:PCG, CSparse, Choldmod,具體定義後面會介紹

到此,就是上面圖的一個簡單理解。

一步步帶你看懂g2o編程流程

小白:師兄,看完了我也不知道編程時具體怎麼編呢!

師兄:我正好要說這個。首先這裏需要說一下,我們梳理是從頂層到底層,但是編程實現時需要反過來,像建房子一樣,從底層開始搭建框架一直到頂層。g2o的整個框架就是按照下圖中我標的這個順序來寫的。

高博在十四講中g2o求解曲線參數的例子來說明,源代碼地址

https://github.com/gaoxiang12/slambook/edit/master/ch6/g2o_curve_fitting/main.cpp

爲了方便理解,我重新加了註釋。如下所示,

typedef g2o::BlockSolver< g2o::BlockSolverTraits<3,1> > Block;  // 每個誤差項優化變量維度爲3,誤差值維度爲1

// 第1步:創建一個線性求解器LinearSolver
Block::LinearSolverType* linearSolver = new g2o::LinearSolverDense<Block::PoseMatrixType>(); 

// 第2步:創建BlockSolver。並用上面定義的線性求解器初始化
Block* solver_ptr = new Block( linearSolver );      

// 第3步:創建總求解器solver。並從GN, LM, DogLeg 中選一個,再用上述塊求解器BlockSolver初始化
g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg( solver_ptr );

// 第4步:創建終極大boss 稀疏優化器(SparseOptimizer)
g2o::SparseOptimizer optimizer;     // 圖模型
optimizer.setAlgorithm( solver );   // 設置求解器
optimizer.setVerbose( true );       // 打開調試輸出

// 第5步:定義圖的頂點和邊。並添加到SparseOptimizer中
CurveFittingVertex* v = new CurveFittingVertex(); //往圖中增加頂點
v->setEstimate( Eigen::Vector3d(0,0,0) );
v->setId(0);
optimizer.addVertex( v );
for ( int i=0; i<N; i++ )    // 往圖中增加邊
{
  CurveFittingEdge* edge = new CurveFittingEdge( x_data[i] );
  edge->setId(i);
  edge->setVertex( 0, v );                // 設置連接的頂點
  edge->setMeasurement( y_data[i] );      // 觀測數值
  edge->setInformation( Eigen::Matrix<double,1,1>::Identity()*1/(w_sigma*w_sigma) ); // 信息矩陣:協方差矩陣之逆
  optimizer.addEdge( edge );
}

// 第6步:設置優化參數,開始執行優化
optimizer.initializeOptimization();
optimizer.optimize(100);

結合上面的流程圖和代碼。下面一步步解釋具體步驟。

1、創建一個線性求解器LinearSolver

我們要求的增量方程的形式是:H△X=-b,通常情況下想到的方法就是直接求逆,也就是△X=-H.inv*b。看起來好像很簡單,但這有個前提,就是H的維度較小,此時只需要矩陣的求逆就能解決問題。但是當H的維度較大時,矩陣求逆變得很困難,求解問題也變得很複雜。

小白:那有什麼辦法嗎?

師兄:辦法肯定是有的。此時我們就需要一些特殊的方法對矩陣進行求逆,你看下圖是GitHub上g2o相關部分的代碼

如果你點進去看,可以分別查看每個方法的解釋,如果不想挨個點進去看,看看下面我的總結就行了

LinearSolverCholmod :使用sparse cholesky分解法。繼承自LinearSolverCCS
LinearSolverCSparse:使用CSparse法。繼承自LinearSolverCCS
LinearSolverPCG :使用preconditioned conjugate gradient 法,繼承自LinearSolver
LinearSolverDense :使用dense cholesky分解法。繼承自LinearSolver
LinearSolverEigen: 依賴項只有eigen,使用eigen中sparse Cholesky 求解,因此編譯好後可以方便的在其他地方使用,性能和CSparse差不多。繼承自LinearSolver

2、創建BlockSolver。並用上面定義的線性求解器初始化。

BlockSolver 內部包含 LinearSolver,用上面我們定義的線性求解器LinearSolver來初始化。它的定義在如下文件夾內:

g2o/g2o/core/block_solver.h

你點進去會發現 BlockSolver有兩種定義方式

一種是指定的固定變量的solver,我們來看一下定義

 using BlockSolverPL = BlockSolver< BlockSolverTraits<p, l> >;

其中p代表pose的維度(注意一定是流形manifold下的最小表示),l表示landmark的維度

另一種是可變尺寸的solver,定義如下

using BlockSolverX = BlockSolverPL<Eigen::Dynamic, Eigen::Dynamic>;

小白:爲何會有可變尺寸的solver呢?

師兄:這是因爲在某些應用場景,我們的Pose和Landmark在程序開始時並不能確定,那麼此時這個塊狀求解器就沒辦法固定變量,此時使用這個可變尺寸的solver,所有的參數都在中間過程中被確定

另外你看block_solver.h的最後,預定義了比較常用的幾種類型,如下所示:

BlockSolver_6_3 :表示pose 是6維,觀測點是3維。用於3D SLAM中的BA
BlockSolver_7_3:在BlockSolver_6_3 的基礎上多了一個scale
BlockSolver_3_2:表示pose 是3維,觀測點是2維

以後遇到了知道這些數字是什麼意思就行了

3、創建總求解器solver。並從GN, LM, DogLeg 中選一個,再用上述塊求解器BlockSolver初始化

我們來看g2o/g2o/core/ 目錄下,發現Solver的優化方法有三種:分別是高斯牛頓(GaussNewton)法,LM(Levenberg–Marquardt)法、Dogleg法,如下圖所示,也和前面的圖相匹配

 

小白:師兄,上圖最後那個OptimizationAlgorithmWithHessian 是幹嘛的?

師兄:你點進去 GN、 LM、 Doglet算法內部,會發現他們都繼承自同一個類:OptimizationWithHessian,如下圖所示,這也和我們最前面那個圖是相符的

 

然後,我們點進去看 OptimizationAlgorithmWithHessian,發現它又繼承自OptimizationAlgorithm,這也和前面的相符總之,在該階段,我們可以選則三種方法:

g2o::OptimizationAlgorithmGaussNewton
g2o::OptimizationAlgorithmLevenberg 
g2o::OptimizationAlgorithmDogleg 

4、創建終極大boss 稀疏優化器(SparseOptimizer),並用已定義求解器作爲求解方法。

創建稀疏優化器

g2o::SparseOptimizer    optimizer;

用前面定義好的求解器作爲求解方法:

 

其中setVerbose是設置優化過程輸出信息用的

SparseOptimizer::setVerbose(bool verbose)

5、定義圖的頂點和邊。並添加到SparseOptimizer中。

這部分比較複雜,我們下一次再介紹。

6、設置優化參數,開始執行優化。

設置SparseOptimizer的初始化、迭代次數、保存結果等。

初始化

SparseOptimizer::initializeOptimization(HyperGraph::EdgeSet& eset)

設置迭代次數,然後就開始執行圖優化了。

SparseOptimizer::optimize(int iterations, bool online)

小白:終於搞明白g2o流程了!謝謝師兄!必須給你個「好看」啊!

注:以上內容部分參考瞭如下文章,感謝原作者:

https://www.jianshu.com/p/e16ffb5b265d

https://blog.csdn.net/heyijia0327/article/details/47686523

討論

我們知道(不知道的話,去查一下十四講)用g2o和ceres庫都能用來進行BA優化,這兩者在使用過程中有什麼不同?

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