第一遍草草看过,看到后面优化公式推导搞不懂了……又得回过头来看下。

1. 术语表

中文名称 英文名称
刚体 rigid body/object
四元数 quaternions
正交矩阵 orthogonal matrix

2. 看第一遍记录

  1. 向量和加减法、内外积,即使坐标系不同,其结果也是一致的。(运算结果与坐标系无关——当然,数值表示时与坐标系有关, 毕竟表示时就使用了坐标系的基底)

  2. 刚体 旋转矩阵 + 平移向量,合并到一起,是 变换矩阵(位置向量变为四维向量,称为齐次坐标

  3. 向量与唯一对应的反对称矩阵(Skew-symmetric Matrix)

    $\newcommand{\bs}{\boldsymbol}$

    向量叉乘(外积) $\bs{a} \times \bs{b}$ 表示一个垂直与 $\bs{a}$, $\bs{b}$ 所在平面,模为 $\lvert \bs a \rvert \lvert \bs b \rvert \sin \left \langle \bs a, \bs b \right \rangle$

    基于向量间叉乘(外积)引入了反对称矩阵和和符号 $\wedge$. 对向量 $\bs{t} = (x, y, z)$, 其反对称矩阵为

    \[\bs{t}^\wedge = \begin{pmatrix} 0 & -z & y \\ z & 0 & -x \\ -y & x & 0 \\ \end{pmatrix}\]
  4. 实战 Eigen 1 : 基础类型

    1. Matrix3d 中的 3d 不是表示3维矩阵,而是表示 3 x 3 double, 对应的还有 Matrix3f, Matrix3i 等 (分别表示 floatint).

      Matrix3d, Vector3f, RowVector3f 等都是 Matrix<scalar, N1, N2> 的宏定义。

    2. Eigen::Matrix 什么时候用动态size(MatrixX*),什么时候用静态size(模板参数)?

      显然,如果是运行时才知道,那必然只能用动态size;

      其次,根据文档

      • 矩阵规模小(一般小于等于16),最好用静态的,这样变量都站栈上,快;

      • 矩阵规模大了(大于32),那也建议用动态的(如可用 MatrixXd(row, col) 初始化),因为 这时用静态的,可能会爆栈。

      可知,模板定义的静态参数,是永远在栈上申请的。

    3. 似乎老版本(3.4前),不支持用 initializer list 初始化矩阵

      文档 支持使用 initializer list 来做初始化,然而我编译总是报错。

       error: no matching function for call to 
       ‘Eigen::Matrix<int, -1, -1>::Matrix(<brace-enclosed initializer list>)’
      

      开始以为是CMake没有设置好 -std=c++11, 折腾半天还是不行。 search网上,信息好少,找到一个 C++ initializer lists for arbitrary length vectors 看起来说3.3.9 还不行;我看了下ubuntu直接装的Eigen3版本,在 /usr/include/eigen3/Eigen/src/Core/util/Macros.h 下,是 3.3.7, 果然不行啊…… 白折腾了。

      又去找了下change log, 也没发现有这个改动; 文档里也没有说从什么版本开始支持的…… 一个字,有点坑啊
      并没有试最新版是否支持——算了,别总在这种细节地方折腾 = =; 老实用comma初始化,不一样吗? 还少写几个括号呢!

    4. auto 有坑! 用Eigen, 别用auto

      看高博的代码里有 Matrix3d m = Matrix3d::Random(); 习惯性地,写为 auto m = Matrix3d::Random(); 结果发现下面的转置结果竟然和前面的矩阵输出对不上啊?用一个固定矩阵、固定类型整了下,确定是auto的问题……

      然后用网上看到的 std::cout << decltype(m)::info 从编译错误中看出auto推断的类型,为:

       Eigen::CwiseNullaryOp<
           Eigen::internal::scalar_random_op<double>, 
           Eigen::Matrix<double, 3, 3, 0, 3, 3>
       >
      

      果然这不是想要的类型啊……

      网上search了下 Eigen auto random 果然有人问了,这个 Random 返回的果然不是固定值,而是一个表达式! 这个表达式就用于延迟求值的——用的时候才算一次。 用 auto 接受了表示式,所有后面每次对这个表达式做操作,就求值一次Random,所以每次都不一样。 而当我们使用固定类型接收时,如期望的Matrix3d, 就会直接触发类型转换,Random只求值一次存储到结果里,固定住了。

      哎,这可真是…… 学到了挺多,但是如果一开始就先去看Api,是不是定位可以快点呢?没事,反正就是学习嘛 = =

      最后,记住上面链接里的话:

      C++11 and the auto keyword
      In short: do not use the auto keywords with Eigen’s expressions, unless you are 100% sure about what you are doing. In particular, do not use the auto keyword as a replacement for a Matrix<> type.

      不熟Eigen,别用 auto

  5. 欧拉角的万向锁问题(Gimbal Lock)

    本质原因,应该还是: 没法用三个变量不带奇异性的表示三维空间内的旋转,正如 用经纬度两个坐标表示铺开的地球平面,在维度为90度时,经度无唯一解:任意经度,在维度=90时, 其实都对应到3维球面的一个点,故是奇异性的。 这个就和万向锁一样:当pitch = 90度时,改变X、Z都无法让物体离开某一个平面; 即在一个位置,X、Z不能有唯一解。

    看到网上说法,万向锁存在的前提,是使用动态欧拉角。即每次变换,都基于变换后的坐标系。

  6. 描述三维空间中的刚体运动(欧式变换),为何要有多种表达: 旋转矩阵+平移向量 $\rightarrow$ 变换矩阵+齐次坐标 $\rightarrow$ 旋转向量+欧拉角 $\rightarrow$ 四元数

    两个坐标系变换,最直观的计算,就是旋转矩阵+平移向量(描述刚体运动)。

    变换矩阵+齐次坐标,是为了解决旋转矩阵+平移向量书写繁琐的问题。没有本质区别,不过表示上的trick。

    旋转向量+欧拉角,针对的是

    1. 变换矩阵16个(旋转矩阵+平移向量同样16个)表示3个自由度的旋转,显得冗余

    2. 旋转矩阵本身有约束(正交矩阵、行列式为1;变换矩阵也有约束)。在求解此矩阵时,很难计算 (带约束求解)

    3. 矩阵表示不够直观,人看不明白究竟怎么旋转的

    这几个问题。但又有万向锁问题……

    四元数是用来解决欧拉角问题的:4个变量表示3维旋转;表达准确,不存在奇异性;

    四元数——基本概念中,说到四元数(相比欧拉角)的优点和应用:“解决万向节死锁(Gimbal Lock)问题, 仅需存储4个浮点数,相比矩阵更加轻量,四元数无论是求逆、串联等操作,相比矩阵更加高效; 现在主流游戏或动画引擎都会以缩放向量+旋转四元数+平移向量的形式进行存储角色的运动数据。” 可以看到,四元数,只是描述了旋转,不含平移。

    这本书这里讲得还是有点难的,学习曲线陡增…… 先混过去

    旋转3个自由度,平移还有3个自由度;所以欧式变换,一个6个自由度。书里比较强调旋转,有时忽略了平移。

  7. 3维空间的更多变换方式: 相似变换、仿射变换、射影变换。

    欧式,相似,仿射,射影,依次变得一般性。射影变换就对应到现实世界到相片的变换。

2. 第二遍关注点

看到后面求解旋转矩阵要使用到群时,忘了为啥要这样做了。于是回过头又看一下——发现原来是旋转矩阵不是随随便便的一个矩阵, 它自身是有限定的,故优化目标函数时,是一个带约束的最优化。通过引入李群,可以将其转变为无约束优化,方便求解。 有关旋转矩阵的性质,具体见下面复习的内容。

2.1 再看向量定义

又看了一遍向量部分的描述:向量可以看做空间中一个点到另一个点的箭头。要和向量坐标区分开:只有定义了一个坐标体系, 这个空间中的向量才可写成坐标的形式。而且,其完备的写法应该是

\[\newcommand{\bs}{\boldsymbol} \bs{a} = \begin{bmatrix}\bs{e_1}, \bs{e_2}, \bs{e_3}\end{bmatrix} \begin{bmatrix}a_1 \\ a_2 \\ a_3 \end{bmatrix} = a_1\bs{e_1} + a_2\bs{e_2} + a_3\bs{e_3}\]

$ \begin{bmatrix}\bs{e_1}, \bs{e_2}, \bs{e_3}\end{bmatrix} $ 是这个空间中的基构成的矩阵!每个基向量是坐标轴上的单位向量。 向量的加减内外积等基础操作,是定义在向量自身上的,和坐标系没啥关系——比如,两个向量的内积结果,因为是一个标量,跟坐标系无关, 所以在任何坐标系下运算,这两个向量的内积结果在数值上都一样的,为 $\vert \bs{a} \vert \vert \bs{b} \vert \cos\langle \bs{a}, \bs{b}\rangle$. 那为啥要用坐标系?显然是为了方便表示和计算向量。 定义了坐标系后,所有向量就在一个体系下,就可以用数值坐标表示出来,且向量间的运算的结果,也可以用数值方法快速求出。

2.2 旋转矩阵的推导和特殊性质

刚体运动,之前只注意到旋转+平移,书上定义是: 两个坐标系间的运动,由一个旋转+平移组成——还应该关注到,刚体运动是两个坐标系的变换。 基于此,旋转矩阵也不是随便定义的,它其实是两个坐标系的基的矩阵乘结果!这可以通过上面向量的描述来推导得到:

向量 $\bs{a}$, 从基 $(\bs{e_1}, \bs{e_2}, \bs{e_3})$ 的坐标系到基 $(\bs{e_1’}, \bs{e_2’}, \bs{e_3’})$ 的坐标系, 向量本身不变,从而有

\[\begin{bmatrix}\bs{e_1}, \bs{e_2}, \bs{e_3} \end{bmatrix} \begin{bmatrix}a_1 \\ a_2 \\ a_3 \end{bmatrix} = \begin{bmatrix}\bs{e_1'}, \bs{e_2'}, \bs{e_3'}\end{bmatrix} \begin{bmatrix}a_1'\\ a_2' \\ a_3'\end{bmatrix} \tag{eq1. 向量相同} \label{ref1}\]

两边左乘矩阵 $\begin{bmatrix}\bs{e_1^T}, \bs{e_2^T}, \bs{e_3^T}\end{bmatrix}^T$, 有:

\[\begin{bmatrix} \bs{e_1^T}\bs{e_1} & \bs{e_1^T}\bs{e_2} & \bs{e_1^T}\bs{e_3} \\ \bs{e_2^T}\bs{e_1} & \bs{e_2^T}\bs{e_2} & \bs{e_2^T}\bs{e_3} \\ \bs{e_3^T}\bs{e_1} & \bs{e_3^T}\bs{e_2} & \bs{e_3^T}\bs{e_3} \\ \end{bmatrix} \begin{bmatrix}a_1 \\ a_2 \\ a_3 \end{bmatrix} = \begin{bmatrix} \bs{e_1^T}\bs{e_1'} & \bs{e_1^T}\bs{e_2'} & \bs{e_1^T}\bs{e_3'} \\ \bs{e_2^T}\bs{e_1'} & \bs{e_2^T}\bs{e_2'} & \bs{e_2^T}\bs{e_3'} \\ \bs{e_3^T}\bs{e_1'} & \bs{e_3^T}\bs{e_2'} & \bs{e_3^T}\bs{e_3'} \\ \end{bmatrix} \begin{bmatrix}a_1' \\ a_2' \\ a_3'\end{bmatrix}\]

左边因为基为单位向量、彼此正交的性质,因此左边矩阵乘结果为 $I$, 因此有

$\newcommand{\eqdef}{\operatorname{\overset{def}{=}}}$

\[\begin{bmatrix}a_1 \\ a_2 \\ a_3 \end{bmatrix} = \begin{bmatrix} \bs{e_1^T}\bs{e_1'} & \bs{e_1^T}\bs{e_2'} & \bs{e_1^T}\bs{e_3'} \\ \bs{e_2^T}\bs{e_1'} & \bs{e_2^T}\bs{e_2'} & \bs{e_2^T}\bs{e_3'} \\ \bs{e_3^T}\bs{e_1'} & \bs{e_3^T}\bs{e_2'} & \bs{e_3^T}\bs{e_3'} \\ \end{bmatrix} \begin{bmatrix}a_1' \\ a_2' \\ a_3'\end{bmatrix} \eqdef \bs{R} \bs{a'}\]

$R$ 就是我们的旋转矩阵,也即坐标系基的矩阵乘结果!因为模都为1,矩阵里每个基向量的内积,结果等于基向量间的余弦值,故也叫方向余弦矩阵。 进一步可以推导:

  1. 旋转矩阵是正交矩阵

    所谓正交矩阵,就是满足下面任意条件的矩阵

    1. $A^T A = I$
    2. $A^T = A^{-1}$, 转置等于逆
    3. $A$ 的每行都是正交的单位向量,或者每列都是正交的单位向量

    这几个都是等价的(第一、第二点比较显然,第三点我没有细究如何推导)。我们可以从第二点出发,通过验证 $R^T = R^{-1}$ 来推出旋转矩阵 $R$ 是正交矩阵.

    1. 求解 $R^T$ 是很简单的, 只需将其旋转即可。
    2. 求解 $R^{-1}$, 最简单是从上述的定义出发:回到 \ref{ref1}, 之前 $R$ 表示从坐标系 $(\bs{e_1’}, \bs{e_2’}, \bs{e_3’})$ 旋转到 $(\bs{e_1}, \bs{e_2}, \bs{e_3})$, 而 $R^{-1}$, 就是反过来,从坐标系 $(\bs{e_1}, \bs{e_2}, \bs{e_3})$ 转到 $(\bs{e_1’}, \bs{e_2’}, \bs{e_3’})$. 于是,在 \ref{ref1} 中左乘 $\begin{bmatrix}\bs{e_1’^T}, \bs{e_2’^T}, \bs{e_3’^T}\end{bmatrix}^T$,这样轮到右边矩阵乘结果为 $I$ 了, 而左边乘出的矩阵就是 $R$ 的逆了。

    打出来太麻烦了,可以直接在纸上写下。$R^T$ 和 $R^{-1}$ 值的确是相等的,故旋转矩阵是正交矩阵。

  2. 旋转矩阵行列式为1

    正交矩阵的行列式可以是 1 或 -1 (由 $A^T A = I$,两边求行列式相等可得). 其中为 1 时就是旋转矩阵,为 -1 时被称为瑕旋转矩阵。

去网上搜索,基本上正交矩阵和旋转矩阵是绑在一起的,两者关联性极大。