第三讲 三维空间刚体运动
一、旋转矩阵
刚体在其运动过程中,在三维空间中占据特定的状态点,在工程学中将其运动状态称为位置坐标系与方向信息共同决定的结果,在机器人学领域则将此定义为完整的位形空间中的一个点,在此情况下需要明确位形参数的选择方案以确保表示的有效性与一致性
1、点、向量和坐标系
1)定义
点: 点是空间中的基本元素,没有长度和体积。
向量: 由一个点指向另外一个点就是一个向量。!!!本书中坐标用列向量表示!!!
坐标系: 点和向量要想与坐标联系起来,说先要选定一个坐标系。
2)点和向量的坐标
三维空间中点的坐标: 在给定坐标系下可以直接表示为(x,y,z)
三维空间中向量的坐标:

系与基的选取有关。其中基的长度为1,是空间中一组线性无关的向量。
3)向量之间的内积和外积
对于向量a,b,其内积可以写成

向量内积的结果是一个标量,大小为|a||b|cos<a,b>,描述向量间的投影关系。
向量外积的结果是一个矢量,大小为|a||b|sin<a,b>,方向垂直于这两个向量。
借助反对称符号^的作用,使得向量的外积表现为一个矩阵与一个列向量相乘的线性运算行为。
反对称矩阵性质:

2、坐标系间的欧式变换
在实际应用场景中的一般情况是,在应用环境中我们会建立多个参考框架以适应复杂的需求,在这种情况下同一向量在同一参考框架下的表示具有差异性,在这种情况下两者的转换关系涉及一个欧式变换矩阵T如上图所示用于直观展示各坐标的相对位置关系

在两个坐标系之间的转换通常表现为一次旋转变换后跟上一次平移操作。这通常被称为刚性变换(rigid transformation)。其显著特征是任何向量在不同坐标系中的长度及其之间的夹角均保持不变。
1)坐标系间的旋转变换
旋转变换公式
如图3-1所示
设世界坐标系为

,其中一组单位正交基为

,向量

在该坐标系下的坐标为
设机器人坐标系为

,其中一组单位正交基为

,向量

在该坐标系下的坐标为
当坐标系

和坐标系

间只存在旋转关系时,向量

在两坐标系下坐标关系为:

其中
,

称矩阵

为旋转矩阵。同时由于

和

均为单位向量,因此

实际上是向量

和

的夹角余弦值,因此矩阵

也被称为方向余弦矩阵。

读作从

到

的旋转矩阵。
旋转矩阵的性质
- 矩阵

为旋转矩阵

矩阵

为正交矩阵且行列式为1 (正交矩阵:若

,则

为正交矩阵)
*

维旋转矩阵的集合被称为特殊正交群:

同一向量在两坐标系下坐标的变换关系
**

**
2)坐标系间的旋转变换和平移变换
平移变换
当坐标系

和坐标系

在旋转变换的基础上还存在平移变换时,向量

在坐标系

和坐标系

下的坐标变换关系为:

其中

是坐标系

原点指向坐标系

原点的向量在坐标系

下的坐标。
!!!注意:

是向量对应的坐标,而不是向量 !!! 所以


是坐标系

原点指向坐标系

原点的向量在坐标系

下的坐标

是坐标系

原点指向坐标系

原点的向量在坐标系

下的坐标
坐标

和坐标

所对应的空间中的向量是一对相反的向量,但坐标

和坐标

是这一对向量在两个不同坐标系

和

下的坐标,因此

坐标系间欧式变换公式(旋转+平移)

其中
,
分别为向量

在坐标系

和坐标系

下的坐标。
是从坐标系

到坐标系

的旋转矩阵

是从坐标系

原点指向坐标系

原点的向量在坐标系

下的坐标。
3)坐标系间欧氏变换公式线性化
进行线性化原因
在实际应用过程中,通常需要在多个坐标系下进行多次坐标变换,而应用

这样一个公式进行多次变换后会很复杂,如进行两次坐标变换后会变成

。因此要对该公式进行线性化。
线性化方法
我们通过增加一个维度将上述的欧式变换公式写成齐次形式:

我们称
为从坐标系

到坐标系

的变换矩阵。

这样的变换矩阵构成的集合成为特殊欧氏群


线性化结果
在不引起误会的情况下,我们将
记作

,将
记作

