3. 3D空间刚体运动:旋转矩阵、四元数、欧拉角、李群与李代数基础

各位同学,欢迎来到我们实战课的第一章。说实话,每次讲这部分内容,我都挺感慨的。刚入行那会儿,我觉得SLAM就是跑跑开源算法,调调参数。直到有一次,我在一个AGV项目里,因为四元数搞反了顺序,导致机器人原地转圈,差点撞到货架。从那以后,我才真正重视起这些“枯燥”的数学基础。

今天我们要聊的,是3D空间中刚体运动的几种数学表达。说白了,就是回答一个问题:一个物体在三维空间里,怎么描述它“动了”以及“转到哪了”? 你想想看,无论是视觉SLAM还是激光雷达建图,核心都在处理这个问题。

核心观点: 旋转矩阵、四元数、欧拉角、李群与李代数,它们不是互相替代的关系,而是不同场景下的不同工具。理解它们的优缺点,比死记硬背公式重要得多。

3D空间刚体运动表达方式 旋转矩阵 SO(3)群元素 9个参数,冗余 适合坐标变换 四元数 4个参数,无奇点 适合插值与滤波 SLAM中常用 欧拉角 直观,3个参数 有万向锁问题 适合人机交互 李群/李代数 SO(3)/se(3) 无约束优化 BA优化的核心 核心关系 旋转矩阵 ↔ 四元数 ↔ 欧拉角 可以互相转换 李群(SO(3)) 与 李代数(so(3)) 通过指数/对数映射关联

3.1 旋转矩阵:最直观,但最“浪费”

旋转矩阵,其实就是3x3的矩阵,属于SO(3)群。它满足两个性质:行列式为+1,且是正交矩阵(转置等于逆)。

我个人习惯,在写代码时用旋转矩阵做坐标变换。为什么呢?因为矩阵乘法可以直接把点从相机坐标系转到世界坐标系,非常直接。

// C++ 示例:用旋转矩阵做坐标变换
Eigen::Matrix3d R;  // 旋转矩阵
Eigen::Vector3d t;  // 平移向量
Eigen::Vector3d p_cam;  // 相机坐标系下的点

// 变换到世界坐标系
Eigen::Vector3d p_world = R * p_cam + t;

我的经验: 旋转矩阵有9个参数,但自由度只有3。这意味着它存在冗余。在优化问题中,直接优化9个参数会引入额外的约束,导致数值不稳定。所以,旋转矩阵适合做变换,不适合做优化变量

3.2 四元数:SLAM中的“万金油”

四元数,说白了就是一个实部加三个虚部:q = w + xi + yj + zk。它只有4个参数,没有奇点问题。我在做视觉SLAM时,几乎所有的位姿估计都用四元数。

为什么会这样?因为四元数插值非常平滑。你想想看,如果两个关键帧之间要插值出中间位姿,用欧拉角会出问题,用旋转矩阵又太复杂。四元数的球面线性插值(Slerp)刚好解决这个问题。

// C++ 示例:四元数初始化与旋转
Eigen::Quaterniond q(1.0, 0.0, 0.0, 0.0);  // 单位四元数
q = Eigen::Quaterniond(0.707, 0.707, 0.0, 0.0);  // 绕Z轴旋转90度

// 用四元数旋转一个点
Eigen::Vector3d v(1, 0, 0);
Eigen::Vector3d v_rot = q * v;  // 注意:Eigen重载了*运算符

避坑指南: 我曾经在项目中犯过一个低级错误——四元数的顺序搞反了。Eigen中四元数的构造函数是(w, x, y, z),但有些库是(x, y, z, w)。一定要确认你用的库的约定! 否则你会发现机器人朝完全相反的方向运动。

3.3 欧拉角:直观,但别在SLAM里用

欧拉角用三个角度表示旋转:偏航(Yaw)、俯仰(Pitch)、横滚(Roll)。非常直观,适合给人看。但它在SLAM里几乎是个“坑”。

最大的问题就是万向锁。当俯仰角达到±90度时,偏航和横滚会失去一个自由度。我记得有一次调试无人机,用欧拉角做姿态控制,结果飞机翻了个跟头——就是因为万向锁导致控制失效。

表达方式 参数数量 优点 缺点 SLAM中使用
旋转矩阵 9 变换直接 冗余,有约束 坐标变换
四元数 4 无奇点,可插值 不够直观 位姿估计
欧拉角 3 直观 万向锁 可视化
李代数 3 无约束优化 需要指数映射 BA优化

3.4 李群与李代数:优化的“秘密武器”

这部分可能是最抽象的,但也是最有用的。李群SO(3)是旋转矩阵的集合,而李代数so(3)是三维向量的集合。它们之间通过指数映射和对数映射关联。

说白了,李代数解决了优化中的一个核心问题:旋转矩阵的优化是有约束的(正交且行列式为1),而李代数是无约束的。在SLAM的后端优化(Bundle Adjustment)中,我们都是在李代数上做优化,然后映射回旋转矩阵。

// C++ 示例:李代数到旋转矩阵的指数映射
Eigen::Vector3d omega(0.1, 0.2, 0.3);  // 李代数 so(3)
Eigen::Matrix3d R = Sophus::SO3d::exp(omega).matrix();  // 指数映射

// 旋转矩阵到李代数的对数映射
Eigen::Vector3d omega2 = Sophus::SO3d::log(Sophus::SO3d(R));

核心理解: 李代数对应的是“旋转的导数”。在优化时,我们求解的是李代数上的增量,然后通过指数映射更新旋转矩阵。这样既保证了旋转矩阵的约束,又让优化变得简单。

3.5 实战中的选择建议

嗯,说了这么多,到底该用哪个?我给大家一个简单的建议:

  • 做坐标变换:用旋转矩阵,直接乘就行
  • 做位姿估计和滤波:用四元数,EKF和ESKF都支持
  • 给人看:转成欧拉角,但注意万向锁
  • 做优化:用李代数,这是SLAM后端的标准做法

我的习惯: 在代码内部,我统一用四元数+平移向量表示位姿。只有在需要做BA优化时,才转成李代数。这样既保证了代码的可读性,又兼顾了优化的效率。

好了,这一章的内容就到这里。记住,这些数学工具没有好坏之分,只有合不合适。在实际项目中,多试试不同的表达方式,你自然就能找到最适合的那一个。


专注资料整理