从高斯牛顿法到LDLT分解:手把手教你优化SLAM中的非线性最小二乘问题
从高斯牛顿法到LDLT分解SLAM非线性优化的数学艺术与工程实践在机器人自主导航和自动驾驶领域SLAM同步定位与地图构建系统的核心挑战之一是如何高效解决非线性优化问题。当我们面对传感器采集的噪声数据时如何让机器人在未知环境中准确定位并构建地图这个问题的答案就隐藏在非线性最小二乘优化的数学框架中。1. 非线性最小二乘问题的数学本质任何SLAM系统都需要解决一个根本问题如何从带有噪声的观测数据中估计出最可能的状态机器人的位姿和地图特征点位置。这个问题天然地可以表述为非线性最小二乘优化minimize∑ᵢ ||fᵢ(x)||²其中fᵢ(x)表示第i个观测的残差函数x代表待优化的状态变量。在视觉SLAM中这些残差可能来自特征点的重投影误差在激光SLAM中则可能是点云匹配的距离误差。高斯牛顿法的精妙之处在于它将复杂的非线性问题通过一阶泰勒展开转化为一系列线性最小二乘问题。具体推导过程如下对残差函数f(x)在当前估计点xₖ处进行一阶泰勒展开 f(xₖ Δx) ≈ f(xₖ) J(xₖ)Δx将展开式代入目标函数 ||f(xₖ Δx)||² ≈ ||f(xₖ) J(xₖ)Δx||²展开并忽略高阶项 ≈ f(xₖ)ᵀf(xₖ) 2f(xₖ)ᵀJ(xₖ)Δx ΔxᵀJ(xₖ)ᵀJ(xₖ)Δx关键提示这里J(xₖ)是残差函数的雅可比矩阵其每一行对应一个残差项每一列对应状态变量的偏导数。通过求导并令导数为零我们得到著名的正规方程 J(xₖ)ᵀJ(xₖ)Δx -J(xₖ)ᵀf(xₖ)这个方程可以简洁地表示为HΔx b其中H是近似Hessian矩阵b是梯度项。2. LDLT分解对称正定方程组的优雅解法在SLAM的实际应用中正规方程中的H矩阵具有两个重要数学特性对称性由JᵀJ的构造方式决定正定性在问题良态时成立这些特性使得LDLT分解成为求解这类方程的理想选择。与Cholesky分解相比LDLT分解具有数值稳定性更高、适用范围更广的优势。LDLT分解将矩阵H分解为三个特殊矩阵的乘积 H LDLᵀ其中L是单位下三角矩阵对角线元素全为1D是对角矩阵Lᵀ是L的转置在Eigen库中这一分解过程被高度优化可以通过简单的API调用实现Eigen::MatrixXd H J.transpose() * J; // 构造Hessian近似 Eigen::VectorXd b -J.transpose() * f; // 构造梯度项 Eigen::VectorXd delta_x H.ldlt().solve(b); // LDLT分解求解2.1 LDLT分解的数值优势为什么在SLAM优化中偏爱LDLT而非其他分解方法主要原因包括分解方法稳定性适用范围计算效率内存需求LDLT高对称矩阵高低Cholesky中对称正定最高低QR最高任意矩阵中高SVD极高任意矩阵低高从表中可以看出LDLT在保持高数值稳定性的同时对对称矩阵不严格要求正定也能有效处理这在实际SLAM应用中尤为重要因为某些场景下Hessian矩阵可能只是半正定的。3. Eigen库中的矩阵计算实践Eigen作为现代C中最强大的线性代数库之一为SLAM优化提供了高效且易用的矩阵运算接口。下面我们深入探讨如何利用Eigen将高斯牛顿法的数学理论转化为实际代码。3.1 雅可比矩阵的计算模式在SLAM实现中雅可比矩阵的计算通常有两种模式解析求导手动推导残差函数对状态变量的偏导数优点精度高计算效率高缺点推导复杂容易出错自动微分利用Eigen的AutoDiff模块优点实现简单不易出错缺点有一定计算开销以下是使用自动微分计算雅可比矩阵的示例template typename T using VectorXT Eigen::MatrixT, Eigen::Dynamic, 1; template typename T VectorXTT computeResidual(const VectorXTT state) { // 实现残差计算 } Eigen::MatrixXd computeJacobian(const Eigen::VectorXd x) { typedef Eigen::AutoDiffScalarEigen::VectorXd ADScalar; VectorXTADScalar x_ad(x.size()); for(int i0; ix.size(); i) { x_ad[i].value() x[i]; x_ad[i].derivatives() Eigen::VectorXd::Unit(x.size(), i); } VectorXTADScalar residuals_ad computeResidual(x_ad); Eigen::MatrixXd J(residuals_ad.size(), x.size()); for(int i0; iresiduals_ad.size(); i) { J.row(i) residuals_ad[i].derivatives(); } return J; }3.2 稀疏性利用与性能优化实际SLAM问题中的Hessian矩阵通常是稀疏的Eigen提供了专门的稀疏矩阵模块来高效处理这种情况#include Eigen/Sparse typedef Eigen::SparseMatrixdouble SpMat; typedef Eigen::Tripletdouble T; void buildSparseSystem(SpMat H, Eigen::VectorXd b) { std::vectorT tripletList; // 填充非零元素到tripletList H.setFromTriplets(tripletList.begin(), tripletList.end()); // 稀疏LDLT求解 Eigen::SimplicialLDLTSpMat solver; solver.compute(H); Eigen::VectorXd delta_x solver.solve(b); }性能提示对于大规模SLAM问题正确利用矩阵稀疏性可以将求解速度提升1-2个数量级。4. 从理论到实践完整的SLAM优化案例让我们通过一个简化的视觉SLAM Bundle Adjustment(BA)问题将前面讨论的所有概念串联起来。考虑一个场景中有3个相机位姿和5个地图点我们需要优化它们的联合状态。4.1 问题建模定义状态向量x包含相机位姿每帧6自由度旋转3DoF平移3DoF地图点坐标每个点3D坐标残差函数计算重投影误差 rᵢ zᵢ - π(Tᵢ * pⱼ)其中zᵢ是观测到的2D特征点π是相机投影函数Tᵢ是第i个相机位姿pⱼ是第j个地图点4.2 完整优化流程实现struct Camera { Eigen::Vector3d rotation; // 旋转向量(轴角) Eigen::Vector3d translation; }; struct MapPoint { Eigen::Vector3d position; }; // 将状态向量解码为相机和地图点 void decodeState(const Eigen::VectorXd x, std::vectorCamera cameras, std::vectorMapPoint points) { // 实现解码逻辑 } // 计算所有残差和雅可比 void computeResidualsAndJacobian( const std::vectorCamera cameras, const std::vectorMapPoint points, const std::vectorObservation observations, Eigen::VectorXd residuals, Eigen::MatrixXd J) { // 实现残差和雅可比计算 } void gaussNewtonOptimization(std::vectorCamera cameras, std::vectorMapPoint points, const std::vectorObservation observations, int max_iterations 10) { for(int iter 0; iter max_iterations; iter) { Eigen::VectorXd x encodeState(cameras, points); Eigen::VectorXd residuals; Eigen::MatrixXd J; computeResidualsAndJacobian(cameras, points, observations, residuals, J); Eigen::MatrixXd H J.transpose() * J; Eigen::VectorXd b -J.transpose() * residuals; Eigen::VectorXd delta_x H.ldlt().solve(b); // 更新状态 x delta_x; decodeState(x, cameras, points); double cost residuals.squaredNorm(); std::cout Iter iter : cost cost std::endl; } }4.3 实际工程中的注意事项在真实SLAM系统实现中还需要考虑以下关键点鲁棒核函数应对异常观测double huberLoss(double residual, double delta) { double abs_r std::abs(residual); if(abs_r delta) { return 0.5 * residual * residual; } else { return delta * (abs_r - 0.5 * delta); } }阻尼策略应对Hessian矩阵病态H lambda * Eigen::MatrixXd::Identity(H.rows(), H.cols());边缘化策略控制问题规模// 使用舒尔补边缘化旧状态 void marginalize(const Eigen::MatrixXd H, const Eigen::VectorXd b, int keep_size, Eigen::MatrixXd H_marg, Eigen::VectorXd b_marg) { Eigen::MatrixXd H11 H.block(0,0,keep_size,keep_size); Eigen::MatrixXd H12 H.block(0,keep_size,keep_size,H.cols()-keep_size); Eigen::MatrixXd H22 H.block(keep_size,keep_size,H.rows()-keep_size,H.cols()-keep_size); Eigen::MatrixXd H22_inv H22.ldlt().solve( Eigen::MatrixXd::Identity(H22.rows(), H22.cols())); H_marg H11 - H12 * H22_inv * H12.transpose(); b_marg b.head(keep_size) - H12 * H22_inv * b.tail(b.size()-keep_size); }在开发VINS-Mono等实际SLAM系统时我发现LDLT分解的数值稳定性对系统整体鲁棒性至关重要。特别是在处理低纹理区域或快速运动场景时Hessian矩阵常常接近奇异这时LDLT相比Cholesky分解能提供更可靠的求解结果。