。(这个可以这么理解,对于

和

当他被要求呈现三维形态时,则表现为三维结构;当他被要求呈现四维形态时,则可以通过添加一个单位来实现相应的扩展。
则两个坐标间的变换公式可记作:

连续的坐标变换可表示为:

这样同一向量在多个坐标系下进行多次变换后公式依然简洁。
二、C++开源线性代数库Eigen
1、库文件的安装和位置查询
库文件安装在终端输入以下命令
sudo apt-get install libeigen3-dev
库文件位置查询在终端输入以下命令
sudo updatedb
locate eigen3
说明
Eigen 是一个完全由头文件构建完成的静态库,并不包含.so和.a这样的符号链接文件。因此,在使用过程中无需引入额外的动态链接器即可直接运行代码。同时处理不同的静态链接库配置问题较为方便。此外,在Ubuntu系统中,默认安装路径位于/usr/include/eigen3/目录下。
2、Eigen库的引用和应用
1)Eigen库的引用
在CMakeLists.txt文件中:
#添加头文件,双引号中是eigen库所在路径,根据实际安装位置变化
include_directorise("/usr/include/eigen3")
在.cpp文件中:
//Eigen核心部分
#include <Eigen/Core>
//稠密矩阵的代数部分
#include <Eigen/Dense>
using namespace Eigen
2)Eigen库的使用
矩阵和向量的定义
//在Eigen库中,所有矩阵和向量均基于模板类Eigen::Matrix实现,并接受三个参数
//格式为:Matrix<数据类型, 行数, 列数>
//示例:创建一个浮点型元素的2行3列矩阵
Matrix<float, 2, 3> matrix_2_3;
//在Eigen库中预定义了许多特定类型的矩阵和向量
//这些类型的底层实现仍基于Eigen::Matrix
例如,在Eigen中可以使用Vector3d或Matrix<double, 3, 1>来表示三维列向量
//注:此处原书有误,请将float更改为double
//对于固定的维度(如3x3),可以直接使用简略形式
Matrix<double, 3, 3> matrix_3x3;
//当无法预先确定矩阵尺寸时,则可以选择动态大小的Matrix
如果无法预先确定矩阵尺寸,则可以选择动态大小的:
Matrix<double,Dynamic,Dynamic> matrix_dynamic;
为矩阵初始化
用数字初始化
在初始化阶段,在每一行依次填充数字,在接着在下一行继续填充。
经过这样的初始化后,
矩阵则表示为:
[
[1,2,3],
[4,5,6]
]
矩阵中元素置零
创建mxn维度的矩阵并初始化其所有元素为零。
生成一个3×3维度的单位矩阵并将其所有元素设为零。
矩阵中元素置随机数
matrix_mn = MatrixXd::Random(m,n); //为mxn维的矩阵赋随机数
matrix_33 = Matrix::Random(); //特例,为3x3维矩阵赋随机数
访问矩阵中的元素
//直接用矩阵名称(行数,列数)就可以访问
//!!!注意:矩阵的行列在这里都是从0开始算的,如下访问矩阵的matrix_23第二行第二列元素为!!!
matrix_23(1,1)
//访问矩阵的第一行第一个元素
matrix_23(0,0)
矩阵类型的转换
在Eigen中无法进行隐式数据类型转换必须进行显式数据类型转换
//什么是隐式数据类型转换
int a = 3;
double b = 2;
double c;
c = a*b;
//在上述c=a*b中,a由原来的int型隐式转换为double型和double型的b相乘,最后得到了double型的c
//探讨显示数据类型的转换方式及其意义
c = (double)a*b;
其中:
(double)表示将数值a从当前类型转换为双精度浮点数,
与双精度浮点数b进行乘法运算,
计算结果存储在双精度浮点数c中。
在不同数据类型的矩阵运算中应采取明确的数据类型转换措施,并通过明确的数据类型转换实现这一目标。
矩阵名称.cast<要转换的数据类型>() 来完成
创建一个浮点型矩阵matrix_23;
定义一个三维向量v_3d;
生成结果矩阵result;
将matrix_23转换为双精度后与v_3d相乘得到结果。
矩阵的维数
!!!在矩阵运算过程中,不能搞错矩阵的维数,否则也会报错。!!!
矩阵的常用计算
四则运算
直接用+-*/就可以
单矩阵运算(以3*3矩阵matrix_33为例)
该矩阵的转置操作
计算该矩阵内所有元素的总和
该矩阵的迹
计算该矩阵的逆
计算该矩阵的行列式
求矩阵的特征值
> 1. SelfAdjointEigenSolver<Matrix3d> eigen_solver(matrix_33.cast<double>())
>
> 2. //求解特征值和特征向量,并存储到变量eigen_solver中,eigen_solver()的()中为待求解的矩阵
>
>
>
> 1. cout << eigen_solver.eigenvalues() << endl; //输出matrix_33的特征值
>
> 2. cout << eigen_solver.eigenvectors() << endl; //输出matrix_33的特征向量
>
>
>
说明:SelfAdjointEigenSolver<>()用于计算任意大小矩形区域内的特征值与特征向量。该算法支持处理不同维度的数据集,并通过自适应网格细化技术提高分析精度。
注意: 此函数仅限于处理double类型的矩阵及其相应的特征值与特征向量。为了实现数据类型的转换以满足计算需求,请将matrix\_33\cast
解矩阵方程matrix_NN * x = matrix_N1
> 1. Matrix<double,N,N> matrix_NN = MatrixXd::Random(N,N); //定义NxN维的矩阵并赋随机数
>
> 2. Matrix<double,N,1> matrix_N1 = MatrixXd::Random(N,1); //定义Nx1维的矩阵并赋随机数
>
>
>
求解方法1:使用矩阵逆求解,但计算量大
> Matrix<double,N,1> x = matrix_NN.inverse()*matrix_N1; //使用x = matrix_N1 * matrix_NN.inverse()公式
求解方法2:使用QR分解求解,计算量小
> Matrix<double,N,1> x = matrix_NN.colPivHouseholderQr().solve(matrix_N1);
求解方法3:使用cholesky分解求解,要求矩阵半正定
> Matrix<double,N,1> x = matrix_NN.ldlt().solve(matrix_N1);
>
运算时间的计算
time_start被初始化为当前时间。
// clock_t是一种C++标准库中的数据类型
time_end被初始化为当前时间。
// CLOCKS_PER_SEC表示每秒有多少个CPU时钟周期
deltime被计算为程序运行所消耗的时间(以秒为单位)。
deltime被计算为程序运行所消耗的时间(以毫秒为单位)。
