海森矩阵(Hessian Matrix)在SLAM图优化和点云配准中的应用介绍

在非线性最小二乘问题中(如 SLAM 或点云配准),通常我们有一个误差函数:

f(x)=∑i∥ei(x)∥2 f(x) = \sum_i \| e_i(x) \|^2 f(x)=i∑∥ei(x)∥2

其中 ei(x)e_i(x)ei(x) 是残差项,对它求 Hessian 就需要用雅可比矩阵:

H=J⊤J+∑iei⊤Hei H = J^\top J + \sum_i e_i^\top H_{e_i} H=J⊤J+i∑ei⊤Hei

通常我们近似为:

H≈J⊤J H \approx J^\top J H≈J⊤J

这就是 高斯-牛顿法Levenberg-Marquardt(LM)方法 中用的近似 Hessian。


1、从误差平方和构建优化系统

最小化目标函数:

f(x)=12∑i∥ri(x)∥2 f(\mathbf{x}) = \frac{1}{2} \sum_{i} \| \mathbf{r}_i(\mathbf{x}) \|^2 f(x)=21i∑∥ri(x)∥2

对其做泰勒二阶展开,令:

  • Δx\Delta \mathbf{x}Δx:当前状态变量的小增量(在李代数中则是 δξ\delta \xiδξ)
  • Ji=∂ri∂x\mathbf{J}_i = \frac{\partial \mathbf{r}_i}{\partial \mathbf{x}}Ji=∂x∂ri:残差对状态的 Jacobian

展开形式如下:

f(x+Δx)≈f(x)+∇f(x)⊤Δx+12Δx⊤HΔx f(\mathbf{x} + \Delta \mathbf{x}) \approx f(\mathbf{x}) + \nabla f(\mathbf{x})^\top \Delta \mathbf{x} + \frac{1}{2} \Delta \mathbf{x}^\top H \Delta \mathbf{x} f(x+Δx)≈f(x)+∇f(x)⊤Δx+21Δx⊤HΔx


2、构建雅可比与 Hessian

根据误差平方和结构,可以推导出:

  • 梯度项

∇f(x)=∑iJi⊤ri \nabla f(\mathbf{x}) = \sum_{i} \mathbf{J}_i^\top \mathbf{r}_i ∇f(x)=i∑Ji⊤ri

  • Hessian 矩阵(高效构建方式):

H=∑iJi⊤Ji H = \sum_{i} \mathbf{J}_i^\top \mathbf{J}_i H=i∑Ji⊤Ji

这是 Gauss-Newton 近似的 Hessian(忽略二阶导项 ∂2r∂x2\frac{\partial^2 r}{\partial x^2}∂x2∂2r)。


3、图优化中的应用场景

1. GTSAM / Ceres / g2o 中

它们都将优化建模为图结构:

  • 节点:状态变量(位姿、地图点);

  • 边:观测(误差函数);

  • 每条边贡献一个残差项 ri(x)r_i(\mathbf{x})ri(x),其 Jacobian 用于构建:

    • 梯度向量 b=−∑iJi⊤ri\mathbf{b} = - \sum_i \mathbf{J}_i^\top \mathbf{r}_ib=−∑iJi⊤ri
    • Hessian 矩阵 H=∑iJi⊤JiH = \sum_i \mathbf{J}_i^\top \mathbf{J}_iH=∑iJi⊤Ji

构建最终线性系统:

HΔx=−b \boxed{H \Delta \mathbf{x} = -\mathbf{b}} HΔx=−b

更新状态:

xk+1=xk+Δx \mathbf{x}_{k+1} = \mathbf{x}_k + \Delta \mathbf{x} xk+1=xk+Δx

在李群中,更新为 Tk+1=exp⁡(Δξ∧)⋅Tk\mathbf{T}_{k+1} = \exp(\Delta \xi^\wedge) \cdot \mathbf{T}_kTk+1=exp(Δξ∧)⋅Tk

G2O中自定义边类中计算 Hessian 的代码示例

以经典的图优化中 EdgeSE2 为例,创建顶点和边。

cpp 复制代码
#include <g2o/core/sparse_optimizer.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/optimization_algorithm_levenberg.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <g2o/types/slam2d/types_slam2d.h>

using namespace g2o;

