Bundle Adjustment(BA)原理与应用:从重投影误差到视觉SLAM优化
2026/8/6 2:49:38 网站建设 项目流程

1. Bundle Adjustment(BA)是什么?从“捆”与“调”说起

如果你玩过摄影,尤其是尝试过用多张照片重建三维场景,那你一定遇到过这样的困境:单张照片的相机位置估得不太准,三维点云看起来有点飘,整体模型总觉得哪里“拧巴”着。Bundle Adjustment,中文常译作“光束法平差”或“捆绑调整”,就是解决这个“拧巴”问题的终极利器。它不是一个具体的算法,而是一个将三维结构(场景点)和相机参数(位置、姿态、内参)联合起来进行全局最优化的数学模型与框架。你可以把它想象成一次对所有观测数据的“大清算”和“总校对”。

为什么叫“捆绑调整”?这里的“Bundle”指的就是从三维空间点发射到各个相机光心所形成的“光束”。在三维重建中,一个空间点会被多个相机看到,形成多条观测光线(光束)。BA的核心思想就是:调整所有相机的位置姿态和所有三维空间点的位置,使得这些重新计算出来的“光束”,能够最好地“穿过”图像上实际观测到的像素点。换句话说,就是让理论投影和实际观测之间的误差总和降到最低。这个过程就像同时拉扯一张由许多橡皮筋(光束)连接的网络(相机和三维点),直到整个网络达到一个整体最协调、受力最均衡的状态。它几乎是所有现代视觉SLAM(同步定位与建图)、运动恢复结构(SfM)和摄影测量系统的核心收尾与优化步骤,其优化效果直接决定了最终三维模型的精度和一致性。

2. BA的数学模型:误差从何而来,又去向何处

要理解BA,必须深入其数学模型的核心。BA本质上是一个大规模的非线性最小二乘优化问题。它的目标函数非常直观,但背后的计算却相当复杂。

2.1 重投影误差:优化的基石

BA优化的目标是最小化重投影误差。什么是重投影误差?我们用一个简单的例子来说明:假设我们有一个估计的三维点 ( P_j = [X_j, Y_j, Z_j]^T ),和一个估计的相机,这个相机有自己的旋转矩阵 ( R_i )、平移向量 ( t_i ) 和内参矩阵 ( K_i )。我们用这个相机参数将三维点 ( P_j ) 投影到图像平面上,得到一个理论像素坐标 ( \hat{u}{ij} )。同时,我们在第 ( i ) 张图像上实际观测到这个点,其像素坐标为 ( u{ij} )。那么,重投影误差就是这两个坐标之间的欧氏距离:

[ e_{ij} = u_{ij} - \pi(K_i (R_i P_j + t_i)) ]

其中,( \pi ) 表示从相机坐标系到像素坐标系的投影函数(包含除法以处理透视投影)。对于所有相机 ( i ) 和所有被观测到的三维点 ( j ),BA的目标就是找到一组最优的相机参数 ( {R_i, t_i, K_i} ) 和三维点坐标 ( {P_j} ),使得所有重投影误差的平方和最小:

[ \min_{{R_i, t_i, K_i}, {P_j}} \sum_{i=1}^{m} \sum_{j=1}^{n} | e_{ij} |^2 ]

这里 ( m ) 是相机数,( n ) 是三维点数。这个求和符号清晰地揭示了BA的规模:一个典型的SfM项目可能有成百上千张图像和数百万个三维点,这意味着优化变量(未知数)的规模可以达到数千万甚至上亿,而观测方程的数量则是这个规模的数倍。

2.2 非线性与线性化:从高斯到列文伯格-马夸尔特

目标函数 ( | e_{ij} |^2 ) 由于相机投影模型 ( \pi ) 和旋转矩阵 ( R ) 的存在,是一个高度非线性的函数。我们无法直接求解其全局最小值。标准的做法是采用迭代优化算法,从一组初始估计值开始,逐步逼近最优解。

最常用的方法是高斯-牛顿法及其稳健变种列文伯格-马夸尔特算法。其核心思想是局部线性化。在每次迭代中,我们在当前参数估计值 ( x )(这里 ( x ) 是一个庞大的向量,包含了所有待优化的相机和三维点参数)处,对误差函数 ( e(x) ) 进行一阶泰勒展开:

