3. 刚体旋转基础:旋转的物理意义、坐标系定义与DCM-四元数转换

各位同学,欢迎来到第三章。这一章,我们聊聊刚体旋转的基础。

说实话,很多做惯导的工程师,最后栽跟头的地方,往往不是卡尔曼滤波有多复杂,而是最基础的旋转概念没搞透。我自己刚入行那会儿,就吃过这个亏——坐标系搞反了,结果解算出来的姿态角,飞机明明是抬头,数据显示却在俯冲。嗯,那场面,挺尴尬的。

3.1 旋转的物理意义:到底什么是“转”?

我们说的“旋转”,不是数学游戏。它描述的是一个刚体,在三维空间里,绕某个轴转了多少角度。

你想想看,一个物体从A姿态到B姿态,中间发生了什么?

  • 旋转轴:绕哪根线转?
  • 旋转角:转了多少度?
  • 旋转顺序:先绕X还是先绕Y?

这三个要素,缺一不可。我见过不少新手,只给角度不给顺序,结果代码跑出来天差地别。记住:旋转顺序不是约定俗成的,是必须明确指定的

核心理解:旋转的本质,是坐标变换。不是物体在动,而是我们描述物体的“参考系”在变。

3.2 坐标系定义:导航系与机体系

做姿态解算,你手里至少要有两把“尺子”。

3.2.1 导航坐标系(n系)

导航系,也叫“世界系”。它是不动的,或者说是相对地球固定的。我们常用的是“北-东-地”(NED)或者“东-北-天”(ENU)。

  • NED:X轴指北,Y轴指东,Z轴指向地心。我个人习惯用NED,因为和大多数飞控的默认设置一致。
  • ENU:X轴指东,Y轴指北,Z轴指天。GPS输出的速度通常就是ENU。

这里有个坑:千万别混用。我曾经在项目里,把NED的加速度计数据和ENU的GPS数据直接融合,结果位置解算直接飞了。嗯,查了两天才发现是坐标系没对齐。

3.2.2 机体坐标系(b系)

机体系,是固定在飞行器上的。原点在重心,X轴指向机头,Y轴指向右翼,Z轴指向下(符合右手定则)。

你想想看,IMU(惯性测量单元)输出的加速度和角速度,都是在机体系下的。而我们想要知道的俯仰、横滚、航向,是相对于导航系的。所以,所有姿态解算的核心,就是求这两个坐标系之间的旋转关系

个人经验:在代码里,我建议用枚举类型把坐标系定义死。比如 typedef enum {NED, ENU, BODY} CoordFrame_t;。这样每次调用函数,都强制传入坐标系参数,能避免很多低级错误。

3.3 方向余弦矩阵(DCM)与四元数的转换

好了,现在我们知道需要描述两个坐标系之间的旋转。怎么描述?有两种主流方式:DCM和四元数。

3.3.1 方向余弦矩阵(DCM)

DCM,说白了就是一个3x3的矩阵。它的每一列,是机体系的一个轴在导航系下的投影。

比如,DCM的第一列,就是机体系X轴在导航系下的方向余弦。所以叫“方向余弦矩阵”。

优点:直观。你一眼就能看出每个轴指向哪。

缺点:有9个参数,冗余。而且随着时间推移,矩阵会慢慢“不正交”,需要不断修正。我在早期项目里用过DCM,每次更新完都要做正交化,挺麻烦的。

3.3.2 四元数

四元数,是一个超复数。形式是 q = w + xi + yj + zk,其中 w 是实部,x, y, z 是虚部。

它用4个参数,就描述了三维旋转。没有冗余,不会出现“非正交”的问题。而且,四元数乘法比矩阵乘法快得多——这对嵌入式平台来说,太重要了。

为什么用四元数? 因为它在计算效率和数值稳定性上,完胜DCM。现在的MEMS IMU姿态解算,几乎清一色用四元数。

3.3.3 DCM与四元数的转换

虽然我们用四元数做解算,但最终输出给飞控或者显示器的,往往是欧拉角。而欧拉角从DCM提取最方便。所以,四元数 -> DCM -> 欧拉角 这条链路,你必须烂熟于心。

下面给出转换公式。记住,这些公式我建议你直接写成函数,不要每次手算。

四元数转DCM:

// 输入:四元数 q = [w, x, y, z]
// 输出:DCM 矩阵 C (3x3)
// 公式来源:S. L. Altmann, "Rotations, Quaternions, and Double Groups"

C[0][0] = 1 - 2*(y*y + z*z);
C[0][1] = 2*(x*y - w*z);
C[0][2] = 2*(x*z + w*y);

C[1][0] = 2*(x*y + w*z);
C[1][1] = 1 - 2*(x*x + z*z);
C[1][2] = 2*(y*z - w*x);

C[2][0] = 2*(x*z - w*y);
C[2][1] = 2*(y*z + w*x);
C[2][2] = 1 - 2*(x*x + y*y);

DCM转四元数:

// 输入:DCM 矩阵 C (3x3)
// 输出:四元数 q = [w, x, y, z]
// 注意:需要保证矩阵是正交的,否则结果会有误差

float trace = C[0][0] + C[1][1] + C[2][2];

if (trace > 0) {
    float S = 0.5f / sqrtf(trace + 1.0f);
    w = 0.25f / S;
    x = (C[2][1] - C[1][2]) * S;
    y = (C[0][2] - C[2][0]) * S;
    z = (C[1][0] - C[0][1]) * S;
} else {
    // 处理特殊情况,比如 trace <= 0 时,需要取最大对角元
    // 这里省略分支代码,实际项目中必须完整实现
}

避坑指南:我曾经在DCM转四元数时,没处理 trace <= 0 的情况。结果在特定姿态下,四元数突然跳变,导致姿态解算发散。记住:这个分支不是可选的,是必须的

3.4 知识体系结构图

下面这张图,是我自己总结的。它把本章的核心逻辑串起来了。你仔细看一遍,应该能明白:旋转的物理意义 -> 坐标系定义 -> 数学表达(DCM/四元数) -> 相互转换,这条线是贯通的。

刚体旋转基础:知识体系 旋转的物理意义 轴 + 角 + 顺序 坐标系定义 导航系(n) vs 机体系(b) 数学表达 DCM vs 四元数 核心转换:DCM ↔ 四元数 四元数 → DCM(用于提取欧拉角) DCM → 四元数(用于初始化/校正) 工程应用:姿态解算、滤波、控制 © 蓝海资料掘金营

这张图里,从上到下,就是我们从物理世界到数学表达,再到工程实现的完整路径。你每次写代码前,不妨先看看这张图,确认自己当前在哪个环节。

3.5 本章小结

这一章,我们搞清楚了:

  • 旋转不是玄学,是轴、角、顺序的组合。
  • 导航系和机体系,是两把尺子,不能混用。
  • DCM直观但冗余,四元数高效且稳定。
  • 两者之间的转换,是姿态解算的“桥梁”,必须写对。

嗯,基础打牢了,后面讲四元数更新、姿态融合,你才能跟得上。记住:地基不稳,楼盖得再高也是危楼