int main() {
    SparseOptimizer optimizer;
    optimizer.setVerbose(true);

    // 设置线性求解器
    auto linearSolver = g2o::make_unique<LinearSolverDense<BlockSolverX::PoseMatrixType>>();
    auto solver = g2o::make_unique<BlockSolverX>(std::move(linearSolver));
    optimizer.setAlgorithm(new OptimizationAlgorithmLevenberg(std::move(solver)));

    // 添加两个顶点
    auto v1 = new VertexSE2();
    v1->setId(0);
    v1->setEstimate(SE2(0, 0, 0));
    optimizer.addVertex(v1);

    auto v2 = new VertexSE2();
    v2->setId(1);
    v2->setEstimate(SE2(1, 0, 0));
    optimizer.addVertex(v2);

    // 添加边(测量为 v1 到 v2 的位姿)
    auto edge = new EdgeSE2();
    edge->setId(0);
    edge->setVertex(0, v1);
    edge->setVertex(1, v2);
    edge->setMeasurement(SE2(1, 0, 0));
    edge->setInformation(Eigen::Matrix3d::Identity());
    optimizer.addEdge(edge);

    // 初始化完毕,计算 Hessian
    optimizer.initializeOptimization();
    optimizer.computeActiveErrors();

    // 手动计算一次误差、雅可比和 Hessian
    edge->computeError();          // e = prediction - measurement
    edge->linearizeOplus();        // J1, J2

    // 获取雅可比矩阵
    Eigen::Matrix<double, 3, 3, Eigen::ColMajor> J1 = edge->jacobianOplusXi();
    Eigen::Matrix<double, 3, 3, Eigen::ColMajor> J2 = edge->jacobianOplusXj();
    Eigen::Matrix3d Omega = edge->information(); // 信息矩阵

    // 手动构造 H 和 b
    Eigen::Matrix<double, 3, 3> H11 = J1.transpose() * Omega * J1;
    Eigen::Matrix<double, 3, 3> H12 = J1.transpose() * Omega * J2;
    Eigen::Matrix<double, 3, 3> H22 = J2.transpose() * Omega * J2;

    Eigen::Vector3d e = edge->error();

    Eigen::Vector3d b1 = J1.transpose() * Omega * e;
    Eigen::Vector3d b2 = J2.transpose() * Omega * e;

    std::cout << "H11:\n" << H11 << "\n";
    std::cout << "H12:\n" << H12 << "\n";
    std::cout << "H22:\n" << H22 << "\n";
    std::cout << "b1:\n" << b1 << "\n";
    std::cout << "b2:\n" << b2 << "\n";

    return 0;
}

2. 在 ICP / 点云优化中

残差形式:

ri=pitarget−T⋅pisource r_i = \mathbf{p}_i^{\text{target}} - T \cdot \mathbf{p}_i^{\text{source}} ri=pitarget−T⋅pisource

残差对位姿变量 δξ\delta \xiδξ 求导,得到 Jacobian,然后构造:

H=∑iJi⊤Ji,b=∑iJi⊤ri H = \sum_i J_i^\top J_i, \quad b = \sum_i J_i^\top r_i H=i∑Ji⊤Ji,b=i∑Ji⊤ri

代码示例(部分内容)

cpp 复制代码
Eigen::Matrix<double, 6, 6> H = Eigen::Matrix<double, 6, 6>::Zero();
Eigen::Matrix<double, 6, 1> b = Eigen::Matrix<double, 6, 1>::Zero();

for (int i = 0; i < N; ++i) {
    Eigen::Vector3d pi = source[i];
    Eigen::Vector3d qi = target[i];

    Eigen::Vector3d transformed = R * pi + t;
    Eigen::Vector3d r = qi - transformed;

    Eigen::Matrix<double, 3, 6> Ji;
    Ji.block<3,3>(0,0) = -Eigen::Matrix3d::Identity();
    Ji.block<3,3>(0,3) = R * skewSymmetric(pi);

    H += Ji.transpose() * Ji;
    b += Ji.transpose() * r;
}

其中 skewSymmetric(p) 为构造叉乘矩阵的函数。



