3、三维空间刚体运动(下):李群与李代数基础、SO(3)与SE(3)的指数/对数映射、BCH公式与扰动模型、Sophus库使用

好,咱们接着聊。上一节我们把旋转矩阵、变换矩阵这些“硬骨头”啃完了。但你有没有想过一个问题——我们在优化位姿的时候,比如做BA(Bundle Adjustment),总不能直接去优化一个矩阵吧?矩阵有9个参数,但自由度只有3(旋转)或6(位姿)。直接优化,约束条件一大堆,搞不好就优化到非旋转矩阵上去了。

这时候,李群和李代数就登场了。说白了,它们就是一套“在流形上做优化”的工具。我个人觉得,这是VIO里最“数学”的一块,但也是最有意思的一块。搞懂了它,你再看那些优化库的代码,心里就有底了。

3.1 李群与李代数:为什么需要它们?

先说说“群”是什么。群就是一个集合加上一种运算,满足封闭性、结合律、有单位元和逆元。旋转矩阵构成的集合,加上矩阵乘法,就是一个群,我们叫它特殊正交群SO(3)。变换矩阵构成的群,叫特殊欧氏群SE(3)。

但问题是,SO(3)和SE(3)在局部看起来是“平坦”的吗?不是。它们是流形,是弯曲的。你想想看,在球面上走直线,走着走着方向就变了。在SO(3)上做加法,结果很可能不在SO(3)上。

李代数就是流形在单位元处的切空间。它是一个向量空间,我们可以在这个向量空间里做加法、做优化,然后再通过指数映射回到流形上。这就是整个思路的核心。

核心思想:在李代数(向量空间)上做优化,再通过指数映射回到李群(流形)上。

我记得我第一次接触这个概念时,觉得特别绕。后来在项目里调试一个VIO系统,发现位姿优化总是不收敛,检查了半天,发现是直接对旋转矩阵做了加法,导致矩阵不再是正交阵。从那以后,我就老老实实用李代数了。

3.2 SO(3)与SE(3)的指数/对数映射

指数映射,就是把李代数上的向量,映射到李群上的元素。对数映射则是反过来。

3.2.1 SO(3)的指数映射

对于SO(3),它的李代数是so(3)。so(3)里的元素是三维向量,或者说是三维向量的反对称矩阵。一个三维向量φ,它的反对称矩阵记作φ^。

指数映射的公式,就是罗德里格斯公式:

R = exp(φ^) = I + (sinθ/θ) * φ^ + ((1-cosθ)/θ²) * (φ^)²

其中θ = ||φ||。你看,这个公式是不是很眼熟?其实就是旋转向量到旋转矩阵的转换。没错,李代数so(3)本质上就是旋转向量。

对数映射就是反过来,从旋转矩阵R求旋转向量φ:

θ = arccos((tr(R)-1)/2)
φ = (θ/(2sinθ)) * (R - R^T)^∨

这里(·)^∨表示从反对称矩阵恢复成向量。

个人经验:在实际代码中,要注意θ接近0时的数值稳定性。当θ很小时,直接用公式会有除零风险。我一般会判断一下,如果θ小于1e-6,就直接用泰勒展开近似。

3.2.2 SE(3)的指数映射

SE(3)的李代数是se(3)。se(3)里的元素是一个六维向量,记作ξ = [ρ, φ]^T。其中ρ是平移部分,φ是旋转部分。

指数映射的公式稍微复杂一点:

T = exp(ξ^) = [R, t; 0, 1]

其中:

R = exp(φ^)
t = J * ρ

这里的J就是之前讲过的雅可比矩阵:

J = (sinθ/θ) * I + (1 - sinθ/θ) * aa^T + ((1-cosθ)/θ) * a^

其中a = φ/θ。

对数映射就是从T = [R, t; 0, 1]求ξ = [ρ, φ]^T。先通过R求φ,再通过t和J求ρ:

ρ = J^(-1) * t

3.3 BCH公式与扰动模型

现在问题来了:我们在李代数上做加法,对应到李群上是什么?

答案是:不是简单的乘法。两个李代数指数映射的乘积,等于它们“和”的指数映射,但这个“和”不是普通的加法,而是由BCH公式给出的。

BCH公式长这样:

ln(exp(A) * exp(B)) = A + B + 1/2[A,B] + 1/12[A,[A,B]] - 1/12[B,[A,B]] + ...

其中[A,B] = AB - BA,是李括号。

这个公式在实际中很少直接用,因为太复杂了。我们更常用的是它的近似形式,也就是扰动模型

扰动模型的核心思想:在李代数上加上一个微小扰动,看李群上的变化。这样就把李群上的乘法,近似成了李代数上的加法。

对于SO(3),左扰动模型是这样的:

R * exp(φ^) ≈ exp((R * φ)^) * R

