Ceres優化


  Ceres Solver是谷歌2010就開始用於解決優化問題的C++庫,2014年開源.在Google地圖,Tango項目,以及著名的SLAM系統OKVIS和Cartographer的優化模塊中均使用了Ceres Solver.

  有關為何SLAM問題可以建模為最小二乘問題,進而使用最優化方法來求解,可以理解這一段話:

  • Maximum likelihood estimation (MLE) is a well-known estimation method used in many robotic and computer vision applications. Under Gaussian assumption, the MLE converts to a nonlinear least squares (NLS) problem. Efficient solutions to NLS exist and they are based on iteratively solving sparse linear systems until convergence. 

  在SLAM領域優化問題還可以使用g2o來求解.不過Ceres提供了自動求導功能,雖然是數值求導,但可以避免復雜的雅克比計算,目前來看Ceres相對於g2o的缺點僅僅是依賴的庫多一些(g2o僅依賴Eigen).但是提供了可以直接對數據進行操作的能力,相對於g2o應用在視覺SLAM中,更加傾向於通用的數值優化,最重要的是提供的官方資料比較全(看g2o簡直受罪...).詳細的介紹可以參考google的文檔:http://ceres-solver.org/features.html

  優化問題的本質是調整優化變量,使得優化變量建模得出的估計值不斷接近觀測數據(使得目標函數下降),是最大似然框架下對優化變量的不斷調整,得到優化變量建模得出的估計值在觀測條件下的無偏估計過程.

  這里已知的是固定的觀測數據z,以及需要調整的初始估計值x0.通常會建模成觀測數據和估計值之間的最小二乘問題(但並不一定是最好的建模方式):

   $\mathop{\arg\min}_{x_{i,j}} \ \ \frac{1}{2}\sum_{i=1}^{m}\sum_{j=1}^{n}\left \|z_{i,j}-h(x{_{i,j}})) \right \| ^{2}$

一些概念:

  • ObjectiveFunction:目標函數;
  • ResidualBlock:殘差(代價函數的二范數,有時不加區分),多個ResidualBlock組成完整的目標函數;
  • CostFunction:代價函數,觀測數據與估計值的差,觀測數據就是傳感器獲取的數據,估計值是使用別的方法獲取(例如初始化,ICP,PnP或者勻速模型...)的從優化變量通過建模得出的觀測值;例如從對極幾何得到的相機位姿,三角化得到的地圖點可以作為優化變量的初始值,但是需要利用坐標系變換和相機模型轉化為2D平面上的像素坐標估計值,與實際測量得到的觀測值之間構建最小二乘問題;
  • ParameterBlock:優化變量;
  • LossFunction:核函數,用來減小Outlier的影響,對應g2o中的edge->setRobustKernel()

求解步驟:

以尋找y=(10-x)的最小值為例

1.定義一個Functor(擬函數/函數對象)類,其中定義的是CostFunction. 需要重載函數調用運算符,從而可以像使用函數一樣使用該類對象.(與普通函數相比,能夠在類中存儲狀態,更加靈活)

  operator()的形參,前面幾個對應 problem.AddResidualBlock(cost_function, NULL, &x);中最后一部分優化變量,最后一個對應殘差

struct CostFunctor {
   template <typename T>
   bool operator()(const T* const x, T* residual) const {
     residual[0] = T(10.0) - x[0];
     return true;
   }
};

這里模板參數T通常為double,在需要求residual的雅克比時,T=Jet

 

2. 建立非線性最小二乘問題,並使用Ceres Solver求解

int main(int argc, char** argv) {
  google::InitGoogleLogging(argv[0]);

  // The variable to solve for with its initial value.
  double initial_x = 5.0;
  double x = initial_x;

  // Build the problem.
  Problem problem;

  // Set up the only cost function (also known as residual). This uses
  // auto-differentiation to obtain the derivative (jacobian).
  CostFunction* cost_function =
      new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
  problem.AddResidualBlock(cost_function, NULL, &x);

  // Run the solver!
  Solver::Options options;
  options.linear_solver_type = ceres::DENSE_QR;
  options.minimizer_progress_to_stdout = true;
  Solver::Summary summary;
  Solve(options, &problem, &summary);

  std::cout << summary.BriefReport() << "\n";
  std::cout << "x : " << initial_x
            << " -> " << x << "\n";
  return 0;
}

 

3. 參數選擇

  在做Bundle Adjustment過程中,建立好優化問題后,需要對優化求解器進行一些參數設置:

Solver::Options options;
options.gradient_tolerance = 1e-16;
options.function_tolerance = 1e-16;
...
  • 梯度閾值 gradient_tolerance.
  • 相鄰兩次迭代之間目標函數之差 function_tolerance.
  • 梯度下降策略 trust_region_strategy 可選levenberg_marquardt,dogleg.
  • 線性增量方程 HΔx=g 求解方法 linear_solver 可選sparse_schur,dense_schur,sparse_normal_cholesky,視覺SLAM中主要采用稀疏Schur Elimination/ Marginalization的方法(也就是消元法),將地圖點的增量邊緣化,先求出相機位姿的增量,可以極大地較少計算量,避免H矩陣直接求逆.
  • 稀疏線性代數庫 sparse_linear_algebra_library 可選suite_sparse,cx_sparse(ceres編譯時需額外編譯),cx_sparse相對suite_sparse,更精簡速度較慢,但是不依賴BLAS和LAPACK.這個通常選擇suite_sparse即可.
  • 稠密線性代數庫 dense_linear_algebra_library 可選eigen,lapack.
  • 邊緣化次序 ParameterBlockOrdering 設置那些優化變量在求解增量方程時優先被邊緣化,一般會將較多的地圖點先邊緣化,不設置ceres會自動決定邊緣化次序,這在SLAM里面常用於指定Sliding Window的范圍.
  • 多線程 這里的設置根據運行平台會有較大不同,對計算速度的影響也是最多的.分為計算雅克比時的線程數num_threads,以及求解線性增量方程時的線程數num_linear_solver_threads.
  • 迭代次數 max_num_iterations,有時迭代多次均不能收斂,可能是初值不理想或者陷入了平坦的區域等等原因,需要設定一個最大迭代次數.

總結:

設置options細節較多,可以參考官方文檔去設定: 

  http://ceres-solver.org/nnls_solving.html#solver-options

通過實驗發現除了多線程以及linear_solver_type,別的對優化性能和結果影響不是很大:

  實驗代碼在:https://github.com/xushangnjlh/xushang_SLAM;

對於93個相機位姿 61203個地圖點 287451個觀測數據的SFM優化問題:

  測試數據集http://grail.cs.washington.edu/projects/bal/final.html

邊緣化的影響:

  • 邊緣化所有地圖點Total Time: 3.6005s 其中Linear solver:1.8905s
  • 邊緣化2/3地圖點Total Time: 5.4723s 其中Linear solver:3.7504s
  • 邊緣化1/3地圖點Total Time: 7.0223s 其中Linear solver:5.3335s

線程影響(在四核CPU上測試):

  • num_threads=1 Total Time:5.9113s
  • num_threads=2 Total Time:4.2645s
  • num_threads=4 Total Time:3.5785s
  • num_threads=8 Total Time:3.6472s

運行結果截圖:

 


免責聲明!

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



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