4、Hessian 矩阵的结构特性

  • 稀疏性:每个残差只与少量变量有关(如仅涉及两个相邻位姿),因此 Hessian 是稀疏对称正定矩阵;
  • 块结构:Hessian 可分块处理(每个块是某对变量之间的二阶互导);
  • 可增量维护:可通过 iSAM 增量式维护 Hessian / 信息矩阵。

5、实际系统构建 Hessian 的代码结构(伪代码)

cpp 复制代码
Matrix H = Zero(n, n); // n 变量总数
Vector b = Zero(n);

for (Residual& r : residuals) {
    Vector r_i = r.evaluate(x);
    Matrix J_i = r.jacobian(x);

    H += J_i.transpose() * J_i;
    b += J_i.transpose() * r_i;
}

Solve(H * dx = -b);
x = x + dx;

6、GTSAM 中的 Hessian 与信息矩阵

在 GTSAM 中:

  • 每个误差项对应一个 GaussianFactor

  • 系统构建整体的 HessianFactor

    12x⊤Hx−x⊤b \frac{1}{2} \mathbf{x}^\top H \mathbf{x} - \mathbf{x}^\top \mathbf{b} 21x⊤Hx−x⊤b

用于求解最大后验估计(MAP)问题。
获取信息矩阵代码示例

cpp 复制代码
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/linear/HessianFactor.h>

using namespace gtsam;

// 假设你已有 factorGraph 和初始值 initialEstimate
NonlinearFactorGraph graph;
Values initialEstimate;

// Step 1: 线性化
GaussianFactorGraph::shared_ptr linearGraph = graph.linearize(initialEstimate);

// Step 2: 转为单个 HessianFactor(组合所有项)
boost::shared_ptr<HessianFactor> hessianFactor = 
    boost::dynamic_pointer_cast<HessianFactor>(linearGraph->toHessianFactor());

// Step 3: 提取 Hessian 矩阵 H 和向量 b
Matrix H;
Vector b;
Vector d; // 误差项常数项
hessianFactor->info(H, b, d); // info = 信息矩阵(H)

std::cout << "Hessian Matrix H:\n" << H << std::endl;
std::cout << "Gradient vector b:\n" << b << std::endl;

7、相关优化方法对 Hessian 的依赖

方法 Hessian 使用方式
Gauss-Newton 使用近似 Hessian H=J⊤JH = J^\top JH=J⊤J
Levenberg-Marquardt 在 Gauss-Newton Hessian 上加阻尼项 H+λIH + \lambda IH+λI
Dogleg 使用 Hessian 构建信赖域
Ceres Solver 自动构建稀疏 Jacobian 与 Hessian,支持 Dense/Schur

8、总结表格

内容 作用
Hessian 矩阵 HHH 描述误差函数的局部二阶结构
如何构建 H=∑iJi⊤JiH = \sum_i J_i^\top J_iH=∑iJi⊤Ji,基于残差 Jacobian
在线性系统中作用 解 HΔx=−bH \Delta x = -bHΔx=−b,得到优化步长
稀疏性 对图优化系统极为重要,可用稀疏求解器(如 CHOLMOD、Eigen::SimplicialLDLT)
GTSAM 应用 HessianFactor、GaussianFactorGraph、稀疏线性解系统
点云注册中作用 提高配准精度、支持高效的刚体估计

相关推荐
屁股割了还要学2 分钟前
【C语言进阶】结构体练习:通讯录
c语言·开发语言·学习·算法·青少年编程
青云交28 分钟前
Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用(355)
java·大数据·机器学习·lstm·金融市场·波动预测·资产配置
码出极致33 分钟前
快排必背模板(Java)| 分治 + 双指针 + 3 步口诀
算法
码出极致40 分钟前
三线程交替打印必背模板(Java)| Lock+Condition+4步口诀
算法
码出极致1 小时前
Java 实现 LRU 缓存:一行代码 vs 手写双向链表(附记忆口诀)
算法
inrgihc1 小时前
基于MySQL实现分布式调度系统的选举算法
数据库·mysql·算法
刚入坑的新人编程1 小时前
暑期算法训练.5
数据结构·c++·算法
秋难降1 小时前
聊聊广度优先搜索~~~
python·算法
alim20121 小时前
图论基本算法
python·算法·图论
码出极致2 小时前
Java 链表反转:双指针法 + 5 步记忆口诀,面试手写 1 分钟搞定
算法