[ e(x + \Delta x) \approx e(x) + J(x) \Delta x ]

其中 ( J(x) ) 是误差函数 ( e ) 关于所有参数 ( x ) 的雅可比矩阵,这是一个极其庞大但稀疏的矩阵。将线性化后的误差代入目标函数,优化问题就变成了一个关于增量 ( \Delta x ) 的线性最小二乘问题:

[ \min_{\Delta x} | e(x) + J(x) \Delta x |^2 ]

求解这个方程(即求解正规方程 ( J^T J \Delta x = -J^T e )),我们就能得到在当前点使得误差下降最快的参数更新方向 ( \Delta x ),然后更新参数:( x \leftarrow x + \Delta x )。如此反复迭代,直到收敛(增量 ( \Delta x ) 足够小或误差下降不明显)。

注意:这里的“稀疏”是BA能够高效求解的关键。一个三维点通常只被少数几个相机看到,一个相机也只看到一部分三维点。因此,雅可比矩阵 ( J ) 中绝大多数元素都是零。利用这种稀疏性,可以极大地减少计算和存储开销,使得优化大规模问题成为可能。

2.3 参数化与流形:旋转的特别处理

在优化过程中,我们需要特别小心地处理相机旋转参数。旋转矩阵 ( R ) 本身是正交矩阵,有 ( R^T R = I ) 且 ( \det(R) = 1 ) 的约束。如果直接在9个矩阵元素上做优化,很容易破坏这些约束,导致优化后的“旋转矩阵”不再是一个有效的旋转。

标准的做法是使用一种更紧凑、无约束的参数化方式,例如李代数 so(3)(三维向量)或四元数。在每次迭代求解出增量 ( \Delta x ) 后,对于旋转部分,我们实际上求解的是在李代数空间中的增量 ( \delta \phi )(一个三维向量)。然后通过指数映射 ( R \leftarrow R \cdot \exp(\delta \phi^{\wedge}) ) 来更新旋转矩阵,这样可以保证更新后的 ( R ) 始终落在旋转矩阵群 ( SO(3) ) 上。这个过程称为“在流形上进行优化”,是处理带约束优化问题的优雅方式。

3. BA的完整工作流程与实操要点

理解了数学模型,我们来看BA在实际的三维重建或SLAM系统中是如何被调用和执行的。它通常不是一个孤立的过程,而是嵌入在一个完整的pipeline中。

3.1 前端数据关联:BA的“原料”准备

BA的输入是什么?不是原始图像,而是已经建立好的观测数据。这包括:

  1. 图像特征点:通常是SIFT、SURF、ORB等特征提取算法得到的像素坐标。
  2. 特征匹配关系:知道哪些图像中的哪些特征点对应的是同一个三维物理点。
  3. 初始的相机位姿和三维点云:这通常由前端流程提供,例如通过对极几何、PnP(Perspective-n-Point)或视觉里程计初步计算得到。这些初始值可能误差较大,但必须足够“靠谱”,以保证BA能收敛到正确的局部最优解(全局最优通常难以保证)。

实操心得:前端的匹配质量直接决定了BA的成败。错误的匹配(外点)会像“毒药”一样污染整个优化过程,导致结果完全错误。因此,在送入BA之前,必须进行严格的外点剔除,常用的方法包括:

  • 交叉验证:双向匹配检查。
  • 几何验证:利用基础矩阵或单应矩阵进行RANSAC,剔除不符合几何约束的匹配对。
  • 时序或空间一致性检查:在SLAM中,可以利用运动连续性进行筛选。

3.2 BA的构建与求解:稀疏线性系统的求解

有了初始值和观测数据,我们就可以构建BA问题。现代BA库(如ceres-solver, g2o, GTSAM)为我们封装了大部分繁琐的步骤。我们需要做的是:

  1. 定义参数块:告诉优化器,哪些变量是相机位姿(可能需要自定义李代数局部参数化),哪些是三维点坐标,哪些是相机内参(焦距、主点、畸变系数等)。
  2. 添加残差块:对于每一个观测(图像i中的点j),构建一个重投影误差残差项,并将其与对应的相机参数块和三维点参数块关联起来。
  3. 配置求解器:选择优化算法(如LM算法),设置迭代次数、收敛阈值等。

