視覺SLAM 十四講——后端設計I(BA)


主要內容

1. 概述

  1)考慮k時刻的狀態估計,用過去0k中的數據來估計現在的狀態分布:

    

  2)按照貝葉斯法則:

    

     注:從左到右,一次為后驗概率,似然概率 和 先驗概率;其中,似然有量測信息給定,先驗概率基於過去所有的狀態估計和量測得來的

  3)對先驗概率進行進一步展開,按照 為條件概率展開:

    

      注:如果考慮更久之前的狀態,可以對上式進行進一步的展開,現在只關注k時刻和k-1時刻。

  4) 繼續處理先驗概率

    一種方法是假設馬爾科夫性,即認為k時刻的狀態只與k-1時刻的狀態有關,而與再之前的無關,以擴展卡爾曼濾波(EKF)為代表的濾波器方法;

    二是依然考慮k時刻的狀態與之前所有狀態的關系,以非線性優化為主體的 優化框架。(SLAM的主流)



2. EKF

  1) 基於馬爾科夫性,先驗概率公式展開可以表示為:

    

    從這個公式中可以看出,k時刻的先驗概率可以表示描述為:基於k-1時刻的后驗概率 ,結合輸入信息以及k-1時刻到k時刻的轉移概率,可以求解出k時刻的先驗概率。

    在線性系統和高斯噪聲下,卡爾曼濾波器構成了該系統中的最大后驗概率估計,即無偏最優估計;而在SLAM這種非線性的情況下,EKF給出了單次線性近似下的最大后驗估計(MAP)。

  2) 討論

    2.1)優點:形式簡潔,應用在計算資源受限,或待估計量比較簡單的場合

    2.2)典型方法及文獻:

      IF(信息濾波)IKF(Iterated KF)UKF(Unscented KF),和粒子濾波器,SWF(Sliding Window Filter),等等,或者用分治法等思路改進EKF的效率。相關文獻如下:

[1] Sujan V A, Dubowsky S. Efficient information-based visual robotic mapping in unstructured environments[J]. The International Journal of Robotics Research, 2005, 24(4): 275-293.

[2] A kalman-filter-based method for pose estimation in visual servoing

F Janabi-Sharifi, M Marey - IEEE transactions on Robotics, 2010 - ieeexplore.ieee.org

[3] Li S, Ni P. Square-root unscented Kalman filter based simultaneous localization and mapping[C]//The 2010 IEEE International Conference on Information and Automation. IEEE, 2010: 2384-2388.

[4] Gil A, Reinoso Ó, Ballesta M, et al. Multi-robot visual SLAM using a Rao-Blackwellized particle filter[J]. Robotics and Autonomous Systems, 2010, 58(1): 68-80.

[5] Sliding window filter with application to planetary landing

G Sibley, L Matthies, G Sukhatme - Journal of Field Robotics, 2010 - Wiley Online Library

[6] Chen S Y. Kalman filter for robot vision: a survey[J]. IEEE Transactions on Industrial Electronics, 2011, 59(11): 4409-4420.

  3) EKF局限性

    3.1) 濾波器方法在一定程度上假設了馬爾科夫性;

    3.2EKF僅在某一點處做了一次線性化,即認為該點處的線性化近似在后驗概率處仍然是有效的。——存在非線性誤差

    3.3EKF需要存儲狀態量的均值和方差,由於SLAM中路標數量很大,因此存儲的量比較大,因此,EKF SLAM被普遍認為不適用於大型場景。


