学习笔记 搜索笔记

原课号:00.4

旋转、角速度与四元数:先把方向这件事说清楚

从坐标轴的投影推导旋转矩阵,再推导角速度与四元数乘法、积分和姿态误差,理解四个数为什么只表示三个自由度。

建议先读:从三个数字开始 · 从解两个未知数开始

本页目录

本节要解决什么问题

机器人转了以后,同一条速度向量在机体系与导航系的三个分量不同。机械臂每个关节也在改变坐标系。若把“物体真的动了”和“只是换一种坐标写法”混为一谈,后面的 IMU 积分、DVL 融合和机械臂正运动学都会出错。

本节只讨论姿态和运动学:描述怎么运动,不讨论需要多大力才能这样运动。力、质量、惯量将在动力学章出现。

先统一约定:R=RnbR=R_{nb} 把机体系 bb 的列向量分量变成导航系 nn 的分量;两个系均为右手系。本节角速度表示机体相对导航系的转动,局部导航系近似惯性;高精度长航时惯导还需从陀螺对惯性空间的角速度中扣除地球自转与导航系转率。四元数采用 Hamilton 乘法、标量在前的 q=[w,x,y,z]Tq=[w,x,y,z]^T。角度参与三角函数时用 rad;角速度用 rad/s。软件库的存储顺序可能不同,不能靠变量名字猜。

1. 为什么旋转矩阵的列就是三个坐标轴

设机体系的三个单位轴在导航系中写成 exn,eyn,ezne_x^n,e_y^n,e_z^n。机体系向量 ab=[ax,ay,az]Ta^b=[a_x,a_y,a_z]^T 的含义是“沿这三根轴各走多少”。因此

an=axexn+ayeyn+azezn=[exneynezn]⏟Rnbab.a^n=a_xe_x^n+a_ye_y^n+a_ze_z^n =\underbrace{\begin{bmatrix}e_x^n&e_y^n&e_z^n\end{bmatrix}}_{R_{nb}}a^b.

这个式子不是凭空定义的矩阵乘法,而是三次轴向分解相加。轴是单位向量且互相垂直,于是每列与自身点乘为一、不同列点乘为零:

RTR=I,R−1=RT.R^TR=I,\qquad R^{-1}=R^T.

因此反向坐标变换只需转置。还需 det⁡R=+1\det R=+1 保持右手性;满足正交性但行列式为负一的矩阵含镜像,不能当刚体旋转。

例如机体绕共同的正 zz 轴按右手方向转 α\alpha,其 xx 轴在导航系中为 [cos⁡α,sin⁡α,0]T[\cos\alpha,\sin\alpha,0]^T,yy 轴为 [−sin⁡α,cos⁡α,0]T[-\sin\alpha,\cos\alpha,0]^T。把列拼起来:

Rz(α)=[cos⁡α−sin⁡α0sin⁡αcos⁡α0001].R_z(\alpha)=\begin{bmatrix} \cos\alpha&-\sin\alpha&0\\ \sin\alpha&\cos\alpha&0\\0&0&1 \end{bmatrix}.

取 α=π/2\alpha=\pi/2,机体系的前向速度 ab=[1,0,0]T m/sa^b=[1,0,0]^T\ \mathrm{m/s} 变为 an=[0,1,0]T m/sa^n=[0,1,0]^T\ \mathrm{m/s}。这是一条很适合放进单元测试的方向检查。

坐标变换为何按右到左复合

若 an=Rnbaba^n=R_{nb}a^b 且 ab=Rbcaca^b=R_{bc}a^c,代入得到 an=RnbRbcaca^n=R_{nb}R_{bc}a^c。所以 Rnc=RnbRbcR_{nc}=R_{nb}R_{bc},靠右的变换先执行。一般 RxRz≠RzRxR_xR_z\ne R_zR_x,因为两次旋转的轴在空间中的含义不同。机械臂的变换链、相机外参链都应先写清上下标,再计算乘积。

2. 叉乘矩阵为什么会出现在所有旋转公式里

对任意 a=[ax,ay,az]Ta=[a_x,a_y,a_z]^T 定义