接下来,求解器会自动完成我们之前描述的线性化、构建稀疏雅可比矩阵、求解增量方程的过程。求解大规模稀疏线性方程 ( J^T J \Delta x = -J^T e ) 是计算的核心。这里通常使用舒尔消元技巧。由于三维点数量远多于相机数量,我们可以先消去三维点变量,得到一个只关于相机参数的、维度小得多的简化方程(称为缩减相机系统),求解完相机参数后再回代求解三维点。这能极大提升计算效率。

一个简单的ceres-solver示例框架

// 假设已有:观测数据 observations, 初始相机位姿 cameras, 初始三维点 points ceres::Problem problem; for (const auto& obs : observations) { int camera_id = obs.camera_id; int point_id = obs.point_id; double observed_x = obs.x; double observed_y = obs.y; ceres::CostFunction* cost_function = SnavelyReprojectionError::Create(observed_x, observed_y); problem.AddResidualBlock(cost_function, nullptr, // 损失函数,如Huber用于鲁棒性 cameras[camera_id].rotation, // 旋转参数块(如四元数数组指针) cameras[camera_id].translation, // 平移参数块 cameras[camera_id].focal_length, // 内参:焦距 cameras[camera_id].k1, cameras[camera_id].k2, // 畸变参数 points[point_id].data()); // 三维点坐标参数块 } // 为旋转参数设置局部参数化(李代数) ceres::LocalParameterization* quaternion_local_parameterization = ...; for (auto& camera : cameras) { problem.SetParameterization(camera.rotation, quaternion_local_parameterization); } ceres::Solver::Options options; options.linear_solver_type = ceres::SPARSE_SCHUR; // 使用稀疏舒尔求解器 options.minimizer_progress_to_stdout = true; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); std::cout << summary.FullReport() << "\n";

3.3 增量式BA与滑动窗口:SLAM中的实时优化

在离线SfM中,我们可以对所有数据进行全局BA。但在在线SLAM系统中,数据源源不断,计算资源有限,无法每次都做全局BA。因此,增量式BA滑动窗口BA成为关键技术。

  • 增量式BA:当新的一帧图像到来时,只优化与新帧相关的相机位姿和三维点,以及与之有较强连接关系的部分旧变量,而不是优化全部。这可以显著减少计算量。
  • 滑动窗口BA:只维护一个固定大小的、最近的若干帧(例如10-20帧)作为一个局部窗口,只对这个窗口内的变量进行BA优化。窗口随着机器人移动而滑动,旧的帧被移出窗口,其状态被固定或边缘化。边缘化是一种将旧状态的信息以先验的形式保留下来,从而不影响后续优化一致性的关键技术。

实操心得:在滑动窗口BA中,边缘化的操作需要格外小心。错误的边缘化(例如边缘化了与当前窗口仍有强约束的观测量)会导致信息丢失,甚至使系统变得不一致,产生漂移。通常,只边缘化那些不再与滑动窗口内变量有直接视觉观测连接的旧状态。

4. BA的挑战、技巧与常见问题排查

即使理解了原理,在实际实现和调试BA时,依然会面临诸多挑战。下面分享一些从实践中得来的经验和常见问题的排查思路。

4.1 数值稳定性与初始化

BA高度依赖于初始值。如果初始值太差(例如,相机位姿误差超过几十度,三维点深度估计完全错误),非线性优化很容易陷入局部极小值或直接发散。

技巧

  • 渐进式重建:从两视图重建开始,用三角化得到初始点云,然后用PnP估计新相机位姿,再用新相机三角化更多点,如此循环,并不断进行小规模的局部BA,逐步扩大重建规模。这比直接用所有数据做全局BA要稳定得多。
  • 尺度归一化:将三维点坐标和相机平移量控制在合理的数值范围内(例如,让场景的包围盒大小在1-10个单位内)。过大或过小的数值会导致雅可比矩阵条件数变差,引发数值计算问题。
  • 鲁棒核函数:使用Huber、Cauchy等鲁棒核函数代替简单的平方损失。这可以降低外点(错误匹配)对整体优化目标的负面影响,使BA对数据噪声更不敏感。

