3. 刚体变换与李群李代数:旋转矩阵、平移向量、欧拉角、四元数、SO(3)与SE(3)、李代数基础

各位同学,欢迎来到第三章。

这一章,我们聊聊自动驾驶感知里最绕不开的数学工具——刚体变换与李群李代数。说实话,很多工程师一听到“李群”、“李代数”就头大,觉得这是纯数学家的玩具。但我在实际项目中吃过亏,才明白这东西有多重要。

你想想看,一辆车在路上跑,激光雷达装在车顶,IMU藏在底盘,摄像头嵌在后视镜里。这些传感器各自有各自的坐标系。怎么把它们统一起来?怎么描述车在三维空间里的运动?答案就在这一章里。

3.1 旋转矩阵:最直观的旋转表达

旋转矩阵,说白了就是一个3x3的正交矩阵,行列式为+1。它描述了一个坐标系相对于另一个坐标系的朝向。

我记得刚入行时,总觉得旋转矩阵太“重”了——9个元素,但真正自由的只有3个。不过它的好处是直观,而且可以连续左乘,实现多次旋转的叠加。

核心性质:

  • 正交性:RTR = I
  • 行列式为+1
  • 逆矩阵等于转置:R-1 = RT

在点云处理中,我们经常用旋转矩阵来对齐两帧点云。比如,你有一帧当前时刻的点云,想把它变换到上一时刻的坐标系下,那就左乘一个旋转矩阵。

// C++示例:点云旋转
Eigen::Matrix3d R;
R = Eigen::AngleAxisd(0.25 * M_PI, Eigen::Vector3d::UnitZ()) *
    Eigen::AngleAxisd(0.1 * M_PI, Eigen::Vector3d::UnitY());

pcl::PointCloud<pcl::PointXYZ>::Ptr transformed_cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::transformPointCloud(*input_cloud, *transformed_cloud, R, Eigen::Vector3d::Zero());

3.2 平移向量:简单但容易踩坑

平移向量就是一个3维向量,表示从原坐标系原点到新坐标系原点的位移。听起来很简单对吧?

但我在项目中遇到过一个问题:平移向量是在哪个坐标系下表达的?是原坐标系,还是目标坐标系?

嗯,这里要注意。通常,平移向量是在原坐标系下表达的。也就是说,如果你想把一个点从坐标系A变换到坐标系B,平移向量表示的是“B的原点在A中的位置”。

我曾经踩过的坑:在做多传感器标定时,把平移向量的方向搞反了。结果标定出来的外参,车明明是直行,点云却往左偏。查了两天才发现是平移向量符号错了。所以,每次写完变换代码,先拿一个已知点验证一下。

3.3 欧拉角:直观但小心万向锁

欧拉角用三个角度来描述旋转:偏航角(yaw)、俯仰角(pitch)、滚转角(roll)。在自动驾驶里,我们通常用ZYX顺序——先绕Z轴转yaw,再绕Y轴转pitch,最后绕X轴转roll。

欧拉角的好处是直观,人脑容易理解。但它的致命问题是万向锁(Gimbal Lock)。当pitch接近±90°时,yaw和roll的旋转轴会重合,丢失一个自由度。

为什么会这样?因为欧拉角本质上是三个依次执行的旋转,当中间那个旋转达到90°时,第一个和第三个旋转轴就对齐了。

我的建议:在代码内部,尽量用旋转矩阵或四元数。欧拉角只用来做可视化展示,或者给用户输入输出。别在算法核心逻辑里用欧拉角做插值或优化,你会后悔的。

3.4 四元数:高效且无奇点

四元数,我个人的最爱。它是一个四维向量q = [w, x, y, z],其中w是实部,x,y,z是虚部。单位四元数可以表示任意三维旋转。

四元数的好处太多了:

  • 没有万向锁问题
  • 插值平滑(球面线性插值SLERP)
  • 计算效率高,比旋转矩阵少乘几次
  • 存储只需要4个浮点数

我记得在做IMU和LiDAR的融合时,IMU输出的就是四元数。直接用四元数做姿态预测,比转成欧拉角再转回来要稳定得多。

