上一篇文章从0开始学习Ceres NO.1中学了用ceres自动求解一个简单的最小化问题。对于同样的问题,这篇文章介绍两个事情:
如果不用模板呢?
在某些情况下我们可能无法定义残差的模板,那么我们不得不确定地给出残差的形式。(具体什么情况我现在也不知道,碰到了再回来补充吧) 那么我们定义残差的结构体就从模板变成了:
struct NumericDiffCostFunctor{
bool operator()(const double* const x, double* residual) const{
residual[0] = 10.0 -x[0];
return true;
}
};
主函数与原来基本相同,只是CostFunction类的定义变成了,这里我们将求导的方式从自动求导转变成了数值微分求导:
ceres::CostFunction* cost_function =
new ceres::NumericDiffCostFunction<NumericDiffCostFunctor, ceres::CENTRAL, 1, 1>(new NumericDiffCostFunctor);
ceres::NumericDiffCostFunction 用来表示接受非模板结构体使用数值微分求导的Cost函数类,跟AutoDiffCostFunction一样都是SizedCostFunction的子类。模板参数列表为
template <typename CostFunctor,
NumericDiffMethodType method = CENTRAL,
int kNumResiduals = 0,
int... Ns>
主函数为
int main(){
double initial_x=5.0;
double x = initial_x;
ceres::Problem problem;
ceres::CostFunction* cost_function =
new ceres::NumericDiffCostFunction<NumericDiffCostFunctor, ceres::CENTRAL, 1, 1>(new NumericDiffCostFunctor);
problem.AddResidualBlock(cost_function, nullptr, &x);
ceres::Solver::Options options;
options.minimizer_progress_to_stdout = true;
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
cout << summary.BriefReport() << endl;
cout << "x: " << initial_x << "-> " << x << endl;
return 0;
}
如果自己定义求导呢?
在某些情况下使用自动求导是不现实的(具体什么情况下以后再补充),那么我们可以直接继承SizedCostFunction自己编写CostFunction类,如下
class QuadraticCostFunction: public ceres::SizedCostFunction<1, 1>{
public:
virtual ~QuadraticCostFunction(){}
virtual bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const{
const double x = parameters[0][0];
residuals[0] = 10-x;
if(jacobians != nullptr && jacobians[0] != nullptr){
jacobians[0][0] = -1;
}
return true;
}
};
在这种简单问题下,我们对CostFunction的定义只有一个任务,即重写Evaluate函数。 Evaluate CostFunction类中计算残差和雅克比矩阵的函数,输入为:parameters参与计算的参数、residuals返回的残差容器、jacobians返回的雅克比容器。当jacobians是nullptr时不用计算残差,否则需要给出雅克比计算的定义,jacobians每一块是residuals.size()*parameters[i].size()(即残差块对第i个参数块的求导),表示每个参数对残差的求导。当jacobians[i]是nullptr时不用计算该行雅克比,在第i个参数被固定不优化时会出现这种情况。 雅克比计算如下:
j
a
c
o
b
i
a
n
s
[
i
]
[
r
?
p
a
r
a
m
e
t
e
r
s
[
i
]
.
s
i
z
e
(
)
+
c
]
=
δ
r
e
s
i
d
u
a
l
[
r
]
δ
p
a
r
a
m
e
t
e
r
s
[
i
]
[
c
]
jacobians[i][r*parameters[i].size()+c]=\frac{\delta residual[r]}{\delta parameters[i][c]}
jacobians[i][r?parameters[i].size()+c]=δparameters[i][c]δresidual[r]? 主函数为
int main(){
double initial_x=5.0;
double x = initial_x;
ceres::Problem problem;
ceres::CostFunction* cost_function = new QuadraticCostFunction;
problem.AddResidualBlock(cost_function, nullptr, &x);
ceres::Solver::Options options;
options.minimizer_progress_to_stdout = true;
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
cout << summary.BriefReport() << endl;
cout << "x: " << initial_x << "-> " << x << endl;
return 0;
}
总结
本文介绍了如何不使用模板来构造微分求导以及如何自己定义求导构造CostFunction
ceres建议使用自动求导(automatic differentiation) 而不是 数值微分求导(numeric differentiation),因为使用模板类的自动求导效率更高,且数值微分求导容易出现数值误差,导致收敛速度慢 ceres还建议除非你必须使用自定义求导,否则还是使用上面两种方式吧
文章的源程序可以去我的github下,CeresLearning中tag名字为Ceres_Turotial_02的文件
文章列表
①Ceres安装和卸载 ubuntu18.04 ②从0开始学习Ceres NO.1 ?从0开始学习Ceres NO.2 ④从0开始学习Ceres NO.3
|