【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 ∣∣e−Hδx∣∣2=∣∣QT(e−Hδx)∣∣2=∣∣QTe−Rδx∣∣2
由于R是上三角矩阵,当H的行数大于列数时,R的底部部分将是零矩阵。因此,我们可以仅保留非零部分来压缩测量,而不会损失信息。
2. 详细数学推导
2.1 压缩前的优化问题
考虑标准非线性最小二乘问题:
min δ ∣ ∣ e − H δ x ∣ ∣ 2 \min_\delta ||e - H\delta x||^2 δmin∣∣e−Hδx∣∣2
其中:
- 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(e−Hδx)∣∣2=δmin∣∣e1−R1δx∣∣2+∣∣e2∣∣2
由于||e₂||²与δx无关,可以忽略,因此等价于:
min δ ∣ ∣ e 1 − R 1 δ x ∣ ∣ 2 \min_\delta ||e_1 - R_1\delta x||^2 δmin∣∣e1−R1δx∣∣2
这是一个维度为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=[c−ssc]
其中:
- 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} c⋅Hx(m−1,n)+s⋅Hx(m,n)=Hx(m−1,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 −s⋅Hx(m−1,n)+c⋅Hx(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. 技术要点总结
-
数值稳定性:使用Givens旋转实现QR分解比直接计算更数值稳定,特别是对于接近奇异的矩阵
-
原地操作:整个算法采用原地操作,避免了额外的内存分配,提高了计算效率
-
应用场景:该技术特别适用于MSCKF等使用滑动窗口优化的算法,当有大量特征观测时,可以显著减少计算量
-
等价性保证:压缩后的优化问题与原始问题在数学上是等价的,不会损失优化精度
-
算法来源:该实现基于《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()); // 相应地调整残差向量大小
}


3万+

被折叠的 条评论
为什么被折叠?