[a]×=[0−azayaz0−ax−ayax0],[a]×b=a×b.[a]_\times=\begin{bmatrix}0&-a_z&a_y\\a_z&0&-a_x\\-a_y&a_x&0\end{bmatrix}, \qquad [a]_\times b=a\times b.

这个矩阵把“与 aa 做叉乘”改写成线性算子;它满足 [a]×T=−[a]×[a]_\times^T=-[a]_\times,因此称反对称矩阵。三个独立数刚好对应一个三维向量。还应记住 a×b=−b×aa\times b=-b\times a,所以 [a]×b=−[b]×a[a]_\times b=-[b]_\times a。后续雅可比里的负号常来自这一步,而不是需要死记的额外规定。

固定在刚体上的点,离转轴的矢量为 rr。在很短的 dtdt 内绕单位轴 uu 转过 dθd\theta,一阶位移为

dr=(u dθ)×r,r˙=ω×r,ω=uθ˙.dr=(u\,d\theta)\times r,\qquad \dot r=\omega\times r,\qquad \omega=u\dot\theta.

为什么是叉乘?位移方向同时垂直于轴与半径,长度为到轴距离乘转角;右手方向由叉乘给出。这就是速度雅可比、刚体杆臂补偿的几何来源。

3. 旋转矩阵的时间导数与角速度

设 aba^b 是刚体上固定向量,故 a˙b=0\dot a^b=0。由 an=Raba^n=Ra^b 得到 a˙n=R˙ab\dot a^n=\dot R a^b;而刚才的刚体运动关系给出 a˙n=[ωn]×Rab\dot a^n=[\omega^n]_\times Ra^b。对所有 aba^b 都成立,因此

R˙=[ωn]×R=R[ωb]×,ωn=Rωb.\dot R=[\omega^n]_\times R=R[\omega^b]_\times, \qquad \omega^n=R\omega^b.

ωb\omega^b 与 ωn\omega^n 是同一个物理角速度在不同坐标系中的分量,不是两个角速度。机载陀螺仪通常输出本体轴向分量,所以积分常用右侧乘法 R[ωb]×R[\omega^b]_\times。

还可对 RTR=IR^TR=I 求导,得 R˙TR+RTR˙=0\dot R^TR+R^T\dot R=0,这证明 RTR˙R^T\dot R 必然反对称。因此它能用三个数表示,正是 [ωb]×[\omega^b]_\times。矩阵有九个数,但旋转瞬时自由度只有三个。

不要把 ω\omega 直接当成滚转、俯仰、偏航角的逐分量导数。对于约定 R=Rz(ψ)Ry(θ)Rx(ϕ)R=R_z(\psi)R_y(\theta)R_x(\phi),先把三个瞬时转轴写到机体系。滚转轴为 exe_x;俯仰轴经过已有滚转的逆变换,为 RxTey=(0,cos⁡ϕ,−sin⁡ϕ)TR_x^Te_y=(0,\cos\phi,-\sin\phi)^T;偏航轴还须逆转俯仰,为 RxTRyTez=(−sin⁡θ,sin⁡ϕcos⁡θ,cos⁡ϕcos⁡θ)TR_x^TR_y^Te_z=(-\sin\theta,\sin\phi\cos\theta,\cos\phi\cos\theta)^T。每根轴分别乘 ϕ˙,θ˙,ψ˙\dot\phi,\dot\theta,\dot\psi 后相加,把三根轴拼成三列便得到

ωb=[10−sin⁡θ0cos⁡ϕsin⁡ϕcos⁡θ0−sin⁡ϕcos⁡ϕcos⁡θ][ϕ˙θ˙ψ˙].\omega^b= \begin{bmatrix} 1&0&-\sin\theta\\ 0&\cos\phi&\sin\phi\cos\theta\\ 0&-\sin\phi&\cos\phi\cos\theta \end{bmatrix} \begin{bmatrix}\dot\phi\\\dot\theta\\\dot\psi\end{bmatrix}.