3. BA與圖優化

  3.1) 投影模型和BA代價函數

 

  3.2) BA求解

    增量線性方程的求解



  3.3)稀疏性和邊緣化

    矩陣H的稀疏結構;

    H矩陣當中的非對角部分的非零矩陣塊可以理解為其對應的兩個變量之間存在聯系,或者可以稱之為約束——圖優化結構與增量方程的稀疏性存在着明顯的聯系

    通常有路標的數量遠遠大於相機位姿的數量,因此H矩陣又稱為箭頭形矩陣或鎬形矩陣。

  3.4) 利用H矩陣的稀疏性加速計算的方法——Schur消元(在SLAM亦稱為Marginalization, 邊緣化)

    

    對線性方程組進行高斯消元,目標是消去右上角的非對角部分E.

    

    求解過程相當於做了條件概率展開,結果是求出了關於xc的邊緣分布,所以又稱為邊緣化。

  3.5S的稀疏性是不規則的,取決於實際觀測的結果。

    S矩陣非對角線上的非零矩陣塊,表示了該處對應的兩個相機變量之間存在着共同觀測的路標點,有時候稱為共視(Co-visibility);

    ORB-SLAM 選擇具有共同觀測的幀作為關鍵幀,因此Schur消元后得到的S是稠密的

  3.6) 魯棒核函數

    核函數保證每條邊的誤差不會大的沒邊而掩蓋掉其他的邊

    常用的核函數:HuberCauchTukey

 

Schur消元也並不意味着將所有路標消元,將相機變量消元也是SLAM當中采用的手段。

 

 

4. BA實現:G2O

 

  4.1) 參數的配置和解析:(command_args.cpp BundleParams.h

 

    可以初始設置參數的名字,默認值,以及各種不同的類型參數

 

    可以解析可執行傳入的參數,解析並保存到對應的變量中,進行使用

 

    可通過-h選項,輸出當前已有的參數名以及相關默認的值

 

      eg: -input ~qifarui/code/slambook-master/ch10/g2o_custombundle/data/problem-16-22106-pre.txt -h (先設置輸入的文件名,然后通過 -h進行輸出)

 

  4.2BALProblem.cpp

 

     PLY點雲文件生成(包括相機中心點的寫入,以及 空間特征點的寫入)

 

      相機,路邊以及量測信息讀入(特征點位置以及相機位姿信息的讀入,以及相機和特征點量測信息)

 

  4.3)利用軸角進行坐標變換

 

    羅德里格斯公式實現坐標變換

 

    利用相機位姿信息求解相機原點在世界坐標系下的坐標( c = -R't)。

 

    軸角和四元數的轉化

 

    利用相機中心在世界坐標系下的坐標生成相機的位姿信息

 

    (Eigen中的Map類——Eigen如何與原生raw C/C++ 數組混合編程

 

  4.4Normalize 函數實現:(作用????

 

    特征點的歸一化實現(相對於中間點的比例縮放)

 

    相機中心點按照特征點, 在幾何體中心點等比例進行縮放。

 

  4.5Perturb()擾動誤差添加

 

    根據設定的標准差,對輸入的特征點坐標,相機位姿(平移加旋轉)添加白噪聲

 

  4.6g2o 優化設置

 

    4.6.1SetSolverOptionsFromFlags 求解設置:

 

      使用何種方法(列文伯格, DogLeg等)來定義非線性優化的下降策略

 

      使用哪類線性求解器(稠密 or稀疏(額外需要對求解器進行設置-矩陣排序)

 

    4.6.2BuildProblem 構造最優化問題,增加邊和頂點:

 

      a 相機頂點類VertexCameraBAL定義,及頂點初始添加(包括相機位姿(旋轉+平移),相機參數)

 

      b 路邊定點VertexCameraBAL定義,及頂點初始添加

 

      注意:

 

        each vertex should have an unique id, no matter it is a camera vertex, or a point vertex

 

        pPoint→setMarginalized(true); // 設置路標點的schur消元

 

 

    4.6.3) EdgeObservationBAL定義, 以及添加

 

      a 誤差計算函數實現

 

      b ()運算付重載,用於投影誤差計算

 

      c CamProjectionWithDistortion:據坐標點以及相機參數(內參 外參以及畸變系數)得到像素坐標點

 

      d ceres庫的自動求導功能使用:AutoDiff,獲得雅克比矩陣;

 

        注:自動求導功能需要類型將需要求導的公式實現在括號運算符里

 

      e  量測信息獲取,並添加(根據相機和路標點ID設置邊連接的頂點,對應邊的量測信息設置

 

      f  量測信息協方差矩陣設置

 

      g 根據配置信息,設置魯棒核函數huber

 

 

    4.6.4) 優化器初始化,根據設置的迭代次數,開啟優化

 

 

  4.7) 將優化器優化的結果寫入的本地優化變量中,包括相機的參數信息以及路標點的位置信息,通過memcpy的形式

 

  4.8) 根據配置的文件名,將最終的結果寫入到點雲ply文件中。

 

注: g2o庫說明:

  1) 由於g2o庫本身公開API說明較少,只能通過網上的開源項目及其本身的源代碼來了解它

  2g2o在做BA的優化時,必須將其所有點雲全部Schur,否則會報錯。其原因在於使用了g2o::LinearSolver<BalBlockSolver::PoseMatrixType>* linearSolver的線性求解器,其中模板參數當中的 PoseMatrixType在程序中為相機姿態參數的維度,於是BA當中Schur消元后解的線性方程組必須是只含有相機的姿態變量。

 

 BA實現g2o結果如下:

  

