《视觉SLAM十四讲》读书笔记(一)

本着重温知识和巩固基础的目的,我最近重新精读一次《十四讲》。考虑到脑子不好使,每次看完就忘记,因此开一个专栏专门用于记录重要知识点。由于本身具备一定的SLAM系统开发经验和理论知识,因此一些太过基础的知识会适当省略过。

3 三维空间刚体运动

知识点:

  1. 三维空间的刚体运动描述方式:旋转矩阵、变换矩阵、四元数和欧拉角
  2. Eigen库的矩阵、几何模块的使用

3.1 旋转矩阵

3.1.1 点、向量和坐标系

[ e 1 , e 2 , e 3 ] [\boldsymbol{e}_{1}, \boldsymbol{e}_{2}, \boldsymbol{e}_{3}] [e1,e2,e3]与坐标 [ a 1 , a 2 , a 3 ] T [a_{1},a_{2} , a_{3} ]^T [a1,a2,a3]T组成向量 a \boldsymbol{a} a
a = [ e 1 , e 2 , e 3 ] [ a 1 a 2 a 3 ] = a 1 e 1 + a 2 e 2 + a 3 e 3 (3.1) \boldsymbol{a}=\left[\boldsymbol{e}_{1}, \boldsymbol{e}_{2}, \boldsymbol{e}_{3}\right]\left[\begin{array}{l} a_{1} \\ a_{2} \\ a_{3} \end{array}\right]=a_{1} \boldsymbol{e}_{1}+a_{2} \boldsymbol{e}_{2}+a_{3} \boldsymbol{e}_{3} \tag{3.1} a=[e1,e2,e3]a1a2a3=a1e1+a2e2+a3e3(3.1)

向量内积(点乘):

a ⋅ b = a T b = ∑ i = 1 3 a i b i = ∣ a ∣ ∣ b ∣ cos ⁡ ⟨ a , b ⟩ (3.2) \boldsymbol{a} \cdot \boldsymbol{b}=\boldsymbol{a}^{\mathrm{T}} \boldsymbol{b}=\sum_{i=1}^{3} a_{i} b_{i}=|\boldsymbol{a}||\boldsymbol{b}| \cos \langle\boldsymbol{a}, \boldsymbol{b}\rangle \tag{3.2} ab=aTb=i=13aibi=abcosa,b(3.2)

向量外积(叉乘):
a × b = ∥ e 1 e 2 e 3 a 1 a 2 a 3 b 1 b 2 b 3 ∥ = [ a 2 b 3 − a 3 b 2 a 3 b 1 − a 1 b 3 a 1 b 2 − a 2 b 1 ] = [ 0 − a 3 a 2 a 3 0 − a 1 − a 2 a 1 0 ] b =  def  a ∧ b (3.3) \boldsymbol{a} \times \boldsymbol{b}=\left\|\begin{array}{lll} \boldsymbol{e}_{1} & e_{2} & e_{3} \\ a_{1} & a_{2} & a_{3} \\ b_{1} & b_{2} & b_{3} \end{array}\right\|=\left[\begin{array}{c} a_{2} b_{3}-a_{3} b_{2} \\ a_{3} b_{1}-a_{1} b_{3} \\ a_{1} b_{2}-a_{2} b_{1} \end{array}\right]=\left[\begin{array}{ccc} 0 & -a_{3} & a_{2} \\ a_{3} & 0 & -a_{1} \\ -a_{2} & a_{1} & 0 \end{array}\right] \boldsymbol{b} \stackrel{\text { def }}{=} \boldsymbol{a}^{\wedge} \boldsymbol{b}\tag{3.3} a×b=e1a1b1e2a2b2e3a3b3=a2b3a3b2a3b1a1b3a1b2a2b1=0a3a2a30a1a2a10b= def ab(3.3)
外积的结果是一个向量,它的方向垂直于这两个向量,大小为 ∣ a ∣ ∣ b ∣ sin ⁡ ⟨ a , b ⟩ |\boldsymbol{a}||\boldsymbol{b}| \sin\langle\boldsymbol{a}, \boldsymbol{b}\rangle absina,b,是两个向量张成的四边形的有向面积。

基于外积,一种向量到矩阵的一一映射关系被定义,反对称矩阵:
a ∧ = [ [ e 1 , e 2 , e 3 ] [ a 1 a 2 a 3 ] ] ∧ = [ 0 − a 3 a 2 a 3 0 − a 1 − a 2 a 1 0 ] (3.4) \boldsymbol{a}^{\wedge} = \left[ \left[\boldsymbol{e}_{1}, \boldsymbol{e}_{2}, \boldsymbol{e}_{3}\right]\left[\begin{array}{l} a_{1} \\ a_{2} \\ a_{3} \end{array}\right]\right] ^{\wedge} = \left[\begin{array}{ccc} 0 & -a_{3} & a_{2} \\ a_{3} & 0 & -a_{1} \\ -a_{2} & a_{1} & 0 \end{array}\right] \tag{3.4} a=[e1,e2,e3]a1a2a3=0a3a2a30a1a2a10(3.4)
注:务必务必记住(3.4)的矩阵形式,在后面公式推导中十分重要。

3.1.2 坐标系间的欧式变换

刚体运动的解释:两个坐标系之间,由一个旋转和一个平移组成的运动。

欧式变换的解释:由旋转和平移组成的一种变换关系。

