1. 安装问题
1.1 Ceres安装
- 安装依赖项
sudo apt-get install liblapack-dev libsuitesparse-dev libcxsparse3.1.2 libgflags-dev libgoogle-glog-dev libgtest-dev
- 进入Ceres目录,然后一顿常规操作
mkdir build
cd build
cmake ..
make -j8
sudo make install #important
最后一句sudo make install很重要,一定不能漏掉,否则无法在/usr/local/include/ceres下找到Ceres的头文件。
1.2 G2O安装
安装依赖项
sudo apt-get install libqt4-dev qt4-qmake libqglviewer-dev libsuitesparse-dev libcxsparse3 libcholmod3
有些博客上面的依赖项,安装的是libcholmod,会出现报错。那是因为找不到这样包,高博在第二版的书中写的是libcholmods。剩下的部分和ceres安装一样
2. Ceres拟合曲线
参考博客https://www.jianshu.com/p/e5b03cf22c80
2.1 三个求解步骤
- 第一步:构建cost function(代价函数),也就是定义残差块(误差项)的结构体。这一部分需要仿函数(Functor)来实现,在结构体内重载()运算符。这种方法可以使得Ceres像调用函数一样,对该类的某个对象(比如a)调用a<double>()方法。
- 第二步:通过代价函数构建待求解的优化问题,比如最小二乘问题。
- 第三步:配置求解器参数并求解问题,这个步骤就是设置方程怎么求解,求解过程是否输出等,然后调用一下solve方法。
2.2 构建代价函数结构体
这里的使用了仿函数的技巧,即在CostFunction结构体内,对()进行重载,这样的话,该结构体的一个实例就能具有类似一个函数的性质,在代码编写过程中就能当做一个函数一样来使用。
struct CURVE_FITTING_COST
{
CURVE_FITTING_COST ( double x, double y ) : _x ( x ), _y ( y ) {}
// 残差的计算
template <typename T>
//重载()运算符,输入待寻优的参数abc,输出残差
bool operator() (
const T* const abc, // 模型参数,有3维,模板类型为T的常量指针
T* residual ) const // 残差
{
residual[0] = T ( _y ) - ceres::exp ( abc[0]*T ( _x ) *T ( _x ) + abc[1]*T ( _x ) + abc[2] ); // y-exp(ax^2+bx+c)
return true;
}
const double _x, _y; // x,y数据
};
关于仿函数,对结构体和类的一个实例,比如Myclass类中的一个实例Obj1,如果Myclass里对()进行了重载,那Obj1被创建以后,就可以将Obj1这个实例当做函数来使用,比如Obj(x)这样。下面的代码可以有助于理解。
//仿函数的示例代码
#include<iostream>
using namespace std;
class Myclass
{
public:
Myclass(int x):_x(x){};
int operator()(const int n)const{
return n*_x;
} //重载()运算符,在括号内输入n,得到n*x
private:
int _x;
};
int main()
{
Myclass Obj1(5);
cout<<Obj1(3)<<endl;
system("pause");
return 0;
} //输出15,因为第一个Obj1(5)中的5是构造函数的变量x,而第二个Obj1(3)中的3是重载运算符()里面的3,是一种运算!
了解了仿函数技巧的使用方法后,在回头看看ceres使用中构造CostFuction 的具体方法:CostFunction结构体中,对括号符号重载的函数中,参数有两个,一个是待优化的变量abc,另一个是残差residual,也就是代价函数的输出。
重载了()符号之后,CostFunction就可以传入AutoDiffCostFunction方法来构建寻优问题了。
2.3 通过代价函数构建最小二乘问题
// 构建最小二乘问题
ceres::Problem problem;
for ( int i=0; i<N; i++ )
{
problem.AddResidualBlock ( // 向问题中添加误差项
// 使用自动求导,模板参数:误差类型,输出维度(abc),输入维度(残差),维数要与前面struct中一致
new ceres::AutoDiffCostFunction<CURVE_FITTING_COST, 1, 3> (
new CURVE_FITTING_COST ( x_data[i], y_data[i] )
),
nullptr, // 核函数,这里不使用,为空
abc // 待估计参数
);
}
构建最小二乘问题时,需要将所有误差项依次添加进去。而每个误差项又是由前面定义的结构体构成的。需要注意的是,每个误差项需要指定代价函数求导的方法,有三种方法可供选择:自动求导、数值求导和自行推导。一般采用自动求导,既方便,又节约运行时时间。
以自动求导为例,ceres::AutoDiffCostFunction是个模板类,后两个模板参数为输出维度和输入维度,必须与前面定义的结构体中的residual和abc的维度一样。
2.4 配置并求解问题
// 配置求解器
ceres::Solver::Options options; // 这里有很多配置项可以填
options.linear_solver_type = ceres::DENSE_QR; // 增量方程如何求解
options.minimizer_progress_to_stdout = true; // 输出到cout
ceres::Solver::Summary summary; // 优化信息
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
ceres::Solve ( options, &problem, &summary ); // 开始优化
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>( t2-t1 );
cout<<"solve time cost = "<<time_used.count()<<" seconds. "<<endl;
// 输出结果
cout<<summary.BriefReport() <<endl;
cout<<"estimated a,b,c = ";
for ( auto a:abc ) cout<<a<<" ";
cout<<endl;
使用ceres::Solver::Options配置求解器。这个类有许多字段,每个字段都提供了一些枚举值供用户选择。所以需要时只要查一查文档就知道怎么设置了。
’