在工程應用中,最後要求解的線性方程組往往是原來的殘差模型進行線性化後的誤差方程。通常情況下,模型的線性化由人工完成,而求解誤差方程則藉助Eigen等矩陣運算庫(參考1)來實現。現在,我們有了另一種選擇,那就是ceres solver。ceres是Google公司用來解決非線性優化問題的開源庫,主要是爲了解決SFM問題中的光束法平差而設計的。與一般的矩陣運算庫不同的是,我們只需要給ceres提供原始的殘差模型就可以了,而無需自己求偏導數。ceres solver的安裝和嚮導可以在下面給出的參考2的鏈接中找到,一個寫的很好的中文用例可以在參考3的連接中找到。參考3給出的更多的是對參考2的翻譯,最後也給出了一個空間後方交會的例子,但是未知數的數量只有6個,屬於比較簡單的例子。本文將結合實際應用,給出一個大規模最小二乘問題的求解實例。
現在有如上所示的一個能量函數,這個函數的第一項是觀測值約束,第二項是未知數的平滑約束,這是一個工程領域十分常見的能量函數模型。我們用這個模型是要解決,利用一幅圖像中稀疏的深度觀測值來估計出一個密集的深度圖(圖1)。因此,對於一幅圖像而言,每個像素對應一個未知數,比如對於1000*1000的一幅圖像將會有100W個未知數。公式1是個非線性方程,但是求解這個問題卻可以通過求偏導轉換爲一個線性方程組,具備確定解。但是由於未知數個數很多,所以利用QR分解等直接法不可能求解。好在ceres提供了豐富的選擇可解決這個大規模的稀疏矩陣的求解問題(詳見參考2)。
圖 1. 左邊爲稀疏深度圖,右邊爲密集深度圖
下面,我們只闡述ceres解決該方程的具體方法,在此之前,要說的是公式1中第二項中的二階偏導可以表示爲離散形式:
這樣的話,就可以直接利用ceres直接構建觀測方程並求解了。
struct CostFunctor1 {
CostFunctor1(double observed_depth):observed_depth(observed_depth)
{}
template <typename T> bool operator()(const T* const depthmap, T* residual) const {
residual[0] = depthmap[0] - observed_depth;
return true;
}
static ceres::CostFunction* Create(const double observed_depth) {
return (new ceres::AutoDiffCostFunction<CostFunctor1, 1, 1>(
new CostFunctor1(observed_depth)));
}
double observed_depth;
};
struct CostFunctor2 {
CostFunctor2(double observed_w):observed_w(observed_w)
{}
template <typename T> bool operator()(const T* const depthleft, const T* const depth,
const T* const depthright,T* residual) const {
residual[0] = T(sqrt(observed_w*50.0))*(T(2.0)*depth[0] - depthleft[0] - depthright[0]);
return true;
}
static ceres::CostFunction* Create(const double observed_w) {
return (new ceres::AutoDiffCostFunction<CostFunctor2, 1, 1, 1, 1>(
new CostFunctor2(observed_w)));
}
double observed_w;
};
ceres::Problem problem;
for (int i=0;i<height;i++)
{
for (int j=0;j<width;j++)
{
if (depthArray[i*width+j] != 0)
{
ceres::CostFunction* cost_function = CostFunctor1::Create(depthArray[i*width+j]);
problem.AddResidualBlock(cost_function,NULL,&densedepthmap[i*width+j]);
}
}
}
for (int i=0;i<height;i++)
{
for (int j=1;j<width-1;j++)
{
ceres::CostFunction* cost_function = CostFunctor2::Create(weightArrayX[i*width+j]);
problem.AddResidualBlock(cost_function,NULL,
&densedepthmap[i*width+j-1],
&densedepthmap[i*width+j],
&densedepthmap[i*width+j+1]);
}
}
for (int i=1;i<height-1;i++)
{
for (int j=0;j<width;j++)
{
ceres::CostFunction* cost_function = CostFunctor2::Create(weightArrayY[i*width+j]);
problem.AddResidualBlock(cost_function,NULL,
&densedepthmap[(i-1)*width+j],
&densedepthmap[i*width+j],
&densedepthmap[(i+1)*width+j]);
}
}
ceres::Solver::Options options;
options.linear_solver_type = ceres::CGNR;
options.num_linear_solver_threads = 6;
options.function_tolerance = 0.1;
options.minimizer_progress_to_stdout = true;
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
std::cout << summary.BriefReport() << "\n";
參考1:(C++矩陣運算庫推薦)http://blog.csdn.net/chenbang110/article/details/12304123
參考2:(Tutorial-Ceres Solver)http://ceres-solver.org/tutorial.html
參考3:(李民錄csdn博客)http://blog.csdn.net/liminlu0314/article/details/15860677