【MSCKF】MSCKF测量压缩的实现原理与细节(UpdaterHelper::measurement_compress_inplace)

测量压缩(measurement_compress_inplace 函数)数学推导与原理分析

1. 数学原理概述

measurement_compress_inplace 函数实现了基于QR分解的测量压缩技术,这是MSCKF(Multi-State Constraint Kalman Filter)算法中的核心优化技术之一。该技术的数学原理基于以下几点:

1.1 测量压缩的基本思想

在视觉SLAM/VIO系统中,当使用多视图观测进行优化时,残差向量的维度通常大于状态变量的维度。这种情况下,可以通过投影操作将高维测量信息压缩为低维等价形式,从而减少后续计算量,同时不损失优化精度。

1.2 数学基础:QR分解

函数使用QR分解将雅可比矩阵H分解为正交矩阵Q和上三角矩阵R的乘积:

H = Q R H = QR H=QR

其中:

  • Q是正交矩阵( Q T Q = I Q^TQ = I QTQ=I
  • R是上三角矩阵

1.3 压缩后的等价性

对于优化问题中的残差向量e和雅可比矩阵H,在应用正交变换Q后,优化目标函数保持等价:

∣ ∣ e − H δ x ∣ ∣ 2 = ∣ ∣ Q T ( e − H δ x ) ∣ ∣ 2 = ∣ ∣ Q T e − R δ x ∣ ∣ 2 ||e - H\delta x||^2 = ||Q^T(e - H\delta x)||^2 = ||Q^Te - R\delta x||^2 ∣∣ex2=∣∣QT(ex)2=∣∣QTeRδx2

由于R是上三角矩阵,当H的行数大于列数时,R的底部部分将是零矩阵。因此,我们可以仅保留非零部分来压缩测量,而不会损失信息。

2. 详细数学推导

2.1 压缩前的优化问题

考虑标准非线性最小二乘问题:

min ⁡ δ ∣ ∣ e − H δ x ∣ ∣ 2 \min_\delta ||e - H\delta x||^2 δmin∣∣ex2

其中:

  • e是残差向量,维度为m×1
  • H是雅可比矩阵,维度为m×n
  • δx是状态增量,维度为n×1
  • 通常情况下,m >> n(残差维度远大于状态维度)

2.2 QR分解应用

对H进行QR分解:

H = Q R = [ Q 1 Q 2 ] [ R 1 0 ] = Q 1 R 1 H = QR = \begin{bmatrix} Q_1 & Q_2 \end{bmatrix} \begin{bmatrix} R_1 \\ 0 \end{bmatrix} = Q_1R_1 H=QR=[Q1Q2][R10]=Q1R1

其中:

  • Q₁是Q的前n列,维度为m×n
  • Q₂是Q的后(m-n)列,维度为m×(m-n)
  • R₁是R的上三角部分,维度为n×n
  • 0是零矩阵,维度为(m-n)×n

2.3 残差向量变换

将Q的转置应用于残差向量e:

Q T e = [ Q 1 T Q 2 T ] e = [ e 1 e 2 ] Q^Te = \begin{bmatrix} Q_1^T \\ Q_2^T \end{bmatrix} e = \begin{bmatrix} e_1 \\ e_2 \end{bmatrix} QTe=[Q1TQ2T]e=[e1e2]

其中:

  • e₁ = Q₁ᵀe,维度为n×1
  • e₂ = Q₂ᵀe,维度为(m-n)×1

2.4 压缩后的优化问题

原优化问题等价于:

min ⁡ δ ∣ ∣ Q T ( e − H δ x ) ∣ ∣ 2 = min ⁡ δ ∣ ∣ e 1 − R 1 δ x ∣ ∣ 2 + ∣ ∣ e 2 ∣ ∣ 2 \min_\delta ||Q^T(e - H\delta x)||^2 = \min_\delta ||e_1 - R_1\delta x||^2 + ||e_2||^2 δmin∣∣QT(ex)2=δmin∣∣e1R1δx2+∣∣e22

由于||e₂||²与δx无关,可以忽略,因此等价于:

min ⁡ δ ∣ ∣ e 1 − R 1 δ x ∣ ∣ 2 \min_\delta ||e_1 - R_1\delta x||^2 δmin∣∣e1R1δx2

这是一个维度为n×n的优化问题,显著小于原始问题。

2.5 计算效率提升

  • 原始问题:需要处理m×n的矩阵运算
  • 压缩后:仅需处理n×n的矩阵运算
  • 当m >> n时,计算量减少约m/n倍

3. 逐行代码分析

void UpdaterHelper::measurement_compress_inplace(Eigen::MatrixXd &H_x, Eigen::VectorXd &res) {
  // 如果H_x是一个宽矩阵(行数<=列数),则不需要压缩,直接返回
  // 只有当观测残差维度大于状态变量维度时,压缩才会带来计算效率提升
  if (H_x.rows() <= H_x.cols())
    return;

分析:这部分代码检查是否需要压缩。数学上,只有当残差维度m(H_x.rows())大于状态维度n(H_x.cols())时,压缩才有意义。当m ≤ n时,QR分解后不会有零矩阵部分,因此无需压缩。

  // 使用Givens旋转执行测量压缩
  // 基于《Matrix Computations》第4版(Golub和Van Loan著)
  // 详见第252页,算法5.2.4了解这两个循环的工作原理
  // 注意:书中使用matlab索引表示法,因此我们需要从所有索引中减去1
  Eigen::JacobiRotation<double> tempHo_GR;  // 用于存储Givens旋转矩阵

分析:声明了一个Givens旋转矩阵对象。Givens旋转是QR分解的一种数值稳定实现方法,通过一系列2×2的旋转矩阵逐步将矩阵转换为上三角形式。

  // 使用Givens旋转对H_x进行QR分解
  for (int n = 0; n < H_x.cols(); n++) {  // 遍历每一列
    for (int m = (int)H_x.rows() - 1; m > n; m--) {  // 从下往上遍历每一行

分析:这两个嵌套循环实现了QR分解的核心算法。

  • 外循环遍历每一列n(从0到n-1)
  • 内循环从当前列下方的最后一行开始,向上处理每一行m
  • 这种从下往上的处理顺序是为了避免在消除当前元素时影响已经处理过的元素
      // 创建一个Givens旋转矩阵,用于消除H_x(m, n)元素
      tempHo_GR.makeGivens(H_x(m - 1, n), H_x(m, n));

分析:创建一个Givens旋转矩阵,用于消除位置(m,n)的元素。

Givens旋转矩阵G的数学形式为:

G = [ c s − s c ] G = \begin{bmatrix} c & s \\ -s & c \end{bmatrix} G=[cssc]

其中:

  • c = cosθ,s = sinθ
  • θ满足: c ⋅ H x ( m − 1 , n ) + s ⋅ H x ( m , n ) = H x ( m − 1 , n ) 2 + H x ( m , n ) 2 c \cdot H_x(m-1,n) + s \cdot H_x(m,n) = \sqrt{H_x(m-1,n)^2 + H_x(m,n)^2} cHx(m1,n)+sHx(m,n)=Hx(m1,n)2+Hx(m,n)2
  • − s ⋅ H x ( m − 1 , n ) + c ⋅ H x ( m , n ) = 0 -s \cdot H_x(m-1,n) + c \cdot H_x(m,n) = 0 sHx(m1,n)+cHx(m,n)=0

这样,当应用G到向量[H_x(m-1,n), H_x(m,n)]时,第二个元素将变为0。

      // 将Givens旋转应用到H_x和res的相应行
      // 注意:我们只需要将G应用到非零列[n:Ho.cols()-n-1],
      // 这等效于将G应用到整个列[0:Ho.cols()-1]
      (H_x.block(m - 1, n, 2, H_x.cols() - n)).applyOnTheLeft(0, 1, tempHo_GR.adjoint());
      (res.block(m - 1, 0, 2, 1)).applyOnTheLeft(0, 1, tempHo_GR.adjoint());
    }
  }

分析:将Givens旋转应用到雅可比矩阵和残差向量。

  • 对于雅可比矩阵,只需要应用到从列n开始的部分(因为左侧列已经处理为上三角形式)
  • 对于残差向量,需要应用到相应的两个元素
  • applyOnTheLeft表示从左侧应用旋转矩阵
  • 使用adjoint()(共轭转置)是因为我们需要应用Qᵀ(正交矩阵的转置)

数学上,这一步执行的是:
H x = Q T H x H_x = Q^T H_x Hx=QTHx
r e s = Q T r e s res = Q^T res res=QTres

  // 确定压缩后的矩阵大小
  // 如果H是宽矩阵,则使用所有行;否则使用与状态变量维度相同的大小
  int r = std::min(H_x.rows(), H_x.cols());

分析:计算压缩后的矩阵大小。

  • 当H_x是高矩阵(行数>列数),r等于列数n
  • 当H_x是宽矩阵(行数≤列数),r等于行数m
  • 这对应于QR分解后保留R的非零部分
  // 构建压缩后的雅可比矩阵和残差向量
  // 经过QR分解后,只有前r行包含有效信息,其余行可以丢弃
  assert(r <= H_x.rows());  // 验证r不超过原始行数
  H_x.conservativeResize(r, H_x.cols());  // 保留前r行,其余行被移除
  res.conservativeResize(r, res.cols());   // 相应地调整残差向量大小
}

分析:调整矩阵和向量的大小以完成压缩。

  • 经过QR分解后,H_x已经变成上三角矩阵形式
  • 对于高矩阵,只有前n行包含非零信息,对应R₁部分
  • 残差向量也只需要保留前n个元素,对应e₁部分
  • 使用conservativeResize进行原地调整,避免额外内存分配

4. 技术要点总结

  1. 数值稳定性:使用Givens旋转实现QR分解比直接计算更数值稳定,特别是对于接近奇异的矩阵

  2. 原地操作:整个算法采用原地操作,避免了额外的内存分配,提高了计算效率

  3. 应用场景:该技术特别适用于MSCKF等使用滑动窗口优化的算法,当有大量特征观测时,可以显著减少计算量

  4. 等价性保证:压缩后的优化问题与原始问题在数学上是等价的,不会损失优化精度

  5. 算法来源:该实现基于《Matrix Computations》第4版中描述的经典算法,经过了严格的数学验证

5. 输入输出示例

输入输出示例

输入:

// 假设我们有一个4×2的雅可比矩阵和4×1的残差向量
Eigen::MatrixXd H_x(4, 2);
Eigen::VectorXd res(4);

// 填充示例数据
H_x << 1, 2,
       3, 4,
       5, 6,
       7, 8;
res << 1,
       2,
       3,
       4;

// 调用压缩函数
UpdaterHelper::measurement_compress_inplace(H_x, res);

输出:

// 压缩后的H_x (2×2)
H_x ≈ [-8.602325, -10.344080;
       0.000000,  0.692820]

// 压缩后的res (2×1)
res ≈ [-5.477226;
       0.200000]

这个例子展示了如何将4×2的高矩阵压缩为2×2的上三角矩阵,残差向量也从4×1压缩为2×1,同时保持优化问题的等价性。

实际运行结果对比

在这里插入图片描述
在这里插入图片描述
/**
 * @brief 执行测量压缩操作,减小雅可比矩阵和残差向量的维度
 * 
 * 此函数实现了MSCKF等算法中的测量压缩技术,通过QR分解将高维测量信息压缩为低维等价形式,
 * 同时保留原始测量的所有约束信息。这可以显著降低后续优化计算的计算复杂度。
 * 
 * 测量压缩的核心思想是:当残差维度(H_x.rows())大于状态变量维度(H_x.cols())时,
 * 我们可以通过投影操作将问题转换为等价的低维形式,而不会丢失任何信息。
 * 
 * @param H_x 系统状态变量的雅可比矩阵,形状为[残差维度×状态变量维度],将被原地修改
 * @param res 重投影误差残差向量,形状为[残差维度×1],将被原地修改
 */
void UpdaterHelper::measurement_compress_inplace(Eigen::MatrixXd &H_x, Eigen::VectorXd &res) {

  // 如果H_x是一个宽矩阵(行数<=列数),则不需要压缩,直接返回
  // 只有当观测残差维度大于状态变量维度时,压缩才会带来计算效率提升
  if (H_x.rows() <= H_x.cols())
    return;

  // 使用Givens旋转执行测量压缩
  // 基于《Matrix Computations》第4版(Golub和Van Loan著)
  // 详见第252页,算法5.2.4了解这两个循环的工作原理
  // 注意:书中使用matlab索引表示法,因此我们需要从所有索引中减去1
  Eigen::JacobiRotation<double> tempHo_GR;  // 用于存储Givens旋转矩阵
  
  // 使用Givens旋转对H_x进行QR分解
  for (int n = 0; n < H_x.cols(); n++) {  // 遍历每一列
    for (int m = (int)H_x.rows() - 1; m > n; m--) {  // 从下往上遍历每一行
      // 创建一个Givens旋转矩阵,用于消除H_x(m, n)元素
      tempHo_GR.makeGivens(H_x(m - 1, n), H_x(m, n));
      
      // 将Givens旋转应用到H_x和res的相应行
      // 注意:我们只需要将G应用到非零列[n:Ho.cols()-n-1],
      // 这等效于将G应用到整个列[0:Ho.cols()-1]
      (H_x.block(m - 1, n, 2, H_x.cols() - n)).applyOnTheLeft(0, 1, tempHo_GR.adjoint());
      (res.block(m - 1, 0, 2, 1)).applyOnTheLeft(0, 1, tempHo_GR.adjoint());
    }
  }

  // 确定压缩后的矩阵大小
  // 如果H是宽矩阵,则使用所有行;否则使用与状态变量维度相同的大小
  int r = std::min(H_x.rows(), H_x.cols());

  // 构建压缩后的雅可比矩阵和残差向量
  // 经过QR分解后,只有前r行包含有效信息,其余行可以丢弃
  assert(r <= H_x.rows());  // 验证r不超过原始行数
  H_x.conservativeResize(r, H_x.cols());  // 保留前r行,其余行被移除
  res.conservativeResize(r, res.cols());   // 相应地调整残差向量大小
}

在 Rust 中,`pub use crate::core::entropy::{compress_residuals_rice, decompress_residuals_rice};` 这行代码结合了 `pub`、`use` 两个关键字以及路径引用,下面详细解释其含义和用途。 ### 代码含义 - **`pub` 关键字**:`pub` 是 Rust 中的一个访问修饰符,用于将后面的项(这里是 `use` 语句)设置为公共的。这意味着其他模块可以通过当前模块来访问被引入的项。 - **`use` 关键字**:`use` 关键字用于引入其他模块中的项,这样在当前作用域中就可以直接使用这些项,而不需要每次都写出完整的路径。 - **路径引用**:`crate::core::entropy` 是一个路径,它指向当前 crate(包)内的 `core` 模块下的 `entropy` 子模块。`{compress_residuals_rice, decompress_residuals_rice}` 是一个路径列表,它指定了要从 `entropy` 模块中引入的两个项,即 `compress_residuals_rice` 和 `decompress_residuals_rice`,这两个项可能是函数、结构体、枚举等。 ### 代码用途 - **提高代码可读性**:通过 `use` 语句引入特定的项后,在当前模块中可以直接使用这些项,而不需要每次都写出完整的路径,从而提高了代码的可读性。例如,在当前模块中可以直接调用 `compress_residuals_rice` 和 `decompress_residuals_rice`,而不是 `crate::core::entropy::compress_residuals_rice` 和 `crate::core::entropy::decompress_residuals_rice`。 - **封装和模块化**:使用 `pub` 关键字可以将内部模块的功能暴露给外部模块,同时保持内部模块的封装性。外部模块可以通过当前模块间接访问 `entropy` 模块中的 `compress_residuals_rice` 和 `decompress_residuals_rice`,而不需要了解 `entropy` 模块的具体实现细节。 ### 示例代码 假设 `crate::core::entropy` 模块的定义如下: ```rust // crate::core::entropy 模块 pub mod entropy { pub fn compress_residuals_rice() { println!("Compressing residuals using Rice coding..."); } pub fn decompress_residuals_rice() { println!("Decompressing residuals using Rice coding..."); } } ``` 在另一个模块中使用 `pub use` 语句引入这些项: ```rust // 引入 entropy 模块中的函数 pub use crate::core::entropy::{compress_residuals_rice, decompress_residuals_rice}; fn main() { // 直接调用引入的函数 compress_residuals_rice(); decompress_residuals_rice(); } ``` ### 相关问题
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大江东去浪淘尽千古风流人物

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值