作为SLAM中位姿估计的重要内容,姿态的表示方法多种多样,估计精度对系统性能的影响也比位置重要的多。众所周知,姿态的描述方法包括等效旋转矢量、欧拉角、旋转矩阵、姿态四元数等等,有大量成熟的文献描述了这些方法的详细定义和相互之间的转换关系。但遗憾的是,这一看似成熟的领域却依旧是实际应用中错误的重灾区。这一现象的原因,归根结底,在于相关概念的混乱,其中最典型的,就是“空间旋转”和“坐标变换”。因此,在深入讨论姿态表示的相关概念之前,我们需要首先明确,我们所谈论的“姿态表示”,到底是什么?
首先我们来考虑第一种情况,记坐标系 f \bm{f} f下一个空间矢量的坐标为 v f \bm{v}^f vf,上标表示矢量所在的坐标系;坐标系 f \bm{f} f保持不变,记该矢量绕 z z z轴旋转 π 2 \frac{\pi}{2} 2π后的矢量为 v ′ f \bm{v}'^f v′f,由线性代数的知识我们知道,两者之间的关系可以由一个矩阵表示,即: v ′ f = R 1 v f \bm{v}'^f=\bm{R}_1\bm{v}^f v′f=R1vf。
接下来我们考虑第二种情况,同样记坐标系 f \bm{f} f下一个空间矢量的坐标为 v f \bm{v}^f vf,现在保持矢量不变,将坐标系 f \bm{f} f绕 z z z轴旋转 π 2 \frac{\pi}{2} 2π后得到坐标系 f 1 \bm{f}_1 f1,同样可以用一个矩阵表示空间矢量 v \bm{v} v在两个坐标系下的关系: v f 1 = R 2 v f \bm{v}^{f_1}=\bm{R}_2\bm{v}^f vf1=R2vf。
显然,直觉告诉我们矩阵 R 1 \bm{R}_1 R1和 R 2 \bm{R}_2 R2并不一样,那两者之间有什么关系呢?
矩阵 R 1 \bm{R}_1 R1表示空间矢量的旋转过程,称为矢量旋转矩阵;与之相对,矩阵 R 2 \bm{R}_2 R2表示坐标系的旋转过程,矢量本身并没有旋转,只是在不同坐标系下的坐标表示不同,因此称为坐标变换矩阵,根据旋转的可逆性,显然有 R 1 R 2 = I \bm{R}_1\bm{R}_2=\bm{I} R1R2=I。这里请大家牢记并理清这两个矩阵定义上的不同,在后续我们讨论各种姿态表示方法之间的转换关系时,一定要首先明确针对的是旋转还是变换,否则将得到完全相反的结论。
这里需要说明,完整的坐标变换矩阵 T \bm{T} T为齐次坐标(Homogeneous Coordinates)形式,包括这里所说的旋转变换矩阵 R \bm{R} R和表示坐标系原点变化的平移变换矢量 t \bm{t} t两部分,即 T = [ R ∣ t ] \bm{T}=[\bm{R}|\bm{t}] T=[R∣t]。但由于本文我们只讨论旋转的相关内容,为了描述方便,以下均将 R \bm{R} R简称为变换矩阵。
使用矩阵来表示旋转存在过参数化问题(对称矩阵的自由度为6维,但实际旋转的自由度只有3维),且难以直观的表示矢量之间的旋转关系。根据刚体运动学的相关知识,有限次的旋转都可以等效为一个旋转轴和一个旋转角的单次旋转。记该单次旋转对应的旋转轴为 k \bm{k} k,旋转角为 θ \theta θ,则矢量 k n \bm{k}n kn称为等效旋转矢量或轴角(Axis-Angle)。
回到上面的问题,旋转前后的矢量之间的关系如何使用等效旋转矢量表示呢?上图表示了同一个坐标系下矢量旋转前后的分解关系,由于所有矢量均在同一个坐标系下表示,为了书写方便,这里我们省略上标 f f f,记旋转前后的矢量分别为 v \bm{v} v和 v ′ \bm{v}' v′,对应的旋转矢量为 k θ \bm{k}\theta kθ。首先我们将矢量分别投影到旋转矢量的垂直方向和平行方向,得到垂直分量 v ⊥ \bm{v}_\perp v⊥和平行分量 v ∥ \bm{v}_\parallel v∥。平行分量可以由向量内积得到:
(1) v ∥ = ( v ⋅ k ) k \bm{v}_\parallel=\left(\bm{v}\cdot\bm{k}\right)\bm{k} \tag{1} v∥=(v⋅k)k(1)
垂直分量 v ⊥ \bm{v}_\perp v⊥则可根据拉格朗日公式 a × ( b × c ) = b ( a ⋅ c ) − c ( a ⋅ b ) \bm{\bm{a}\times(\bm{b}\times\bm{c})=\bm{b}(\bm{a}\cdot\bm{c})-\bm{c}(\bm{a}\cdot\bm{b})} a×(b×c)=b(a⋅c)−c(a⋅b)得到:
(2) v ⊥ = v − ( v ⋅ k ) k = − k × ( k × v ) \bm{v}_\perp=\bm{v}-\left(\bm{v}\cdot\bm{k}\right)\bm{k}=-\bm{k}\times(\bm{k}\times\bm{v}) \tag{2} v⊥=v−(v⋅k)k=−k×(k×v)(2)
平行分量在旋转过程中保持不变 v ∥ ′ = v ∥ \bm{v}'_\parallel=\bm{v}_\parallel v∥′=v∥,旋转后的垂直分量为:
(3) v ⊥ ′ = v ⊥ cos θ + k × v ⊥ sin θ \bm{v}'_\perp=\bm{v}_\perp\cos\theta+\bm{k}\times\bm{v}_\perp\sin\theta \tag{3} v⊥′=v⊥cosθ+k×v⊥sinθ(3)
将 v ⊥ = v − v ∥ \bm{v}_\perp=\bm{v}-\bm{v}_\parallel v⊥=v−v∥带入式 ( 3 ) (3) (3)并注意到 k × v ∥ = 0 \bm{k}\times\bm{v}_\parallel=\bm{0} k×v∥=0得到:
(4) v ′ = v ∥ ′ + v ⊥ ′ = v ∥ + ( v − v ∥ ) cos θ + k × ( v − v ∥ ) sin θ = cos θ v + ( 1 − cos θ ) v ∥ + sin θ k × v = cos θ v + ( 1 − cos θ ) ( v ⋅ k ) k + sin θ k × v \begin{aligned} \bm{v}'&=\bm{v}'_\parallel+\bm{v}'_\perp\\ &=\bm{v}_\parallel+(\bm{v}-\bm{v}_\parallel)\cos\theta+\bm{k}\times(\bm{v}-\bm{v}_\parallel)\sin\theta\\ &=\cos\theta\bm{v}+(1-\cos\theta)\bm{v}_\parallel+\sin\theta\bm{k}\times\bm{v}\\ &=\cos\theta\bm{v}+(1-\cos\theta)(\bm{v}\cdot\bm{k})\bm{k}+\sin\theta\bm{k}\times\bm{v} \end{aligned} \tag{4} v′=v∥′+v⊥′=v∥+(v−v∥)cosθ+k×(v−v∥)sinθ=cosθv+(1−cosθ)v∥+sinθk×v=cosθv+(1−cosθ)(v⋅k)k+sinθk×v(4)
由于 ( v ⋅ k ) = k k T v (\bm{v}\cdot\bm{k})=\bm{k}\bm{k}^T\bm{v} (v⋅k)=kkTv,带入上式,并与 v ’ = R v \bm{v}’=\bm{R}\bm{v} v’=Rv联立,可以得到旋转矩阵 R \bm{R} R的表达式为:
(5) R = cos θ I + ( 1 − cos θ ) k k T + sin θ k ∧ \bm{R}=\cos\theta\bm{I}+(1-\cos\theta)\bm{k}\bm{k}^T+\sin\theta\bm{k}^\wedge \tag{5} R=cosθI+(1−cosθ)kkT+sinθk∧(5)
至此,我们得到了使用等效旋转矢量表示的旋转前后矢量坐标之间的关系,式 ( 5 ) (5) (5)称为罗德里格斯公式(Rodrigues’s Formula),表征了等效旋转矢量和旋转矩阵之间的关系。
注意这里的矩阵 R \bm{R} R是旋转矩阵而非变换矩阵。有时候(甚至更多时候)我们会用等效旋转矢量表示坐标系的旋转而非矢量的旋转。例如,坐标系 f 1 \bm{f}_1 f1经过等效旋转矢量 k θ \bm{k}\theta kθ旋转后,得到坐标系 f 2 \bm{f}_2 f2,则由罗德里格斯计算得到的矩阵 R \bm{R} R表征的是 f 2 → f 1 \bm{f}_2\rightarrow\bm{f}_1 f2→f1的坐标变换,即:
(6) v f 1 = R v f 2 \bm{v}^{f_1}=\bm{R}\bm{v}^{f_2} \tag{6} vf1=Rvf2(6)
使用等效旋转矢量表示姿态依旧存在一个问题——“奇异性”,因此我们需要找到一种即紧凑,又不容易产生奇异的方式描述姿态。由于复数天然可以用来表示复平面上的旋转,使用复数表示空间三维旋转的方式也应运而生,即姿态四元数。
一个完整的姿态四元数包含一个实部和三个虚部,且模长为1:
(7) q = q w + q x i + q y j + q z k ∥ q ∥ = 1 \begin{aligned} &\bm{q}=q_w+q_x\bm{i}+q_y\bm{j}+q_z\bm{k}\\ &\|\bm{q}\|=1 \end{aligned} \tag{7} q=qw+qxi+qyj+qzk∥q∥=1(7)
这里我们采用的是实部在前,虚部在后的方式,称为汉密尔顿四元数(Hamilton Quaternion);同样亦存在实部位于最后的定义方式(例如后面要将的C++线性代数库Eigen),在使用四元数相关公式时(例如四元数微分、四元数和旋转矩阵的转换等)一定要明确四元数分量的排列顺序。
四元数和旋转矢量之间的关系定义为:
(8) q = cos θ 2 + sin θ 2 i + sin θ 2 j + sin θ 2 k \bm{q}=\cos\frac{\theta}{2}+\sin\frac{\theta}{2}\bm{i}+\sin\frac{\theta}{2}\bm{j}+\sin\frac{\theta}{2}\bm{k} \tag{8} q=cos2θ+sin2θi+sin2θj+sin2θk(8)
由式 ( 8 ) (8) (8)得到的四元数称为旋转四元数,使用旋转四元数进行矢量旋转的操作为:
(9) v ′ f = q v f q ∗ \bm{v}'^f = \bm{q}\bm{v}^f\bm{q}^\ast \tag{9} v′f=qvfq∗(9)
式中, q ∗ = cos θ 2 − sin θ 2 i − sin θ 2 j − sin θ 2 k \bm{q}^\ast=\cos\frac{\theta}{2}-\sin\frac{\theta}{2}\bm{i}-\sin\frac{\theta}{2}\bm{j}-\sin\frac{\theta}{2}\bm{k} q∗=cos2θ−sin2θi−sin2θj−sin2θk为 q \bm{q} q的共轭四元数。
同样,当四元数 q \bm{q} q表示坐标系 f 1 → f 2 \bm{f}_1\rightarrow\bm{f}_2 f1→f2的旋转时,式 ( 9 ) (9) (9)表征的是 f 2 → f 1 \bm{f}_2\rightarrow\bm{f}_1 f2→f1的坐标变换关系,即:
(10) v f 1 = q v f 2 q ∗ \bm{v}^{f_1}=\bm{q}\bm{v}^{f_2}\bm{q}^\ast \tag{10} vf1=qvf2q∗(10)
由于四元数与其共轭表示相反方向的旋转,自然有 v f 2 = q ∗ v f 1 q \bm{v}^{f_2}=\bm{q}^\ast\bm{v}^{f_1}\bm{q} vf2=q∗vf1q,将其与式 ( 6 ) (6) (6)联立可以得到坐标系 f 1 → f 2 \bm{f}_1\rightarrow\bm{f}_2 f1→f2之间旋转四元数 q f 1 f 2 \bm{q}_{f_1}^{f_2} qf1f2和坐标变换矩阵 R f 1 f 2 \bm{R}_{f_1}^{f_2} Rf1f2之间的关系为:
(11) R f 1 f 2 [ x y z ] = [ ( q w 2 + q x 2 − q y 2 − q z 2 ) x + 2 ( q w q z + q x q y ) y + 2 ( q x q z − q 2 q y ) z 2 ( q x q y − q w q z ) x + ( q w 2 − q x 2 + q y 2 − q z 2 ) y + 2 ( q w q x + q y q z ) z 2 ( q w q y − q x q z ) x + 2 ( q y q z − q w q x ) y + ( q w 2 − q x 2 − q y 2 + q z 2 ) z ] = [ ( q w 2 + q x 2 − q y 2 − q z 2 ) 2 ( q w q z + q x q y ) 2 ( q x q z − q 2 q y ) 2 ( q x q y − q w q z ) ( q w 2 − q x 2 + q y 2 − q z 2 ) 2 ( q w q x + q y q z ) 2 ( q w q y − q x q z ) 2 ( q y q z − q w q x ) ( q w 2 − q x 2 − q y 2 + q z 2 ) ] [ x y z ] \begin{aligned} \bm{R}_{f_1}^{f_2}\left[\begin{array}{c}x\\y\\z\end{array}\right]&=\left[\begin{array}{c} \left(q_w^2+q_x^2-q_y^2-q_z^2\right)x + 2\left(q_wq_z+q_xq_y\right)y+2\left(q_xq_z-q_2q_y\right)z\\ 2\left(q_xq_y-q_wq_z\right)x+\left(q_w^2-q_x^2+q_y^2-q_z^2\right)y+2\left(q_wq_x+q_yq_z\right)z\\ 2\left(q_wq_y-q_xq_z\right)x+2\left(q_yq_z-q_wq_x\right)y+\left(q_w^2-q_x^2-q_y^2+q_z^2\right)z \end{array}\right]\\ &=\left[\begin{array}{ccc} \left(q_w^2+q_x^2-q_y^2-q_z^2\right) & 2\left(q_wq_z+q_xq_y\right) & 2\left(q_xq_z-q_2q_y\right)\\ 2\left(q_xq_y-q_wq_z\right) & \left(q_w^2-q_x^2+q_y^2-q_z^2\right) & 2\left(q_wq_x+q_yq_z\right)\\ 2\left(q_wq_y-q_xq_z\right) & 2\left(q_yq_z-q_wq_x\right) & \left(q_w^2-q_x^2-q_y^2+q_z^2\right) \end{array}\right]\left[\begin{array}{c}x\\y\\z\end{array}\right] \end{aligned} \tag{11} Rf1f2⎣⎡xyz⎦⎤=⎣⎡(qw2+qx2−qy2−qz2)x+2(qwqz+qxqy)y+2(qxqz−q2qy)z2(qxqy−qwqz)x+(qw2−qx2+qy2−qz2)y+2(qwqx+qyqz)z2(qwqy−qxqz)x+2(qyqz−qwqx)y+(qw2−qx2−qy2+qz2)z⎦⎤=⎣⎡(qw2+qx2−qy2−qz2)2(qxqy−qwqz)2(qwqy−qxqz)2(qwqz+qxqy)(qw2−qx2+qy2−qz2)2(qyqz−qwqx)2(qxqz−q2qy)2(qwqx+qyqz)(qw2−qx2−qy2+qz2)⎦⎤⎣⎡xyz⎦⎤(11)
由于解算存在奇异,且对转序敏感,欧拉角在实际的姿态解算中应用并不多;但欧拉角的表示方式比较符合易于想象,因此在大多数情况下只用于结果的显示和保存,这里暂且略过不谈。
作为应用最广泛的C++开源线性代数运算库,几何模块Eigen::Geometry
提供了刚体运动相关的各项功能,所有角度的单位以弧度(radius)表示。
Eigen中的坐标变换矩阵即普通的3x3双精度矩阵Matrix3d
,构造方式和普通的矩阵一致,例如:
Eigen::Matrix3d rotation_matrix;
rotation_matrix << x_00,x_01,x_02,x_10,x_11,x_12,x_20,x_21,x_22;
Eigen中的等效旋转矢量拥有自己的专属模板类AngelAxis
,一般使用预定义好的double
型子类AngleAxisd
,构造方式如下:
Eigen::AngleAxisd rvec(double angle, Eigen::Vector3d axis)//直接由参数构造
Eigen::AngleAxisd rvec(rotation_matrix)// 通过变换矩阵构造
需要注意的是构造时传入的变换矩阵为逆矩阵,即若构造 f 1 → f 2 \bm{f}_1\rightarrow\bm{f}_2 f1→f2的旋转矢量,则需要传入变换矩阵 R f 2 f 1 \bm{R}_{f_2}^{f_1} Rf2f1。构造完成后,使用成员函数toRotationMatrix()
可以获得逆变换矩阵 R f 2 f 1 \bm{R}_{f_2}^{f_1} Rf2f1。
Eigen中的旋转四元数归属于模板类Quaternion
,一般同样使用预定好的double
型子类Quaterniond
,旋转四元数可以直接由旋转矢量构建:
Eigen::Quaterniond q(rvec)// 直接由旋转矢量构建
Eigen::Quaterniond q1(q_w, q_x, q_y, q_z)// 直接由分量构建;
Eigen::Quaterniond q2(rotation_matrix)// 直接由变换矩阵构建;
Eigen中使用旋转矢量和四元数对矢量进行运算时,由于操作符*
重载为矩阵运算方式,因此只接收左乘操作,运算表示旋转而非变换。
Eigen::AngleAxisd rvec(M_PI/2.0, Eigen::VEctor3d(0, 0, 1)); // 绕z轴旋转pi/2
Eigen::Matrix3d R = rvec.toRotationMatrix();
Eigen::Quaterniond q(rvec);
Eigen::Vector3d a{1., 0., 0.}
std::cout << "q * a: " << std::endl << q * a << std:: endl;
std::cout << "R * a: " << std::endl << R * a <<std::endl;
std::cout << "rvec * a" << std::endl << rvec * a << std::endl;
在上面这个例子中,无论是使用四元数、旋转矩阵(注意不是变换矩阵)还是旋转矢量,我们均得到了一致的结果。
综上,在进行姿态运算时,一定要理清到底是矢量的旋转还是坐标系的旋转,使用的矩阵到底是旋转矩阵还是变换矩阵,否则很容易获得完全相反的结果。