跳转至

坐标变换与旋转矩阵(推导入门)

一句话:姿态的本质是一个 3×3 旋转矩阵。本篇从"坐标系怎么转"的第一性原理出发,把单个轴的旋转矩阵旧系→新系的"搬家"含义三个角怎么合成、以及主动/被动旋转的区别一次讲清楚。

💡 如果你卡在更前面一步 —— "凭什么一堆数字(矩阵)就能代表旋转?" —— 请先看下面紧跟着的第零节,它只用加减乘就把这件事拆开了,读完再回第一节。

来源与可靠性说明

本篇素材整理自一次与 AI(DeepSeek)的问答对话(坐标变换和选转矩阵.md),然后由我结合agent整理为一篇wiki,并做了数值交叉核对:单轴被动旋转矩阵、合成矩阵 \(C_n^b=R_xR_yR_z\)、转置互逆 \(C_b^n=(C_n^b)^T\)、以及"反着转倒着乘"公式 \(C_b^n=R_z(-\psi)R_y(-\theta)R_x(-\phi)\) 均用 Python 数值验证完全一致。 ⚠️ 关键提醒:被动旋转 / 主动旋转的符号约定在不同教材、不同代码里不统一。本篇采用由"主动旋转(坐标系不动,向量自己转)"->"被动旋转(向量不动、坐标系旋转)"的约定;落地到具体工程(如本项目 NED+body、PSINS)时,请以该工程的约定为准,不要直接照搬符号。

本篇一图流


零、在看任何公式之前:矩阵凭什么代表旋转?

如果下面第一节的 \(R_x,R_y,R_z\) 让你觉得"这些数字凭什么就是旋转",先把这一节读完。它不依赖后面任何内容,全程只有加减乘。

0.1 先破一个误解

矩阵里没有任何"旋转的动作"。矩阵是一张照片 —— 转完之后,坐标系长什么样。

教材常把"动作"和"结果"混着讲,于是你会觉得旋转是被某种魔法"赋予"给矩阵的。实际上顺序是反的:先规定基向量搬到哪儿,矩阵自己就长出来了。

0.2 第一步:矩阵 × 向量,其实只有一种算法

忘掉"行乘列"的口诀。记 \(R\) 的两个列向量为 \(c_1,\ c_2\),向量 \(v=\begin{bmatrix}a\\b\end{bmatrix}\),那么真正的定义只有一句:

\[ R\,v = a\,c_1 + b\,c_2 \]

矩阵乘向量 = 拿向量的每个分量去给矩阵的列当权重,然后加起来。 纯算术,零几何。

0.3 第二步:所以"列"到底是什么?

把单位向量 \(e_1=\begin{bmatrix}1\\0\end{bmatrix}\) 喂进去,套上面那句定义(此时 \(a=1,\ b=0\)):

\[ R\,e_1 = 1\cdot c_1 + 0\cdot c_2 = c_1 \]

第 1 列,就是 \(e_1\) 被这个矩阵送到的地方。 同理,第 2 列就是 \(e_2\) 的落点。

这是全篇最该记住的一句:矩阵的列不是数字排列,是"基向量搬家后的新地址"。

0.4 第三步:想转 θ,只需要问两个问题

现在反过来用 —— 不是"写出矩阵再猜它干嘛",而是先规定落点,矩阵自己就填满

下面画的是主动旋转的画面(箭头自己转、坐标系钉死不动),因为它最直观;本文正文从第一节起改用被动旋转(坐标系转),差别见紧接着的 0.4b。

  • \(e_1\)(指向正右方、长度 1)逆时针转 θ 后落在哪?→ 单位圆上 θ 角处 → \((\cos\theta,\ \sin\theta)\) → 第 1 列
  • \(e_2\)(指向正上方)跟着转 θ 后落在哪?→ 比 \(e_1\) 超前 90° → \((-\sin\theta,\ \cos\theta)\) → 第 2 列

两列并起来:

\[ R(\theta)=\begin{bmatrix}\cos\theta & -\sin\theta\\[2pt] \sin\theta & \cos\theta\end{bmatrix} \]

矩阵不是被"赋予"了旋转含义,而是你用两个落点把它填满之后,它自然就只能干旋转这一件事。

旋转矩阵三步推导(静态)

0.4b 岔路口:刚才那张图是"主动"的,本文正文却是"被动"的

读到这里先停一下 —— 往下要换约定了

0.4 画的是主动旋转(向量转、坐标系钉死不动),推出来的是 \(R_{\text{act}}=\begin{bmatrix}c&-s\\ s&c\end{bmatrix}\)。 而本文第一节往下(\(R_x,\ R_y,\ R_z,\ C_n^b,\ C_b^n\))全部改用被动旋转(坐标系转、向量不动), 其单轴矩阵的 2×2 子块是 \(\begin{bmatrix}c&s\\ -s&c\end{bmatrix}\) —— 正好是前者的转置

两者不是谁对谁错,而是同一件事的两种说法,并且互为转置

\[ R_{\text{act}}(\theta)=\begin{bmatrix}c&-s\\[2pt] s&c\end{bmatrix} \quad\Longleftrightarrow\quad R_{\text{pas}}(\theta)=R_{\text{act}}(\theta)^{T}=\begin{bmatrix}c&s\\[2pt] -s&c\end{bmatrix} \]

主动 vs 被动:矩阵互为转置

那 0.2–0.3 的"列 = 新地址"还成立吗?成立 —— 但"地址"换了量法:

主动(0.4 的图) 被动(第一节往下)
谁在动 向量转 θ,坐标系不动 坐标系转 θ,向量不动
第 1 列 \(e_1\) 被搬到了哪 \(=(\cos\theta,\ \sin\theta)\) \(e_1\) 在新系下重新读 \(=(\cos\theta,\ -\sin\theta)\)
一句话 列 = 旧轴「搬到哪」 列 = 旧轴「在新系里怎么读」

所以"看列"这个动作两边通用,别以为换了约定前面就白学了 —— 变的只是"地址在哪种坐标里量"。 惯导里 99% 的坐标变换(\(C_b^n\)\(C_n^b\))都是被动的,这才是本文正文选被动的原因;完整的主动/被动对比与可拖拽演示见第四节

0.5 验算:它真的只是旋转吗?

取 θ = 40°、\(v=(2,1)\)\(\cos40^\circ=0.766\)\(\sin40^\circ=0.643\)

\[ Rv=\begin{bmatrix}0.766&-0.643\\0.643&0.766\end{bmatrix}\begin{bmatrix}2\\1\end{bmatrix} =\begin{bmatrix}2(0.766)-1(0.643)\\2(0.643)+1(0.766)\end{bmatrix} =\begin{bmatrix}0.889\\2.052\end{bmatrix} \]

两条检验:

  • 长度不变\(|v|=\sqrt{5}=2.236\)\(|Rv|=2.236\) → 没有拉伸;
  • 两列仍是单位长且互相垂直:点积 \(=0\)、每列模长 \(=1\) → 没有剪切。

一个矩阵只要满足"列是单位长度 + 互相垂直",它只能做两件事:旋转(\(\det=+1\))或镜像(\(\det=-1\))。这里 \(\det R=\cos^2\theta+\sin^2\theta=1>0\),所以是旋转,不是翻面。

0.6 动起来看 / 亲手拖

会循环播放的版本(θ 从 0° 转到 360°):

旋转矩阵三步推导(动画)

👉 旋转矩阵三步推导 · 交互演示

交互页里可以:切换 ①②③ 三步、拖 θ / a / b、点"自动扫一遍"。右侧读数会实时显示两列数值、\(\det R\)、以及 \(|v|\)\(|Rv|\) 的对比。

0.7 用同一双眼睛回头看第九节的 P 矩阵

\[P=\begin{bmatrix}0&1&0\\1&0&0\\0&0&-1\end{bmatrix}\]
  • 第 1 列 \(=(0,1,0)\) → 原来的 x 轴搬到 y 的位置(东改叫北
  • 第 2 列 \(=(1,0,0)\) → 原来的 y 轴搬到 x(北改叫东
  • 第 3 列 \(=(0,0,-1)\) → z 轴原地反向(上改叫下

P 不是旋转,是"把三个轴重新贴标签"。 但它和旋转矩阵用的是完全相同的读法 —— 看列,看基向量搬去哪了。旋转是"搬家",P 是"改名",操作形式一模一样,都是矩阵。

这也解释了为什么 \(C_{NED}=P\,C_{PSINS}\,P'\) 要左乘一次、右乘一次:左边 \(P\)输出那头的标签,右边 \(P'\)输入那头的标签,两头名字都换完,中间的旋转本身一点没动。


一、先把单个轴的旋转矩阵讲清楚

约定切换

从这里开始是被动旋转(坐标系转、向量不动)。它的单轴矩阵子块是 \(\begin{bmatrix}c&s\\ -s&c\end{bmatrix}\), 是第零节 0.4 那个主动矩阵的转置。若还没看 0.4b,请先回去看一眼再往下读,否则符号会对不上。

\(c_\alpha=\cos\alpha,\ s_\alpha=\sin\alpha\)。在被动旋转约定下,绕各轴的单轴矩阵为:

绕 X 轴(横滚 \(\phi\)):

\[ R_x(\phi)=\begin{bmatrix}1&0&0\\0&c_\phi&s_\phi\\0&-s_\phi&c_\phi\end{bmatrix} \]

绕 Y 轴(俯仰 \(\theta\)):

\[ R_y(\theta)=\begin{bmatrix}c_\theta&0&-s_\theta\\0&1&0\\s_\theta&0&c_\theta\end{bmatrix} \]

绕 Z 轴(偏航 \(\psi\)):

\[ R_z(\psi)=\begin{bmatrix}c_\psi&s_\psi&0\\-s_\psi&c_\psi&0\\0&0&1\end{bmatrix} \]

这些矩阵是怎么来的? 旋转只发生在"垂直于旋转轴的那个平面"里,该平面内 2×2 的投影子块就是 \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)(被动形式;很多教材的主动形式是它的转置 \(\begin{bmatrix}c&-s\\s&c\end{bmatrix}\))。X、Y、Z 轴的不同,只在于"旋转发生在哪两根轴之间"。

为什么绕 X、Z 的符号一致,绕 Y 的子块却像是被"转置"了?

这是本篇最该记住的一点。对比上面三个矩阵的 2×2 子块:

  • X:变化在 Y–Z 平面,子块 \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)
  • Z:变化在 X–Y 平面,子块 \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)
  • Y:变化在 X–Z 平面,子块 \(\begin{bmatrix}c&-s\\s&c\end{bmatrix}\)中间那对符号反了

底层机理 = 右手系的循环顺序;而且三轴其实遵守同一条规则。 正循环是 \(X \rightarrow Y \rightarrow Z \rightarrow X\)(叉乘满足 \(X\times Y=Z,\ Y\times Z=X,\ Z\times X=Y\))。绕任意轴旋转时,垂直平面里的 2×2 子块,只要按"循环正方向(下一轴 → 上一轴)"来排,都是同一个样子 \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)(被动形式):

  • 绕 X:垂直平面 Y–Z,循环正方向 Y→Z → \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)
  • 绕 Z:垂直平面 X–Y,循环正方向 X→Y → \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)
  • 绕 Y:垂直平面 Z–X,循环正方向 Z→X → 按 (Z,X) 顺序写本来也是 \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)

那为什么 Y 写出来像被转置了? 因为矩阵按 [X,Y,Z] 的列顺序排,Y 是中间那根轴,它的垂直平面 (Z,X) 被拆到了矩阵的两个对角 (1,3) 和 (3,1),而不是像 X、Z 那样落在一个连续的 2×2 块里。于是"下一轴→上一轴"的正向符号(+s)出现在左下角 (3,1),视觉上就和 X、Z 的"右下角 (2,3)/(1,2) 是 +s"左右镜像了——本质是同一条规则,只是被矩阵的列书写顺序"扭"了一下

结论:绕 Y 的符号不是写错,也不是特例,更不是"X→Z 逆循环"。三根轴都遵守同一条右手系循环规则;Y 的子块之所以看着像转置,纯粹是因为 Y 是中间轴、它的 2×2 块被摊到了矩阵的两个对角。配图把三张子矩阵并排摆出来,一眼就能看出 Y 是"那个不同的"。


二、"旧系 → 新系":这就是坐标变换(被动旋转的含义)

上面每个单轴矩阵,描述的其实是同一件事:向量原地不动,坐标系转了一个角,然后读出这个向量在新坐标系下的坐标。这就是"旧系 → 新系"的搬家公式。

记旧坐标系为 1、新坐标系为 2,被动旋转矩阵 \(C_1^2\) 的含义就是:

\[ \mathbf{v}^{(2)} = C_1^2 \, \mathbf{v}^{(1)} \]

即"把旧系坐标 \(\mathbf{v}^{(1)}\) 左乘 \(C_1^2\),得到新系坐标 \(\mathbf{v}^{(2)}\)"。

直觉例子:绕 Z 把坐标系逆时针转 \(+\theta\)。旧系里一个在 \(\phi\) 方向的向量,在新系里"看起来"夹角变成了 \(\phi-\theta\)(因为坐标系自己转走了)。这正是上面 \(R_z\) 矩阵在做的运算。

所以第一章的三个单矩阵,本质就是"绕某轴转 θ 时,旧坐标→新坐标"的投影算子。 被动旋转 = 坐标轴在动、世界没动;我们只是换了一副"眼镜"看同一个向量。


三、三个角合起来:乘法顺序与 \(C_n^b\) 等式

单轴解决了,怎么把横滚、俯仰、偏航三个角合成一个完整的姿态矩阵?

惯导里最常见的约定是 Z–Y–X 外禀(固定轴)顺序:先偏航 \(\psi\)(绕 Z)、再俯仰 \(\theta\)(绕 Y)、最后横滚 \(\phi\)(绕 X)。写成矩阵左乘

\[ C_n^b = R_x(\phi)\,R_y(\theta)\,R_z(\psi) \]

记忆口诀:矩阵从右往左读,对应旋转从先到后。最先发生的旋转(Z)放在最右边。

把三个单矩阵乘开(已数值验证)就是:

\[ C_n^b= \begin{bmatrix} c_\theta c_\psi & c_\theta s_\psi & -s_\theta \\ s_\phi s_\theta c_\psi - c_\phi s_\psi & s_\phi s_\theta s_\psi + c_\phi c_\psi & s_\phi c_\theta \\ c_\phi s_\theta c_\psi + s_\phi s_\psi & c_\phi s_\theta s_\psi - s_\phi c_\psi & c_\phi c_\theta \end{bmatrix} \]

\(C_n^b\) 读作"导航系 n → 载体系 b":把导航系下的向量,投影到载体系下。这就是把第一章的"旧系→新系"逻辑,连做三次(旧 n 依次转成新 b)的结果。


四、主动旋转 vs 被动旋转(正式对比 + 交互演示)

前面全程用"被动旋转",但很多人符号搞混,根子就在没分清主动和被动。这里正式对比一下。

主动旋转(alibi) 被动旋转(alias / 坐标变换)
谁在动 向量转,坐标系不动 坐标系转,向量不动
物理画面 "把这根箭头转 30°" "换个姿态的坐标系重新读同一个点"
同一 θ 的矩阵 \(R(\theta)=\begin{bmatrix}c&-s\\s&c\end{bmatrix}\) \(C(\theta)=R(\theta)^T=\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)
惯导里 较少直接出现 坐标变换默认用这个

关键关系:对同一旋转角 θ,主动矩阵与被动矩阵互为转置。 这也回扣了第一章——你看到的"被动形式的子块",正是主动形式的转置。

为了把这件事"看"明白,我做了一个可拖拽的交互演示(强烈建议打开玩两下):

👉 主动 vs 被动旋转 · 交互演示

它用左右两图对比:左图向量转(主动),右图坐标系转(被动),并实时联动矩阵。重点看矩阵第 1 列(绿框)——它永远等于"旧 X 轴单位向量 \([1,0,0]^T\) 喂进矩阵的结果":

  • 主动时,旧 X 轴转到 \(+\theta\) 处 → 第一列 \([\cos\theta,\ \sin\theta]^T\)
  • 被动时,旧 X 轴相对新系落在 \(-\theta\) 处 → 第一列 \([\cos\theta,\ -\sin\theta]^T\)

这正是"主动矩阵 = 被动矩阵转置"的几何来源,也是第一章里"绕 Y 子块像被转置"的同一套逻辑在 2D 上的体现。


五、反过来:从载体系到导航系 \(C_b^n\)

已知 \(C_n^b\),要回到反向变换 \(C_b^n\),有两种完全等价、结果一致的求法(已数值验证每个元素都相同):

方法一:转置(最省事) —— 旋转矩阵是正交矩阵,逆等于转置:

\[ C_b^n = (C_n^b)^T \]

方法二:"反着转、倒着乘"(最有物理美感,不用碰转置) —— 既然 \(C_n^b\) 是把 n 系按 Z→Y→X 顺序转过去,回到 n 系就是"按完全相反的顺序、转相反的角度":

\[ C_b^n = R_z(-\psi)\,R_y(-\theta)\,R_x(-\phi) \]

把单矩阵角度取反(\(\sin(-\alpha)=-\sin\alpha,\ \cos(-\alpha)=\cos\alpha\))直接乘开,得到:

\[ C_b^n= \begin{bmatrix} c_\theta c_\psi & s_\phi s_\theta c_\psi - c_\phi s_\psi & c_\phi s_\theta c_\psi + s_\phi s_\psi \\ c_\theta s_\psi & s_\phi s_\theta s_\psi + c_\phi c_\psi & c_\phi s_\theta s_\psi - s_\phi c_\psi \\ -s_\theta & s_\phi c_\theta & c_\phi c_\theta \end{bmatrix} \]

工程上我们通常用 \(C_b^n\)(机体→导航)把机载加速度计、磁力计数据投影到水平系;它也常被称为"姿态矩阵 / 方向余弦矩阵(DCM)"。


六、延伸铺垫:矢量叉乘与反对称矩阵

(本节只点题;完整定义、性质、数字验证与惯导联系已单独成篇 → 矢量叉乘与反对称矩阵

矢量叉乘 \(\mathbf{a}\times\mathbf{b}\) 给出一个同时垂直于 a、b 的新向量,方向由右手定则决定;惯导里科氏项、姿态运动学里的 \(\boldsymbol\omega\times\) 项都源于它。为把叉乘写进矩阵运算,引入反对称矩阵 \([\mathbf{a}]_\times\),其核心性质是:

\[ \mathbf{a}\times\mathbf{b} = [\mathbf{a}]_\times\,\mathbf{b} \]

后面你会在 ESKF 的雅可比、姿态误差方程里反复见到 \([\boldsymbol\omega]_\times\)——它就是"叉乘的矩阵写法"。双重反对称恒等式与各性质的数值验证,请移步上方链接的专篇。


七、常见坑(提前标出来)

坐标变换相关的经典翻车

  • 被动 / 主动没分清:本篇正文用被动。若参考的书是主动约定,单矩阵整体是本文的转置,合成符号也会跟着反。先看清楚对方约定(见第四节交互演示)。
  • 同一篇里换约定却没察觉第零节画的是主动画面,第一节起是被动矩阵,两者互为转置——不是写错了,是 0.4b 明确交代过的切换点。看到 \(\begin{bmatrix}c&-s\\s&c\end{bmatrix}\)\(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\) 同时出现时,先确认各自属于哪一边。
  • 乘法顺序搞反:多轴合成时矩阵从右往左读(先做的在右)。Z–Y–X 要写成 \(R_zR_yR_x\) 的连乘顺序,不是把 \(R_xR_yR_z\) 的顺序读反。
  • 取反角度时只反了 sin 没反 cos\(\cos(-\alpha)=\cos\alpha\)(不变),只有 \(\sin\) 变号。
  • 以为绕 Y 的符号是写错了:不是。那是右手系循环顺序决定的,见第一节。

八、自测题(合上能答才算懂)

  1. 单轴被动旋转矩阵里,绕 X、Z 的子块是 \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\),绕 Y 是 \(\begin{bmatrix}c&-s\\s&c\end{bmatrix}\)。为什么会这样?一句话说清"绕 Y 子块像被转置"的几何根源。
  2. 被动旋转说的是"旧系→新系"的什么操作?写出 \(C_1^2\) 把旧坐标变新坐标的公式。
  3. \(C_n^b=R_xR_yR_z\) 中,矩阵乘法为什么要"从右往左读"?最先发生的旋转(Z)放在哪边?
  4. 主动矩阵和被动矩阵有什么关系?为什么?
  5. 已知 \(C_n^b\),求反向 \(C_b^n\) 的两种方法各是什么?
  6. 反对称矩阵 \([\mathbf{a}]_\times\) 有什么用?(提示:叉乘的矩阵写法)

答案都在上面。第 1、4 题如果还模糊,建议打开第四节的交互演示,并回看 01 坐标系与变换数学地基:矩阵、概率与协方差


九、工程实例:同一架飞机,两套坐标标签(适配阵 P)

前面的章节都在讲「旋转矩阵」——把一个向量在同一套坐标轴里转个角度。本节是不同的事:我们有两套不同的坐标轴命名约定,物理上指向同一方向,只是把轴的「名字」换了一套。这种情况要靠一个坐标轴改名字典来衔接,而不是旋转。

9.1 两套约定从哪来

  • PSINS / miniins(MATLAB 版):导航系用 ENU(东 East / 北 North / 天 Up),机体用 x=右、y=前、z=上(即「右-前-上」)。
  • 固件 C 版(AHRS 板):导航系用 NED(北 North / 东 East / 下 Down),机体用 FRD(前 Front / 右 Right / 下 Down)。

两套导航系(ENU vs NED)与两套机体系(PSINS 体 vs FRD)只是轴标签不同,物理量完全一样。要让 MATLAB 里验证过的算法能 1:1 搬到固件对拍,需要一个固定换算阵:

\[P=\begin{bmatrix}0&1&0\\1&0&0\\0&0&-1\end{bmatrix},\qquad C_{NED}=P\cdot C_{PSINS}\cdot P'\]

9.2 它到底干了什么:只是「换标签」

下图左边是 ENU(东/北/天),右边是 NED(北/东/下)。注意两幅图里飞机机头都朝北、物理朝向一模一样,变的只是坐标轴的名字排布:

P 矩阵:坐标轴改名字典(静态)

  • 第 1、2 轴交换:ENU 的(东,北)在 NED 里写成(北,东)——数值上就是把向量的前两个分量对调;
  • 第 3 轴取反:天(Up) 变成 下(Down),即 \(U=-D\)

所以 \(P\cdot P=I\):换两次标签回到原样,它根本没旋转任何东西,纯属「字典」。方向余弦矩阵因此按 \(C_{NED}=P\,C_{PSINS}\,P'\) 变换(先换输入轴、再换输出轴)。

9.3 动态看:逐个轴对应

下面这张是会动的版本——高亮会依次扫过「北↔北、东↔东、天↔地下(取反)」,你会看到飞机始终没动,变的只是标签:

P 矩阵:坐标轴改名字典(动画)

9.4 亲手转一转(交互演示)

打开 P 矩阵交互演示:拖动滑块改变机头朝向,观察同一架飞机在 ENU 和 NED 下报出的坐标数字如何按 \(P\) 互换。结论还是那句——\(P\) 不是旋转,是坐标标签的字典。

用处:C 移植(阶段 D)做 SIL 对拍时,MATLAB 版与固件版的四元数 / DCM 必须逐位一致,差异只在 att↔DCM 转换与参考向量取值(NED 重力 G=[0;0;+g]、磁北、wnie 等)。换算就靠这里的 \(P\)。详见 Mini-INS 内化库约定。


十、动手练习:主动/被动旋转 + 惯导 4 坐标系(配 Mini-INS)

本节来源与用法说明

本节是按「模块③:主动 vs 被动旋转 + 惯导的 4 个坐标系」的学习提纲重建的练习页(原逐字笔记未带入当前编辑上下文)。数学结论与下面的 Mini-INS 代码均已逐项核对;如果你手头有豆包那版逐字叙述,可直接覆盖本节的文字,但不要改掉第 10.2 节的「勘误」框——那是 DeepSeek 对豆包验证2 的纠正,且已用本库代码证伪。

运行约定:本节所有 MATLAB 片段,先在 MATLAB 里 addpath(genpath('docs/惯性导航/assets/miniins')) 一次性加载 core/,即可直接调用 euler2cnb 等。本库 euler2cnb(att) 返回 \(C_b^n\)(体→导航,PSINS 约定),与正文第 3 节 \(C_n^b=R_xR_yR_z\)、第 5 节 \(C_b^n=R_z(-\psi)R_y(-\theta)R_x(-\phi)\) 数值一致。

10.1 先认全:惯导的 4 个坐标系(i → e → n → b)

本文的 \(C_n^b\) 只是惯导全程在倒腾的 4 个右手直角坐标系里的最后一段。完整链条:

名字 原点 / 姿态 衔接到下一系靠什么
i(ECI,地心惯性系) 不随地球转,恒星参考 地心,轴指向远处恒星 \(C_i^e\):地球自转 \(\boldsymbol\omega_{ie}\)
e(ECEF,地心地固系) 随地球一起转 地心,z 轴沿自转轴 \(C_e^n\):载具位置(纬度/经度)
n(导航系 / 当地水平系) 原点在载具,ENU 或 NED 随载具平移、但不随载具转 \(C_n^b\):姿态(俯仰/横滚/偏航)
b(载体系 / 机体) 固连在载具上(FRD 或 x右y前z上) 随载具一起动 —(链条终点)

约定记法(与本文一致):上标=目标系、下标=源系。\(C_a^b\) 表示「把 a 系坐标投影到 b 系」。于是从惯性系一路到机体:

\[C_i^b = C_n^b \cdot C_e^n \cdot C_i^e\]

(矩阵从右往左读:先 i→e,再 e→n,最后 n→b。)本文主角 \(C_n^b\) 只负责最后一段「导航系→载体系」;前面 i→e、e→n 由地球模型与位置决定,细节见 01 坐标系与变换

10.2 五个动手练习(算一遍 + 用 Mini-INS 跑一遍)

练习 1 — \(C_b^n\)\(C_n^b\) 互为转置(一步验证)

:正文第 3 节给 \(C_n^b=R_xR_yR_z\),第 5 节给 \(C_b^n=(C_n^b)^T=R_z(-\psi)R_y(-\theta)R_x(-\phi)\)。两者是同一旋转的正反两向,正交矩阵的逆等于转置,所以必互为转置。

(取一组非零姿态,直接验证):

addpath(genpath('docs/惯性导航/assets/miniins'));
att = [10 20 30]'*pi/180;     % roll=10°, pitch=20°, yaw=30°
Cbn = euler2cnb(att);         % ★ 本库返回 C_b^n(体→导航)
Cnb = Cbn';                    % C_n^b(导航→体)= 转置
norm(Cbn*Cnb - eye(3))        % ≈ 1e-16 → 正交,印证 C_n^b=(C_b^n)^T

练习 2 — 偏航 90°:\(C_b^n\) 的第一列到底是 \((0,1,0)^T\) 还是 \((0,-1,0)^T\)?【勘误重点】

:令 pitch=roll=0、yaw=90°,\(C_b^n=R_z(-90^\circ)\)。套用正文第 5 节公式(或直接写 \(R_z\) 被动块 \(\begin{bmatrix}c&s\\-s&c\end{bmatrix}\)\(\psi=-90^\circ\)):

\[C_b^n(90^\circ)=R_z(-90^\circ)=\begin{bmatrix}0&-1&0\\1&0&0\\0&0&1\end{bmatrix}\]

第一列 = \((0,1,0)^T\)

Cbn = euler2cnb([0;0;pi/2]);
Cbn(:,1)          % ans = [0; 1; 0]  → 第一列确实是 (0,1,0)^T

勘误:豆包「验证2」把 \(C_n^b\) 误当成了 \(C_b^n\)(DeepSeek 正解)

原结论写:偏航 90° 时 \(C_b^n\) 的第一列为 \((0,-1,0)^T\)这是错的。

正解:偏航 90°、\(C_b^n\) 的第一列 = \((0,1,0)^T\)

错在哪?豆包算出来的 \((0,-1,0)^T\) 其实是 \(C_n^b\)(导航→体) 的第一列,不是 \(C_b^n\)(体→导航)的。两者互为转置:

\[C_b^n(90^\circ)=\begin{bmatrix}0&-1&0\\1&0&0\\0&0&1\end{bmatrix},\qquad C_n^b=(C_b^n)^T=\begin{bmatrix}0&1&0\\-1&0&0\\0&0&1\end{bmatrix}\]
  • \(C_b^n\)(新→旧 / 体读旧导航轴)第一列 = \((0,1,0)^T\)
  • \(C_n^b\)(旧→新 / 导航读旧体轴)第一列 = \((0,-1,0)^T\)

一句话记忆:「旧→新」看列用 \(C_n^b\);「新→旧」看列用 \(C_b^n\);二者互为转置。 豆包算的是前者却标成了后者。

用 Mini-INS 一键证伪(本库 euler2cnb 返回的就是 \(C_b^n\)):

euler2cnb([0;0;pi/2])        % → [0 -1 0; 1 0 0; 0 0 1],第一列 [0;1;0]
euler2cnb([0;0;pi/2])'       % → C_n^b,第一列 [0;-1;0]  ← 豆包算的其实是这个

练习 3 — 主动矩阵 = 被动矩阵转置(2D 直验)

:取 \(\theta=45^\circ\)。主动(向量转)\(R_{act}=\begin{bmatrix}c&-s\\s&c\end{bmatrix}\),被动(坐标系转,本文约定)\(R_{pas}=\begin{bmatrix}c&s\\-s&c\end{bmatrix}\),显然 \(R_{pas}=R_{act}^T\)。——这正是第零节 0.4b 与第四节讲的那对「互为转置」。

(不依赖库,纯算术核对):

th = 45*pi/180; c=cos(th); s=sin(th);
Ract = [c -s; s c];      % 主动(向量转)
Rpas = [c s; -s c];      % 被动(坐标系转,本文正文约定)
norm(Rpas - Ract')       % ≈ 0 → 互为转置

想要 3D 联动的「主动 vs 被动」可视化,打开 第四节交互演示,重点盯第一列(绿框)。

练习 4 — 4 坐标系链:i→b 要连乘几个矩阵、各段靠什么驱动

:由 10.1 的表,从惯性系到机体需连乘三段:\(C_i^b = C_n^b\cdot C_e^n\cdot C_i^e\)

  • \(C_i^e\):由地球自转角速度 \(\boldsymbol\omega_{ie}\) 决定(绕极轴匀速转);
  • \(C_e^n\):由载具位置(纬度/经度)决定(把 ECEF 转到当地水平);
  • \(C_n^b\):由姿态(俯仰/横滚/偏航)决定——本文主角。

(看本库如何把 i→e 的地球量暴露出来):

glv = glvs;
glv.wie         % 地球自转角速度 (rad/s),就是驱动 C_i^e 那段「i→e」的旋转率
                % 在导航系 n 里它表现为 wnie = [0; wie*cos(lat); -wie*sin(lat)](北/东/天分量),由 earth.m 按纬度算出

实务里 \(C_i^e\)\(C_e^n\) 由地球模型(earth.m)与位置算出;\(C_n^b\) 由姿态更新实时给出。三者拼起来才是完整的 i→b 姿态链。

练习 5 — 综合:用「三步法」手算第一列,再用 Mini-INS 复核

:取 \(att=[\phi,\theta,\psi]=[5^\circ,-10^\circ,60^\circ]\)。回到第零节的核心句——「第 1 列 = 旧 X 轴单位向量 \([1,0,0]^T\) 喂进矩阵的结果」。所以 \(C_b^n\) 的第一列,就是旧体 X 轴在导航系下读到的方向余弦。

att = [5 -10 60]'*pi/180;
Cbn = euler2cnb(att);
col1 = Cbn(:,1)                 % 旧体 X 轴在导航系下的方向余弦(=「三步法」读到的第一列)
Cbn*[1;0;0] - col1              % ≈ 0 → 印证「第 1 列 = 旧 X 轴喂进矩阵的结果」

col1 与手算的 \(C_b^n\)(正文第 5 节展开式)第一列逐项比对,应完全一致——这就把「看列 = 基向量新地址」从 2D 三步法一路打通到了 3D 姿态阵。


关联与延伸

外链 Wiki