栓州 发表于 2026-3-14 00:40:01

最小二乘问题详解16:束平差工程实践总结

1. 引言

在上一篇文章《最小二乘问题详解15:束平差原理与基础实现》中,我们从几何模型出发,系统推导了 束平差(Bundle Adjustment, BA) 的数学形式,并基于 Ceres Solver 实现了一个基础但完整的 BA 系统。在理想合成数据下,该系统能够将初始存在偏差的相机位姿与 3D 点高效优化至亚像素级精度,充分验证了 BA 作为“全局一致性精修”工具的强大能力。
然而,理论上的优雅并不总能直接转化为工程上的稳定。在真实的 BA 应用场景( SFM / SLAM / 稀疏重建)中不会那么理想,往往会遇到各种问题。其中最为常见的就是由于误匹配造成的外点(Outliers)污染的问题。
2. 实例

为了更直观地说明外点对 BA 优化的影响,以及如何在工程实践中提升 BA 的稳健性,这里通过一个完整的实例进行演示。在该示例中,我们构造一个简单但具有代表性的 多视图重建场景。具体设置如下:

[*]相机数量:5 个
[*]空间点数量:100 个
[*]相机模型:理想 pinhole 相机
[*]观测噪声:像素级高斯噪声
[*]外点比例:10%
在生成观测数据时,我们首先按照真实几何关系计算每个 3D 点在相机中的投影位置,并叠加 高斯像素噪声。随后按照设定的比例随机注入 错误匹配(外点),即直接生成随机像素坐标来替代真实观测值,从而模拟真实特征匹配中不可避免的误匹配问题。具体代码实现如下:
#include #include #include #include #include #include #include using namespace std;struct Camera {double q;double t;};struct Point3D {double xyz;};struct Observation {int cam_id;int pt_id;double x;double y;};struct ReprojectionError {ReprojectionError(double x, double y, double fx, double fy, double cx,                  double cy)      : x_(x), y_(y), fx_(fx), fy_(fy), cx_(cx), cy_(cy) {}template   bool operator()(const T* const q, const T* const t, const T* const point,                  T* residuals) const {    T p;    ceres::QuaternionRotatePoint(q, point, p);    p += t;    p += t;    p += t;    T xp = p / p;    T yp = p / p;    T u = T(fx_) * xp + T(cx_);    T v = T(fy_) * yp + T(cy_);    residuals = u - T(x_);    residuals = v - T(y_);    return true;}static ceres::CostFunction* Create(double x, double y, double fx, double fy,                                     double cx, double cy) {    return new ceres::AutoDiffCostFunction(      new ReprojectionError(x, y, fx, fy, cx, cy));}double x_, y_;double fx_, fy_, cx_, cy_;};double ReprojectionErrorValue(const Camera& c, const Point3D& p,                              const Observation& o, double fx, double fy,                              double cx, double cy) {Eigen::Quaterniond q(c.q, c.q, c.q, c.q);Eigen::Vector3d t(c.t, c.t, c.t);Eigen::Vector3d P(p.xyz, p.xyz, p.xyz);Eigen::Vector3d Pc = q * P + t;double u = fx * Pc.x() / Pc.z() + cx;double v = fy * Pc.y() / Pc.z() + cy;double du = u - o.x;double dv = v - o.y;return sqrt(du * du + dv * dv);}double ComputeRMSE(const vector& cams, const vector& pts,                   const vector& obs, double fx, double fy,                   double cx, double cy) {double err = 0;int n = 0;for (auto& o : obs) {    double e =      ReprojectionErrorValue(cams, pts, o, fx, fy, cx, cy);    err += e * e;    n++;}return sqrt(err / n);}void SolveBA(vector& cams, vector& pts,             const vector& obs, double fx, double fy, double cx,             double cy, bool robust) {ceres::Problem problem;ceres::Manifold* q_manifold = new ceres::QuaternionManifold();for (auto& c : cams) {    problem.AddParameterBlock(c.q, 4, q_manifold);    problem.AddParameterBlock(c.t, 3);}for (auto& p : pts) problem.AddParameterBlock(p.xyz, 3);for (auto& o : obs) {    ceres::CostFunction* cost =      ReprojectionError::Create(o.x, o.y, fx, fy, cx, cy);    ceres::LossFunction* loss = nullptr;    if (robust) loss = new ceres::HuberLoss(1.0);    problem.AddResidualBlock(cost, loss, cams.q, cams.t,                           pts.xyz);}problem.SetParameterBlockConstant(cams.q);problem.SetParameterBlockConstant(cams.t);ceres::Solver::Options options;options.linear_solver_type = ceres::SPARSE_SCHUR;options.max_num_iterations = 50;options.minimizer_progress_to_stdout = true;ceres::Solver::Summary summary;ceres::Solve(options, &problem, &summary);cout
页: [1]
查看完整版本: 最小二乘问题详解16:束平差工程实践总结