文章目录
一、Ceres Solver 介绍
Ceres-solver 是由Google开发的开源C++库,用于解决具有边界约束的非线性最小二乘优化和一般无约束优化问题,成熟、功能丰富、高性能。与一般优化问题不同的是,非线性最小二乘优化问题的目标函数具有明确的物理意义——残差。
具有边界约束的非线性最小二乘鲁棒优化问题形式如下:
min x 1 2 ∑ i ρ i ( ∥ f i ( x i 1 , ⋯ , x i k ) ∥ 2 ) , l j ≤ x j ≤ u j \min _x \frac{1}{2} \sum_i \rho_i\left(\left\|f_i\left(x_{i 1}, \cdots, x_{i k}\right)\right\|^2\right), l_j \leq x_j \leq u_j xmin21i∑ρi(∥fi(xi1,⋯,xik)∥2),lj≤xj≤uj
- ( x i 1 , ⋯ , x i k ) (x_{i1},⋯,x_{ik}) (xi1,⋯,xik)在Ceres中被称为参数块(
ParameterBlock
),通常是几组标量的集合,例如,相机的位姿可以定义成是一组包含3个参数的平移向量(用于描述相机的位置),和包含4个参数的四元数(用于描述相机姿态),当然,参数块也可以只有一个参数, l j l_j lj和 u j u_j uj是参数块中对应每个参数的边界; - f i ( ⋅ ) f_i(\cdot) fi(⋅) 在Ceres中被称为代价函数(
CostFuntion
),是关于参数块的函数,在一个优化问题中,可能会存在多个代价函数; - ρ i ( ⋅ ) \rho_i(\cdot) ρi(⋅)在Ceres中被称为损失函数(
LossFuntion
),是一个标量函数,将代价函数计算出的值映射到另一个区间中的值,用于减少异常值或外点(outliers
)对非线性最小二乘优化问题的影响,作用有点类似于机器学习中的激活函数,例如,直线拟合时,对于距离直线非常远的点,应当减少它的权重,损失函数并非是必须的,可以为空(NULL
),此时,损失函数值等同于代价函数计算值,即 ρ i ( t ) = t \rho_i(t)=t ρi(t)=t;
当损失函数为空,且参数没有边界时,就是我们熟悉的非线性最小二乘问题,如下:
min x 1 2 ∑ i ( ∥ f i ( x i 1 , ⋯ , x i k ) ∥ 2 ) , l j = − ∞ u j = ∞ \min _x \frac{1}{2} \sum_i \left(\left\|f_i\left(x_{i 1}, \cdots, x_{i k}\right)\right\|^2\right),\qquad l_j =-\infty \quad u_j =\infty xmin21i∑(∥fi(xi1,⋯,xik)∥2),lj=−∞uj=∞
一般情况下,最小二乘问题与鲁棒最小二乘问题的区别在于鲁棒最小二乘会指定损失函数,具体效果在后续的有关曲线拟合的学习笔记中会有所体现。
- ρ i ( ∥ f i ( x i 1 , ⋯ , x i k ) ∥ 2 ) \rho_i\left(\left\|f_i\left(x_{i 1}, \cdots, x_{i k}\right)\right\|^2\right) ρi(∥fi(xi1,⋯,xik)∥2)在Ceres中被称为残差块(
ResidualBlock
),残差块中包含了参数块、代价函数、损失函数,因此,在添加残差块时,必须指定参数集合、代价函数,视具体情况是否指定损失函数。
统计学中的曲线拟合、计算机视觉中的相机标定、视觉SLAM中的地图生成等问题都可以描述成以上形式。
二、Ceres 使用基本步骤
Ceres 求解过程主要有两大步骤,构建最小二乘问题和求解最小二乘问题,具体步骤如下:
1. 构建最小二乘问题
- 用户自定义残差计算模型,可能存在多个;
- 构建Ceres代价函数(
CostFuntion
),将用户自定义残差计算模型添加至CostFuntion
,可能存在多个CostFuntion
,为每个CostFuntion
添加用户自定义残差计算模型,并指定用户自定义残差计算模型的导数计算方法; - 构建Ceres问题(
Problem
),并在Problem
中添加残差块(ResidualBlock
),可能存在多个ResidualBlock
,为每个ResidualBlock
指定CostFuntion
,LossFuntion
以及参数块(ParameterBlock
);
2. 求解最小二乘问题
- 配置求解器参数Options,即设置Problem求解方法及参数。例如迭代次数、步长等等;
- 输出日志内容Summary;
- 优化求解Solve。
三、使用案例
Ceres 安装 参考之前的 Ubuntu20.04安装VINS_Mono 和 VINS_Fusion 。
1. Ceres Helloworld
以求解如下函数的最小值为例:
f ( x ) = 1 2 ( 10 − x ) 2 f(x)=\frac{1}{2}(10-x)^2 f(x)=21(10−x)2
对于求解该函数的最小值问题,可以构建成一个非常简单的优化问题,虽然一眼就能看出 x = 10时函数能够获取最小值,但以此为例,可以说明使用 Ceres解决一般优化问题或者非线性最小二乘问题的基本步骤。
1.1 用户自定义残差计算模型
// 用户自定义残差计算模型
struct MyCostFunctorAutoDiff
{
// 模板函数
template<typename Type>
bool operator()(const Type* const x, Type* residual) const
{
// 输入参数x和输出参数residual都只有1维
residual[0] = 10.0 - x[0];
return true;
}
};
注意: operator()
是一个模板函数,输入和输出的参数类型都是Type
类型,当仅需要获得残差值作为输出时,Ceres在调用MyCostFunctorAutoDiff::operator<Type>()
时可以指定Type的类型为double,当需要获得Jacobians值(微分或导数)作为输出时,Ceres在调用MyCostFunctorAutoDiff::operator<Type>()
时可以指定Type
的类型为Jet
。关于operator()
仿函数参考 c++仿函数 functor。
1.2 构建Ceres代价函数CostFuntion
// 构建Ceres代价函数CostFuntion,用来计算残差,残差计算方法为用户自定义残差计算模型MyCostFunctorAutoDiff
// 只存在一个代价函数,使用自动微分方法AutoDiffCostFunction来计算导数
// AutoDiffCostFunction<MyCostFunctorAutoDiff, 1, 1>模板参数中,需要依次指定
// 用户自定义残差计算模型MyCostFunctorAutoDiff、输出(resudual)维度大小、输入(参数x)维度大小
// 这两个维度大小需要与残差计算模型中输入、输出参数的维度一致,对应residual[0]和x[0]
ceres::CostFunction* cost_function =
new ceres:: AutoDiffCostFunction<MyCostFunctorAutoDiff, /* 用户自定义残差计算模型 */\
1, /* 输出(resudual)维度大小 */\
1 /* 输入(参数x)维度大小 */>(new MyCostFunctorAutoDiff);
说明:
- 只存在一个代价函数;
- 使用自动微分方法来计算导数;
AutoDiffCostFunction<MyCostFunctorAutoDiff, 1, 1>
模板参数中,需要依次指定用户自定义残差计算模型MyCostFunctorAutoDiff
、输出(resudual
)维度大小、输入(参数x
)维度大小,这两个维度大小需要与残差计算模型中输入、输出参数的维度一致,对应residual[0]
和x[0]
;
1.3 构建Ceres问题Problem
// 构建非线性最小二乘问题
ceres::Problem problem;
// 添加残差块,需要依次指定代价函数,损失函数,参数块
// 损失函数为单位函数
problem.AddResidualBlock(cost_function, nullptr, &x);
说明:
- 添加残差块
ResidualBlock
时,需要依次指定代价函数CostFunction
,损失函数LossFunction
(损失函数为单位函数),参数块ParameterBlock
; - 只添加一项残差块。