头文件只有一个"ceres/ceres.h"
调试几个经典例子的过程1 2 3 4 5 6 7 #include "ceres/ceres.h" using ceres::AutoDiffCostFunction;using ceres::CostFunction;using ceres::Problem;using ceres::Solver;using ceres::Solve;
1 2 3 4 5 6 7 8 9 10 11 struct Functor { template <typename T> bool operator () (const T* const x, T* residual) const { residual[0 ] = 10.0 - x[0 ]; residual[1 ] = 3.0 - x[1 ]; return true ; } };
这里的变量为二维的,目标函数为10.0-x
和3.0-x
如果这里不写10.0而是10,就会报错。 Ceres中没有int,只接受double
对于residual[1] = T(0.0);
的情况,如果不加T
,编译就会报错 error: no match for ‘operator=’ (operand types are ‘ceres::Jet’ and ‘double’) . Jet类型是ceres内置类型,我们要把double转成Jet类型,也就是T(0.0)
1 2 3 4 5 6 7 8 struct TestLocalParam { template <typename T> bool operator () (const T* x, const T* delta, T* x_plus_delta) const { x_plus_delta[0 ] = x[0 ] + 0.4 * delta[0 ]; x_plus_delta[1 ] = x[1 ] + 0.4 * delta[1 ]; return true ; } };
CostFunctor
的operator
就是计算残差,不带平方项,平方是ceres自动添加,相当于最小二乘的函数 f
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 double x[] = {0.5 , 2.0 };Problem problem; problem.AddParameterBlock (x, 2 , (new ceres::AutoDiffLocalParameterization <TestLocalParam, 2 , 1 > ()) ); CostFunction* cost_func = new AutoDiffCostFunction<Functor, 2 , 2 >(new Functor); problem.AddResidualBlock (cost_func, nullptr , x);
创建最小二乘问题,可以使用Problem::AddResidualBlock()
和Problem::AddParameterBlock()
。 可以使用Problem::AddParameterBlock()
显式添加参数块,这将导致额外的正确性检查; 然而,如果参数块不存在,Problem::AddResidualBlock()
会隐式添加参数块。
AddParameterBlock()
显式地向Problem添加了一个参数块。它还允许用户将LocalParameterization对象与参数块关联起来。
AddResidualBlock
默认会先调用AddParameterBlock
,一般不用后者。
损失函数LossFunction
,给异常值做限定,如果是nullptr,该项的代价就是残差的平方范数
代价函数携带关于它所期望的参数块大小的信息。函数检查这些参数是否与parameter_blocks中列出的参数块的大小匹配。如果检测到不匹配,程序将中止。
Solver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Solver::Options option; option.minimizer_progress_to_stdout = true ; option.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; Solver::Summary summary; Solve (option, &problem, &summary); std::cout << summary.BriefReport () << std::endl<<std::endl; std::cout <<"x[0]: " << x[0 ] <<" x[1]: " << x[1 ]<< std::endl; return 0 ; }
优先选用自动微分算法,某些情况可能需要用到解析微分算法,尽量避免数值微分算法。
曲线拟合 使用十四讲第六章的曲线拟合的例子,已知 N 个数据 ,它们符合曲线 ,通过曲线拟合求 a, b, c
跟上面的例子不同,这次目标函数为 , 残差维度为3(未知a,b,c)
CMake配置如下1 2 3 4 5 6 7 8 find_package(OpenCV REQUIRED) INCLUDE_DIRECTORIES(${OpenCV_DIRS}) target_link_libraries(node_2 ${catkin_LIBRARIES} ${CERES_LIBRARIES} ${OpenCV_LIBS} )
先生成带有高斯噪声的数据1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <opencv2/opencv.hpp> double a =1.0 , b=2.0 , c=1.0 ;double abc[3 ] = {0 ,0 ,0 };vector<double > x_data, y_data; int N = 240 ; double w_sigma = 1.0 ; cv::RNG rng; for (unsigned int i =0 ; i<N; i++){ double x = i/100.0 ; x_data.push_back (x); y_data.push_back (exp (a*x*x + b*x + c) + rng.gaussian (w_sigma) ); }
高斯噪声的产生: cv::RNG rng; rng.gaussian(w_sigma)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct Functor { Functor (double x, double y): m_x (x), m_y (y) { } #if 0 template <typename T> bool operator () ( const T* const a, const T* const b, const T* const c, T* residual) const { residual[0 ] = m_y - exp (a[0 ] * m_x * m_x + b[0 ] * m_x + c[0 ]); return true ; } #endif template <typename T> bool operator () ( const T* const abc, T* residual) const { residual[0 ] = m_y - exp (abc[0 ] * m_x * m_x + abc[1 ] * m_x + abc[2 ]); return true ; } const double m_x, m_y; };
因为自变量x是一维的,而不是向量,所以残差只计算一维 residual[0]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Problem problem; for (unsigned int i=0 ; i<N; i++){ CostFunction* cost_func = new AutoDiffCostFunction<Functor,1 ,3 >(new Functor (x_data[i], y_data[i]) ); problem.AddResidualBlock (cost_func, nullptr , abc); } Solver::Options option; option.minimizer_progress_to_stdout = true ; option.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; Solver::Summary summary; Solve (option, &problem, &summary);cout << summary.BriefReport () << endl <<endl; cout << "a: " << abc[0 ] <<" b: " << abc[1 ]<< " c: " << abc[2 ] <<endl;
参考:Curve Fitting(曲线拟合) Problem类 资料