G2O使用總結

g2o整體框架:https://blog.csdn.net/wubaobao1993/article/details/79319215

再看頂點和邊https://blog.csdn.net/wubaobao1993/article/details/79328569

頂點和邊之外的solver: https://blog.csdn.net/wubaobao1993/article/details/79336069

正文
寫在前面
進來對g2o優化庫進行了學習,雖然才模仿着寫了兩個例程,但是對於整個g2o的理解和使用方面還是多了不少的感觸,特此寫下博客,對這些天的學習進行記錄。

一、g2o的整體結構
說到整體的結構,不得不用一張比較概括的圖來說明:
在這裏插入圖片描述

這張圖最好跟着畫一下,這樣能更好的理解和掌握,例如我第一次看的時候根本沒有注意說箭頭的類型等等的細節。

那麼從圖中我們其實比較容易的就看出來整個庫裏面較爲重要的類之間的繼承以及包含關係,也可以看出整個框架裏面最重要的東西就是SparseOptimizer這個類(或者說實例)。

頂點(Vertex)和邊(Edge)
順着圖往上看,可以看到我們所使用的優化器最終是一個超圖(hyperGrahp),而這個超圖包含了許多頂點(Vertex)和邊(Edge)。這兩個類型是我們在看程序和寫程序中比較關注的東西了,g2o不像Ceres,內部很多東西其實作者都已經寫好了(此刻一鞠躬),很適合我們這些新興的懶惰青年,但是同時,我們也失去了一個比較完整的瞭解內部關係的機會,不過這個東西我們可以通過看內部的實現補回來(但是編程實踐是沒有機會了),順便看看大牛寫的代碼~

那麼扯回來,在圖優化中,頂點代表了要被優化的變量,而邊則是連接被優化變量的橋樑,因此,也就造成了說我們在程序中見得較多的就是這兩種類型的初始化和賦值。

在整個優化過程中,頂點的值會越來越趨近於最有值,優化完畢後則可以將頂點的優化值作爲最優值進行使用;邊則是連接頂點的類型,在SLAM問題中,一般是邊連接要被優化的空間點(Point)和機器人的位姿(Pose),當然,邊還可以連接一個頂點(類似與參數估計,邊的數量由量測的數量決定),也可以連接多個頂點(超圖,這裏筆者還沒有遇到過,就不妄言了),邊在圖優化中的一個很大的作用就是計算誤差(視覺SLAM中計算的就是空間點的映射誤差),同時計算該誤差對於被優化變量的jacobian矩陣,也是比較重要的存在。

自定義頂點(Vertex)和邊(Edge)
我們在用g2o的時候,不會一帆風順的就能適合自身機器人的實際情況,總會遇到自己獨特的頂點類型和邊類型,此時我們需要對頂點和邊進行重寫,那麼重寫也比較簡單,這裏簡單進行記錄。

在整體框架圖中,可以看到不管是頂點還是邊,都可以說是繼承自baseXXX這個類的,因此我們在自定義的時候,也可以仿照着繼承這兩個類,當然也可以繼承自g2o中較爲“成熟”的類,不管怎樣,都要重寫下述的函數。

自定義頂點

virtual bool read(std::istream& is);  
virtual bool write(std::ostream& os) const;  
virtual void oplusImpl(const number_t* update);  
virtual void setToOriginImpl();  

其中read,write函數可以不進行覆寫,僅僅聲明一下就可以,setToOriginImpl設定被優化變量的原始值,oplusImpl比較重要,我們根據增量方程計算出增量之後,就是通過這個函數對估計值進行調整的,因此這個函數的內容一定要寫對,否則會造成一直優化卻得不到好的優化結果的現象。

自定義邊

virtual bool read(std::istream& is);  
virtual bool write(std::ostream& os) const;  
virtual void computeError();  
virtual void linearizeOplus();

read和write函數同上,computeError函數是使用當前頂點的值計算的測量值與真實的測量值之間的誤差,linearizeOplus函數是在當前頂點的值下,該誤差對優化變量的偏導數,即jacobian。

自定義的總結
不管是自定義邊還是頂點,除了自己加入的一些變量,還都要對一些g2o框架要調用的函數進行覆寫,這些函數用戶可以聲明爲實函數(即不加virtual),但是筆者還是建議聲明爲虛函數。

優化算法(Algorithm,BlockSolver,linearSolver)
順着整體結構圖往下看,可以看到這部分其實算是整個g2o裏面比較隱晦的部分,設計到優化的算法,塊求解器,線性求解器等等部分,在程序中,這部分通常位於g2o算法的開頭配置部分,一般情況下我們可以隨着一個例程進行配置即可,這裏對這部分進行了稍微淺顯的理解,特意寫在這裏(下面的東西純屬個人理解了,如有不妥還請大神指出:)。

linearSolver,線性求解器
我們知道在求解增量方程HdeltaX=-b的時候,通常情況下想到線性求解,很簡單嘛,deltaX=-H.inv*b,的確,當H的維度較小的時候,上述問題變得簡單,只需要矩陣的求逆就能解決問題,但是當H的維度較大時,問題變得複雜,此時我們就需要一些特殊的方法對矩陣進行求逆,g2o中主要有圖中所示的三種方法,PCG,CSparse和Cholmod方法。

注意,這裏說再多,線性求解器僅僅只是完成了一個求解的功能,可以說是整個優化中比較靠後的計算部分了。

BlockSolver,塊求解器(參數塊求解器?)
塊求解器是包含線性求解器的存在,之所以是包含,是因爲塊求解器會構建好線性求解器所需要的矩陣塊(也就是H和b),之後給線性求解器讓它進行運算,邊的jacobian也就是在這個時候發揮了自己的光和熱。

這裏再記錄下一個比較容易混淆的問題,也就是在初始化塊求解器的時候的參數問題。

大部分的例程在初始化塊求解器的時候都會使用如下的程序代碼:

std::unique_ptr<g2o::BlockSolver_6_3::LinearSolverType> linearSolver = g2o::make_unique<g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType>>();  

其中的BlockSolver_6_3有兩個參數,分別是6和3,在定義的時候可以看到這是一個模板的重命名(模板類的重命名只能用using)

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

其中p代表pose的維度,l表示landmark的維度,且這裏都表示的是增量的維度(這裏筆者也不是很確定,但是從後續的程序中可以看出是增量的維度而並非是狀態變量的維度)。

因此(後面的話以SLAM情況爲例),對於僅僅優化位姿的應用而言,這裏l的值是沒有太大影響的,因爲H矩陣中並沒有Hll的塊,因此這裏的維度也就沒有用武之地了。

Algorithm,優化算法
這裏就不多講了,從圖中可以看到主要有三種方法:GN,LM,PSD,不同的方法主要表現在最終的H矩陣構造不同。

總結

  1. 對g2o優化庫的整體框架有了更好的瞭解

  2. 對圖中的頂點和邊有了更加深刻的認識

  3. 理解了g2o初始化部分的程序與意圖

文章來源:
https://www.cnblogs.com/feifanrensheng/articles/8681952.html

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