本文作為《徹底搞懂視覺-慣性SLAM:VINS-Fusion原理精講與源碼剖析》課程補(bǔ)充材料在使用Ceres-Solver進(jìn)行解析求導(dǎo)時,需要繼承CostFunction類,并重寫virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const函數(shù),以來在殘差項計算的同時給出對應(yīng)的雅克比矩陣。顯而易見,在構(gòu)建殘差項的時候,我們通過AddResidualBlock(...)函數(shù)將上述構(gòu)建的CostFunction傳入ceres::Problem中。那么,上述重寫的bool Evaluate(...)函數(shù)在何時被調(diào)用呢?追溯、閱讀這個過程,會讓我們對非線性優(yōu)化問題的求解、以及函數(shù)實現(xiàn)方面有更深入的理解。下面以Ceres-Solver的1.14.0版本源碼(源碼鏈接:https://github.com/ceres-solver/ceres-solver/releases/tag/1.14.0)為例,給出用Ceres做優(yōu)化、求解問題時,其調(diào)用我們所實現(xiàn)的virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const函數(shù)的過程。1.internal/ceres/solver.cc文件
ceres::Solve(options, &problem, &summary);該函數(shù)的實現(xiàn)在文件internal/ceres/solver.cc的631行中。簡單可以看到,它又調(diào)用了Solver::Solve(...)函數(shù)。void Solver::Solve(const Solver::Options& options, Problem* problem, Solver::Summary* summary);
在Solver::Solve(...)函數(shù)中,首先對問題進(jìn)行了預(yù)處理。然后在代碼的第602行,調(diào)用了Minimize(...)函數(shù)。void Minimize(internal::PreprocessedProblem* pp, Solver::Summary* summary);
Minimize(...)函數(shù)根據(jù)求解配置中的選擇,選擇不同類型的優(yōu)化器。在代碼464行minimizer->Minimize(...)(基類的虛函數(shù),所以去找子類的實現(xiàn))時調(diào)用所選擇的求解器進(jìn)行求解。求解器以的實現(xiàn)主要有三種,下文以Levenberg-Marquadt法所屬的TrustRegionMinimizer求解器為例。2. internal/ceres/trust_region_minimizer.h文件
TrustRegionMinimizer在internal/ceres/trust_region_minimizer.h文件中重寫實現(xiàn)的方法Minimize(...):void TrustRegionMinimizer::Minimize(const Minimizer::Options& options, double* parameters, Solver::Summary* solver_summary)
第113行調(diào)用HandleSuccessfulStep()函數(shù)。在HandleSuccessfulStep()函數(shù)的實現(xiàn)代碼777行中,調(diào)用了EvaluateGradientAndJacobian(...)函數(shù)。EvaluateGradientAndJacobian(...)這個函數(shù)是一個重要的函數(shù),就是在這里算的一系列東西,我們看源代碼的注釋便可知。internal/ceres/trust_region_minimizer.cc文件第214行開始:bool TrustRegionMinimizer::EvaluateGradientAndJacobian( bool new_evaluation_point) {...}
EvaluateGradientAndJacobian(...)的實現(xiàn)中,在代碼230行調(diào)用了evaluator_->Evaluate(...)。這又是一個基類的虛函數(shù)。我們看ProgramEvaluator類的實現(xiàn),在internal/ceres/program_evaluator.h文件中。3.internal/ceres/program_evaluator.h文件
ProgramEvaluator類所實現(xiàn)的Evaluate(...)函數(shù)中,internal/ceres/program_evaluator.h文件第254行,我們看到調(diào)用了residual_block->Evaluate(...),這里是不是有些眼熟了。我們就是將自己所實現(xiàn)的CostFunction類添加到了殘差塊ResidualBlock中去了。所以接下來我們看residual_block->Evaluate(...)的實現(xiàn)。4.internal/ceres/residual_block.cc文件
residual_block->Evaluate(...)的實現(xiàn)在文件internal/ceres/residual_block.cc中,自第68行開始。bool ResidualBlock::Evaluate(const bool apply_loss_function, double* cost, double* residuals, double** jacobians, double* scratch) const {...}
在實現(xiàn)代碼里的第111行,我們發(fā)現(xiàn)調(diào)用了我們所實現(xiàn)的virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const函數(shù):if (!cost_function_->Evaluate(parameters.get(), residuals, eval_jacobians)) { return false; }
在這里調(diào)用,實現(xiàn)了待優(yōu)化變量的傳入,并計算出了殘差項與雅克比矩陣。至此,我們追溯了我們所實現(xiàn)的virtual bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const函數(shù)是如何在Ceres-Solver做優(yōu)化時被調(diào)用的。