bal problem have 16 cameras and 22106 points. 
Forming 83718 observatoins. 
beginning problem...
Normalization complete...
begin optimizaiton ..
iteration= 0     chi2= 1593976.900329     time= 0.430721     cumTime= 0.430721     edges= 83718     schur= 1     lambda= 9370.114177     levenbergIter= 1
iteration= 1     chi2= 1503859.189085     time= 0.327325     cumTime= 0.758047     edges= 83718     schur= 1     lambda= 3123.371392     levenbergIter= 1
iteration= 2     chi2= 1340948.148116     time= 0.322999     cumTime= 1.08105     edges= 83718     schur= 1     lambda= 1041.123797     levenbergIter= 1
iteration= 3     chi2= 1104714.717556     time= 0.335624     cumTime= 1.41667     edges= 83718     schur= 1     lambda= 347.041266     levenbergIter= 1
iteration= 4     chi2= 879105.141579     time= 0.323237     cumTime= 1.73991     edges= 83718     schur= 1     lambda= 115.680422     levenbergIter= 1
iteration= 5     chi2= 723703.972964     time= 0.318097     cumTime= 2.058     edges= 83718     schur= 1     lambda= 38.560141     levenbergIter= 1
iteration= 6     chi2= 615709.929828     time= 0.317493     cumTime= 2.3755     edges= 83718     schur= 1     lambda= 12.853380     levenbergIter= 1
iteration= 7     chi2= 503775.361750     time= 0.316782     cumTime= 2.69228     edges= 83718     schur= 1     lambda= 4.284460     levenbergIter= 1
iteration= 8     chi2= 381980.060879     time= 0.320198     cumTime= 3.01248     edges= 83718     schur= 1     lambda= 1.428153     levenbergIter= 1
iteration= 9     chi2= 267385.644652     time= 0.34434     cumTime= 3.35682     edges= 83718     schur= 1     lambda= 0.476051     levenbergIter= 1
iteration= 10     chi2= 170314.069365     time= 0.342958     cumTime= 3.69977     edges= 83718     schur= 1     lambda= 0.158684     levenbergIter= 1
iteration= 11     chi2= 108750.143596     time= 0.325639     cumTime= 4.02541     edges= 83718     schur= 1     lambda= 0.052895     levenbergIter= 1
iteration= 12     chi2= 79228.387608     time= 0.33411     cumTime= 4.35952     edges= 83718     schur= 1     lambda= 0.035263     levenbergIter= 1
iteration= 13     chi2= 62903.760510     time= 0.359433     cumTime= 4.71896     edges= 83718     schur= 1     lambda= 0.023509     levenbergIter= 1
iteration= 14     chi2= 51637.899996     time= 0.332307     cumTime= 5.05126     edges= 83718     schur= 1     lambda= 0.015672     levenbergIter= 1
iteration= 15     chi2= 42491.525780     time= 0.352168     cumTime= 5.40343     edges= 83718     schur= 1     lambda= 0.010448     levenbergIter= 1
iteration= 16     chi2= 37296.044380     time= 0.32017     cumTime= 5.7236     edges= 83718     schur= 1     lambda= 0.005843     levenbergIter= 1
iteration= 17     chi2= 36145.312948     time= 0.375634     cumTime= 6.09924     edges= 83718     schur= 1     lambda= 0.001948     levenbergIter= 1
iteration= 18     chi2= 36069.306555     time= 0.334505     cumTime= 6.43374     edges= 83718     schur= 1     lambda= 0.000649     levenbergIter= 1
iteration= 19     chi2= 36067.831214     time= 0.32886     cumTime= 6.7626     edges= 83718     schur= 1     lambda= 0.000216     levenbergIter= 1
optimization complete.. 