// 四元数转旋转矩阵
Eigen::Quaterniond q(0.707, 0.0, 0.707, 0.0);  // w, x, y, z
Eigen::Matrix3d R = q.toRotationMatrix();

// 旋转一个点
Eigen::Vector3d p(1.0, 0.0, 0.0);
Eigen::Vector3d p_rotated = q * p;  // 注意:四元数乘法

3.5 SO(3)与SE(3):旋转与刚体变换的数学结构

SO(3)是特殊正交群,所有3x3旋转矩阵的集合。SE(3)是特殊欧几里得群,所有4x4刚体变换矩阵的集合。

说白了,SO(3)管旋转,SE(3)管旋转加平移。一个SE(3)元素长这样:

| R  t |
| 0  1 |

其中R是3x3旋转矩阵,t是3x1平移向量。

为什么要把它们叫做“群”?因为满足群的四条公理:封闭性、结合律、单位元、逆元。你想想看,两个旋转矩阵相乘还是旋转矩阵,对吧?这就是封闭性。

实际应用:在SLAM和点云配准中,我们经常用SE(3)来表示相机的位姿。比如,ICP算法的每一步迭代,都是在SE(3)上寻找一个增量,使得点云对齐误差最小。

3.6 李代数基础:为什么要学它?

好,终于到了最“劝退”的部分。李代数,到底是什么?

简单说,李代数是李群在单位元处的切空间。SO(3)对应的李代数是so(3),SE(3)对应的李代数是se(3)。

你可能会问:我直接用旋转矩阵做优化不行吗?

行,但不好。因为旋转矩阵有正交约束,直接优化的话,每次迭代完还得投影回SO(3)上,麻烦得很。而李代数是无约束的,可以在欧几里得空间里随便优化,最后通过指数映射回到李群上。

我在做图优化时,就深刻体会到了李代数的威力。用李代数表示位姿,优化变量只有6个(3个旋转+3个平移),而且不用考虑约束,优化器跑得又快又稳。

核心公式:

  • 指数映射:exp(so(3)) → SO(3)
  • 对数映射:log(SO(3)) → so(3)
  • 对于小量δ,有:exp(δ) ≈ I + δ(一阶近似)
// 使用Sophus库(一个常用的李代数C++库)
#include <sophus/so3.hpp>
#include <sophus/se3.hpp>

// 构造SO(3)
Sophus::SO3d R = Sophus::SO3d::rotZ(0.5);

// 李代数so(3)是一个3维向量
Eigen::Vector3d omega(0.1, 0.0, 0.0);
Sophus::SO3d R_new = Sophus::SO3d::exp(omega) * R;

// SE(3)同理
Sophus::SE3d T(R, Eigen::Vector3d(1.0, 2.0, 3.0));
Eigen::Vector3d t = T.translation();
Sophus::SO3d R_part = T.so3();

3.7 知识体系总览

下面这张图,是我自己整理的本章知识结构。你可以把它当作一个思维导图来用。

刚体变换与李群李代数 旋转表示方法 变换矩阵与群 优化与李代数 旋转矩阵 欧拉角 四元数 轴角 SO(3)群 SE(3)群 平移向量 齐次坐标 so(3)李代数 se(3)李代数 指数映射 对数映射 核心:用无约束的李代数优化,映射回有约束的李群

3.8 实战中的选择建议

讲了这么多,你可能会问:在实际项目中,到底该用哪种表示?

我的经验是这样的:

场景 推荐表示 原因
传感器标定 SE(3) + 李代数优化 无约束优化,收敛快
点云配准(ICP) 四元数 + 平移向量 插值平滑,无奇点
IMU姿态估计 四元数 IMU原生输出,避免转换误差
可视化/用户交互 欧拉角 人脑友好,容易理解
SLAM后端优化 SE(3) + 李代数 图优化标准做法

好了,这一章的内容就到这里。记住,数学工具是拿来用的,不是拿来背的。多写代码,多跑实验,慢慢就熟了。

可可精品资料库