三列分别是滚转、俯仰和偏航转轴在当前机体系的方向。这个变换的行列式为 cos⁡θ\cos\theta;当俯仰为 ±π/2\pm\pi/2 时两种欧拉参数变化无法区分,逆映射不存在。这是参数化奇异,不是机器人在物理上突然少了一个自由度。

4. 从轴角到单位四元数

绕单位向量 uu 旋转角度 θ\theta,用

q=[wv]=[cos⁡(θ/2)usin⁡(θ/2)]q=\begin{bmatrix}w\\v\end{bmatrix} =\begin{bmatrix}\cos(\theta/2)\\u\sin(\theta/2)\end{bmatrix}

表示。这里 v∈R3v\in\mathbb R^3 是四元数向量部分,不是机器人线速度。它满足 w2+vTv=1w^2+v^Tv=1,四个存储分量受一个约束,故局部仍是三个自由度。

为什么是半角?下面的旋转采用 q⊗[0,a]⊗q∗q\otimes[0,a]\otimes q^*,同一四元数从左右各作用一次。把乘法展开会产生 cos⁡2(θ/2)−sin⁡2(θ/2)=cos⁡θ\cos^2(\theta/2)-\sin^2(\theta/2)=\cos\theta 和 2sin⁡(θ/2)cos⁡(θ/2)=sin⁡θ2\sin(\theta/2)\cos(\theta/2)=\sin\theta,恰好得到角度 θ\theta 的旋转,而不是 2θ2\theta。

Hamilton 乘法规定

[w1v1]⊗[w2v2]=[w1w2−v1Tv2w1v2+w2v1+v1×v2].\begin{bmatrix}w_1\\v_1\end{bmatrix}\otimes \begin{bmatrix}w_2\\v_2\end{bmatrix} = \begin{bmatrix}w_1w_2-v_1^Tv_2\\ w_1v_2+w_2v_1+v_1\times v_2\end{bmatrix}.

上半部分是标量,下半部分是三维向量。叉乘项使其通常不交换。单位四元数的共轭与逆为 q∗=q−1=[w,−v]Tq^*=q^{-1}=[w,-v]^T;非单位四元数的逆还需除以模长平方,不能省略。

把 aa 写成纯四元数 [0,a]T[0,a]^T。先只做左边一次乘法:

q⊗[0,a]T=[−vTa,  wa+v×a]T.q\otimes[0,a]^T=[-v^Ta,\;wa+v\times a]^T.

再右乘 q∗=[w,−v]Tq^*=[w,-v]^T,其向量部分为

(vTa)v+w(wa+v×a)−(wa+v×a)×v.(v^Ta)v+w(wa+v\times a)-(wa+v\times a)\times v.

使用 a×v=−v×aa\times v=-v\times a 和 (v×a)×v=(vTv)a−v(vTa)(v\times a)\times v=(v^Tv)a-v(v^Ta) 收集同类项,标量部分相消,向量部分得到

a′=(w2−vTv)a+2v(vTa)+2w(v×a).a'= (w^2-v^Tv)a+2v(v^Ta)+2w(v\times a).

所以对应的旋转矩阵是

R(q)=(w2−vTv)I+2vvT+2w[v]×.R(q)=(w^2-v^Tv)I+2vv^T+2w[v]_\times.

这一步既解释四元数怎么真正作用于向量,也提供独立于“库函数”的验证办法。代入轴角表达式,就得到下一节推导的 Rodrigues 公式。

例:q=[2/2,0,0,2/2]Tq=[\sqrt2/2,0,0,\sqrt2/2]^T,即绕正 zz 轴转九十度,应该把 [1,0,0]T[1,0,0]^T 变为 [0,1,0]T[0,1,0]^T。−q-q 代入 R(q)R(q) 也完全一样,因为所有项都含两个四元数分量。于是 qq 与 −q-q 表示同一个姿态,四元数对旋转是双覆盖,不能把四元数分量跳号误判为物体瞬间转了一圈。

5. 从陀螺仪读数到姿态更新

