☰
Ceres Solver 特性全解析:SensorsCalibration 自动驾驶多传感器标定背后的非线性优化引擎
2026/10/16 6:07:54 网站建设 项目流程
  • 自动驾驶
  • 计算机视觉

【免费下载链接】SensorsCalibration

OpenCalib: A Multi-sensor Calibration Toolbox for Autonomous Driving

项目地址:https://gitcode.com/gh_mirrors/se/SensorsCalibration
点击查看免费下载

导读

Ceres Solver 是一个专为求解大规模非线性最小二乘问题而设计的开源 C++ 优化库,也是本仓库 SensorsCalibration(OpenCalib:自动驾驶多传感器标定工具箱)在相机内参标定、PNP 外参求解、单应矩阵精化、激光雷达-相机联合标定等多个模块中统一使用的优化后端。本文以仓库内随附的 Ceres 官方特性文档(features.rst)为主体骨架,结合仓库源码中真实调用方式,逐条拆解 Ceres 的建模 API、求解器选择、鲁棒损失函数、局部参数化、协方差估计等核心特性,并给出可直接复制的代码片段与参数配置,帮助读者理解"为什么标定代码里到处是ceres::Problem",以及如何为自己的优化任务选型。

一、代码质量:久经生产验证的工程沉淀

特性文档首先强调的并非算法本身,而是工程的可靠性。文档明确指出 Ceres Solver 曾在 Google 内部生产环境持续使用超过四年,代码干净、经过充分测试并有完善的文档支持,且持续活跃开发(详见 features.rst)。

从仓库内的实际依赖形态可以印证这一点:SensorsCalibration 在factory_calib、lidar2car、lidar2camera/joint_calib、lidar2imu/auto_calib、online_calib/lidar2imu、online_calib/vins_camera2imu等多个模块下都以3rdparty/ceres目录形式整包携带了 Ceres 源码,其中 version.h 标明随附版本为 Ceres 2.0.0。把核心优化器源码直接打入标定工程,避免了不同环境下的版本漂移问题——这也是标定结果可复现、可追溯的重要工程保障。

二、建模 API:逐项构建目标函数

非线性最小二乘问题的难点之一在于:用户很少一开始就能给出完整且精确的问题描述。Ceres 的建模 API 设计目标就是让用户能够"一项一项地"构建并修改目标函数,而不必担心这些修改会如何影响底层问题的稀疏结构。

2.1 Problem 与 ResidualBlock 的三步套路

从仓库源码看,使用 Ceres 建模永远是同一套三板斧:

  1. 创建ceres::Problem problem;
  2. 通过problem.AddResidualBlock(cost_function, loss_function, param1, param2, ...)逐条加入残差项;
  3. 通过problem.AddParameterBlock(param, size, local_parameterization)声明参数块的流形几何(可选)。

以 pnp_solver.cpp 中的外参精化为例,其流程是:先用 EPNP / 单应分解得到旋转矩阵与平移的初值,再将旋转转成四元数,然后交给 Ceres 做整体精化:

// refine extrinsic Eigen::Quaterniond rot_q(*R); rot_q.normalize(); double q_param[4] = {rot_q.w(), rot_q.x(), rot_q.y(), rot_q.z()}; double t_param[3] = {(*t)(0), (*t)(1), (*t)(2)}; ceres::LocalParameterization *q_parameterization = new ceres::EigenQuaternionParameterization(); ceres::Problem problem; problem.AddParameterBlock(q_param, 4, q_parameterization); // 四元数流形 problem.AddParameterBlock(t_param, 3); // 平移为普通欧氏空间 for (int i = 0; i < count; i++) { ceres::CostFunction *cost_function = CameraProjectFactor::Create(world_pt, img_pt, K); problem.AddResidualBlock(cost_function, NULL, q_param, t_param); } ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT; options.max_num_iterations = 30; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary);

这段代码是 Ceres 建模 API 的典型用法:残差项只关心"输入参数块 + 输出残差",至于参数块之间的耦合关系与雅可比矩阵的稀疏结构,全部由 Ceres 在内部统一管理。

2.2 求导方式:自动、数值与解析随意混搭

手工推导雅可比矩阵是使用优化库时最繁琐、最容易出错的一环。特性文档强调:Ceres 内置了自动微分(Automatic Differentiation)与数值微分(Numeric Differentiation),用户几乎不需要手写导数,而且可以把自动、数值与解析三种微分方式按任意组合混用在同一个问题里。

自动微分的实现原理见 autodiff_cost_function.h:用户只需编写一个模板化的operator()(functor),把模板参数T当作普通标量来写残差表达式,Ceres 在求导时会将T替换为 Jet 类型(见 jet.h)以同时计算函数值与导数,对用户完全透明。模板参数依次声明"残差维度、各参数块维度",例如:

// AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2> // ^residual维度 ^x维度 ^y维度 CostFunction* cost_function = new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>( new MyScalarCostFunctor(1.0));

仓库中的 pnp_solver.cpp 正是如此,残差为 3 维、旋转角轴与平移各 3 维:

static ceres::CostFunction* create(...) { return new ceres::AutoDiffCostFunction<YCostFunctorPnP, 3, 3, 3>( new YCostFunctorPnP(objpoint, lidarpoint, tra)); }

在 functor 内部,旋转部分直接调用ceres::AngleAxisRotatePoint(rot, point_in, point_out)(见 pnp_solver.cpp),这是 Ceres 内置的角轴旋转工具,同样支持 Jet 类型,因而可以无缝嵌入自动微分链路。可见在标定场景中,用户几乎只用写"残差长什么样",完全不用碰雅可比矩阵。

2.3 鲁棒损失函数:压制外点

凡是有数据的优化问题,就会有外点(outlier)。特性文档给出的建议是:残差用 LossFunction 来"塑形",降低外点对最终解的影响。其数学约定见 loss_function.h:残差项对总代价的贡献为1/2 * rho(s),其中s = ||residuals||²,rho即损失函数。

文档明确指出在关注区域(s < 3)内,常见损失函数的鲁棒化程度排序为:

TrivialLoss >= HuberLoss >= SoftLOneLoss >= CauchyLoss

其中核心几个的内置实现(见 loss_function.h):

损失函数数学形式特点
TrivialLossrho(s) = s等价于不设损失函数,等价于AddResidualBlock时传NULL
HuberLossrho(s) = s(s <= 1),rho(s) = 2*sqrt(s) - 1(s >= 1)经典鲁棒损失,小残差按二次、大残差按线性
SoftLOneLossrho(s) = 2*(sqrt(1 + s) - 1)类似 Huber 但处处光滑

所有损失函数都支持尺度参数a,通过s -> a² * rho(s / a²)的伸缩来调节"在多大残差处开始鲁棒化",且不改变s = 0附近的行为。在 pnp_solver.cpp 中可以看到被注释掉的候选写法ceres::LossFunction *loss_function = new ceres::HuberLoss(0.1);,说明该标定流程刻意保留了切换鲁棒损失的余地——当匹配点质量有保证时用普通二次损失,当特征点误匹配较多时启用 Huber 损失。官方文档同样建议:先从非鲁棒损失开始,只有在普通平方损失不收敛时才尝试鲁棒损失。

2.4 局部参数化:描述流形上的优化

很多参数并不生活在欧氏空间中,例如旋转矩阵/四元数。若把四元数的 4 个分量当作独立变量直接优化,会在优化中引入冗余自由度(零方向),导致数值不稳定。特性文档指出:此时用户应通过LocalParameterization对象声明参数所在流形的局部切空间几何。

其抽象定义见 local_parameterization.h:核心是Plus映射x_plus_delta = Plus(x, delta),delta是切空间中的增量,维度小于等于x。以四元数为例,delta为 3 维,Plus定义为四元数乘法:

Plus(x, delta) = [cos(|delta|), sin(|delta|) * delta / |delta|] * x

这样优化只在切空间的 3 个自由度上进行,同时保证四元数约束(模长恒为 1)自动满足。仓库 pnp_solver.cpp 中new ceres::EigenQuaternionParameterization()+AddParameterBlock(q_param, 4, q_parameterization)就是这一特性的直接应用,对应的流形实现位于 autodiff_local_parameterization.h。

三、求解器选择:信任域与线搜索两大体系

特性文档指出:不同问题在规模、稀疏结构、时间/内存预算与解质量要求上各不相同,没有一种算法通吃所有场景,因此 Ceres 提供了丰富的求解器家族(枚举定义见 types.h 的MinimizerType:TRUST_REGION与LINE_SEARCH两大类)。

3.1 信任域求解器(Trust Region Solvers)

Ceres 支持Levenberg-Marquardt、Powell 的 Dogleg与Subspace Dogleg三种信任域策略(TrustRegionStrategyType,见 types.h),默认即 LM 法。这些方法每一步的核心计算量都集中在一个线性系统求解上,为此 Ceres 配套了完整的线性求解器矩阵(LinearSolverType,见 types.h):

线性求解器适用问题底层依赖
DENSE_QR稠密小规模问题Eigen 或 LAPACK
DENSE_NORMAL_CHOLESKY稠密正规方程Eigen 或 LAPACK
DENSE_SCHUR具有二部图结构的稠密问题(如 bundle adjustment)Eigen 的稠密 Cholesky
SPARSE_NORMAL_CHOLESKY大规模稀疏问题SuiteSparse / CXSparse / Eigen 稀疏分解
SPARSE_SCHUR大规模稀疏 BA 问题CHOLMOD(SuiteSparse)
ITERATIVE_SCHUR超大规模 BA 问题Ceres 自研共轭梯度实现,可配多种预条件子
CGNR正规方程上的共轭梯度—

稠密后端可选 Eigen 或 LAPACK(DenseLinearAlgebraLibraryType,见 types.h),稀疏后端可选 SuiteSparse、CXSparse、Eigen 稀疏或 Accelerate(SparseLinearAlgebraLibraryType,见 types.h)。对于 bundle adjustment 类问题,Ceres 还提供基于 Schur 补的自定义稠密、稀疏与迭代线性求解器,并支持ParameterBlockOrdering指定变量消元顺序(见 solver.h)——在 BA 中把"路标点"与"相机位姿"分成两组消元可大幅提速。

3.2 线搜索求解器(Line Search Solvers)

当问题规模大到无法存储和分解雅可比矩阵、或者只需要低成本的低精度解时,可切换线搜索体系(MinimizerType::LINE_SEARCH)。Ceres 提供多种非线性共轭梯度变体(Fletcher-Reeves / Polak-Ribiere / Hestenes-Stiefel,见 types.h),以及BFGS与L-BFGS两类拟牛顿方法(LineSearchDirectionType,见 types.h),线搜索准则可选 Armijo 或 Wolfe(LineSearchType,见 types.h)。L-BFGS 只维护最近 M 步的参数与梯度观测窗口,无需构造稠密逆 Hessian 近似,因此特别适合参数规模庞大而内存受限的场景。

3.3 Solver::Options 关键默认值

solver.h 中定义了Solver::Options的全部默认值,了解这些默认值有助于判断"哪些参数必须显式设置":

选项默认值说明
minimizer_typeTRUST_REGION默认走信任域体系
trust_region_strategy_typeLEVENBERG_MARQUARDT默认 LM 策略(见 solver.h)
linear_solver_typeSPARSE_NORMAL_CHOLESKY(无稀疏库时DENSE_QR)见 solver.h
preconditioner_typeJACOBI迭代求解器预条件子,另有 SCHUR_JACOBI、CLUSTER_JACOBI 等(见 types.h)
max_num_iterations50最大迭代数(solver.h)
num_threads1Jacobian 求值与线性求解的线程数
function_tolerance1e-6代价相对下降的收敛阈值(solver.h)
gradient_tolerance1e-10梯度范数收敛阈值(solver.h)
parameter_tolerance1e-8参数步长收敛阈值(solver.h)
use_nonmonotonic_stepsfalse非单调信任域步长(允许代价短暂上升以"越过巨石")

仓库中标定代码的选型可作参照:外参精化(pnp_solver.cpp)在参数块少、雅可比稠密时选DENSE_QR+ LM,并把max_num_iterations收紧到 30;而单应矩阵精化(find_homography.cpp)则使用DENSE_SCHUR,同时通过solver_options.callbacks.push_back(&callback)注册TerminationCheckingCallback,在平均对称距离达到预期阈值时提前终止(见 find_homography.cpp 的SOLVER_TERMINATE_SUCCESSFULLY),并设置logging_type = ceres::SILENT静默运行——这对量产标定工具的日志收敛非常重要。