生成的點雲結果通過 meshlab 打開以后,結果如下:(上為initial, 下為final)

 

 

 

 

5. BA實現:Ceres

  該部分將介紹Ceres實現,其中和g2o相同部分的輸入,配置解析等代碼講不會介紹。

  

  5.1)最小二乘優化問題構建:Problem

    

void BuildProblem(BALProblem* bal_problem, Problem* problem, const BundleParams& params)
{
    const int point_block_size = bal_problem->point_block_size();
    const int camera_block_size = bal_problem->camera_block_size();
    double* points = bal_problem->mutable_points();
    double* cameras = bal_problem->mutable_cameras();

    // Observations is 2 * num_observations long array observations
    // [u_1, u_2, ... u_n], where each u_i is two dimensional, the x 
    // and y position of the observation. 
    const double* observations = bal_problem->observations();

    for(int i = 0; i < bal_problem->num_observations(); ++i){
        CostFunction* cost_function;

        // Each Residual block takes a point and a camera as input 
        // and outputs a 2 dimensional Residual
      
        cost_function = SnavelyReprojectionError::Create(observations[2*i + 0], observations[2*i + 1]);

        // If enabled use Huber's loss function. 
        LossFunction* loss_function = params.robustify ? new HuberLoss(1.0) : NULL;

        // Each observatoin corresponds to a pair of a camera and a point 
        // which are identified by camera_index()[i] and point_index()[i]
        // respectively.
        double* camera = cameras + camera_block_size * bal_problem->camera_index()[i];
        double* point = points + point_block_size * bal_problem->point_index()[i];

     
        problem->AddResidualBlock(cost_function, loss_function, camera, point);
    }

}

    5.1.1) 向問題中添加殘差,各個觀測的殘差信息

      problem->AddResidualBlock(cost_function, loss_function, camera, point);

      其中參數包括代價函數(殘差計算函數) 損失函數(魯棒核函數——根據配置項進行設定),

      待估計的參數位姿和路標點

    5.1.2) 代價函數:ceres::AutoDiffCostFunction<SnavelyReprojectionError,2,9,3>

      自動求導;

      模板參數:殘差計算定義類SnavelyReprojectionError, 殘差維度,待估計相機參數維度, 待估計路標點維度;

      new的構造參數: SnavelyReprojectionError變量

       

class SnavelyReprojectionError
{
public:
    SnavelyReprojectionError(double observation_x, double observation_y):observed_x(observation_x),observed_y(observation_y){}

template<typename T>
    bool operator()(const T* const camera,
                const T* const point,
                T* residuals)const{                  
        // camera[0,1,2] are the angle-axis rotation
        T predictions[2];
        CamProjectionWithDistortion(camera, point, predictions);
        residuals[0] = predictions[0] - T(observed_x);
        residuals[1] = predictions[1] - T(observed_y);

        return true;
    }

    static ceres::CostFunction* Create(const double observed_x, const double observed_y){
        return (new ceres::AutoDiffCostFunction<SnavelyReprojectionError,2,9,3>(
            new SnavelyReprojectionError(observed_x,observed_y)));
    }


private:
    double observed_x;
    double observed_y;
};

    5.1.3) 根據相機和路標點的連接索引ID,將對應的量測信息和待估計參數添加到Problem

       (其中待估計參數直接復用原有變量的地址,不會額外進行申請變量,而在g2o中將這些信息以邊,頂點這種額外的變量類型添加到優化問題中,同時優化完成以后,又需要將這些信息返回到之前的變量中

  5.2) 求解器設置:Solver::Options

    5.2.1) 最大迭代次數,最小化進度輸出,線程個數設置等

    options->max_num_iterations = params.num_iterations;
    options->minimizer_progress_to_stdout = true;
    options->num_threads = params.num_threads;

    5.2.2)優化方法增量方程求解中:下降策略 or 信賴區域策略

    StringToTrustRegionStrategyType(params.trust_region_strategy,
                                        &options->trust_region_strategy_type);

    5.2.3)線性求解器設置

    ceres::StringToLinearSolverType(params.linear_solver, &options->linear_solver_type);
    ceres::StringToSparseLinearAlgebraLibraryType(params.sparse_linear_algebra_library, &options->sparse_linear_algebra_library_type);
    ceres::StringToDenseLinearAlgebraLibraryType(params.dense_linear_algebra_library, &options->dense_linear_algebra_library_type);
    options->num_threads = params.num_threads;

    5.2.4Schur消元順序管理類型ceres::ParameterBlockOrdering設置:

      通過編號大小設置消元的優先級,優先消元編號最小的變量。