旋转矩阵的定义:
设单位正交基 [ e 1 , e 2 , e 3 ] \left[\boldsymbol{e}_{1}, \boldsymbol{e}_{2}, \boldsymbol{e}_{3}\right] [e1,e2,e3]经过一次旋转后变成了 [ e 1 ′ , e 2 ′ , e 3 ′ ] \left[\boldsymbol{e}_{1}', \boldsymbol{e}_{2}', \boldsymbol{e}_{3}'\right] [e1,e2,e3]。对于同一个向量 a \boldsymbol{a} a,它在两个坐标系下的坐标为 [ a 1 , a 2 , a 3 ] [a_{1}, a_{2}, a_{3}] [a1,a2,a3] [ a 1 ′ , a 2 ′ , a 3 ′ ] [a_{1}', a_{2}', a_{3}'] [a1,a2,a3],由于向量本身没变,所以存在如下约束:
[ e 1 , e 2 , e 3 ] [ a 1 a 2 a 3 ] = [ e 1 ′ , e 2 ′ , e 3 ′ ] [ a 1 ′ a 2 ′ a 3 ′ ] (3.5) \left[\boldsymbol{e}_{1}, \boldsymbol{e}_{2}, \boldsymbol{e}_{3}\right]\left[\begin{array}{l} a_{1} \\ a_{2} \\ a_{3} \end{array}\right]=\left[\boldsymbol{e}_{1}', \boldsymbol{e}_{2}', \boldsymbol{e}_{3}'\right]\left[\begin{array}{c} a_{1}^{\prime} \\ a_{2}^{\prime} \\ a_{3}^{\prime} \end{array}\right]\tag{3.5} [e1,e2,e3]a1a2a3=[e1,e2,e3]a1a2a3(3.5)
左右两边同事左乘 [ e 1 T e 2 T e 3 T ] \left[\begin{array}{c} \boldsymbol{e}_{1}^\mathrm{T} \\ \boldsymbol{e}_{2}^\mathrm{T} \\ \boldsymbol{e}_{3}^\mathrm{T} \end{array}\right] e1Te2Te3T
[ a 1 a 2 a 3 ] = [ e 1 T e 1 ′ e 1 T e 2 ′ e 1 T e 3 ′ e 2 T e 1 ′ e 2 T e 2 ′ e 2 T e 3 ′ e 3 l e 1 ′ e 3 T e 2 ′ e 3 T e 3 ′ ] [ a 1 ′ a 2 ′ a 3 ′ ] =  def  R a ′ (3.6) \left[\begin{array}{l} a_{1} \\ a_{2} \\ a_{3} \end{array}\right]=\left[\begin{array}{lll} \boldsymbol{e}_{1}^{\mathrm{T}} \boldsymbol{e}_{1}^{\prime} & \boldsymbol{e}_{1}^{\mathrm{T}} \boldsymbol{e}_{2}^{\prime} & \boldsymbol{e}_{1}^{\mathrm{T}} \boldsymbol{e}_{3}^{\prime} \\ \boldsymbol{e}_{2}^{\mathrm{T}} \boldsymbol{e}_{1}^{\prime} & \boldsymbol{e}_{2}^{\mathrm{T}} \boldsymbol{e}_{2}^{\prime} & \boldsymbol{e}_{2}^{\mathrm{T}} \boldsymbol{e}_{3}^{\prime} \\ \boldsymbol{e}_{3}^{\mathrm{l}} \boldsymbol{e}_{1}^{\prime} & \boldsymbol{e}_{3}^{\mathrm{T}} \boldsymbol{e}_{2}^{\prime} & \boldsymbol{e}_{3}^{\mathrm{T}} \boldsymbol{e}_{3}^{\prime} \end{array}\right]\left[\begin{array}{l} a_{1}^{\prime} \\ a_{2}^{\prime} \\ a_{3}^{\prime} \end{array}\right] \stackrel{\text { def }}{=} \boldsymbol{R} \boldsymbol{a}^{\prime}\tag{3.6} a1a2a3=e1Te1e2Te1e3le1e1Te2e2Te2e3Te2e1Te3e2Te3e3Te3a1a2a3= def Ra(3.6)
旋转矩阵 R \boldsymbol{R} R各分量是两个坐标系基的内积,由于基向量的长度为1,因此其实际上是各基向量夹角的余弦值。所以这个矩阵也叫方向余弦。

旋转矩阵特性总结:

  1. 矩阵各分量是各基向量夹角的余弦值;
  2. 它是行列式为1的正交矩阵;
  3. R − 1 = R T \boldsymbol{R}^{-1}=\boldsymbol{R}^T R1=RT

Eigen关于旋转矩阵的定义和计算

  1. 初始化旋转矩阵
Eigen::Matrix3d rotation_matrix;
rotation_matrix<<x_00,x_01,x_02,x_10,x_11,x_12,x_20,x_21,x_22;
  1. 旋转矩阵转旋转向量
Eigen::AngleAxisd rotation_vector(rotation_matrix);


Eigen::AngleAxisd rotation_vector;
rotation_vector=rotation_matrix;

Eigen::AngleAxisd rotation_vector;
rotation_vector.fromRotationMatrix(rotation_matrix);
  1. 旋转矩阵转欧拉角(Z-Y-X,即RPY)
Eigen::Vector3d eulerAngle=rotation_matrix.eulerAngles(2,1,0);
  1. 旋转矩阵转四元数
Eigen::Quaterniond quaternion(rotation_matrix);

Eigen::Quaterniond quaternion;quaternion=rotation_matrix;

欧氏空间的坐标变换:
a 1 = R 12 a 2 + t 12 (3.10) \boldsymbol{a}_{1}=\boldsymbol{R}_{12} \boldsymbol{a}_{2}+\boldsymbol{t}_{12}\tag{3.10} a1=R12a2+t12(3.10)
R 12 \boldsymbol{R}_{12} R12:从坐标系2到坐标系1的旋转,或者把坐标系2的向量变换到坐标系1中。
t 12 \boldsymbol{t}_{12} t12:从坐标系2到坐标系1的向量,或者坐标系1原点指向坐标系2原点的向量,在坐标系1下取的坐标。

记忆方法:向量乘在矩阵的右边,它的下标是从右读到左的。

3.1.3 变换矩阵和齐次坐标

齐次坐标和变换矩阵的表达形式:
[ a ′ 1 ] = [ R t 0 T 1 ] [ a 1 ] =  def  T [ a 1 ] (3.11) \left[\begin{array}{c} a^{\prime} \\ 1 \end{array}\right]=\left[\begin{array}{ll} R & t \\ 0^{\mathrm{T}} & 1 \end{array}\right]\left[\begin{array}{l} a \\ 1 \end{array}\right] \stackrel{\text { def }}{=} \boldsymbol{T}\left[\begin{array}{l} a \\ 1 \end{array}\right]\tag{3.11} [a1]=[R0Tt1][a1]= def T[a1](3.11)

齐次变换的逆:
T − 1 = [ R T − R T t 0 T 1 ] (3.14) \boldsymbol{T}^{-1}=\left[\begin{array}{cc} \boldsymbol{R}^{\mathrm{T}} & -\boldsymbol{R}^{\mathrm{T}} t \\ \mathbf{0}^{\mathrm{T}} & 1 \end{array}\right]\tag{3.14} T1=[RT0TRTt1](3.14)

3.2 实践:Eigen

#include <iostream>

using namespace std;

#include <ctime>
// Eigen 核心部分
#include <Eigen/Core>
// 稠密矩阵的代数运算(逆,特征值等)
#include <Eigen/Dense>

using namespace Eigen;

#define MATRIX_SIZE 50

/****************************
* 本程序演示了 Eigen 基本类型的使用
****************************/

int main(int argc, char **argv) {
  // Eigen 中所有向量和矩阵都是Eigen::Matrix,它是一个模板类。它的前三个参数为:数据类型,行,列
  // 声明一个2*3的float矩阵
  Matrix<float, 2, 3> matrix_23;

  // 同时,Eigen 通过 typedef 提供了许多内置类型,不过底层仍是Eigen::Matrix
  // 例如 Vector3d 实质上是 Eigen::Matrix<double, 3, 1>,即三维向量
  Vector3d v_3d;
  // 这是一样的
  Matrix<float, 3, 1> vd_3d;

  // Matrix3d 实质上是 Eigen::Matrix<double, 3, 3>
  Matrix3d matrix_33 = Matrix3d::Zero(); //初始化为零
  // 如果不确定矩阵大小,可以使用动态大小的矩阵
  Matrix<double, Dynamic, Dynamic> matrix_dynamic;
  // 更简单的
  MatrixXd matrix_x;
  // 这种类型还有很多,我们不一一列举

  // 下面是对Eigen阵的操作
  // 输入数据(初始化)
  matrix_23 << 1, 2, 3, 4, 5, 6;
  // 输出
  cout << "matrix 2x3 from 1 to 6: \n" << matrix_23 << endl;

  // 用()访问矩阵中的元素
  cout << "print matrix 2x3: " << endl;
  for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 3; j++) cout << matrix_23(i, j) << "\t";
    cout << endl;
  }

  // 矩阵和向量相乘(实际上仍是矩阵和矩阵)
  v_3d << 3, 2, 1;
  vd_3d << 4, 5, 6;

  // 但是在Eigen里你不能混合两种不同类型的矩阵,像这样是错的
  // Matrix<double, 2, 1> result_wrong_type = matrix_23 * v_3d;
  // 应该显式转换
  Matrix<double, 2, 1> result = matrix_23.cast<double>() * v_3d;
  cout << "[1,2,3;4,5,6]*[3,2,1]=" << result.transpose() << endl;

  Matrix<float, 2, 1> result2 = matrix_23 * vd_3d;
  cout << "[1,2,3;4,5,6]*[4,5,6]: " << result2.transpose() << endl;

  // 同样你不能搞错矩阵的维度
  // 试着取消下面的注释,看看Eigen会报什么错
  // Eigen::Matrix<double, 2, 3> result_wrong_dimension = matrix_23.cast<double>() * v_3d;

  // 一些矩阵运算
  // 四则运算就不演示了,直接用+-*/即可。
  matrix_33 = Matrix3d::Random();      // 随机数矩阵
  cout << "random matrix: \n" << matrix_33 << endl;
  cout << "transpose: \n" << matrix_33.transpose() << endl;      // 转置
  cout << "sum: " << matrix_33.sum() << endl;            // 各元素和
  cout << "trace: " << matrix_33.trace() << endl;          // 迹
  cout << "times 10: \n" << 10 * matrix_33 << endl;               // 数乘
  cout << "inverse: \n" << matrix_33.inverse() << endl;        // 逆
  cout << "det: " << matrix_33.determinant() << endl;    // 行列式

  // 特征值
  // 实对称矩阵可以保证对角化成功
  SelfAdjointEigenSolver<Matrix3d> eigen_solver(matrix_33.transpose() * matrix_33);
  cout << "Eigen values = \n" << eigen_solver.eigenvalues() << endl;
  cout << "Eigen vectors = \n" << eigen_solver.eigenvectors() << endl;

  // 解方程
  // 我们求解 matrix_NN * x = v_Nd 这个方程
  // N的大小在前边的宏里定义,它由随机数生成
  // 直接求逆自然是最直接的,但是求逆运算量大

  Matrix<double, MATRIX_SIZE, MATRIX_SIZE> matrix_NN
      = MatrixXd::Random(MATRIX_SIZE, MATRIX_SIZE);
  matrix_NN = matrix_NN * matrix_NN.transpose();  // 保证半正定
  Matrix<double, MATRIX_SIZE, 1> v_Nd = MatrixXd::Random(MATRIX_SIZE, 1);

  clock_t time_stt = clock(); // 计时
  // 直接求逆
  Matrix<double, MATRIX_SIZE, 1> x = matrix_NN.inverse() * v_Nd;
  cout << "time of normal inverse is "
       << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
  cout << "x = " << x.transpose() << endl;

  // 通常用矩阵分解来求,例如QR分解,速度会快很多
  time_stt = clock();
  x = matrix_NN.colPivHouseholderQr().solve(v_Nd);
  cout << "time of Qr decomposition is "
       << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
  cout << "x = " << x.transpose() << endl;

  // 对于正定矩阵,还可以用cholesky分解来解方程
  time_stt = clock();
  x = matrix_NN.ldlt().solve(v_Nd);
  cout << "time of ldlt decomposition is "
       << 1000 * (clock() - time_stt) / (double) CLOCKS_PER_SEC << "ms" << endl;
  cout << "x = " << x.transpose() << endl;

  return 0;
}

3.3 旋转向量和欧拉角

3.3.1 旋转向量

旋转轴为一个单位长度的向量 n \boldsymbol{n} n,角度为 θ \theta θ

旋转向量特性:旋转轴上的向量在旋转后不发生改变
R n = n (3.18) \boldsymbol{R} \boldsymbol{n}=\boldsymbol{n} \tag{3.18} Rn=n(3.18)
转轴 n \boldsymbol{n} n是矩阵 R \boldsymbol{R} R特征值1对应的特征向量。

求特征向量的方法:先求特征值,再一一代入方程 ( R − I ) n = 0 (\boldsymbol{R}-\boldsymbol{I})\boldsymbol{n}=\boldsymbol{0} (RI)n=0求特征向量。

旋转向量 → \rightarrow 旋转矩阵

R = cos ⁡ θ I + ( 1 − cos ⁡ θ ) n n T + sin ⁡ θ n ∧ (3.15) \boldsymbol{R}=\cos \theta \boldsymbol{I}+(1-\cos \theta) \boldsymbol{n} \boldsymbol{n}^{\mathrm{T}}+\sin \theta \boldsymbol{n}^{\wedge} \tag{3.15} R=cosθI+(1cosθ)nnT+sinθn(3.15)

旋转矩阵 → \rightarrow 旋转向量

