4、Ceres Solver入门:Ceres的安装与配置、自动求导与数值求导
说到图优化,就绕不开Ceres Solver。这个库在SLAM圈子里几乎是标配,Google出品,质量有保障。我最早接触Ceres是在做后端优化的时候,那时候还在手写高斯牛顿,后来发现Ceres把求导、迭代、收敛判断全包了,真香。
这一节,咱们就聊聊Ceres的安装配置,以及它最核心的两个求导方式——自动求导和数值求导。你想想看,优化问题的本质就是最小化误差,而求导是其中最关键的一步。Ceres帮你把这一步简化了很多。
4.1 Ceres的安装与配置
安装Ceres其实不复杂,但有几个坑需要注意。我踩过不少,给你总结一下。
4.1.1 依赖项准备
Ceres依赖几个基础库:Eigen(线性代数)、glog/gflags(日志和参数解析)、以及可选的SuiteSparse(稀疏矩阵求解)。我个人习惯用apt安装,省事。
# 安装依赖
sudo apt-get install libeigen3-dev libgoogle-glog-dev libgflags-dev
sudo apt-get install libatlas-base-dev libsuitesparse-dev
这里要注意,Eigen版本不能太低。Ceres 2.x要求Eigen 3.3以上。我曾经在Ubuntu 16.04上装过,默认的Eigen版本不够,编译报错,折腾了半天才发现是版本问题。
4.1.2 编译与安装
从GitHub拉源码,然后cmake三板斧:
git clone https://github.com/ceres-solver/ceres-solver.git
cd ceres-solver
mkdir build && cd build
cmake ..
make -j4
sudo make install
嗯,这里有个小技巧。cmake的时候可以加一些选项,比如关闭测试来加快编译:
cmake -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF ..
我一般还会加上 -DUSE_CUDA=OFF,因为SLAM里很少用到GPU加速的Ceres。
4.1.3 验证安装
装完之后,写个简单的CMakeLists.txt来验证:
cmake_minimum_required(VERSION 3.10)
project(ceres_test)
find_package(Ceres REQUIRED)
include_directories(${CERES_INCLUDE_DIRS})
add_executable(test_ceres test_ceres.cpp)
target_link_libraries(test_ceres ${CERES_LIBRARIES})
如果编译通过,说明安装成功了。我习惯在终端里跑一下Ceres自带的示例程序,比如 simple_bundle_adjuster,看看输出是否正常。
cmake -DSUITESPARSE=OFF ..。对于大多数SLAM问题,没有SuiteSparse也能跑,只是求解速度慢一点。
4.2 自动求导 vs 数值求导
Ceres最吸引我的地方,就是它提供了多种求导方式。说白了,你不需要手动推导雅可比矩阵了。这对我们这种懒人来说,简直是福音。
4.2.1 数值求导(NumericDiff)
数值求导的原理很简单——用差分近似代替导数。比如前向差分:
f'(x) ≈ (f(x + h) - f(x)) / h
在Ceres里,用数值求导需要这样写:
struct CostFunctor {
template <typename T>
bool operator()(const T* const x, T* residual) const {
residual[0] = 10.0 - x[0];
return true;
}
};
// 使用数值求导
ceres::CostFunction* cost_function =
new ceres::NumericDiffCostFunction<CostFunctor, ceres::CENTRAL, 1, 1>(new CostFunctor);
注意这里的 ceres::CENTRAL,它指定了差分方式。有FORWARD、CENTRAL、RIDDERS三种。我一般用CENTRAL,精度比FORWARD高,但计算量翻倍。RIDDERS精度最高,但慢,适合对精度要求极高的场景。
- 优点:实现简单,不需要推导导数公式
- 缺点:精度受步长h影响,计算速度慢,对病态问题不稳定
我在项目中遇到过一个问题:用数值求导优化相机位姿,结果迭代了100多次还不收敛。后来换成自动求导,20次就搞定了。说白了,数值求导的精度问题在非线性优化里会被放大。
4.2.2 自动求导(AutoDiff)
自动求导是Ceres的杀手锏。它不像数值求导那样近似,也不像符号求导那样需要推导表达式。它利用链式法则,在计算过程中同时计算导数。
用自动求导的写法:
struct CostFunctor {
template <typename T>
bool operator()(const T* const x, T* residual) const {
residual[0] = T(10.0) - x[0];
return true;
}
};
// 使用自动求导
ceres::CostFunction* cost_function =
new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
你看,代码几乎一样,只是把 NumericDiffCostFunction 换成了 AutoDiffCostFunction。但内部机制完全不同。自动求导会把 T 替换成 Jet 类型,这个 Jet 同时保存值和导数,通过重载运算符实现链式求导。
4.2.3 对比总结
| 对比项 | 自动求导 | 数值求导 |
|---|---|---|
| 精度 | 机器精度(无截断误差) | 受步长h影响,有截断误差 |
| 速度 | 快(与手写解析求导接近) | 慢(每次需要多次函数求值) |
| 实现难度 | 中等(需要模板化代码) | 简单(直接写残差函数) |
| 适用场景 | 大多数优化问题 | 自动求导不支持的特殊情况 |
4.3 一个完整的例子
咱们用Ceres来解一个简单的曲线拟合问题。假设我们有观测数据点,想拟合一条曲线 y = exp(m*x + c)。这个例子在SLAM里很常见,比如拟合传感器的响应曲线。
#include <ceres/ceres.h>
#include <iostream>
// 残差结构体
struct ExponentialResidual {
ExponentialResidual(double x, double y) : x_(x), y_(y) {}
template <typename T>
bool operator()(const T* const m, const T* const c, T* residual) const {
residual[0] = T(y_) - exp(m[0] * T(x_) + c[0]);
return true;
}
private:
const double x_;
const double y_;
};
int main() {
// 模拟数据
double m_true = 0.3, c_true = 0.1;
double m_est = 0.0, c_est = 0.0;
// 构建问题
ceres::Problem problem;
for (int i = 0; i < 100; ++i) {
double x = i * 0.1;
double y = exp(m_true * x + c_true);
problem.AddResidualBlock(
new ceres::AutoDiffCostFunction<ExponentialResidual, 1, 1, 1>(
new ExponentialResidual(x, y)),
nullptr, &m_est, &c_est);
}
// 配置求解器
ceres::Solver::Options options;
options.minimizer_progress_to_stdout = true;
options.max_num_iterations = 100;
// 求解
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
std::cout << summary.FullReport() << std::endl;
std::cout << "True m: " << m_true << ", c: " << c_true << std::endl;
std::cout << "Est m: " << m_est << ", c: " << c_est << std::endl;
return 0;
}
这个例子虽然简单,但包含了Ceres的核心流程:定义残差、构建问题、配置求解器、求解。在SLAM闭环检测中,我们也是用同样的流程来优化位姿图。
- 定义残差结构体,重载
operator() - 用
AutoDiffCostFunction或NumericDiffCostFunction包装 - 添加到
Problem中 - 配置
Solver::Options,调用Solve
4.4 知识体系图
为了让你更直观地理解Ceres Solver在SLAM中的位置,我画了一张图:
这张图展示了Ceres在闭环检测中的位置。说白了,闭环检测给你的是约束,Ceres负责把这些约束变成优化问题,然后求解。自动求导和数值求导就是求解过程中的核心工具。
好了,这一节的内容就到这里。Ceres的安装配置和求导方式,是后续图优化的基础。你先把这些基础打牢,后面讲位姿图优化的时候,就能直接上手了。
公众号:蓝海资料掘金营,微信deep3321