void SetOrdering(BALProblem* bal_problem, ceres::Solver::Options* options, const BundleParams& params)
{
    const int num_points = bal_problem->num_points();
    const int point_block_size = bal_problem->point_block_size();
    double* points = bal_problem->mutable_points();

    const int num_cameras = bal_problem->num_cameras();
    const int camera_block_size = bal_problem->camera_block_size();
    double* cameras = bal_problem->mutable_cameras();


    if (params.ordering == "automatic")
        return;

    ceres::ParameterBlockOrdering* ordering = new ceres::ParameterBlockOrdering;

    // The points come before the cameras
    for(int i = 0; i < num_points; ++i)
       ordering->AddElementToGroup(points + point_block_size * i, 0);
       
    
    for(int i = 0; i < num_cameras; ++i)
        ordering->AddElementToGroup(cameras + camera_block_size * i, 1);

    options->linear_solver_ordering.reset(ordering);

}

    5.2.5) 梯度閾值,相鄰兩次迭代之間目標函數差值的閾值設置

    options.gradient_tolerance = 1e-16;
    options.function_tolerance = 1e-16;

  5.3 求解函數:Solve

    參數輸入:配置信息,優化問題,輸出優化信息

    Solver::Summary summary;
    Solve(options, &problem, &summary);
    std::cout << summary.FullReport() << "\n";

 

Ceres優勢:

  1)能夠在原數組上進行操作

  2)優化設置非常便捷(Solver::Options類型成員變量賦值, schur消元順序等)

  3API說明詳細,可讀性高,多讀讀源碼,加深理解和應用

 
輸出結果:

bal problem have 16 cameras and 22106 points. 
Forming 83718 observatoins. 
beginning problem...
Normalization complete...
the problem is successfully build..
iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
   0  4.185660e+06    0.00e+00    2.16e+07   0.00e+00   0.00e+00  1.00e+04        0    1.27e-01    3.83e-01
   1  1.980525e+05    3.99e+06    5.34e+06   2.40e+03   9.60e-01  3.00e+04        1    2.56e-01    6.39e-01
   2  5.086543e+04    1.47e+05    2.11e+06   1.01e+03   8.22e-01  4.09e+04        1    2.41e-01    8.80e-01
   3  1.859667e+04    3.23e+04    2.87e+05   2.64e+02   9.85e-01  1.23e+05        1    2.43e-01    1.12e+00
   4  1.803857e+04    5.58e+02    2.69e+04   8.66e+01   9.93e-01  3.69e+05        1    2.39e-01    1.36e+00
   5  1.803391e+04    4.66e+00    3.11e+02   1.02e+01   1.00e+00  1.11e+06        1    2.41e-01    1.60e+00
   6  1.803390e+04    1.85e-03    2.12e+00   4.60e-01   1.01e+00  3.32e+06        1    2.41e-01    1.84e+00
   7  1.803390e+04    1.01e-06    9.61e-02   8.99e-03   1.03e+00  9.95e+06        1    2.39e-01    2.08e+00
   8  1.803390e+04    1.62e-09    4.97e-03   1.98e-04   1.05e+00  2.98e+07        1    2.40e-01    2.32e+00

結果如下:

 

 

參考鏈接

http://www.ceres-solver.org/index.html

[ceres-solver] AutoDiff

Ceres(一)自動求導(AutomaticDiff

SLAM十四講 解決第10num_linear_solver_threads找不到的問題

 ceres-solver無腦搞定協方差小技巧

 SuiteSparse庫與CXsparse庫

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM