當前位置: 華文問答 > 數位

研究SLAM,對編程的要求有多高?

2016-10-18數位

我還沒寫你們就開始點贊了,有點過意不去啊……

題主說MATLAB,主要原因是大多數人本科階段接觸的都是MATLAB,所以希望之後研究SLAM也用它。

MATLAB確實有很多優點:語法簡單,開發速度快,偵錯方便,功能豐富。然而,在SLAM領域,MATLAB缺點也很明顯,主要是這兩個:

  • 需要正版軟體(你不能實機上也裝個盜版MATLAB吧);
  • 執行效率不高;
  • 需要一個巨大的安裝包;
  • 而相對的,C++的優勢在於直接使用,有很高的執行效率,不過開發速度和偵錯方面慢於MATLAB。不過光執行效率這一條,就夠許多SLAM方案選擇C++作為開發語言了,因為執行效率真的很重要。同一個演算法,拿MATLAB寫出來實作不能即時,拿C++寫的能即時,你說用哪個?

    當然MATLAB也有一些用武之地。我見過一些SLAM相關的公開課程,讓學生用MATLAB做仿真,交作業,這沒有問題,比如SLAM toolbox 。同樣的,比較類似於MATLAB的Python(以及octave)亦常被用於此道。它們在開發上的快捷帶來了很多便利,當你想要驗證一些數學理論、思想時,這些都是不錯的工具。所謂技多不壓身,題主掌握MATLAB和Python當然是很棒的。


    但是一牽涉到實用,你會發現幾乎所有的方案都在用C++。 因為執行效率實在是太重要了。


    那既然有心思學MATLAB,為什麽不學好C++呢?


    ---------------------分割線------------------

    接下來說說C++大概要學到什麽程度。用程式設計師的話說,C++語言比較特殊,你可以說自己精通了Java,但千萬不要說自己精通了C++。C++非常之博大精深,有數不清的特性,而且隨著時間還會不斷變化更新。不過,大多數人都用不著學會所有的C++特性,因為許多東西一輩子都用不到。


    作為SLAM研究人員,我們面對的主要是演算法層面的開發,所以更關心如何有效地實作各種相關的演算法。而相對的,那些復雜的軟體架構,設計模式,我個人認為在SLAM中倒是占次要地位的。畢竟您用SLAM的目的是計算一個位置以及建個地圖,並不是要去寫一套能夠自動更新的、多人網上對戰功能的機器人大戰平台。您的主要精力可能會花在矩陣運算、分塊、非線性最佳化的實作、影像處理上面;您可能對並行、指令集加速、GPU加速等話題感興趣,也可以花點時間學習;你還可能想用樣版來拓展你的演算法,也不妨一試。相應的,很多功能性的東西,比如說UI、網路通訊等等,當你用到的時候不妨接觸一下,但專註於SLAM上時就不必專門去學習了。


    話雖如此,SLAM所需的C++水平,大抵要高於你在書本上看到的那些個範例程式碼。因為那些程式碼是作者用來向初學者介紹語法的,所以會盡量簡單。而實際見到的程式碼往往結合了各種奇特的技巧,乍看起來會顯得高深莫測。比方說你在教科書裏看的大概是這樣:


    int main ( int argc, char** argv ) { vector<string> vec; vec.push_back("abc"); for ( int i=0; i<vec.size(); i++ ) { // ... } return 0; }


    你看了C++ Primer Plus,覺得C++也不過如此,並沒有啥特別難以理解的地方。然而實際程式碼大概是這樣的:


    巢狀的樣版類(來自g2o的塊求解器):

    g2o :: BlockSolver < g2o :: BlockSolverTraits < 3 , 1 > >:: LinearSolverType * linearSolver = new g2o :: LinearSolverDense < g2o :: BlockSolver < g2o :: BlockSolverTraits < 3 , 1 > >:: PoseMatrixType > (); g2o :: BlockSolver < g2o :: BlockSolverTraits < 3 , 1 > >* solver_ptr = new g2o :: BlockSolver < g2o :: BlockSolverTraits < 3 , 1 > > ( linearSolver ); g2o :: OptimizationAlgorithmLevenberg * solver = new g2o :: OptimizationAlgorithmLevenberg ( solver_ptr ); g2o :: SparseOptimizer optimizer ; optimizer . setAlgorithm ( solver );


    樣版元(來自ceres的自動求導):

    virtual bool Evaluate ( double const * const * parameters , double * residuals , double ** jacobians ) const { if ( ! jacobians ) { return internal :: VariadicEvaluate < CostFunctor , double , N0 , N1 , N2 , N3 , N4 , N5 , N6 , N7 , N8 , N9 > :: Call ( * functor_ , parameters , residuals ); } return internal :: AutoDiff < CostFunctor , double , N0 , N1 , N2 , N3 , N4 , N5 , N6 , N7 , N8 , N9 >:: Differentiate ( * functor_ , parameters , SizedCostFunction < kNumResiduals , N0 , N1 , N2 , N3 , N4 , N5 , N6 , N7 , N8 , N9 >:: num_residuals (), residuals , jacobians ); }


    C11新特性(來自SVO特征提取部份)

    void Frame :: setKeyPoints () { for ( size_t i = 0 ; i < 5 ; ++ i ) if ( key_pts_ [ i ] != NULL ) if ( key_pts_ [ i ] -> point == NULL ) key_pts_ [ i ] = NULL ; std :: for_each ( fts_ . begin (), fts_ . end (), [ & ]( Feature * ftr ){ if ( ftr -> point != NULL ) checkKeyPoints ( ftr ); }); }



    謎之運算(來自SVO的深度濾波器):

    void DepthFilter :: updateSeed ( const float x , const float tau2 , Seed * seed ) { float norm_scale = sqrt ( seed -> sigma2 + tau2 ); if ( std :: isnan ( norm_scale )) return ; boost :: math :: normal_distribution < float > nd ( seed -> mu , norm_scale ); float s2 = 1. / ( 1. / seed -> sigma2 + 1. / tau2 ); float m = s2 * ( seed -> mu / seed -> sigma2 + x / tau2 ); float C1 = seed -> a / ( seed -> a + seed -> b ) * boost :: math :: pdf ( nd , x ); float C2 = seed -> b / ( seed -> a + seed -> b ) * 1. / seed -> z_range ; float normalization_constant = C1 + C2 ; C1 /= normalization_constant ; C2 /= normalization_constant ; float f = C1 * ( seed -> a + 1. ) / ( seed -> a + seed -> b + 1. ) + C2 * seed -> a / ( seed -> a + seed -> b + 1. ); float e = C1 * ( seed -> a + 1. ) * ( seed -> a + 2. ) / (( seed -> a + seed -> b + 1. ) * ( seed -> a + seed -> b + 2. )) + C2 * seed -> a * ( seed -> a + 1.0f ) / (( seed -> a + seed -> b + 1.0f ) * ( seed -> a + seed -> b + 2.0f )); // update parameters float mu_new = C1 * m + C2 * seed -> mu ; seed -> sigma2 = C1 * ( s2 + m * m ) + C2 * ( seed -> sigma2 + seed -> mu * seed -> mu ) - mu_new * mu_new ; seed -> mu = mu_new ; seed -> a = ( e - f ) / ( f - e / f ); seed -> b = seed -> a * ( 1.0f - f ) / f ; }


    我不知道你們看到這些程式碼是什麽心情,總之我當時內心的感受是:臥槽這怎麽和教科書裏的完全不一樣啊!而且研究了半天發現人家居然是對的啊!


    [我不是很擅長貼表情圖總之你們腦補一下就好]


    總而言之,對C++的水平要求應該是在教科書之上的。而且這個水平的提高,多數時候建立在你不斷地看別人程式碼、碼自己程式碼的過程之上。它是反復練習出來的,並不是僅僅透過看書就能領會的。特別是對於視覺SLAM問題,很多時候你沒法照著論文把一套方案實作出來,這很大程度上取決於你的理論和程式碼功底。


    所以,請盡早開始學習C++,盡早開始使用C++,才是研究SLAM的正確之道。不要長期仿徨在自己的舒適區裏猶豫不決,這樣是沒有進步的。(同樣的道理亦適用於想研究SLAM但不願意學習Linux的朋友們)


    ---------------------分割線------------------

    題主還提到閉環檢測的庫,稍微列幾個:

    DBoW系列,來自TRO12的一篇文章,裏頭用k-means++訓練的字典樹。與OpenCV結合緊密,原理亦比較簡單。

    GitHub - dorian3d/DBoW2: Enhanced hierarchical bag-of-word library for C++

    GitHub - rmsalinas/DBow3: Improved version of DBow2


    FABMAP系列,用了Chow-Liu樹,來自Cummins的一系列論文。作者自己提供過一個開源版本,OpenCV也有人實作了一個,所以一共兩種。

    FabMap原版

    OpenCV:OpenFABMAP


    DLoopDetector:在DBoW2基礎上開發的回環檢測庫

    https://github.com/dorian3d/DLoopDetector


    建議題主從DBoW2或者DBoW3開始入手研究。原理和實作都相對簡單一些,效果也比較好。


    我自己也在寫一本SLAM的書籍,裏頭帶有大量的演算法實驗。現在還在草稿期,明年出版後題主可以參考一下。鑒於評論區對新書比較感興趣,那我貼個草稿的目錄吧。(由於是草稿所以最後見到的可能會有出入,而且有幾節我也還沒寫完。)所有寫著「實踐部份」的都附有例項程式碼。