设陀螺测量 ωm\omega_m、偏置估计 b^g\hat b_g,短区间内的角速度近似常量,先算 ω=ωm−b^g\omega=\omega_m-\hat b_g 和转角向量 δθ=ωΔt\delta\theta=\omega\Delta t。它们分别是 rad/s 与 rad;不能把角速度直接塞进轴角。

令 a=∥δθ∥a=\|\delta\theta\|,对应的小旋转四元数为

δq=[cos⁡(a/2)sin⁡(a/2)aδθ],qk+1=qk⊗δq.\delta q=\begin{bmatrix}\cos(a/2)\\ \dfrac{\sin(a/2)}{a}\delta\theta\end{bmatrix}, \qquad q_{k+1}=q_k\otimes\delta q.

右乘是因为 δθ\delta\theta 在机体系表达。若角速度在导航系表达,应构造导航系小旋转并左乘,不能只换乘法方向而不换分量。

当 a→0a\rightarrow0,sin⁡(a/2)/a→1/2\sin(a/2)/a\rightarrow1/2,程序应采用级数 1/2−a2/48+⋯1/2-a^2/48+\cdots,避免零除。使用极限得到微分形式

q˙=12q⊗[0ωb].\dot q=\frac12q\otimes\begin{bmatrix}0\\\omega^b\end{bmatrix}.

简单 Euler 步进 q+q˙Δtq+\dot q\Delta t 会产生模长漂移,应归一化;使用指数增量也建议为浮点舍入作轻量归一化。归一化只能修正单位模约束,不能消除陀螺偏置,也不能替代高动态下的圆锥运动补偿。

6. 为什么姿态误差用三个数,而不是四元数相减

两个四元数直接相减不尊重单位球面,而且 qq 与 −q-q 的差很大却表示同姿态。定义右局部误差:

qtrue=q^⊗δq(δθ),Rtrue=R^Exp⁡(δθ).q_{\mathrm{true}}=\hat q\otimes\delta q(\delta\theta), \qquad R_{\mathrm{true}}=\hat R\operatorname{Exp}(\delta\theta).

q^\hat q 是名义姿态,δθ\delta\theta 是围绕该姿态的小三维转角误差。小角下 δq≃[1,δθT/2]T\delta q\simeq[1,\delta\theta^T/2]^T,因此可以在三维空间为误差建立高斯近似,而名义姿态仍保存在单位四元数里。

惯导常用的名义存储是位置三维、速度三维、四元数四维、加速度偏置三维、陀螺偏置三维,总共十六个数;误差状态是

δx=[δpT,δvT,δθT,δbaT,δbgT]T∈R15.\delta x=[\delta p^T,\delta v^T,\delta\theta^T,\delta b_a^T,\delta b_g^T]^T\in\mathbb R^{15}.

这不是漏了一维,而是避免把四元数的单位模约束当成独立的不确定方向。不能将一个 15×1515\times15 的协方差直接按普通加法更新十六维名义状态;姿态必须通过小旋转注入。

7. 插值、误差角与实际实现边界

若要比较两个姿态,先求 qe=q1−1⊗q2q_e=q_1^{-1}\otimes q_2;为了取不超过 π\pi 的主旋转角,可把其符号选为标量部分非负,然后

θe=2atan2⁡(∥ve∥,we).\theta_e=2\operatorname{atan2}(\|v_e\|,w_e).

不要只算四元数欧氏距离。刚好在 π\pi 附近,最短轴的符号并不唯一,算法需要一致的分支约定。

对单位四元数插值,先在 q1Tq2<0q_1^Tq_2<0 时将 q2q_2 取负,避免绕长路。令 β=arccos⁡(q1Tq2)\beta=\arccos(q_1^Tq_2),球面插值为

q(s)=sin⁡((1−s)β)sin⁡βq1+sin⁡(sβ)sin⁡βq2,0≤s≤1.q(s)=\frac{\sin((1-s)\beta)}{\sin\beta}q_1+ \frac{\sin(s\beta)}{\sin\beta}q_2,\quad 0\le s\le1.