4.2 计算效率与稀疏性利用

BA的规模可能非常大,直接求解是不现实的。高效利用稀疏性是关键。

技巧

  • 选择合适的线性求解器SPARSE_SCHUR(Ceres) 或Preconditioned Conjugate Gradient(PCG) 是处理BA类问题的常用选择。对于超大规模问题,可能需要使用迭代求解器。
  • 参数排序:优化器内部对参数块的排序会影响消元效率。通常将点云参数放在相机参数之后,有利于舒尔消元。
  • 并行化:现代BA库支持多线程构建雅可比矩阵和残差,能有效利用多核CPU。

4.3 常见问题与排查表

在实际运行BA时,如果结果不理想,可以按照以下清单进行排查:

问题现象可能原因排查与解决思路
优化不收敛,误差震荡或增大1. 学习率(LM算法的阻尼因子)设置不当。
2. 初始值太差,落入“错误”的盆地。
3. 存在大量外点,模型无法拟合。
1. 调整LM算法的初始阻尼因子和缩放因子。
2. 检查并改进前端初始化的质量,尝试更渐进的重建策略。
3. 启用鲁棒核函数(如Huber),并检查前端匹配的RANSAC阈值是否合理。
优化后模型严重扭曲或缩放错误1. 尺度模糊性未解决(纯旋转或纯平移序列)。
2. 优化过程中固定了错误的基准(如错误固定了某个相机位姿)。
1. 确保输入数据有足够的视差变化,或者引入一个尺度观测(如IMU、已知长度的物体)。
2. 在BA中,通常需要固定第一个相机的位姿和尺度(或固定两个相机间的基线长度)来锁定坐标系。检查固定策略是否正确。
部分区域点云模糊,重投影误差仍大1. 该区域纹理匮乏,特征点少或匹配不准。
2. 相机在该区域视角变化太小,观测条件差(近退化配置)。
1. 尝试使用更密集的特征提取或光流追踪,或引入其他传感器(如深度相机)。
2. 无法从根本上解决,可考虑在BA中降低这些不可靠观测的权重。
优化速度极慢1. 问题规模太大,内存不足。
2. 线性求解器选择不当。
3. 参数化或自动求导开销大。
1. 采用滑动窗口或关键帧策略,控制优化规模。
2. 尝试不同的线性求解器(如ITERATIVE_SCHUR配合好的预条件子)。
3. 对于简单模型,考虑使用解析导数代替自动求导。
内存占用过高雅可比矩阵存储方式低效,或问题规模超出内存。1. 确保使用的是稀疏模式。
2. 使用SPARSE_NORMAL_CHOLESKY求解器时,注意其内存消耗与问题稀疏模式有关,有时SPARSE_SCHUR更省内存。

4.4 高级话题:BA的扩展

基础的BA优化的是几何一致性。在实际应用中,BA常常被扩展以融合更多信息和约束:

  • 带IMU的视觉惯性BA:在误差函数中不仅包含视觉重投影误差,还包含IMU的预积分误差项,共同优化视觉和惯性状态。这是视觉惯性SLAM(如VINS-Mono, ORB-SLAM3)的核心。
  • 光度BA:不依赖于特征点,而是直接最小化图像像素强度的差异(光度误差),常用于直接法SLAM(如LSD-SLAM, DSO)。
  • 全局位姿图优化:在大型场景中,先进行局部BA,然后将局部BA的结果(关键帧位姿)作为节点,将它们的相对约束作为边,构建一个位姿图,再进行一次轻量级的全局优化,以消除累积漂移,这是许多SLAM系统后端的标准流程。

BA的魅力在于其框架的通用性和强大的纠错能力。它像一位严谨的校对员,通过全局视角审视所有局部测量之间的矛盾,并给出一个整体最优的妥协方案。掌握BA,不仅是学会调用一个优化库,更是理解了多视图几何优化问题的核心思想。在实际项目中,耐心地调试前端数据关联、精心设计优化参数块结构、明智地选择求解策略,比单纯追求算法理论上的最优更为重要。每一次BA迭代后看到误差曲线稳步下降,点云变得清晰紧实,那种感觉,正是三维视觉工程师的快乐源泉之一。

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

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

立即咨询