第二讲:三维空间刚体运动(上)

各位同学好,我是你们的VIO课程讲师。今天我们来聊聊三维空间刚体运动,这是整个VIO系统的数学基础。说白了,就是搞清楚相机和IMU在三维空间里到底是怎么动的。

我个人习惯把这一讲拆成两半:上半场讲旋转的数学表达,下半场讲Eigen库怎么用。今天我们先搞定上半场。

1. 旋转矩阵:最直观的旋转表达

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

举个例子:假设相机坐标系下有个点pc,世界坐标系下是pw。那么:

p_c = R * p_w + t

这里的R就是旋转矩阵。嗯,这里要注意:R的每一列都是单位正交的,而且RT = R-1

核心性质:

  • RTR = I(正交性)
  • det(R) = +1(右手系)
  • 旋转矩阵构成特殊正交群SO(3)

我在项目中遇到过一个问题:直接用9个参数表示旋转,优化时很容易跑飞。因为旋转矩阵的约束太强了,你随便加个微小扰动,它就不再是旋转矩阵了。所以实际工程中,我们很少直接用旋转矩阵做优化。

3. 旋转向量:最紧凑的表达

旋转向量,也叫轴角。它用一个三维向量表示旋转:方向是旋转轴,模长是旋转角度。

为什么需要它?因为旋转矩阵有9个参数,但自由度只有3个。说白了就是冗余。旋转向量只有3个参数,紧凑多了。

从旋转向量到旋转矩阵,用罗德里格斯公式:

R = cosθ * I + (1-cosθ) * nnT + sinθ * n^

其中n是单位旋转轴,θ是旋转角,n^是n的反对称矩阵。

我的经验:旋转向量在SLAM后端优化中非常常用。因为它是无约束的3维向量,可以直接作为优化变量。我曾经在VINS-Mono里就用旋转向量做状态量,效果不错。

4. 欧拉角:最符合直觉的表达

欧拉角把旋转分解成绕三个轴的依次旋转。常见的顺序有ZYX、ZYZ等。在航空领域,常用的是偏航-俯仰-滚转(Yaw-Pitch-Roll)。

但欧拉角有个致命问题:万向锁。当俯仰角接近±90°时,偏航和滚转的旋转轴会重合,丢失一个自由度。

避坑指南:我曾经在无人机项目里用欧拉角做姿态控制,结果飞机做大机动时直接失控了。后来查了半天,发现是万向锁导致的。从那以后,我只要涉及大角度旋转,一律用四元数。

所以欧拉角适合做可视化展示,但不适合做数学计算。你想想看,如果两个姿态之间的角度差很大,用欧拉角插值会得到非常诡异的路径。

5. 四元数:最优雅的表达

四元数,说白了就是复数的推广。一个四元数q = w + xi + yj + zk,其中i²=j²=k²=ijk=-1。

单位四元数可以表示旋转:

q = [cos(θ/2), n*sin(θ/2)]

其中n是单位旋转轴,θ是旋转角。

四元数的优点:

  • 无万向锁
  • 插值平滑(球面线性插值SLERP)
  • 计算效率高(比旋转矩阵快)

四元数的乘法:

q1 * q2 = [w1*w2 - v1·v2, w1*v2 + w2*v1 + v1×v2]

嗯,这里要注意:四元数乘法不满足交换律。顺序很重要!

核心公式:用四元数旋转一个三维点p:

p' = q * p * q-1

其中p写成纯四元数[0, px, py, pz]

6. Eigen库基础操作与几何模块

Eigen是C++里最常用的线性代数库。我建议你直接把它当成标配,就像Python里的NumPy一样。

先看基础操作:

#include <Eigen/Core>

// 定义矩阵
Eigen::Matrix3d R;  // 3x3 double矩阵
Eigen::Vector3d v;  // 3维向量

// 赋值
R << 1, 0, 0,
     0, 1, 0,
     0, 0, 1;

v << 1, 2, 3;

// 运算
Eigen::Vector3d result = R * v;  // 矩阵乘向量

再看几何模块:

#include <Eigen/Geometry>

// 旋转向量
Eigen::AngleAxisd rotation_vector(M_PI/4, Eigen::Vector3d(0,0,1));

// 旋转矩阵
Eigen::Matrix3d R = rotation_vector.matrix();

// 四元数
Eigen::Quaterniond q(rotation_vector);

// 欧拉角
Eigen::Vector3d euler = R.eulerAngles(2,1,0);  // ZYX顺序

我的习惯:在VIO代码里,我通常用四元数做状态量,用旋转向量做增量更新。因为四元数做乘法方便,旋转向量做加法方便。两者配合,完美。

Eigen的几何模块还提供了变换矩阵:

Eigen::Isometry3d T = Eigen::Isometry3d::Identity();
T.rotate(rotation_vector);
T.pretranslate(Eigen::Vector3d(1,2,3));

Eigen::Vector3d p_w(0,0,0);
Eigen::Vector3d p_c = T * p_w;  // 齐次坐标变换

这里要注意:Isometry3d本质上是4x4矩阵,但Eigen会优化它,只存储旋转和平移部分,节省计算量。

知识体系总览

下面这张图总结了四种旋转表达方式的关系:

三维空间旋转表达方式 旋转矩阵 9参数,有约束 旋转向量 3参数,无约束 欧拉角 3参数,有万向锁 四元数 4参数,无奇点 罗德里格斯 分解 转换公式 应用场景:SLAM后端优化、IMU预积分、相机位姿估计 工程推荐:四元数做状态量 + 旋转向量做增量更新 四种表达方式各有优劣,实际工程中需根据场景灵活选择

这张图把四种表达方式的关系和推荐用法都画出来了。我个人建议:做VIO时,状态量用四元数,更新量用旋转向量。这样既避免了万向锁,又方便做优化。

重要提醒:Eigen默认是列优先存储。如果你用行优先的矩阵,记得加Eigen::RowMajor。我曾经因为这个bug调了一整天,最后发现是存储顺序搞反了。

好了,今天的内容就到这里。旋转矩阵、旋转向量、欧拉角、四元数,这四种表达方式各有各的用武之地。Eigen库的几何模块把这些都封装好了,用起来很方便。下一讲我们会继续聊三维空间刚体运动的下半场:李群李代数。


公众号:蓝海资料掘金营,微信deep3321