β\beta 是四维单位球上的夹角,不是直接的物理旋转角;两者相差因子二。β\beta 很小时用归一化线性插值,点积进入 arccos⁡\arccos 前截断到 [−1,1][-1,1] 以应对舍入。四元数避免了欧拉角那种局部积分奇异,但仍有双覆盖、单位模约束及全局分支问题,不是“从此没有任何数学特殊情况”。

8. 手写练习:先用方向测试抓出乘法错误

实现标量在前四元数乘法、旋转向量到四元数、向量旋转。测试至少包括零旋转、九十度绕轴、正负号等价、组合不交换、接近零的增量。输入都要求有限数值、四元数单位模或显式归一化;生产版本应拒绝零范数四元数。

展开 C++17 参考实现与测试
#include <array>
#include <cmath>
#include <cassert>
#include <stdexcept>
using V = std::array<double,3>;
struct Q { double w; V v; };
V add(V a,V b){return {a[0]+b[0],a[1]+b[1],a[2]+b[2]};}
V mul(double s,V a){return {s*a[0],s*a[1],s*a[2]};}
double dot(V a,V b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}
V cross(V a,V b){return {a[1]*b[2]-a[2]*b[1],
 a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]};}
Q product(Q a,Q b){return {a.w*b.w-dot(a.v,b.v),
 add(add(mul(a.w,b.v),mul(b.w,a.v)),cross(a.v,b.v))};}
Q unit(Q q){double n=std::sqrt(q.w*q.w+dot(q.v,q.v));
 if(!std::isfinite(n)||n<1e-15)throw std::invalid_argument("bad q");
 return {q.w/n,mul(1/n,q.v)};}
Q fromRotvec(V x){double a=std::sqrt(dot(x,x));
 double s=a<1e-6?0.5-a*a/48.0:std::sin(a/2)/a;
 return unit({std::cos(a/2),mul(s,x)});}
V rotate(Q q,V x){q=unit(q);
 return add(add(mul(q.w*q.w-dot(q.v,q.v),x),
 mul(2*dot(q.v,x),q.v)),mul(2*q.w,cross(q.v,x)));}
int main(){const double pi=std::acos(-1.0);V ex{1,0,0};
 Q q=fromRotvec({0,0,pi/2});V y=rotate(q,ex);
 assert(std::abs(y[0])<1e-12&&std::abs(y[1]-1)<1e-12);
 V yn=rotate({-q.w,mul(-1,q.v)},ex);
 for(int i=0;i<3;i++)assert(std::abs(y[i]-yn[i])<1e-12);
 Q z=fromRotvec({0,0,0});assert(std::abs(z.w-1)<1e-12);
 Q tiny=fromRotvec({1e-12,0,0});assert(std::isfinite(tiny.v[0]));
 Q a=fromRotvec({pi/2,0,0}),b=fromRotvec({0,0,pi/2});
 V ab=rotate(product(a,b),ex),ba=rotate(product(b,a),ex);
 assert(std::abs(ab[2]-ba[2])>0.9);
}

把代码独立保存为 quaternion_demo.cpp,用 g++ -std=c++17 -O2 quaternion_demo.cpp -o quaternion_demo 编译,再运行。无断言失败表示这些案例通过,不等于覆盖全部浮点边界。

9. 如何连接 ArduSub、机械臂与 SLAM

  • 读飞控姿态接口时先核查输出四元数表示哪个方向的变换,以及数组顺序;不要根据一个库的约定推断另一个库。
  • 对 DVL 速度做机体到导航系变换需要 RnbR_{nb};反向预测机体系测量需要 RnbTR_{nb}^T,两者的姿态雅可比符号不同。
  • 对机械臂而言,每个关节改变一个相对坐标系,再将各段变换相乘;关节角向量不是四元数,末端整体姿态才需要旋转表示。
  • 对 SLAM 而言,四元数存储姿态,优化器在局部三维转角上求增量,不能无约束优化四个分量后假装约束不存在。

原始资料与继续阅读

本节的数例、展开步骤和练习为独立教学推导;外部资料用于核对定义与约定,不照搬论文段落。

本章编程练习

查看全部练习

仅保存在当前浏览器,可在学习记录页导出。