c++中如何使用Ceres Solver进行非线性优化? (最小二乘法实例)

Ceres Solver 的 CostFunction 是用于定义残差函数的接口,必须以 ∑ᵢ ‖fᵢ(x)‖² 形式表达优化问题;用户需实现 operator() 计算残差,支持自动/数值微分,参数块顺序与维度须严格匹配。

什么是 Ceres Solver 的 CostFunction

Ceres 中的优化问题必须表达为残差(residual)形式,即最小化 ∑ᵢ ‖fᵢ(x)‖²。你不能直接写目标函数,而是要实现一个 CostFunction 子类或使用 AutoDiffCostFunction / NumericDiffCostFunction 包装计算逻辑。

最常用的是自动微分:你只提供残差计算的 operator(),Ceres 自动求导。关键点:

  • operator() 的第一个参数是参数块指针数组(const double* const*),第二个是残差输出(double*),返回 bool
  • 参数块顺序必须和 AddParameterBlock() 一致,且每个块大小要匹配
  • 残差维度(输出长度)和参数块维度(输入长度)在构造 AutoDiffCostFunction 时就固定,写错会导致段错误或收敛失败

如何构造并运行一个最小二乘优化问题?

以拟合曲线 y = a·exp(b·x) + c 为例,有 3 个待估参数 a, b, c,已知若干 (x_i, y_i) 数据点:

struct ExponentialResidual {
  ExponentialResidual(double x, double y) : x_(x), y_(y) {}
  template 
  bool operator()(const T* const a, const T* const b, const T* const c,
                  T* residual) const {
    residual[0] = y_ - (*a) * exp(*b * x_) - (*c);
    return true;
  }
 private:
  const double x_, y_;
};

// 构建问题 ceres::Problem problem; double a = 1.0, b = 0.1, c = 0.0; std::vector> data = {{0.0, 1.2}, {1.0, 3.1}, {2.0, 8.0}}; for (const auto& p : data) { ceres::CostFunction* cost_function = new ceres::AutoDiffCostFunction( new ExponentialResidual(p.first, p.second)); problem.AddResidualBlock(cost_function, nullptr, &a, &b, &c); }

// 配置并求解 ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; options.minimizer_progress_to_stdout = true; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); printf("Final a=%f, b=%f, c=%f\n", a, b, c);

注意:AutoDiffCostFunction 表示:残差维度 1,三个参数块各占 1 维;若参数是向量(如旋转四元数),维度就得改成 4。

为什么优化不收敛或报 Check failed: is_valid

常见原因不是模型本身,而是数值或配置问题:

  • 初始值太离谱(例如 b 初始为 1e6 导致 exp(b*x) 溢出),改用合理量级初值(如对数尺度预估)
  • 残差函数里用了未定义行为:比如 log(x)x ≤ 0,或除零、sqrt 负数 —— 加 if (x 并返回 false 让 Ceres 跳过该残差
  • 没设置 options.max_num_iterations,而问题病态导致迭代太久,建议设为 100 左右先试
  • 用了 nullptr 作为损失函数(loss function),但数据含大离群点,应改用 new ceres::HuberLoss(0.1)

C++ 编译时链接失败:找不到 ceres::Problemceres::Solve

不是头文件没包含,而是链接阶段缺失库或顺序错误。确保:

  • 链接时包含 -lceres,且放在你的目标文件之后(如 g++ main.o -lceres -lglog -lgflags -lpthread -o app
  • 如果用 CMake,不要只写 find_package(Ceres),还要 target_link_libraries(your_target ${CERES_LIBRARIES}),且 ${CERES_LIBRARIES} 必须在你自己源文件编译目标之后
  • 确认安装的 Cere

    s 是带 Solver 支持的(非 minimal 版本),检查 ceres-config.cmake 中是否含 ceres_FOUNDCERES_SOLVER_LIBRARY

最易忽略的一点:Ceres 默认关闭 OpenMP 和 SuiteSparse,如果你的优化变量多于几百维,又没开 -DCERES_USE_EIGEN_SPARSE=ON-DCERES_USESuiteSparse=ON,它会静默退回到稠密求解器,速度骤降且内存爆炸 —— 这类问题不会报错,只会让你等十分钟还不出结果。