转角:
tr ⁡ ( R ) = cos ⁡ θ tr ⁡ ( I ) + ( 1 − cos ⁡ θ ) tr ⁡ ( n n T ) + sin ⁡ θ tr ⁡ ( n ∧ ) = 3 cos ⁡ θ + ( 1 − cos ⁡ θ ) = 1 + 2 cos ⁡ θ (3.16) \begin{aligned} \operatorname{tr}(\boldsymbol{R})=& \cos \theta \operatorname{tr}(\boldsymbol{I})+(1-\cos \theta) \operatorname{tr}\left(\boldsymbol{n} \boldsymbol{n}^{\mathrm{T}}\right)+\sin \theta \operatorname{tr}\left(\boldsymbol{n}^{\wedge}\right) \\ =& 3 \cos \theta+(1-\cos \theta) \\ =& 1+2 \cos \theta \\ \end{aligned}\tag{3.16} tr(R)===cosθtr(I)+(1cosθ)tr(nnT)+sinθtr(n)3cosθ+(1cosθ)1+2cosθ(3.16)
θ = arccos ⁡ tr ⁡ ( R ) − 1 2 (3.17) \theta=\arccos \frac{\operatorname{tr}(\boldsymbol{R})-1}{2} \tag{3.17} θ=arccos2tr(R)1(3.17)
转轴:
转轴 n \boldsymbol{n} n是矩阵 R \boldsymbol{R} R特征值1对应的特征向量。

Eigen关于旋转向量的定义和计算

一、旋转向量

  1. 初始化旋转向量:旋转角为alpha,旋转轴为(x,y,z)
Eigen::AngleAxisd rotation_vector(alpha,Vector3d(x,y,z))
  1. 旋转向量转旋转矩阵
Eigen::Matrix3d rotation_matrix;
rotation_matrix=rotation_vector.matrix();

Eigen::Matrix3d rotation_matrix;
rotation_matrix=rotation_vector.toRotationMatrix();
  1. 旋转向量转欧拉角(Z-Y-X,即RPY)
Eigen::Vector3d eulerAngle=rotation_vector.matrix().eulerAngles(2,1,0);
  1. 旋转向量转四元数
Eigen::Quaterniond quaternion(rotation_vector);

Eigen::Quaterniond quaternion;Quaterniond quaternion;

Eigen::Quaterniond quaternion;quaternion=rotation_vector;

3.3.2 欧拉角

欧拉角是一种非常直观的描述旋转的方式。太基础的知识我们不展开说,着重说下旋转顺序的影响。

例子:XYZ轴的旋转定义:先绕X轴,再绕Y轴,最后绕Z轴旋转。同理,可以定义ZYZZYX等旋转方式。如果讨论得更细一些,则还需要区分每次是绕固定轴旋转的(基于全局坐标系,计算是从右到左的乘法),还是绕旋转之后的轴旋转的(基于基体坐标系,计算是从左到右的乘法)。

实际上,这种定义方式的不确定性会给我们的计算带来许多麻烦。所幸在特定领域,欧拉角通常有统一的定义方式:偏航-俯仰-翻滚 (yaw-pitch-roll)。它等价于ZYX轴的旋转(绕旋转之后的轴)。此时,可以使用 [ r , p , y ] T [r,p,y]^T [r,p,y]T这样一个三维向量描述任意旋转。ZYX转角相当于把任意旋转分解成以下3各轴上的转角:

  1. 绕物体的Z轴旋转,得到偏航角yaw
  2. 旋转之后的Y轴旋转,得到俯仰角pitch
  3. 旋转之后的X轴旋转,得到翻滚角roll

欧拉角 → \rightarrow 旋转矩阵

绕不同轴旋转的基础旋转矩阵:
R x = [ 1 0 0 0 cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ ] , R y = [ cos ⁡ ϕ 0 sin ⁡ ϕ 0 1 0 − sin ⁡ ϕ 0 cos ⁡ ϕ ] , R z = [ cos ⁡ ψ − sin ⁡ ψ 0 sin ⁡ ψ cos ⁡ ψ 0 0 0 1 ] R_{x}=\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta \\ 0 & \sin \theta & \cos \theta \end{array}\right], R_{y}=\left[\begin{array}{ccc} \cos \phi & 0 & \sin \phi \\ 0 & 1 & 0 \\ -\sin \phi & 0 & \cos \phi \end{array}\right], R_{z}=\left[\begin{array}{ccc} \cos \psi & -\sin \psi & 0 \\ \sin \psi & \cos \psi & 0 \\ 0 & 0 & 1 \end{array}\right] Rx=1000cosθsinθ0sinθcosθ,Ry=cosϕ0sinϕ010sinϕ0cosϕ,Rz=cosψsinψ0sinψcosψ0001
假设有一个坐标系 b \boldsymbol{b} b(其上有一个点 p \boldsymbol{p} p(点和向量是空间中一样东西,只有当选取坐标系时才讨论它的的坐标),坐标也用 p \boldsymbol{p} p 表示),它要绕X轴逆时针转 α \boldsymbol{\alpha} α 角度,那么在旋转之后的坐标系下点 p \boldsymbol{p} p的坐标(记为 p ′ \boldsymbol{p}' p)变成了多少?做一下数学转换得到:
R x ∗ p = p ′ R_{x} * p=p^{\prime} Rxp=p
转换一下得到
p = R x T ∗ p ′ p=R_{x}^{T} * p^{\prime} p=RxTp

  1. (绕固定轴旋转的ZYX转角,或者说是基于全局坐标系)当从 w \boldsymbol{w} w系依次进行ZYX顺序的旋转得到 b \boldsymbol{b} b系,那么根据上式可得 R b w R_{b w} Rbw
    R b w = [ R z ∗ R y ∗ R x ] T = R x T ∗ R y T ∗ R z T = [ 1 0 0 0 cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ ] T ∗ [ cos ⁡ ϕ 0 sin ⁡ ϕ 0 1 0 − sin ⁡ ϕ 0 cos ⁡ ϕ ] T ∗ [ cos ⁡ ψ − sin ⁡ ψ 0 sin ⁡ ψ cos ⁡ ψ 0 0 0 1 ] T = [ 1 0 0 0 cos ⁡ θ sin ⁡ θ 0 − sin ⁡ θ cos ⁡ θ ] ∗ [ cos ⁡ ϕ 0 − sin ⁡ ϕ 0 1 0 sin ⁡ ϕ 0 cos ⁡ ϕ ] ∗ [ cos ⁡ ψ sin ⁡ ψ 0 − sin ⁡ ψ cos ⁡ ψ 0 0 0 1 ] \begin{aligned} R_{b w} &= [R_{z} * R_{y} * R_{x}]^T =R_{x}^{T} * R_{y}^{T} * R_{z}^{T} \\ &=\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta \\ 0 & \sin \theta & \cos \theta \end{array}\right]^{T} *\left[\begin{array}{ccc} \cos \phi & 0 & \sin \phi \\ 0 & 1 & 0 \\ -\sin \phi & 0 & \cos \phi \end{array}\right]^{T} *\left[\begin{array}{cc} \cos \psi & -\sin \psi & 0 \\ \sin \psi & \cos \psi & 0 \\ 0 & 0 & 1 \end{array}\right]^{T} \\ &=\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & \cos \theta & \sin \theta \\ 0 & -\sin \theta & \cos \theta \end{array}\right] *\left[\begin{array}{ccc} \cos \phi & 0 & -\sin \phi \\ 0 & 1 & 0 \\ \sin \phi & 0 & \cos \phi \end{array}\right] *\left[\begin{array}{ccc} \cos \psi & \sin \psi & 0 \\ -\sin \psi & \cos \psi & 0 \\ 0 & 0 & 1 \end{array}\right] \end{aligned} Rbw=[RzRyRx]T=RxTRyTRzT=1000cosθsinθ0sinθcosθTcosϕ0sinϕ010sinϕ0cosϕTcosψsinψ0sinψcosψ0001T=1000cosθsinθ0sinθcosθcosϕ0sinϕ010sinϕ0cosϕcosψsinψ0sinψcosψ0001
  2. (绕旋转之后的轴旋转的ZYX转角,或者说是基于基体坐标系) w \boldsymbol{w} w系变换到 b \boldsymbol{b} b系所进行的ZYX顺序的旋转的含义是什么呢?我们知道 t w b t_{wb} twb b \boldsymbol{b} b系的坐标原点在 w \boldsymbol{w} w系下的坐标,那么此处的ZYX顺序的旋转就是指: b \boldsymbol{b} b系在 w \boldsymbol{w} w系下的欧拉角,表示 w \boldsymbol{w} w系按照此欧拉角顺序旋转即可得到 b \boldsymbol{b} b系!既然是 b \boldsymbol{b} b系在 w \boldsymbol{w} w系下的欧拉角,那么我们求解的应该是 R w b R_{wb} Rwb,所以对 R b w R_{bw} Rbw做转置,得到 R w b R_{wb} Rwb ,如下:
    R w b = R b w T = R z ∗ R y ∗ R x = [ cos ⁡ ψ − sin ⁡ ψ 0 sin ⁡ ψ cos ⁡ ψ 0 0 0 1 ] ∗ [ cos ⁡ ϕ 0 sin ⁡ ϕ 0 1 0 − sin ⁡ ϕ 0 cos ⁡ ϕ ] ∗ [ 1 0 0 0 cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ ] = [ cos ⁡ ϕ ∗ cos ⁡ ψ cos ⁡ ψ ∗ sin ⁡ ϕ ∗ sin ⁡ θ − cos ⁡ θ ∗ sin ⁡ ψ sin ⁡ θ ∗ sin ⁡ ψ + cos ⁡ θ ∗ cos ⁡ ψ ∗ sin ⁡ ϕ cos ⁡ ϕ ∗ sin ⁡ ψ cos ⁡ θ ∗ cos ⁡ ψ + sin ⁡ ϕ ∗ sin ⁡ θ ∗ sin ⁡ ψ cos ⁡ θ ∗ sin ⁡ ϕ ∗ sin ⁡ ψ − cos ⁡ ψ ∗ sin ⁡ θ − sin ⁡ ϕ cos ⁡ ϕ ∗ sin ⁡ θ cos ⁡ ϕ ∗ cos ⁡ θ ] \begin{aligned} R_{w b}=R_{b w}^{T} &=R_{z} * R_{y} * R_{x} \\ &=\left[\begin{array}{ccc} \cos \psi & -\sin \psi & 0 \\ \sin \psi & \cos \psi & 0 \\ 0 & 0 & 1 \end{array}\right] *\left[\begin{array}{ccc} \cos \phi & 0 & \sin \phi \\ 0 & 1 & 0 \\ -\sin \phi & 0 & \cos \phi \end{array}\right] *\left[\begin{array}{ccc} 1 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta \\ 0 & \sin \theta & \cos \theta \end{array}\right] \end{aligned} \\= \begin{bmatrix} \cos\phi*\cos\psi & \cos\psi*\sin\phi*\sin\theta - \cos\theta*\sin\psi & \sin\theta*\sin\psi + \cos\theta*\cos\psi*\sin\phi\\ \cos\phi*\sin\psi & \cos\theta*\cos\psi + \sin\phi*\sin\theta*\sin\psi & \cos\theta*\sin\phi*\sin\psi - \cos\psi*\sin\theta\\ -\sin\phi & \cos\phi*\sin\theta & \cos\phi*\cos\theta \end{bmatrix} Rwb=RbwT=RzRyRx=cosψsinψ0sinψcosψ0001cosϕ0sinϕ010sinϕ0cosϕ1000cosθsinθ0sinθcosθ=cosϕcosψcosϕsinψsinϕcosψsinϕsinθcosθsinψcosθcosψ+sinϕsinθsinψcosϕsinθsinθsinψ+cosθcosψsinϕcosθsinϕsinψcosψsinθcosϕcosθ

旋转矩阵 → \rightarrow 欧拉角

由于旋转矩阵转欧拉角的过程非常多变,而且主要是通过上式进行反三角运算,我更推荐大家细读Eigen中的旋转矩阵 → \rightarrow 欧拉角转换代码

// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef EIGEN_EULERANGLES_H
#define EIGEN_EULERANGLES_H

namespace Eigen { 

/** \geometry_module \ingroup Geometry_Module
  *
  *
  * \returns the Euler-angles of the rotation matrix \c *this using the convention defined by the triplet (\a a0,\a a1,\a a2)
  *
  * Each of the three parameters \a a0,\a a1,\a a2 represents the respective rotation axis as an integer in {0,1,2}.
  * For instance, in:
  * \code Vector3f ea = mat.eulerAngles(2, 0, 2); \endcode
  * "2" represents the z axis and "0" the x axis, etc. The returned angles are such that
  * we have the following equality:
  * \code
  * mat == AngleAxisf(ea[0], Vector3f::UnitZ())
  *      * AngleAxisf(ea[1], Vector3f::UnitX())
  *      * AngleAxisf(ea[2], Vector3f::UnitZ()); \endcode
  * This corresponds to the right-multiply conventions (with right hand side frames).
  * 
  * The returned angles are in the ranges [0:pi]x[-pi:pi]x[-pi:pi].
  * 
  * \sa class AngleAxis
  */
template<typename Derived>
EIGEN_DEVICE_FUNC inline Matrix<typename MatrixBase<Derived>::Scalar,3,1>
// a0, a1, a2指旋转轴(内部旋转),例如(2,1,0)表示旋转顺序为Z-Y-X, 
//(2,0,2)表示旋转顺序为Z-X-Z
MatrixBase<Derived>::eulerAngles(Index a0, Index a1, Index a2) const
{
  EIGEN_USING_STD_MATH(atan2)
  EIGEN_USING_STD_MATH(sin)
  EIGEN_USING_STD_MATH(cos)
  /* Implemented from Graphics Gems IV */
  EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(Derived,3,3)

  //存储结果,假设我们下面旋转顺序为Z-Y-X, 输入参数为(2,1,0)
  // 所以res存储顺序为 res = (yaw, pitch, roll)
  Matrix<Scalar,3,1> res;
  typedef Matrix<typename Derived::Scalar,2,1> Vector2;

  const Index odd = ((a0+1)%3 == a1) ? 0 : 1;
  const Index i = a0;
  const Index j = (a0 + 1 + odd)%3;
  const Index k = (a0 + 2 - odd)%3;
  // odd=1, i=2, j=1, k=0
  
  //先不管这种情况
  if (a0==a2)
  {
    res[0] = atan2(coeff(j,i), coeff(k,i));
    if((odd && res[0]<Scalar(0)) || ((!odd) && res[0]>Scalar(0)))
    {
      if(res[0] > Scalar(0)) {
        res[0] -= Scalar(EIGEN_PI);
      }
      else {
        res[0] += Scalar(EIGEN_PI);
      }
      Scalar s2 = Vector2(coeff(j,i), coeff(k,i)).norm();
      res[1] = -atan2(s2, coeff(i,i));
    }
    else
    {
      Scalar s2 = Vector2(coeff(j,i), coeff(k,i)).norm();
      res[1] = atan2(s2, coeff(i,i));
    }
    
    // With a=(0,1,0), we have i=0; j=1; k=2, and after computing the first two angles,
    // we can compute their respective rotation, and apply its inverse to M. Since the result must
    // be a rotation around x, we have:
    //
    //  c2  s1.s2 c1.s2                   1  0   0 
    //  0   c1    -s1       *    M    =   0  c3  s3
    //  -s2 s1.c2 c1.c2                   0 -s3  c3
    //
    //  Thus:  m11.c1 - m21.s1 = c3  &   m12.c1 - m22.s1 = s3
    
    Scalar s1 = sin(res[0]);
    Scalar c1 = cos(res[0]);
    res[2] = atan2(c1*coeff(j,k)-s1*coeff(k,k), c1*coeff(j,j) - s1 * coeff(k,j));
  } 
  else
  {
    // yaw = res[0]
    res[0] = atan2(coeff(j,k), coeff(k,k));
    // 计算cos(pitch)
    Scalar c2 = Vector2(coeff(i,i), coeff(i,j)).norm();
    if((odd && res[0]<Scalar(0)) || ((!odd) && res[0]>Scalar(0))) {
      if(res[0] > Scalar(0)) {
        res[0] -= Scalar(EIGEN_PI);
      }
      else {
        res[0] += Scalar(EIGEN_PI);
      }
      res[1] = atan2(-coeff(i,k), -c2);
    }
    else
      res[1] = atan2(-coeff(i,k), c2);
    // 计算sin(yaw)
    Scalar s1 = sin(res[0]);
    // 计算cos(yaw)
    Scalar c1 = cos(res[0]);
    // roll = res[2]
    res[2] = atan2(s1*coeff(k,i)-c1*coeff(j,i), c1*coeff(j,j) - s1 * coeff(k,j));
  }
  if (!odd)
    res = -res;
  
  return res;
}

} // end namespace Eigen

#endif // EIGEN_EULERANGLES_H
万向锁问题

欧拉角的重大缺点:万向锁问题(Gimbal Lock):
在俯仰角为±90°时,第一次旋转和第三次旋转将使用同一个轴,使得系统丢失了一个自由度(由3次旋转变成了2次旋转)。欧拉角不适用于插值和迭代,往往只用于人机交互中。我们也很少在SLAM程序中直接使用欧拉角表示姿态(当然,LOAM用了大量的欧拉角表示姿态…,毕竟人家是大佬 ),同样不会在滤波或优化中使用欧拉角表示旋转(因为它具有奇异性)。
在这里插入图片描述
注意:上图的旋转方式都是绕旋转之后的轴旋转的。

Eigen关于欧拉角的定义和计算

  1. 初始化欧拉角(Z-Y-X,即RPY)
Eigen::Vector3d eulerAngle(yaw,pitch,roll);
  1. 欧拉角转旋转向量
Eigen::AngleAxisd rollAngle(eulerAngle(2),Eigen::Vector3d::UnitX());
Eigen::AngleAxisd pitchAngle(eulerAngle(1),Eigen::Vector3d::UnitY());
Eigen::AngleAxisd yawAngle(eulerAngle(0),Eigen::Vector3d::UnitZ()); 
Eigen::AngleAxisd rotation_vector;
rotation_vector=yawAngle*pitchAngle*rollAngle;
  1. 欧拉角转旋转矩阵
Eigen::AngleAxisd rollAngle(eulerAngle(2),Eigen::Vector3d::UnitX());
Eigen::AngleAxisd pitchAngle(eulerAngle(1),Eigen::Vector3d::UnitY());
Eigen::AngleAxisd yawAngle(eulerAngle(0),Eigen::Vector3d::UnitZ()); 
Eigen::Matrix3d rotation_matrix;
rotation_matrix=yawAngle*pitchAngle*rollAngle;
  1. 欧拉角转四元数
Eigen::AngleAxisd rollAngle(eulerAngle(2),Eigen::Vector3d::UnitX());
Eigen::AngleAxisd pitchAngle(eulerAngle(1),Eigen::Vector3d::UnitY());
Eigen::AngleAxisd yawAngle(eulerAngle(0),Eigen::Vector3d::UnitZ()); 
Eigen::Quaterniond quaternion;
quaternion=yawAngle*pitchAngle*rollAngle;

3.4 四元数

3.4.1 四元数的定义与表达方式

q = q 0 + q 1 i + q 2 j + q 3 k (3.19) \boldsymbol{q}=q_{0}+q_{1} \mathrm{i}+\mathrm{q}_{2} \mathrm{j}+\mathrm{q}_{3} \mathrm{k} \tag{3.19} q=q0+q1i+q2j+q3k(3.19)
{ i 2 = j 2 = k 2 = − 1 i j = k , j i = − k j k = i , k j = − i k i = j , i k = − j (3.20) \left\{\begin{array}{l} \mathrm{i}^{2}=\mathrm{j}^{2}=\mathrm{k}^{2}=-1 \\ \mathrm{ij}=\mathrm{k}, \mathrm{ji}=-\mathrm{k} \\ \mathrm{jk}=\mathrm{i}, \mathrm{kj}=-\mathrm{i} \\ \mathrm{ki}=\mathrm{j}, \mathrm{ik}=-\mathrm{j} \end{array}\right. \tag{3.20} i2=j2=k2=1ij=k,ji=kjk=i,kj=iki=j,ik=j(3.20)

也可以用一个标量和一个向量来表达四元数:
q = [ s , v ] T , s = q 0 ∈ R , v = [ q 1 , q 2 , q 3 ] T ∈ R 3 . \boldsymbol{q}=[s, \boldsymbol{v}]^{\mathrm{T}}, \quad s=q_{0} \in \mathbb{R}, \quad \boldsymbol{v}=\left[q_{1}, q_{2}, q_{3}\right]^{\mathrm{T}} \in \mathbb{R}^{3} . q=[s,v]T,s=q0R,v=[q1,q2,q3]TR3.

可以用单位四元数表示三维空间中任意一个旋转。例如,乘以 i i i对应着旋转180°,这样才能保证 i j = k ij=k ij=k的性质。而 i 2 = − 1 i^2=-1 i2=1,意味着绕 i i i轴旋转360°后得到一个相反的东西。这个东西要旋转两周才会和它原先的样子相等。

3.4.2 四元数的运算

现有两个四元数 q a \boldsymbol{q}_a qa q b \boldsymbol{q}_b qb,它们的向量表示为 [ s a , v a ] T [s_a, \boldsymbol{v}_a]^{\mathrm{T}} [sa,va]T [ s b , v b ] T [s_b, \boldsymbol{v}_b]^{\mathrm{T}} [sb,vb]T,或者原始四元数表示为:
q a = s a + x a i + y a j + z a k , q b = s b + x b i + y b j + z b k \boldsymbol{q}_{a}=s_{a}+x_{a} \mathrm{i}+\mathrm{y}_{\mathrm{a}} \mathrm{j}+\mathrm{z}_{\mathrm{a}} \mathrm{k}, \quad \mathrm{q}_{\mathrm{b}}=\mathrm{s}_{\mathrm{b}}+\mathrm{x}_{\mathrm{b}} \mathrm{i}+\mathrm{y}_{\mathrm{b}} \mathrm{j}+\mathrm{z}_{\mathrm{b}} \mathrm{k} qa=sa+xai+yaj+zak,qb=sb+xbi+ybj+zbk

  1. 加法和减法
    q a ± q b = [ s a ± s b , v a ± v b ] T (3.22) \boldsymbol{q}_{a} \pm \boldsymbol{q}_{b}=\left[s_{a} \pm s_{b}, \boldsymbol{v}_{a} \pm \boldsymbol{v}_{b}\right]^{\mathrm{T}}\tag{3.22} qa±qb=[sa±sb,va±vb]T(3.22)

  2. 乘法(不等价于旋转)
    q a \boldsymbol{q}_a qa的每一项与 q b \boldsymbol{q}_b qb相乘,最后相加:
    q a q b = s a s b − x a x b − y a y b − z a z b + ( s a x b + x a s b + y a z b − z a y b ) i + ( s a y b − x a z b + y a s b + z a x b ) j + ( s a z b + x a y b − y a x b + z a s b ) k (3.23) \begin{aligned} \boldsymbol{q}_{a} \boldsymbol{q}_{b}=& s_{a} s_{b}-x_{a} x_{b}-y_{a} y_{b}-z_{a} z_{b} \\ &+\left(s_{a} x_{b}+x_{a} s_{b}+y_{a} z_{b}-z_{a} y_{b}\right) \mathrm{i} \\ &+\left(s_{a} y_{b}-x_{a} z_{b}+y_{a} s_{b}+z_{a} x_{b}\right) \mathrm{j} \\ &+\left(s_{a} z_{b}+x_{a} y_{b}-y_{a} x_{b}+z_{a} s_{b}\right) \mathrm{k} \end{aligned}\tag{3.23} qaqb=sasbxaxbyaybzazb+(saxb+xasb+yazbzayb)i+(saybxazb+yasb+zaxb)j+(sazb+xaybyaxb+zasb)k(3.23)
    简洁形式:
    q a q b = [ s a s b − v a T v b , s a v b + s b v a + v a × v b ] T (3.24) \boldsymbol{q}_{a} \boldsymbol{q}_{b}=\left[s_{a} s_{b}-\boldsymbol{v}_{a}^{\mathrm{T}} \boldsymbol{v}_{b}, s_{a} \boldsymbol{v}_{b}+s_{b} \boldsymbol{v}_{a}+\boldsymbol{v}_{a} \times \boldsymbol{v}_{b}\right]^{\mathrm{T}}\tag{3.24} qaqb=[sasbvaTvb,savb+sbva+va×vb]T(3.24)

  3. 模长
    ∥ q a ∥ = s a 2 + x a 2 + y a 2 + z a 2 = s a 2 + v T v (3.25) \left\|\boldsymbol{q}_{a}\right\|=\sqrt{s_{a}^{2}+x_{a}^{2}+y_{a}^{2}+z_{a}^{2}}=\sqrt{s_{a}^{2}+\boldsymbol{v}^{\mathrm{T}} \boldsymbol{v}}\tag{3.25} qa=sa2+xa2+ya2+za2 =sa2+vTv (3.25)
    单位四元数特点:
    ∥ q a q b ∥ = ∥ q a ∥ ∥ q b ∥ (3.26) \left\|\boldsymbol{q}_{a} \boldsymbol{q}_{b}\right\|=\left\|\boldsymbol{q}_{a}\right\|\left\|\boldsymbol{q}_{b}\right\|\tag{3.26} qaqb=qaqb(3.26)

  4. 共轭
    虚部取成相反数:
    q a ∗ = s a − x a i − y a j − z a k = [ s a , − v a ] T (3.27) \boldsymbol{q}_{a}^{*}=s_{a}-x_{a} \mathrm{i}-\mathrm{y}_{\mathrm{a}} \mathrm{j}-\mathrm{z}_{\mathrm{a}} \mathrm{k}=\left[\mathrm{s}_{\mathrm{a}},-\mathrm{v}_{\mathrm{a}}\right]^{\mathrm{T}}\tag{3.27} qa=saxaiyajzak=[sa,va]T(3.27)
    四元数共轭与其本身相乘,会得到一个实四元数,实部为模长的平方:
    q ∗ q = q q ∗ = [ s a 2 + v T v , 0 ] T (3.28) \boldsymbol{q}^{*} \boldsymbol{q}=\boldsymbol{q} \boldsymbol{q}^{*}=\left[s_{a}^{2}+\boldsymbol{v}^{\mathrm{T}} \boldsymbol{v}, \mathbf{0}\right]^{\mathrm{T}}\tag{3.28} qq=qq=[sa2+vTv,0]T(3.28)


  5. q − 1 = q ∗ / ∥ q ∥ 2 (3.29) \boldsymbol{q}^{-1}=\boldsymbol{q}^{*} /\|\boldsymbol{q}\|^{2}\tag{3.29} q1=q/q2(3.29)
    四元数和自己的逆的乘积为实四元数1:
    q q − 1 = q − 1 q = 1 (3.30) \boldsymbol{q} \boldsymbol{q}^{-1}=\boldsymbol{q}^{-1} \boldsymbol{q}=\boldsymbol{1}\tag{3.30} qq1=q1q=1(3.30)
    如果 q a \boldsymbol{q}_a qa是单位四元数,有:
    q ∗ = q − 1 (3.31-a) \boldsymbol{q}^* =\boldsymbol{q}^{-1} \tag{3.31-a} q=q1(3.31-a)
    乘积的逆具有和矩阵相似的性质
    ( q a q b ) − 1 = q b − 1 q a − 1 (3.31-b) \left(\boldsymbol{q}_{a} \boldsymbol{q}_{b}\right)^{-1}=\boldsymbol{q}_{b}^{-1} \boldsymbol{q}_{a}^{-1}\tag{3.31-b} (qaqb)1=qb1qa1(3.31-b)

  6. 数乘
    k q = [ k s , k v ] T (3.32) k\boldsymbol{q}=[ks,k\boldsymbol{v}]^T\tag{3.32} kq=[ks,kv]T(3.32)

3.4.3 用四元数表示旋转

一个空间三位点 p = [ x , y , z ] ∈ R 3 \boldsymbol{p}=[x,y,z]\in\mathbb{R}^3 p=[x,y,z]R3,经过单位四元数 q \boldsymbol{q} q旋转作用后得到 p ′ \boldsymbol{p}' p,计算过程如下:

把三维空间点用一个虚四元数来描述
p = [ 0 , x , y , z ] T = [ 0 , v ] T . \boldsymbol{p}=[0, x, y, z]^{\mathrm{T}}=[0, \boldsymbol{v}]^{\mathrm{T}} . p=[0,x,y,z]T=[0,v]T.
旋转后的点 p ′ \boldsymbol{p}' p可表示为如下四元数乘积
p ′ = q p q − 1 (3.33) \boldsymbol{p}^{\prime}=\boldsymbol{q} \boldsymbol{p q}^{-1}\tag{3.33} p=qpq1(3.33)
p ′ \boldsymbol{p}^{\prime} p计算结果必为纯虚四元数。虚部即为旋转之后点的坐标。

3.4.4 四元数到其他旋转表示的转换

首先,四元数乘法可以写成一种矩阵的乘法。设 q = [ s , v ] T \boldsymbol{q}=[s,\boldsymbol{v}]^T q=[s,v]T,定义如下的符号 + ^+ + ⊕ ^{\oplus}
q + = [ s − v T v s I + v ∧ ] , (3.34-a) q^{+}= \left[\begin{array}{cc} s & -\boldsymbol{v}^{\mathrm{T}} \\ \boldsymbol{v} & s \boldsymbol{I}{\color{red}+}\boldsymbol{v}^{\wedge} \end{array}\right], \tag{3.34-a} q+=[svvTsI+v],(3.34-a)
q ⊕ = [ s − v T v s I − v ∧ ] (3.34-b) \quad q^{\oplus}=\left[\begin{array}{cc} s & -\boldsymbol{v}^{\mathrm{T}} \\ \boldsymbol{v} & s \boldsymbol{I}{\color{red}-}\boldsymbol{v}^{\wedge} \end{array}\right]\tag{3.34-b} q=[svvTsIv](3.34-b)

这两个符号将四元数映射成为一个 4 × 4 4 \times 4 4×4的矩阵,四元数乘法可以写成矩阵的形式

q 1 + q 2 = [ s 1 − v 1 T v 1 s 1 I + v 1 ∧ ] [ s 2 v 2 ] = [ − v 1 T v 2 + s 1 s 2 s 1 v 2 + s 2 v 1 + v 1 ∧ v 2 ] = q 1 q 2 (3.35) \boldsymbol{q}_{1}^{+} \boldsymbol{q}_{2}=\left[\begin{array}{cc} s_{1} & -\boldsymbol{v}_{1}^{\mathrm{T}} \\ \boldsymbol{v}_{1} & s_{1} \boldsymbol{I}+\boldsymbol{v}_{1}^{\wedge} \end{array}\right]\left[\begin{array}{l} s_{2} \\ \boldsymbol{v}_{2} \end{array}\right]=\left[\begin{array}{c} -\boldsymbol{v}_{1}^{\mathrm{T}} \boldsymbol{v}_{2}+s_{1} s_{2} \\ s_{1} \boldsymbol{v}_{2}+s_{2} \boldsymbol{v}_{1}+\boldsymbol{v}_{1}^{\wedge} \boldsymbol{v}_{2} \end{array}\right]=\boldsymbol{q}_{1} \boldsymbol{q}_{2}\tag{3.35} q1+q2=[s1v1v1Ts1I+v1][s2v2]=[v1Tv2+s1s2s1v2+s2v1+v1v2]=q1q2(3.35)
同理亦可证
q 1 q 2 = q 1 + q 2 = q 2 ⊕ q 1 (3.36) \boldsymbol{q}_{1} \boldsymbol{q}_{2}=\boldsymbol{q}_{1}^{+} \boldsymbol{q}_{2}=\boldsymbol{q}_{2}^{\oplus} \boldsymbol{q}_{1}\tag{3.36} q1q2=q1+q2=q2q1(3.36)
前面说到的空间三维点旋转问题可转化为:
p ′ = q p q − 1 = q + p + q − 1 = q + ( q − 1 ) ⊕ p (3.37) \begin{aligned} \boldsymbol{p}^{\prime} &=\boldsymbol{q} \boldsymbol{p} \boldsymbol{q}^{-1}=\boldsymbol{q}^{+} \boldsymbol{p}^{+} \boldsymbol{q}^{-1} \\ &=\boldsymbol{q}^{+} (\boldsymbol{q}^{-1})^{\oplus} \boldsymbol{p} \end{aligned}\tag{3.37} p=qpq1=q+p+q1=q+(q1)p(3.37)
其中两个符号对应的矩阵:
q + ( q − 1 ) ⊕ = [ s − v T v s I + v ∧ ] [ s v T − v s I + v ∧ ] = [ 1 0 0 T v v T + s 2 I + 2 s v ∧ + ( v ∧ ) 2 ] (3.38) \boldsymbol{q}^{+}\left(\boldsymbol{q}^{-1}\right)^{\oplus}=\left[\begin{array}{cc} s & -\boldsymbol{v}^{\mathrm{T}} \\ \boldsymbol{v} & s \boldsymbol{I}+\boldsymbol{v}^{\wedge} \end{array}\right]\left[\begin{array}{cc} s & \boldsymbol{v}^{\mathrm{T}} \\ -\boldsymbol{v} & s \boldsymbol{I}+\boldsymbol{v}^{\wedge} \end{array}\right]=\left[\begin{array}{cc} 1 & \mathbf{0} \\ \mathbf{0}^{\mathrm{T}} & \boldsymbol{v} \boldsymbol{v}^{\mathrm{T}}+s^{2} \boldsymbol{I}+2 s \boldsymbol{v}^{\wedge}+\left(\boldsymbol{v}^{\wedge}\right)^{2} \end{array}\right]\tag{3.38} q+(q1)=[svvTsI+v][svvTsI+v]=[10T0vvT+s2I+2sv+(v)2](3.38)

四元数 → \rightarrow 旋转矩阵

因为 p ′ \boldsymbol{p}^{\prime} p p \boldsymbol{p} p 都是虚四元数, 所以事实上该矩阵的右下角即给出了从四元数到旋转矩阵的变换关系:
R = v v T + s 2 I + 2 s v ∧ + ( v ∧ ) 2 (3.39) \boldsymbol{R}=\boldsymbol{v} \boldsymbol{v}^{\mathrm{T}}+s^{2} \boldsymbol{I}+2 s \boldsymbol{v}^{\wedge}+\left(\boldsymbol{v}^{\wedge}\right)^{2}\tag{3.39} R=vvT+s2I+2sv+(v)2(3.39)
为了得到四元数到旋转向量的转换公式, 对上式两侧求迹, 得
tr ⁡ ( R ) = tr ⁡ ( v v T + 3 s 2 + 2 s ⋅ 0 + tr ⁡ ( ( v ∧ ) 2 ) = v 1 2 + v 2 2 + v 3 2 + 3 s 2 − 2 ( v 1 2 + v 2 2 + v 3 2 ) = ( 1 − s 2 ) + 3 s 2 − 2 ( 1 − s 2 ) = 4 s 2 − 1 (3.40) \begin{aligned} \operatorname{tr}(\boldsymbol{R}) &=\operatorname{tr}\left(\boldsymbol{v} \boldsymbol{v}^{\mathrm{T}}+3 s^{2}+2 s \cdot 0+\operatorname{tr}\left(\left(\boldsymbol{v}^{\wedge}\right)^{2}\right)\right.\\ &=v_{1}^{2}+v_{2}^{2}+v_{3}^{2}+3 s^{2}-2\left(v_{1}^{2}+v_{2}^{2}+v_{3}^{2}\right) \\ &=\left(1-s^{2}\right)+3 s^{2}-2\left(1-s^{2}\right) \\ &=4 s^{2}-1 \end{aligned}\tag{3.40} tr(R)=tr(vvT+3s2+2s0+tr((v)2)=v12+v22+v32+3s22(v12+v22+v32)=(1s2)+3s22(1s2)=4s21(3.40)
由式 (3.17) 得
θ = arccos ⁡ tr ⁡ ( R − 1 ) 2 = arccos ⁡ ( 2 s 2 − 1 ) (3.41) \begin{aligned} \theta &=\arccos \frac{\operatorname{tr}(\boldsymbol{R}-1)}{2} \\ &=\arccos \left(2 s^{2}-1\right) \end{aligned}\tag{3.41} θ=arccos2tr(R1)=arccos(2s21)(3.41)

cos ⁡ θ = 2 s 2 − 1 = 2 cos ⁡ 2 θ 2 − 1 (3.42) \cos \theta=2 s^{2}-1=2 \cos ^{2} \frac{\theta}{2}-1\tag{3.42} cosθ=2s21=2cos22θ1(3.42)
所以有
θ = 2 arccos ⁡ s . (3.43) \theta=2 \arccos s .\tag{3.43} θ=2arccoss.(3.43)

四元数 → \rightarrow 旋转向量

总结,四元数到旋转向量的转换公式如下:
{ θ = 2 arccos ⁡ q 0 [ n x , n y , n z ] T = [ q 1 , q 2 , q 3 ] T / sin ⁡ θ 2 (3.44) \left\{\begin{array}{l} \theta=2 \arccos q_{0} \\ {\left[n_{x}, n_{y}, n_{z}\right]^{\mathrm{T}}=\left[q_{1}, q_{2}, q_{3}\right]^{\mathrm{T}} / \sin \frac{\theta}{2}} \end{array}\right.\tag{3.44} {θ=2arccosq0[nx,ny,nz]T=[q1,q2,q3]T/sin2θ(3.44)

四元数 → \rightarrow 欧拉角

一般是先让四元数转旋转矩阵后,再转欧拉角。

Eigen关于欧拉角的定义和计算

  1. 初始化四元数
Eigen::Quaterniond quaternion(w,x,y,z);
  1. 四元数转旋转向量
Eigen::AngleAxisd rotation_vector(quaternion);

Eigen::AngleAxisd rotation_vector;rotation_vector=quaternion;
  1. 四元数转旋转矩阵
Eigen::Matrix3d rotation_matrix;rotation_matrix=quaternion.matrix();

Eigen::Matrix3d rotation_matrix;rotation_matrix=quaternion.toRotationMatrix();
  1. 四元数转欧拉角(Z-Y-X,即RPY)
Eigen::Vector3d eulerAngle=quaternion.matrix().eulerAngles(2,1,0);

3.5 相似、仿射、射影变换

等距变换(欧氏变换)

T = [ R t 0 T 1 ] \boldsymbol{T}=\left[\begin{array}{ll} \boldsymbol{R} & \boldsymbol{t} \\ \mathbf{0}^{\mathrm{T}} & 1 \end{array}\right] T=[R0Tt1]

Eigen关于欧氏变换的定义

欧氏变换矩阵(4 × 4) : Eigen::Isometry3d
等距变换(Isometry Transform)可以看作是维持任意两点距离不变的仿射变换,也称做欧氏变换、刚体运动,在实际场景中使用比较多。在Eigen中已经内置好了一些常用的等距变换可以直接调用,如下。

/** \ingroup Geometry_Module */
typedef Transform<float,2,Isometry> Isometry2f;
/** \ingroup Geometry_Module */
typedef Transform<float,3,Isometry> Isometry3f;
/** \ingroup Geometry_Module */
typedef Transform<double,2,Isometry> Isometry2d;
/** \ingroup Geometry_Module */
typedef Transform<double,3,Isometry> Isometry3d;

新建方法十分简单,直接类型名+变量名即可,赋值可以通过它的成员函数.rotate().translate()完成(须先初始化为单位阵)。三维等距变换是一个4×4的矩阵。但在构建一个变换前,你首先要想清楚两个问题:这个变换顺序 (先平移后旋转 or 先旋转后平移) 是什么?这个变换相对谁改变 (固定轴 or 自身)?不同的答案会得到不同的结果,代码也不尽相同。以下是一个标准的新建并初始化按固定轴先平移再旋转变换的例子:

# include<iostream>
# include<Eigen\Dense>
# include<Eigen\Geometry>

using namespace std;
using namespace Eigen;

int main(){
	AngleAxisd rotation(3.1415926 / 4, Vector3d(1, 0, 1).normalized());
	Vector3d translation(1, 3, 4);
	Isometry3d T= Isometry3d::Identity();

	T.translate(translation);
	T.rotate(rotation);
	
	return 0;
}

上述代码中构建了一个角轴表示的旋转、一个平移向量以及一个初始的单位变换。注意这里必须要调用Isometry3d::Identity()对它进行初始化(或者调用.setIdentity()设置),后面的.translate().rotate()函数才有用。因为这些函数本质上都是矩阵乘法,而如果没初始化,Isometry3d中的元素全为0(或者非常大的负数),这样等于是和0矩阵相乘,结果会不对。

如果你要说我就不初始化为单位阵行不行?也可以,那就不要调用.translate().rotate()函数,直接调用.matrix()函数获取对应的变换矩阵,然后手动自己设置(构造),像下面这样。

# include<iostream>
# include<Eigen\Dense>
# include<Eigen\Geometry>

using namespace std;
using namespace Eigen;

int main(){
	AngleAxisd rotation(3.1415926 / 4, Vector3d(1, 0, 1).normalized());
	Vector3d translation(1, 3, 4);
	Isometry3d T;

	// 设置旋转R部分
	T.matrix().block(0, 0, 3, 3) = rotation.toRotationMatrix();
	// 设置平移t部分,.homogeneous()函数用于将平移向量变成对应的齐次表达形式
	T.matrix().col(3) = translation.homogeneous();
	// 设置最后一行
	T.matrix().row(3) = Vector4d(0, 0, 0, 1);

	cout << "rotation matrix:" << endl << rotation.toRotationMatrix() << endl << endl;
	cout << "translation vector:" << endl << translation << endl << endl;
	cout << "translation matrix:" << endl << T.matrix() << endl << endl;
	system("pause");
	return 0;
}

上面两种方式演示了基本的新建变换的步骤,可以发现很多成员函数如.translate().rotate().pretranslate().prerotate()等起了关键作用,这些函数是什么意思、怎么用,下面简单进行说明。

这些函数准确来说是用来“应用变换”的,变换在线代里即为矩阵相乘,这些函数将当前变换与输入的参数相乘,从而得到新的变换。有且仅当变换为单位阵时,这样等价于对变换赋值。A.translate(B)等价于A×B,而A.pretranslate(B)等价于B×A,对应于左乘和右乘的区别。

在应用角度而言,回到之前说的两个问题,这些函数就与它们有关。简单来说就是,凡是前面带pre的函数,其变化都是相对于上一步变化之前的状态进行的。这样说可能有些绕,举例说就是我要新建一个按固定轴平移旋转(世界坐标系下)、先平移后旋转的变换。但我首先设置了旋转,然后再设置平移。这个时候设置平移就不能用.translate()了,而应该用.pretranslate()。因为第一步已经对坐标系进行了旋转,后面的平移是在旋转后的坐标系中进行的,这显然和我设想的不一样。我需要在未旋转的坐标系上(世界坐标系)进行平移。下面代码演示了这种现象:

# include<iostream>
# include<Eigen\Dense>
# include<Eigen\Geometry>

using namespace std;
using namespace Eigen;

int main(){
	AngleAxisd rotation(3.1415926 / 4, Vector3d(1, 0, 1).normalized());
	Vector3d translation(1, 3, 4);
	Isometry3d T1 = Isometry3d::Identity();
	Isometry3d T2 = Isometry3d::Identity();
	Isometry3d T3 = Isometry3d::Identity();

	T1.translate(translation);
	T1.rotate(rotation);

	T2.rotate(rotation);
	T2.pretranslate(translation);

	T3.rotate(rotation);
	T3.translate(translation);

	cout << "rotation matrix:" << endl << rotation.toRotationMatrix() << endl << endl;
	cout << "translation vector:" << endl << translation << endl << endl;
	cout << "translation matrix1:" << endl << T1.matrix() << endl << endl;
	cout << "translation matrix1:" << endl << T2.matrix() << endl << endl;
	cout << "translation matrix1:" << endl << T3.matrix() << endl << endl;
	system("pause");
	return 0;
}

在代码中建了3个变换,其中1和2是等价的,而3的平移部分作为对比没有使用.pretranslate(),可以看到和我们预期的不一样。其实这等于是按自身进行变换了,这在欧拉角的旋转中倒是有用。之前说过欧拉角有两种不同的定义方式:按自身旋转和按固定轴旋转。

常用的一些成员函数列举如下:

.translation():无参数,返回当前变换平移部分的向量表示(可修改),可以索引[]获取各分量

.translationExt():无参数,如果当前变换是仿射的话,返回平移部分(可修改);如果是射影变换的话,返回最后一列(可修改)

.translate():有参数,用于在当前变换上应用右乘,A.translate(B)等价于A×B

.pretranslate():有参数,用于在当前变换上应用左乘,A.pretranslate(B)等价于B×A

.rotation():无参数,返回只读的当前变换的旋转部分,以旋转矩阵表示

.rotate():有参数,用于设置当前变换的旋转部分,输入参数可以为角轴、四元数、旋转矩阵等。A.rotate(B)等价于A×B

.prerotate():有参数,用于设置当前变换的旋转部分,输入参数可以为角轴、四元数、旋转矩阵等。A.prerotate(B)等价于B×A

.matrix():返回变换对应的矩阵(可修改)

.cols():变换对应矩阵的列数

.rows():变换对应矩阵的行数

.linear()&.linearExt():返回变换的线性部分,对于Isometry而言就是旋转对应的旋转矩阵

.inverse():返回当前变换的逆变换

.Identity():返回一个单位变换,一般可用于Isometry的初始化中,将变换设为一个单位阵:Isometry3d::Identity()

.setIdentity():将当前变换变成单位变换,变换对应的矩阵变成单位阵,也可用于初始化赋值

.cast():将当前类型的变换转换为其它类型的变换,如Isometry3dIsometry3f

.data():返回一个指向变换内部矩阵的指针,矩阵按照列优先存储

.computeRotationScaling():将当前变换的线性部分分解成rotationscaling的乘积

.computeScalingRotation():将当前变换的线性部分分解成scalingrotation的乘积

.fromPositionOrientationScale():从一个3D对象的位置、指向以及缩放来设置一个变换

.prescale():有参数,用于在当前变换上应用右乘,A.translate(B)等价于A×B

.scale():有参数,用于在当前变换上应用右乘,A.translate(B)等价于A×B

.preshear():只针对2D情况,沙尔变换,有参数,用于在当前变换上应用左乘,A.preshear(B)等价于B×A

.shear():只针对2D情况,沙尔变换,有参数,用于在当前变换上应用右乘,A.shear(B)等价于A×B

.affine():无参数,返回当前变换的仿射部分(可修改),对于Isometry3d而言,返回的是一个4×3的矩阵

makeAffine():将变换对应的矩阵的最后一行设为0,0,…,1

.isApprox():判断当前Isometry是否与给定Isometry近似相等,是的话返回true。判断精度由传入的第二个参数决定。

简单使用示例如下:

# include<iostream>
# include<Eigen\Dense>
# include<Eigen\Geometry>

using namespace std;
using namespace Eigen;

int main(){
	Isometry3d transform;
	transform.setIdentity();

	transform.translate(Vector3d(1.5, 2, 3));
	transform.rotate(AngleAxisd(0.1, Vector3d::UnitX()));
	cout << "transform matrix:\n" << transform.matrix() << endl << endl;

	Vector3d vec1(4.3, 5, 3.2),vec2;
	vec2 = transform*vec1;
	cout << vec1 << endl << endl;
	cout << vec2 << endl;
	system("pause");
	return 0;
}

构造了一个三维刚体变换并将其应用到了vec1这个向量上。

相似变换

相似变换的旋转部分多一个缩放因子 s s s,表示我们在对向量旋转之后,可以在 x , y , z x,y,z x,y,z三个坐标上进行均匀缩放。由于含有缩放,相似变换不再保持图形的面积不变。
T S = [ s R t 0 T 1 ] \boldsymbol{T}_{S}=\left[\begin{array}{ll} s \boldsymbol{R} & \boldsymbol{t} \\ \mathbf{0}^{\mathrm{T}} & 1 \end{array}\right] TS=[sR0Tt1]
一个边长未1的立方体通过相似变换后,变成边长为10的样子(但仍然是立方体)。三维相似变换的集合也叫作相似变换群,记作 S i m ( 3 ) Sim(3) Sim(3)

仿射变换

T A = [ A t 0 T 1 ] \boldsymbol{T}_{A}=\left[\begin{array}{ll} \boldsymbol{A} & \boldsymbol{t} \\ \mathbf{0}^{\mathrm{T}} & 1 \end{array}\right] TA=[A0Tt1]
与欧式变换不同的是,仿射变换只要求 A \boldsymbol{A} A是一个可逆矩阵,而不必是正交矩阵。仿射变换也叫做正交投影。经过仿射变换之后,立方体就不再是方的了,但是各个面仍然是平行四边形。

Eigen关于仿射变换的定义

仿射变换(4 × 4) : Eigen::Affine3d

仿射变换是空间直角坐标变换的一种,保留线的平行性。仿射变换一般按照TRSI的顺序进行构建,T-translation,R-rotation,S-scaling,I-identity。

Transform<float,N,Affine> t = concatenation_of_any_transformations;
Transform<float,3,Affine> t = Translation3f(p) * AngleAxisf(a,axis) * Scaling(s);

为了方便Eigen中已经定义好了一些常用类型,如下:

/** \ingroup Geometry_Module */
typedef Transform<float,2,Affine> Affine2f;
/** \ingroup Geometry_Module */
typedef Transform<float,3,Affine> Affine3f;
/** \ingroup Geometry_Module */
typedef Transform<double,2,Affine> Affine2d;
/** \ingroup Geometry_Module */
typedef Transform<double,3,Affine> Affine3d;

由于仿射变换的成员函数和Isometry是一模一样的,这里就不再介绍了。下面是一个简单的使用示例。

# include<iostream>
# include<Eigen\Dense>
# include<Eigen\Geometry>

using namespace std;
using namespace Eigen;

int main(){
	Affine2d affine;
	affine.setIdentity();
	affine.translate(Vector2d(1.2, 3));
	affine.rotate(Rotation2Dd(0.1));
	cout << "affine matrix:\n" << affine.matrix() << endl << endl;
	Matrix<double, 2, 3> vecs1,vecs2;
	vecs1 << 1, 2, 3, 4, 5, 6;
	cout << "original vectors:\n" << vecs1 << endl << endl;
	vecs2 = affine*vecs1;
	cout << "affined vectors:\n" << vecs2 << endl;
	system("pause");
	return 0;
}

利用2D仿射变换对多个向量进行了批量变换。

射影变换

T A = [ A t α T 1 ] \boldsymbol{T}_{A}=\left[\begin{array}{ll} \boldsymbol{A} & \boldsymbol{t} \\ \boldsymbol{\alpha}^{\mathrm{T}} & 1 \end{array}\right] TA=[AαTt1]
它的左上角为可逆矩阵 A \boldsymbol{A} A,右上角为平移 t \boldsymbol{t} t,左下角为缩放 α T \boldsymbol{\alpha}^{\mathrm{T}} αT。由于采用了齐次坐标,当 v ≠ 0 v\neq0 v=0时,我们可以对整个矩阵除以 v v v得到一个右下角为1的矩阵;否则得到右下角为0的矩阵。因此,2D的射影变换一共有8个自由度,3D则共有15个自由度。从真实世界到相机照片的变换可以看成一个射影变换。

Eigen关于射影变换的定义

射影变换(4 × 4) : Eigen::Projective3d

射影变换是最一般的变换,其保证的是接触平面的相交或相切。Eigen中预定好的一些类型如下。

/** \ingroup Geometry_Module */
typedef Transform<float,2,Projective> Projective2f;
/** \ingroup Geometry_Module */
typedef Transform<float,3,Projective> Projective3f;
/** \ingroup Geometry_Module */
typedef Transform<double,2,Projective> Projective2d;
/** \ingroup Geometry_Module */
typedef Transform<double,3,Projective> Projective3d;

它的成员函数和Isometry也是一模一样的,这里也不再介绍了。下面是一个简单的使用示例。

# include<iostream>
# include<Eigen\Dense>
# include<Eigen\Geometry>

using namespace std;
using namespace Eigen;

int main(){
	Projective3d prj;
	prj.setIdentity();
	cout << prj.matrix() << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
下面示例图片来自:相似、仿射、射影变换区别
在这里插入图片描述

总结:
(1)刚性变换:只有物体的 R \boldsymbol{R} R t \boldsymbol{t} t发生改变,形状不变。
(2)等距变换: R \boldsymbol{R} R t \boldsymbol{t} t的复合,等距变换前后长度,面积,线线之间的角度不变。自由度6。
(3)相似变换:等距变换和均匀缩放( s \boldsymbol{s} s)的一个复合,类似相似三角形,体积比不变。自由度7。
(4)仿射变换(正交投影):平移变换和非均匀变换( A \boldsymbol{A} A)的复合,不变量是平行线。自由度12。
(5)射影变换(透视变换):图像中的点的齐次坐标的一般非奇异线性变换。 不变量是:重合关系,长度的交比。自由度15。

实践:Eigen几何模块

#include <iostream>
#include <cmath>

using namespace std;

#include <Eigen/Core>
#include <Eigen/Geometry>

using namespace Eigen;

// 本程序演示了 Eigen 几何模块的使用方法

int main(int argc, char **argv) {

  // Eigen/Geometry 模块提供了各种旋转和平移的表示
  // 3D 旋转矩阵直接使用 Matrix3d 或 Matrix3f
  Matrix3d rotation_matrix = Matrix3d::Identity();
  // 旋转向量使用 AngleAxis, 它底层不直接是Matrix,但运算可以当作矩阵(因为重载了运算符)
  AngleAxisd rotation_vector(M_PI / 4, Vector3d(0, 0, 1));     //沿 Z 轴旋转 45 度
  cout.precision(3);
  cout << "rotation matrix =\n" << rotation_vector.matrix() << endl;   //用matrix()转换成矩阵
  // 也可以直接赋值
  rotation_matrix = rotation_vector.toRotationMatrix();
  // 用 AngleAxis 可以进行坐标变换
  Vector3d v(1, 0, 0);
  Vector3d v_rotated = rotation_vector * v;
  cout << "(1,0,0) after rotation (by angle axis) = " << v_rotated.transpose() << endl;
  // 或者用旋转矩阵
  v_rotated = rotation_matrix * v;
  cout << "(1,0,0) after rotation (by matrix) = " << v_rotated.transpose() << endl;

  // 欧拉角: 可以将旋转矩阵直接转换成欧拉角
  Vector3d euler_angles = rotation_matrix.eulerAngles(2, 1, 0); // ZYX顺序,即yaw-pitch-roll顺序
  cout << "yaw pitch roll = " << euler_angles.transpose() << endl;

  // 欧氏变换矩阵使用 Eigen::Isometry
  Isometry3d T = Isometry3d::Identity();                // 虽然称为3d,实质上是4*4的矩阵
  T.rotate(rotation_vector);                                     // 按照rotation_vector进行旋转
  T.pretranslate(Vector3d(1, 3, 4));                     // 把平移向量设成(1,3,4)
  cout << "Transform matrix = \n" << T.matrix() << endl;

  // 用变换矩阵进行坐标变换
  Vector3d v_transformed = T * v;                              // 相当于R*v+t
  cout << "v tranformed = " << v_transformed.transpose() << endl;

  // 对于仿射和射影变换,使用 Eigen::Affine3d 和 Eigen::Projective3d 即可,略

  // 四元数
  // 可以直接把AngleAxis赋值给四元数,反之亦然
  Quaterniond q = Quaterniond(rotation_vector);
  cout << "quaternion from rotation vector = " << q.coeffs().transpose()
       << endl;   // 请注意coeffs的顺序是(x,y,z,w),w为实部,前三者为虚部
  // 也可以把旋转矩阵赋给它
  q = Quaterniond(rotation_matrix);
  cout << "quaternion from rotation matrix = " << q.coeffs().transpose() << endl;
  // 使用四元数旋转一个向量,使用重载的乘法即可
  v_rotated = q * v; // 注意数学上是qvq^{-1}
  cout << "(1,0,0) after rotation = " << v_rotated.transpose() << endl;
  // 用常规向量乘法表示,则应该如下计算
  cout << "should be equal to " << (q * Quaterniond(0, 1, 0, 0) * q.inverse()).coeffs().transpose() << endl;

  return 0;
}
遗传算法优化BP神经网络(GABP)是种结合了遗传算法(GA)和BP神经网络的优化预测方法。BP神经网络是种多层前馈神经网络,常用于模式识别和预测问题,但其容易陷入局部最优。而遗传算法是种模拟自然选择和遗传机制的全局优化方法,能够有效避免局部最优 。GABP算法通过遗传算法优化BP神经网络的权重和阈值,从而提高网络的学习效率和预测精度 。 种群:遗传算法中个体的集合,每个个体代表种可能的解决方案。 编码:将解决方案转化为适合遗传操作的形式,如二进制编码。 适应度函数:用于评估个体解的质量,通常与目标函数相反,目标函数值越小,适应度越高。 选择:根据适应度保留优秀个体,常见方法有轮盘赌选择、锦标赛选择等。 交叉:两个父代个体交换部分基因生成子代。 变异:随机改变个体的部分基因,增加种群多样性。 终止条件:当迭代次数或适应度阈值达到预设值时停止算法 。 初始化种群:随机生成组神经网络参数(权重和阈值)作为初始种群 。 计算适应度:使用神经网络模型进行训练和预测,根据预测误差计算适应度 。 选择操作:根据适应度选择优秀个体 。 交叉操作:对选择的个体进行交叉,生成新的子代个体 。 变异操作:对子代进行随机变异 。 替换操作:用新生成的子代替换掉部分旧种群 。 重复步骤2-6,直到满足终止条件 。 适应度函数通常以预测误差为基础,误差越小,适应度越高。常用的误差指标包括均方根误差(RMSE)或平均绝对误差(MAE)等 。 GABP代码中包含了适应度函数的定义、种群的生成、选择、交叉、变异以及训练过程。代码注释详尽,便于理解每个步骤的作用 。 GABP算法适用于多种领域,如时间序列预测、经济预测、工程问题的优化等。它特别适合解决多峰优化问题,能够有效提高预测的准确性和稳定性 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值