这个公式在求导时特别有用。我们想求R * p对R的导数,就可以用扰动模型,把对R的求导转化成对φ的求导。

我记得在写VIO的雅可比矩阵时,一开始总是搞不清楚该用左扰动还是右扰动。后来我统一用左扰动,因为左扰动的雅可比形式更简洁,而且和大多数开源代码(比如OKVIS、VINS-Mono)保持一致。

3.4 Sophus库使用

理论说完了,咱们来看看实际怎么用。Sophus是一个C++库,专门用来处理SO(3)和SE(3)的。它封装了指数映射、对数映射、扰动模型等操作,用起来非常方便。

先看一个简单的例子:

#include <sophus/so3.hpp>
#include <sophus/se3.hpp>
#include <iostream>

int main() {
    // 创建一个SO(3)对象,从旋转矩阵
    Eigen::Matrix3d R = Eigen::AngleAxisd(M_PI/4, Eigen::Vector3d(0,0,1)).toRotationMatrix();
    Sophus::SO3d SO3_R(R);
    
    // 从旋转向量
    Sophus::SO3d SO3_v(0, 0, M_PI/4);
    
    // 从四元数
    Eigen::Quaterniond q(R);
    Sophus::SO3d SO3_q(q);
    
    // 对数映射:SO(3) -> so(3)
    Eigen::Vector3d so3 = SO3_R.log();
    std::cout << "so3: " << so3.transpose() << std::endl;
    
    // 指数映射:so(3) -> SO(3)
    Sophus::SO3d SO3_updated = Sophus::SO3d::exp(so3 + Eigen::Vector3d(0.1, 0, 0));
    
    // 扰动模型:左乘一个小扰动
    Sophus::SO3d SO3_perturbed = Sophus::SO3d::exp(Eigen::Vector3d(0.01, 0, 0)) * SO3_R;
    
    return 0;
}

再看SE(3)的例子:

#include <sophus/se3.hpp>

int main() {
    // 创建一个SE(3)对象
    Eigen::Matrix3d R = Eigen::AngleAxisd(M_PI/4, Eigen::Vector3d(0,0,1)).toRotationMatrix();
    Eigen::Vector3d t(1, 0, 0);
    Sophus::SE3d SE3_Rt(R, t);
    
    // 对数映射:SE(3) -> se(3)
    Eigen::Matrix<double, 6, 1> se3 = SE3_Rt.log();
    std::cout << "se3: " << se3.transpose() << std::endl;
    
    // 指数映射:se(3) -> SE(3)
    Sophus::SE3d SE3_updated = Sophus::SE3d::exp(se3 + Eigen::Matrix<double, 6, 1>::Zero());
    
    // 对点进行变换
    Eigen::Vector3d pt(1, 2, 3);
    Eigen::Vector3d pt_transformed = SE3_Rt * pt;
    
    return 0;
}

避坑指南:我曾经在Sophus的版本上踩过坑。老版本(比如1.0.0)的接口和新版本(比如22.04)的接口不一样。老版本用SO3Group,新版本用SO3d。建议统一用新版本,并且注意Eigen的对齐问题,特别是STL容器里放Sophus对象时,要加上EIGEN_MAKE_ALIGNED_OPERATOR_NEW。

3.5 知识体系总览

为了让你更直观地理解这一章的知识结构,我画了一张图:

三维空间刚体运动(下):李群与李代数 李群 (Lie Group) SO(3): 旋转矩阵 SE(3): 变换矩阵 流形结构 封闭性、结合律 单位元、逆元 李代数 (Lie Algebra) so(3): 旋转向量 se(3): 六维向量 切空间 向量空间 李括号 指数映射 exp() 对数映射 log() BCH公式与扰动模型 ln(exp(A) * exp(B)) = A + B + 1/2[A,B] + ... 左扰动:R * exp(φ^) ≈ exp((R·φ)^) * R 右扰动:exp(φ^) * R ≈ R * exp((R^T·φ)^) Sophus库使用 SO3d / SE3d 类 .log() / .exp() 映射 左乘扰动、变换点 核心:在李代数(向量空间)上优化 → 指数映射 → 李群(流形)

这张图把这一章的核心逻辑串起来了。左边是李群,右边是李代数,中间通过指数/对数映射连接。下面是BCH公式和Sophus库,它们是实际工程中用的工具。

嗯,这一章的内容确实有点抽象。但别急,等你真正开始写VIO代码,处理位姿优化时,你就会发现这些工具是多么好用。下一节我们会把这些知识用到实际的VIO前端中,看看怎么用李代数来优化相机位姿。

我的建议:学这一章,不要死记公式。你只需要记住两件事:第一,优化要在李代数上做;第二,Sophus库帮你处理了所有映射细节。剩下的,多写代码,多调试,自然就理解了。

专注资料整理

专注资料整理