四、速度与解质量

特性文档在"速度"一项中指出,Ceres 经过了大量针对性优化:C++ 模板化、手写线性代数例程,以及基于OpenMP 或 C++11 线程的 Jacobian 求值与线性求解多线程化(对应Solver::Options::num_threads)。模板化既是自动微分的载体(Jet 类型编译期展开),也让求解器在编译期完成大量分支优化,运行时开销更低。

在"解质量"方面,Ceres 官方文档宣称其是 Mondragon 与 Borchers 用于非线性最小二乘求解器基准测试的 NIST 问题集上表现最好的求解器之一(原文见 features.rst)。需要注意的是,这属于 Ceres 官方文档的自我陈述,可作为选型时的参考依据,而非本仓库的独立验证结论。

五、协方差估计:量化解的置信度

特性文档特别强调:Ceres 是少数能够在大规模问题上做协方差分析的求解器之一。通过 covariance.h 提供的ceres::CovarianceAPI,用户可以评估解的全部或部分协方差矩阵,从而量化参数估计的敏感度与不确定性。从CovarianceAlgorithmType(见 types.h)可以看出它支持DENSE_SVD与SPARSE_QR两种算法:小规模稠密问题用 SVD 分解给出最稳定的结果,大规模稀疏问题用稀疏 QR 保持内存与时间可控。在标定工程中,这一特性可用于评估外参标定结果的可信度区间,判断是否需要补充采集数据。

六、可移植性与许可

特性文档最后强调了两点工程友好性:

  • 可移植性:Ceres 可运行于Linux、Windows、Mac OS X、Android与iOS,覆盖从车载工控机到移动端的大部分部署形态;
  • BSD 许可:宽松的 BSD 协议允许用户将 Ceres 随自己的应用自由发布,这也是本仓库选择将 Ceres 作为3rdparty整包携带的许可基础。

七、在 SensorsCalibration 中的真实落地

汇总本仓库源码,可以清晰看到 Ceres 特性文档中的每一项能力都在标定工程中有对应落地:

  1. 建模 API:所有标定残差(重投影误差、点对距离误差)都以 functor +AutoDiffCostFunction形式构建,无需手写雅可比;
  2. 局部参数化:旋转一律用EigenQuaternionParameterization或角轴 +AngleAxisRotatePoint表达,保证流形约束;
  3. 鲁棒损失:保留了HuberLoss等损失函数的切换入口,用于应对特征误匹配;
  4. 线性求解器选型:小规模稠密问题用DENSE_QR,带二部图结构的问题用DENSE_SCHUR,大规模 BA 类问题可升级SPARSE_SCHUR/ITERATIVE_SCHUR;
  5. 回调与提前终止:通过Solver::Options::callbacks实现"达到指定重投影精度即收敛"的工程化控制(见 find_homography.cpp)。

对应源码入口:外参精化与 PNP 求解见 pnp_solver.cpp、单应矩阵精化见 find_homography.cpp、外参粗标定程序见 run_extrinsic_calib.cpp,Ceres 头文件全集与官方文档则位于 factory_calib/3rdparty/ceres 目录下(ceres.h 聚合了全部核心头文件)。

总结

Ceres Solver 的核心竞争力可以概括为"建模与求解解耦":用户只负责用 ResidualBlock、LossFunction、LocalParameterization 描述问题,求解策略(信任域/线搜索、稠密/稀疏线性代数、多线程)则交给经过生产验证、性能优化的求解器家族。在 SensorsCalibration 中,这套能力被直接复用为相机标定与多传感器外参标定的统一优化后端——理解features.rst中列出的每一项特性,就等于掌握了标定工具箱的底层工作原理,也为自研优化问题选型提供了完整的决策依据。

  • 自动驾驶
  • 计算机视觉

【免费下载链接】SensorsCalibration

OpenCalib: A Multi-sensor Calibration Toolbox for Autonomous Driving

项目地址:https://gitcode.com/gh_mirrors/se/SensorsCalibration
点击查看免费下载

相关推荐

上一篇:英雄联盟LCU自动化工具League-Toolkit:5个高效配置技巧与实战指南
下一篇:3步拯救你的B站缓存视频:m4s-converter让珍贵内容永不消失

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询