这一课解决最基础也最容易被软件掩盖的问题:同一条速度为什么会有三个不同的数字?输入是带坐标标签的向量、姿态和传感器安装位置;输出是正确的导航速度、姿态传播式和传感器点速度模型。后续动力学、EKF、位置控制和推进器分配,都以这里的约定为共同语言。不要先记旋转矩阵;先想清楚物理向量没有改变,改变的是用哪组三根基向量描述它。
开始前只需掌握这些
先读 00.1 向量与矩阵乘法 ;本课出现姿态求导时,再回看 00.2 导数 。第一遍只要求会解释 v n = R b n v b v^n=R_b^nv^b v n = R b n v b 、会算一例杆臂速度。欧拉角和四元数部分可在这两个问题完成后分第二遍阅读。
符号 含义与维度 单位/坐标约定 n , b , c n,b,c n , b , c 三个右手坐标系 n n n :北东下;b b b :前右下;c c c :相机右下前R b n R_b^n R b n 3 × 3 3\times3 3 × 3 姿态矩阵,把b b b 分量变为n n n 分量无量纲;列向量是b b b 基轴在n n n 的表示 p n , v n p^n,v^n p n , v n 参考点位置、对地速度;各3 × 1 3\times1 3 × 1 m、m / s \mathrm{m/s} m/s ;n n n ω b = [ p r , q r , r r ] T \omega^b=[p_r,q_r,r_r]^T ω b = [ p r , q r , r r ] T 机体对n n n 角速度;3 × 1 3\times1 3 × 1 r a d / s \mathrm{rad/s} rad/s ;b b b ,角速率下标避免混同位置p p p ϕ , θ , ψ ; q = [ q 0 , q v T ] T \phi,\theta,\psi;q=[q_0,q_v^T]^T ϕ , θ , ψ ; q = [ q 0 , q v T ] T 横滚俯仰偏航;标量在前Hamilton单位四元数 rad;q q q 描述b → n b\to n b → n ℓ b \ell^b ℓ b 参考点到传感器的固定杆臂;3 × 1 3\times1 3 × 1 m;b b b
从基向量投影到可检查的旋转矩阵
假设研究区域足够小,可把导航系看成局部惯性系;姿态描述刚性机体,安装外参在本课保持常数。令机体三根单位轴在导航系中的坐标为矩阵的三列。将一个向量沿机体三轴展开,再对导航三轴取投影,就得到第一行等式。基轴正交使内积保持不变,所以逆矩阵是转置;右手性再要求行列式为正一,而不仅是正交。
a n = [ e b x n e b y n e b z n ] a b = R b n a b , ( R b n ) T R b n = I 3 , det R b n = 1 , R n b = ( R b n ) T . \begin{aligned}a^n&=[e_{b_x}^n\ e_{b_y}^n\ e_{b_z}^n]a^b=R_b^n a^b,\\(R_b^n)^TR_b^n&=I_3,\qquad\det R_b^n=1,\qquad R_n^b=(R_b^n)^T.\end{aligned} a n ( R b n ) T R b n = [ e b x n e b y n e b z n ] a b = R b n a b , = I 3 , det R b n = 1 , R n b = ( R b n ) T .
(1.1)
把式( 1.1 ) (1.1) ( 1.1 ) 拆成一次真正的换基
这里的 a a a 可以是速度、力或位移,但一次计算中必须始终是同一种物理量;上标只是分量所用坐标系,绝不是幂。e b x n e_{b_x}^n e b x n 是机体前轴在导航系中的三个方向余弦。第一步把物理向量写成“前轴乘前向分量,加右轴乘侧向分量,加下轴乘垂直分量”;第二步把三项并成矩阵乘法。第三步计算任意两列的内积:相同列得一,不同列得零,便得到正交性。若矩阵只是交换两个轴而没有同步反号,行列式可能是负一,那是镜像,不是刚体姿态。
例如 a b = ( 1 , 2 , 3 ) T a^b=(1,2,3)^T a b = ( 1 , 2 , 3 ) T 且机头向东,三项分别贡献导航系的 ( 0 , 1 , 0 ) T (0,1,0)^T ( 0 , 1 , 0 ) T 、( − 2 , 0 , 0 ) T (-2,0,0)^T ( − 2 , 0 , 0 ) T 和 ( 0 , 0 , 3 ) T (0,0,3)^T ( 0 , 0 , 3 ) T ,相加得 a n = ( − 2 , 1 , 3 ) T a^n=(-2,1,3)^T a n = ( − 2 , 1 , 3 ) T 。长度平方仍为 14 14 14 ,提供了比单看符号更强的检查。代码实现中先决定矩阵列的意义,再决定存储采用行主序还是列主序;数组布局不会改变数学定义。
采用ZYX姿态参数化:先以偏航确定水平朝向,再俯仰,再横滚。下面矩阵乘法从右向左作用于向量;不能把乘法顺序与口头描述的姿态构造混为一谈。记号c c c 和s s s 分别表示对应角的余弦和正弦。将三个基本矩阵相乘,就逐项得到最后的完整矩阵;例如第一列仅受俯仰和偏航影响,因为绕自身前轴的横滚不改变前轴方向。
R x ( ϕ ) = [ 1 0 0 0 c ϕ − s ϕ 0 s ϕ c ϕ ] , R y ( θ ) = [ c θ 0 s θ 0 1 0 − s θ 0 c θ ] , R z ( ψ ) = [ c ψ − s ψ 0 s ψ c ψ 0 0 0 1 ] , R b n = R z R y R x = [ c ψ c θ c ψ s θ s ϕ − s ψ c ϕ c ψ s θ c ϕ + s ψ s ϕ s ψ c θ s ψ s θ s ϕ + c ψ c ϕ s ψ s θ c ϕ − c ψ s ϕ − s θ c θ s ϕ c θ c ϕ ] . \begin{aligned}R_x(\phi)&=\begin{bmatrix}1&0&0\\0&c_\phi&-s_\phi\\0&s_\phi&c_\phi\end{bmatrix},\quad R_y(\theta)=\begin{bmatrix}c_\theta&0&s_\theta\\0&1&0\\-s_\theta&0&c_\theta\end{bmatrix},\\R_z(\psi)&=\begin{bmatrix}c_\psi&-s_\psi&0\\s_\psi&c_\psi&0\\0&0&1\end{bmatrix},\\R_b^n&=R_zR_yR_x=\begin{bmatrix}c_\psi c_\theta&c_\psi s_\theta s_\phi-s_\psi c_\phi&c_\psi s_\theta c_\phi+s_\psi s_\phi\\s_\psi c_\theta&s_\psi s_\theta s_\phi+c_\psi c_\phi&s_\psi s_\theta c_\phi-c_\psi s_\phi\\-s_\theta&c_\theta s_\phi&c_\theta c_\phi\end{bmatrix}.\end{aligned} R x ( ϕ ) R z ( ψ ) R b n = 1 0 0 0 c ϕ s ϕ 0 − s ϕ c ϕ , R y ( θ ) = c θ 0 − s θ 0 1 0 s θ 0 c θ , = c ψ s ψ 0 − s ψ c ψ 0 0 0 1 , = R z R y R x = c ψ c θ s ψ c θ − s θ c ψ s θ s ϕ − s ψ c ϕ s ψ s θ s ϕ + c ψ c ϕ c θ s ϕ c ψ s θ c ϕ + s ψ s ϕ s ψ s θ c ϕ − c ψ s ϕ c θ c ϕ .
(1.2)
为什么式( 1.2 ) (1.2) ( 1.2 ) 中某些负号不能“凭感觉调整”
绕向下轴正转时,前轴朝右转,因此偏航矩阵第一列的东向分量是 + s i n ψ +sin\psi + s in ψ 。绕向右轴正转时,前轴向上抬,因而俯仰矩阵第一列的下向分量是 − sin θ -\sin\theta − sin θ 。这两个右手规则已经固定了大部分容易写错的符号。矩阵第三列表示机体下轴,不是重力本身;只有水平时它才与导航下轴重合。
完整乘法可以手算一个元素:乘积第一行第二列先取 R y R x R_yR_x R y R x 的第二列 ( sin θ sin ϕ , cos ϕ , cos θ sin ϕ ) T (\sin\theta\sin\phi,\cos\phi,\cos\theta\sin\phi)^T ( sin θ sin ϕ , cos ϕ , cos θ sin ϕ ) T ,再与 R z R_z R z 第一行 ( cos ψ , − sin ψ , 0 ) (\cos\psi,-\sin\psi,0) ( cos ψ , − sin ψ , 0 ) 点乘,得到 cos ψ sin θ sin ϕ − sin ψ cos ϕ \cos\psi\sin\theta\sin\phi-\sin\psi\cos\phi cos ψ sin θ sin ϕ − sin ψ cos ϕ 。每个元素都按同样规则产生,不存在独立调正负号的自由。实现时统一以弧度传入三角函数;把数值 90 90 90 直接传给 std::sin 并不是九十度。
手算:水平、向东即偏航90度时,机体前速0.2 m / s \mathrm{m/s} m/s 变为导航东速0.2 m / s \mathrm{m/s} m/s ;导航北向目标则在机体系表现为左向。相机光轴若恰好对齐机体前轴,则相机右、下、前三轴分别对应机体右、下、前。此时下面的安装矩阵可以直接由三根轴填写;真实安装有偏角时应再乘标定旋转,不能沿用一个任意交换轴。
R b n ( 0 , 0 , π / 2 ) = [ 0 − 1 0 1 0 0 0 0 1 ] , R c b = [ 0 0 1 1 0 0 0 1 0 ] , R c n = R b n R c b . R_b^n(0,0,\pi/2)=\begin{bmatrix}0&-1&0\\1&0&0\\0&0&1\end{bmatrix},\qquad R_c^b=\begin{bmatrix}0&0&1\\1&0&0\\0&1&0\end{bmatrix},\qquad R_c^n=R_b^nR_c^b. R b n ( 0 , 0 , π /2 ) = 0 1 0 − 1 0 0 0 0 1 , R c b = 0 1 0 0 0 1 1 0 0 , R c n = R b n R c b .
(1.3)
式( 1.3 ) (1.3) ( 1.3 ) 的安装外参与姿态为什么必须分开
R c b R_c^b R c b 是安装标定结果,艇体转向时通常不变;R b n R_b^n R b n 是运行中的姿态,随时间变化。组合矩阵的中间坐标必须消去:先把相机分量变成机体分量,再变成导航分量,故顺序是 R b n R c b R_b^nR_c^b R b n R c b ,不是反过来。拿相机光轴单位向量 ( 0 , 0 , 1 ) T (0,0,1)^T ( 0 , 0 , 1 ) T 做检查:安装矩阵将它变成机体 ( 1 , 0 , 0 ) T (1,0,0)^T ( 1 , 0 , 0 ) T ,九十度偏航又变成导航 ( 0 , 1 , 0 ) T (0,1,0)^T ( 0 , 1 , 0 ) T 。如果最终不是东向,错误发生在轴约定或乘法顺序,而不是传感器噪声。对DVL安装也做同一检查,只是设备轴向不必等于这里的相机约定。
先把叉乘矩阵按一行一行乘开
后面的 [ ω b ] × [\omega^b]_\times [ ω b ] × 就是 00.1 的叉乘表 ,没有额外定义。若只绕下轴转,ω b = [ 0 , 0 , r r ] T \omega^b=[0,0,r_r]^T ω b = [ 0 , 0 , r r ] T ,则
[ ω b ] × = [ 0 − r r 0 r r 0 0 0 0 0 ] , [ ω b ] × ℓ b = [ − r r ℓ y r r ℓ x 0 ] . [\omega^b]_\times=\begin{bmatrix}0&-r_r&0\\r_r&0&0\\0&0&0\end{bmatrix},
\qquad
[\omega^b]_\times\ell^b=\begin{bmatrix}-r_r\ell_y\\r_r\ell_x\\0\end{bmatrix}. [ ω b ] × = 0 r r 0 − r r 0 0 0 0 0 , [ ω b ] × ℓ b = − r r ℓ y r r ℓ x 0 .
ℓ x , ℓ y \ell_x,\ell_y ℓ x , ℓ y 是杆臂前、右分量(m),r r r_r r r 是偏航角速度(rad/s),结果单位为 m/s。令 r r = 0.5 r a d / s r_r=0.5\ \mathrm{rad/s} r r = 0.5 rad/s 、ℓ b = [ 0.2 , 0 , 0 ] T m \ell^b=[0.2,0,0]^T\ \mathrm m ℓ b = [ 0.2 , 0 , 0 ] T m ,就得到传感器相对参考点向右 0.1 m / s 0.1\ \mathrm{m/s} 0.1 m/s 的速度。把叉乘换成矩阵是为了以后统一做乘法和求导,不是为了让物理更复杂。
欧拉角变化率不是陀螺仪的三个读数
陀螺仪输出的是瞬时机体角速度。欧拉角变化率却分别绕三个不同的中间轴发生。将偏航轴、俯仰轴和横滚轴都投到最终机体系,再相加,得到下式左矩阵。对其求逆便得到右侧的T T T 矩阵;因此只有姿态接近水平时,三个欧拉角速率才近似等于机体角速度。俯仰达到正负90度时,余弦分母为零,是参数化失效,不是机器人角速度无穷大。
ω b = [ 1 0 − s θ 0 c ϕ s ϕ c θ 0 − s ϕ c ϕ c θ ] [ ϕ ˙ θ ˙ ψ ˙ ] , [ ϕ ˙ θ ˙ ψ ˙ ] = T ( ϕ , θ ) ω b , T = [ 1 s ϕ tan θ c ϕ tan θ 0 c ϕ − s ϕ 0 s ϕ / c θ c ϕ / c θ ] . \begin{aligned}\omega^b&=\begin{bmatrix}1&0&-s_\theta\\0&c_\phi&s_\phi c_\theta\\0&-s_\phi&c_\phi c_\theta\end{bmatrix}\begin{bmatrix}\dot\phi\\\dot\theta\\\dot\psi\end{bmatrix},\\\begin{bmatrix}\dot\phi\\\dot\theta\\\dot\psi\end{bmatrix}&=T(\phi,\theta)\omega^b,\quad T=\begin{bmatrix}1&s_\phi\tan\theta&c_\phi\tan\theta\\0&c_\phi&-s_\phi\\0&s_\phi/c_\theta&c_\phi/c_\theta\end{bmatrix}.\end{aligned} ω b ϕ ˙ θ ˙ ψ ˙ = 1 0 0 0 c ϕ − s ϕ − s θ s ϕ c θ c ϕ c θ ϕ ˙ θ ˙ ψ ˙ , = T ( ϕ , θ ) ω b , T = 1 0 0 s ϕ tan θ c ϕ s ϕ / c θ c ϕ tan θ − s ϕ c ϕ / c θ .
(1.4)
用两个标量方程亲手解出式( 1.4 ) (1.4) ( 1.4 )
先看机体系第二、第三行:q r = cos ϕ θ ˙ + sin ϕ cos θ ψ ˙ q_r=\cos\phi\dot\theta+\sin\phi\cos\theta\dot\psi q r = cos ϕ θ ˙ + sin ϕ cos θ ψ ˙ ,r r = − sin ϕ θ ˙ + cos ϕ cos θ ψ ˙ r_r=-\sin\phi\dot\theta+\cos\phi\cos\theta\dot\psi r r = − sin ϕ θ ˙ + cos ϕ cos θ ψ ˙ 。第一式乘 cos ϕ \cos\phi cos ϕ 减第二式乘 sin ϕ \sin\phi sin ϕ ,交叉项抵消,得到 θ ˙ = cos ϕ q r − sin ϕ r r \dot\theta=\cos\phi q_r-\sin\phi r_r θ ˙ = cos ϕ q r − sin ϕ r r 。第一式乘 sin ϕ \sin\phi sin ϕ 加第二式乘 cos ϕ \cos\phi cos ϕ ,得到 cos θ ψ ˙ = sin ϕ q r + cos ϕ r r \cos\theta\dot\psi=\sin\phi q_r+\cos\phi r_r cos θ ψ ˙ = sin ϕ q r + cos ϕ r r 。最后将 ψ ˙ \dot\psi ψ ˙ 代回第一行 p r = ϕ ˙ − sin θ ψ ˙ p_r=\dot\phi-\sin\theta\dot\psi p r = ϕ ˙ − sin θ ψ ˙ ,三行逆矩阵就都出来了。
例如横滚零、俯仰三十度,只测到机体偏航角速率 r r = 0.2 r a d / s r_r=0.2\ \mathrm{rad/s} r r = 0.2 rad/s ,实际欧拉角速率是 ϕ ˙ ≈ 0.11547 \dot\phi\approx0.11547 ϕ ˙ ≈ 0.11547 、θ ˙ = 0 \dot\theta=0 θ ˙ = 0 、ψ ˙ ≈ 0.23094 r a d / s \dot\psi\approx0.23094\ \mathrm{rad/s} ψ ˙ ≈ 0.23094 rad/s 。把陀螺仪第三项直接积分成偏航会系统性算错。T T T 是速率参数变换,不是正交旋转矩阵,因此不能用转置代替逆。
为了避免欧拉角奇异,积分内部用单位四元数。令旋转轴为单位向量a a a 、旋转角为α \alpha α ,则四元数标量部是半角余弦、向量部是半角正弦乘轴。把纯向量四元数夹在q q q 与其共轭之间,展开Hamilton乘法便得到旋转矩阵。下式中的叉乘矩阵定义也固定了所有正负号。q q q 与负q q q 表示同一姿态,比较两个四元数前应先令其内积非负。
[ a ] × = [ 0 − a 3 a 2 a 3 0 − a 1 − a 2 a 1 0 ] , q = [ cos ( α / 2 ) a sin ( α / 2 ) ] , R ( q ) = ( q 0 2 − q v T q v ) I 3 + 2 q v q v T + 2 q 0 [ q v ] × , q ˙ = 1 2 q ⊗ [ 0 , ( ω b ) T ] T = 1 2 [ 0 − ( ω b ) T ω b − [ ω b ] × ] q . \begin{aligned}[a]_\times&=\begin{bmatrix}0&-a_3&a_2\\a_3&0&-a_1\\-a_2&a_1&0\end{bmatrix},\quad q=\begin{bmatrix}\cos(\alpha/2)\\a\sin(\alpha/2)\end{bmatrix},\\R(q)&=(q_0^2-q_v^Tq_v)I_3+2q_vq_v^T+2q_0[q_v]_\times,\\\dot q&=\tfrac12q\otimes[0,(\omega^b)^T]^T=\tfrac12\begin{bmatrix}0&-(\omega^b)^T\\\omega^b&-[\omega^b]_\times\end{bmatrix}q.\end{aligned} [ a ] × R ( q ) q ˙ = 0 a 3 − a 2 − a 3 0 a 1 a 2 − a 1 0 , q = [ cos ( α /2 ) a sin ( α /2 ) ] , = ( q 0 2 − q v T q v ) I 3 + 2 q v q v T + 2 q 0 [ q v ] × , = 2 1 q ⊗ [ 0 , ( ω b ) T ] T = 2 1 [ 0 ω b − ( ω b ) T − [ ω b ] × ] q .
(1.5)
式( 1.5 ) (1.5) ( 1.5 ) :四元数乘法到底增加了什么
Hamilton乘法把两个四元数 ( s , x ) (s,x) ( s , x ) 、( t , y ) (t,y) ( t , y ) 定义为 ( s t − x T y , s y + t x + x × y ) (st-x^Ty,\ sy+tx+x\times y) ( s t − x T y , sy + t x + x × y ) ,其中 s , t s,t s , t 为标量、x , y x,y x , y 为三维向量。对单位四元数,共轭 ( q 0 , − q v ) (q_0,-q_v) ( q 0 , − q v ) 就是逆。将纯向量 ( 0 , a b ) (0,a^b) ( 0 , a b ) 先左乘 q q q 再右乘共轭,并收集 a b a^b a b 的系数:标量平方贡献单位矩阵项,向量外积贡献第二项,叉乘贡献第三项,这正是式中的 R ( q ) R(q) R ( q ) 。因此它不是另加一套物理,而是同一旋转的无奇异参数表示。
微分式中的 1 / 2 1/2 1/2 来自半角,不是可调增益。四元数四个分量无量纲,其导数单位为每秒;角速度以弧度每秒输入。手算九十度偏航时 q = ( 2 / 2 , 0 , 0 , 2 / 2 ) T q=(\sqrt2/2,0,0,\sqrt2/2)^T q = ( 2 /2 , 0 , 0 , 2 /2 ) T ,矩阵第一行第二列是 − 1 -1 − 1 、第二行第一列是 + 1 +1 + 1 ,与前面的换基矩阵一致。源码中遇到四元数构造函数,要先读清标量在前还是在后、表示机体到地面还是其逆,不能只凭类型名移植公式。
最后一行来自一个短时间间隔内的增量四元数:标量部近似一,向量部近似角速度乘时间的一半。机体角速度对应右乘增量;若改成导航角速度,乘法方向也要改。数值上可以直接用有限角度增量右乘,然后归一化,而不是让欧拉积分逐渐破坏单位长度。
δ q = [ cos ( ∥ ω b ∥ Δ t / 2 ) ω b ∥ ω b ∥ sin ( ∥ ω b ∥ Δ t / 2 ) ] , q k + 1 = q k ⊗ δ q ∥ q k ⊗ δ q ∥ , R ˙ b n = R b n [ ω b ] × . \begin{aligned}\delta q&=\begin{bmatrix}\cos(\|\omega^b\|\Delta t/2)\\\dfrac{\omega^b}{\|\omega^b\|}\sin(\|\omega^b\|\Delta t/2)\end{bmatrix},\quad q_{k+1}=\frac{q_k\otimes\delta q}{\|q_k\otimes\delta q\|},\\\dot R_b^n&=R_b^n[\omega^b]_\times.\end{aligned} δ q R ˙ b n = cos ( ∥ ω b ∥Δ t /2 ) ∥ ω b ∥ ω b sin ( ∥ ω b ∥Δ t /2 ) , q k + 1 = ∥ q k ⊗ δ q ∥ q k ⊗ δ q , = R b n [ ω b ] × .
(1.6)
式( 1.6 ) (1.6) ( 1.6 ) 怎样变成一拍一拍的传播
令 Δ t \Delta t Δ t 是两次IMU有效时间戳之差,单位秒;本式假设这一拍内角速度恒定。先算转角 α = ∥ ω b ∥ Δ t \alpha=\|\omega^b\|\Delta t α = ∥ ω b ∥Δ t ,再算其一半的正余弦,最后按Hamilton规则右乘增量。右乘表示增量轴是在当前机体系表达。归一化是消除舍入误差,不会修正陀螺偏置。若角速度变动很快,还需使用更细积分或圆锥运动补偿,而不是仅提高归一化频率。
静止时应直接得到单位增量;以 1 r a d / s 1\ \mathrm{rad/s} 1 rad/s 绕下轴转动 0.1 s 0.1\ \mathrm{s} 0.1 s 时,标量部约为 0.99875026 0.99875026 0.99875026 ,第三向量分量约为 0.04997917 0.04997917 0.04997917 。传播后用同一姿态旋转机体前轴,结果应为 ( 0.99500417 , 0.09983342 , 0 ) T (0.99500417,0.09983342,0)^T ( 0.99500417 , 0.09983342 , 0 ) T 。这个检查同时验证了半角、乘法方向与单位。
零角速度时,增量四元数直接定义为 δ q = [ 1 , 0 , 0 , 0 ] T \delta q=[1,0,0,0]^T δ q = [ 1 , 0 , 0 , 0 ] T ,不能按上式字面做零除零。为稳定处理很小的角速度,可令 x = ∥ ω b ∥ Δ t / 2 x=\|\omega^b\|\Delta t/2 x = ∥ ω b ∥Δ t /2 ,将向量部写成 ( Δ t / 2 ) sinc ( x ) ω b (\Delta t/2)\operatorname{sinc}(x)\omega^b ( Δ t /2 ) sinc ( x ) ω b ,这里 sinc ( x ) = sin x / x \operatorname{sinc}(x)=\sin x/x sinc ( x ) = sin x / x ,并取 sinc ( 0 ) = 1 \operatorname{sinc}(0)=1 sinc ( 0 ) = 1 ;小角度时使用 1 − x 2 / 6 1-x^2/6 1 − x 2 /6 即可避免数值损失。
杆臂:从位置求导,不靠背补偿符号
同一刚体上参考点 O O O 和传感器S S S 的位置相差旋转后的固定杆臂。对这个位置关系逐项求导:参考点产生平移速度,旋转矩阵导数产生切向速度。再乘转置把导航分量转回机体,就得到传感器模型。观测预测应加杆臂速度;从传感器读数还原参考点速度则应减去它,这两个问题不能混为一个“补偿加号”。
p S n = p O n + R b n ℓ b , v S n = v O n + R b n [ ω b ] × ℓ b , v S b = ( R b n ) T v O n + ω b × ℓ b , v O n = R b n ( v S b − ω b × ℓ b ) . \begin{aligned}p_S^n&=p_O^n+R_b^n\ell^b,\\v_S^n&=v_O^n+R_b^n[\omega^b]_\times\ell^b,\\v_S^b&=(R_b^n)^Tv_O^n+\omega^b\times\ell^b,\\v_O^n&=R_b^n(v_S^b-\omega^b\times\ell^b).\end{aligned} p S n v S n v S b v O n = p O n + R b n ℓ b , = v O n + R b n [ ω b ] × ℓ b , = ( R b n ) T v O n + ω b × ℓ b , = R b n ( v S b − ω b × ℓ b ) .
(1.7)
式( 1.7 ) (1.7) ( 1.7 ) 中参考点必须先讲清
下标 O O O 是我们希望估计和控制的参考点,S S S 是传感器测量中心;杆臂方向必须是从 O O O 指向 S S S 。若厂家给的是反方向,先取负再代入。推导第一行时两点位置都在同一个导航原点下,才能相减;推导第二行时杆臂在机体系恒定,所以没有额外的 R ℓ ˙ R\dot\ell R ℓ ˙ 。第三行把整个式子左乘 R T R^T R T ,而不是仅旋转平移部分。最后一行是求参考点速度的逆问题。
一个有效的程序测试是先正向合成传感器速度,再用补偿函数还原参考点速度。令 v O n = ( 0.1 , 0 , 0 ) T v_O^n=(0.1,0,0)^T v O n = ( 0.1 , 0 , 0 ) T 、偏航九十度、ω b = ( 0 , 0 , 0.2 ) T \omega^b=(0,0,0.2)^T ω b = ( 0 , 0 , 0.2 ) T ,仍用本课杆臂,则传感器机体速度为 ( 0 , − 0.172 , 0 ) T (0,-0.172,0)^T ( 0 , − 0.172 , 0 ) T ;补偿后应恢复导航北向 0.1 m / s 0.1\ \mathrm{m/s} 0.1 m/s 。若直接把观测旋转到导航系,会得到北向 0.172 m / s 0.172\ \mathrm{m/s} 0.172 m/s ,误差正是旋转杆臂带来的那一项。
使用项目杆臂示例( − 0.360 , 0 , 0.125 ) (-0.360,0,0.125) ( − 0.360 , 0 , 0.125 ) m,原地以0.2 r a d / s \mathrm{rad/s} rad/s 绕向下轴偏航,叉乘给出( 0 , − 0.072 , 0 ) m / s (0,-0.072,0)\,\mathrm{m/s} ( 0 , − 0.072 , 0 ) m/s 。传感器显示向左运动完全符合刚体几何,不应把它吸收进DVL偏置。若漏掉这一项,滤波器可能错误地修正参考点速度,进而让位置积分漂移。
离线算法、源码入口与手写练习
参考实现:C++17,先独立手写再展开
// 独立教学实现,C++17;单位m、s、rad,机体FRD,导航NED
#include <array>
#include <cmath>
#include <cassert>
#include <iostream>
using V = std :: array < double , 3 >;
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 ]};}
V rz ( double psi , V v ){ double c = std :: cos (psi),s = std :: sin (psi); return {c * v[ 0 ] - s * v[ 1 ],s * v[ 0 ] + c * v[ 1 ],v[ 2 ]};}
V referenceVelocity ( double psi , V sensor , V omega , V lever ){
V rotational = cross (omega,lever);
for ( int i = 0 ;i < 3 ; ++ i)sensor[i] -= rotational[i];
return rz (psi,sensor);
}
using Q = std :: array < double , 4 >; // 标量在前,b到n
Q multiply ( Q a , Q b ){
return {a[ 0 ] * b[ 0 ] - a[ 1 ] * b[ 1 ] - a[ 2 ] * b[ 2 ] - a[ 3 ] * b[ 3 ],
a[ 0 ] * b[ 1 ] + a[ 1 ] * b[ 0 ] + a[ 2 ] * b[ 3 ] - a[ 3 ] * b[ 2 ],
a[ 0 ] * b[ 2 ] - a[ 1 ] * b[ 3 ] + a[ 2 ] * b[ 0 ] + a[ 3 ] * b[ 1 ],
a[ 0 ] * b[ 3 ] + a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ] + a[ 3 ] * b[ 0 ]};
}
Q propagate ( Q q , V w , double dt ){
double norm = std :: sqrt (w[ 0 ] * w[ 0 ] + w[ 1 ] * w[ 1 ] + w[ 2 ] * w[ 2 ]);
double x = norm * dt / 2 ;
double sinc = std :: abs (x) < 1 e- 6 ? 1 - x * x / 6 : std :: sin (x) / x;
double k = dt * sinc / 2 ;
Q next = multiply (q,{ std :: cos (x),k * w[ 0 ],k * w[ 1 ],k * w[ 2 ]});
double n = 0 ; for ( double v:next)n += v * v;
for ( double & v:next)v /= std :: sqrt (n);
return next;
}
int main (){
const double pi = std :: acos ( - 1.0 );
V v = referenceVelocity (pi / 2 ,{ 0 , - .172 , 0 },{ 0 , 0 , .2 },{ - .360 , 0 , .125 });
assert ( std :: abs (v[ 0 ] - .1 ) < 1 e- 12 && std :: abs (v[ 1 ]) < 1 e- 12 );
Q q = propagate ({ 1 , 0 , 0 , 0 },{ 0 , 0 , 1 }, .1 );
assert ( std :: abs (q[ 0 ] - std :: cos ( .05 )) < 1 e- 12 );
assert ( std :: abs (q[ 3 ] - std :: sin ( .05 )) < 1 e- 12 );
Q stationary = propagate ({ 1 , 0 , 0 , 0 },{ 0 , 0 , 0 }, .1 );
assert (stationary[ 0 ] == 1 && stationary[ 3 ] == 0 );
std ::cout << v[ 0 ] << " " << v[ 1 ] << " " << v[ 2 ] << " \n " ;
}
项目阅读顺序:ArduSub/handle_dvl_data.cpp 的 dvl_data_BI 先核对设备轴向和m m / s \mathrm{mm/s} mm/s 到m / s \mathrm{m/s} m/s ;AP_AHRS_NavEKF::writeBodyFrameOdom 进入机体系里程计接口;libraries/AP_NavEKF3/AP_NavEKF3_PosVelFusion.cpp 的 FuseBodyVel 再查看预测和杆臂如何组合。报告中的创新采用预测减观测,本教材统一观测减预测,两者只是整套更新符号要一致。随后看 AP_InertialNav_NavEKF::update:NED米到NEU厘米需要第三轴反号并乘100。
练习一:手写矩阵乘法验证R T R = I 3 R^TR=I_3 R T R = I 3 ,并用航向90度把北向1 m / s \mathrm{m/s} m/s 转回机体。答案要点:得到( 0 , − 1 , 0 ) m / s (0,-1,0)\,\mathrm{m/s} ( 0 , − 1 , 0 ) m/s 。练习二:将杆臂改为( 0.3 , 0.1 , 0 ) (0.3,0.1,0) ( 0.3 , 0.1 , 0 ) m、偏航角速度改为−0.4 r a d / s \mathrm{rad/s} rad/s ,算传感器速度。答案:( 0.04 , − 0.12 , 0 ) m / s (0.04,-0.12,0)\,\mathrm{m/s} ( 0.04 , − 0.12 , 0 ) m/s 。练习三:从单位四元数出发,以1 r a d / s \mathrm{rad/s} rad/s 偏航积分0.1 s s s ,写出增量四元数并转换到矩阵;应得q = ( cos 0.05 , 0 , 0 , sin 0.05 ) T q=(\cos0.05,0,0,\sin0.05)^T q = ( cos 0.05 , 0 , 0 , sin 0.05 ) T ,矩阵偏航0.1 rad。三题都先手算再写断言,而不是拿绘图“看起来正确”作为判据。
TB01-S1 · Thor I. Fossen:MSS Rzyx.m
精读路线:逐项对照ZYX矩阵,再以零角和90度偏航检验列向量方向;本课其余推导由基变换和刚体求导展开。
原始C++逐行阅读:ArduSub 4.1.1怎样填旋转矩阵
下面是官方 Matrix3<T>::from_euler 中实际存在的三行,不是教学伪代码。来源:ArduPilot,ArduSub-4.1.1,固定提交 66c674c1927d5d31401e6b4bc48b8eb3ee7e066e;原文件第26—43行 。版权 Andrew Tridgell,2012;以下原文片段依 GNU GPL v3-or-later 提供,许可证 。
c.x = - sp;
c.y = sr * cp;
c.z = cr * cp;
函数开头将 sp、cp 定义为俯仰的正弦和余弦,将 sr、cr 定义为横滚的正弦和余弦;成员 c 是第三行。因此这三行就是 [ − sin θ , sin ϕ cos θ , cos ϕ cos θ ] [-\sin\theta,\sin\phi\cos\theta,\cos\phi\cos\theta] [ − sin θ , sin ϕ cos θ , cos ϕ cos θ ] 。第一行的负号与“机首抬起时,机体前轴在NED下向的分量为负”一致。第二行与第三行分别描述机体右轴和下轴在导航下向的投影。将横滚、俯仰都设零,第三行变成 ( 0 , 0 , 1 ) (0,0,1) ( 0 , 0 , 1 ) ;只给三十度俯仰,第三行变成 ( − 0.5 , 0 , 3 / 2 ) (-0.5,0,\sqrt3/2) ( − 0.5 , 0 , 3 /2 ) 。
读完整函数时,逐项把 a.x、a.y、a.z 对应教材第一行,把 b 对应第二行,把 c 对应第三行。接着看同文件的 to_euler:它用第三行第一列恢复俯仰,用第三行第二、第三列恢复横滚,用第二行第一列与第一行第一列恢复偏航。正反转换使用的是同一约定。项目DVL安装轴交换属于进入这套姿态关系之前的传感器外参,不应通过修改这九个通用元素来修正某一台传感器安装。
零基础停步自检
若姿态不变、杆臂为零,传感器与参考点速度还需要杆臂补偿吗?
展开答案与原因
不需要。ω b × ℓ b = 0 \omega^b\times\ell^b=0 ω b × ℓ b = 0 ;坐标变换仍可能需要,但不能把换坐标误称为杆臂补偿。