机器人的运动规划过程中会涉及大量的矩阵计算,而 EIGEN 库对于矩阵计算而言,其意义不言而喻。所以最近学习了一下 EIGEN 库的使用。并在此过程中记录自己的感悟和理解。本篇通过Eigen库的Quick reference guide来了解整个库。
文章目录
- 1. Eigen模块和头文件(Modules and Header files)
- 2. 数组、矩阵和向量类型(Array, matrix and vector types)
- 3. 算法运算符(Arithmetic Operators)
- 4. 系数、数组运算符(Coefficient-wise & Array operators)
- 5. 简化函数(Reductions)
- 6. 子矩阵(Sub-matrices)
- 7. 其它操作(Miscellaneous operations)
- 8. 对角矩阵、三角矩阵和伴随矩阵(Diagonal, Triangular, and Self-adjoint matrices)
- 9. 参考资料
新学的知识,总喜欢自己动手整理一下,不知道是不是强迫症。整理完才发现,太长了,头疼。
1. Eigen模块和头文件(Modules and Header files)
EIGEN库其由核心模块和若干附属模块构成,每一模块都有对应的头文件供编程引用。其文件组织结构列表如下:
模组 | 头文件 | 说明 |
---|---|---|
Core | #include <Eigen/Core> |
矩阵(Matrix)和数据(Array)类,并涉及基本的线性代数和数组操作 |
Geometry | #include <Eigen/Geometry> |
涉及变换、平移、缩放、2D和3D旋转、四元数、角轴(AngleAxis) |
LU | #include <Eigen/LU> |
逆运算、行列式、LU分解器 (FullPivLU, PartialPivLU) |
Cholesky | #include <Eigen/Cholesky> |
LLT and LDLT Cholesky 因式分解器 |
Householder | #include <Eigen/Householder> |
Householder变换,也被其它线性代数模组所引用 |
SVD | #include <Eigen/SVD> |
基于最小二乘解算的SVD分解(JacobiSVD, BDCSVD) |
QR | #include <Eigen/QR> |
QR分解器 (HouseholderQR, ColPivHouseholderQR, FullPivHouseholderQR) |
Eigenvalues | #include <Eigen/Eigenvalues> |
特征值特征向量分解(EigenSolver, SelfAdjointEigenSolver, ComplexEigenSolver) |
Sparse | #include <Eigen/Sparse> |
稀疏矩阵存储和相关基础线性代数(SparseMatrix, SparseVector) |
#include <Eigen/Dense> |
Includes Core, Geometry, LU, Cholesky, SVD, QR, and Eigenvalues header files | |
#include <Eigen/Eigen> |
Includes Dense and Sparse header files (整个EIGEN库) |
对于不涉及稀疏矩阵运算的,头文件使用#include <Eigen/Dense>
就可以满足要求,或者直接包含整个EIGEN库#include <Eigen/Eigen>
,全面!任性!
2. 数组、矩阵和向量类型(Array, matrix and vector types)
Eigen库提供了矩阵和向量两种对象,都是由模版类进行表示的,矩阵用Matrix进行表示,而1维和2维数组则由模版类Array表示。Eigen库提供的模版类形式如下:
typedef Matrix< Scalar, RowsAtCompileTime, ColsAtCompileTime, Options > MyMatrixType;
typedef Array< Scalar, RowsAtCompileTime, ColsAtCompileTime, Options > MyArrayType;
- Scalar :是数据结构中标量元素的类型,例如 float, double, bool, int 等,即在模版类定义自定义数据类型时,其内部的元素只能是标量。
- RowsAtCompileTime:指定编译时矩阵的行数,或用 Dynamic 参数,在运行时确定行数。
- ColsAtCompileTime:指定矩阵的列数,或用 Dynamic 参数,在运行时确定行数。
- Options:其值可以是 ColMajor 或 RowMajor ,默认为 ColMajor
以下组合方式都是有效的:为了注释明显用%
表示注释。
Matrix<double, 6, Dynamic> % Dynamic number of columns (heap allocation)
Matrix<double, Dynamic, 2> % Dynamic number of rows (heap allocation)
Matrix<double, Dynamic, Dynamic, RowMajor> % Fully dynamic, row major (heap allocation)
Matrix<double, 13, 3> % Fully fixed (usually allocated on stack)
以上代码定义中,第一行定义的矩阵其行数是固定的,而列数是动态的,同理第二行定义。第三行则行列都是动态确定,最后一行则是指定了矩阵的固定行列数。这种方式,真是学到了。
而Eigen库为了使用上的方便,对常用的矩阵和数组类型进行了定义,如果不是个性化所需的数据类型,使用常用的已定义类型更加方便。矩阵类型中,已定义的有以下类型:
Matrix<float,Dynamic,Dynamic> <=> MatrixXf
Matrix<double,Dynamic,1> <=> VectorXd
Matrix<int,1,Dynamic> <=> RowVectorXi
Matrix<float,3,3> <=> Matrix3f
Matrix<float,4,1> <=> Vector4f
数组类型有以下已定义类型:
Array<float,Dynamic,Dynamic> <=> ArrayXXf
Array<double,Dynamic,1> <=> ArrayXd
Array<int,1,Dynamic> <=> RowArrayXi
Array<float,3,3> <=> Array33f
Array<float,4,1> <=> Array4f
<=>
后面表示定义的数据类型,就像double,float,int
等一样,可以在包含头文件的代码中直接使用。例如:
using namespace Eigen;
MatrixXf variable_1;
VectorXd variable_2;
Matrix3f variable_3;
Vector4f variable_4;
ArrayXXf variable_5;
Eigen::Array33f variable_6;
在使用时要加命名空间,如果不想直接使用命名空间,则需要在数据类型之前加上作用域说明,如以上代码中的最后一行。同时矩阵和数组类型之间也有内在的转换机制。如下示例代码:
Array44f a1, a2; % 4 X 4 的二维数组
Matrix4f m1, m2; % 4 X 4 的矩阵
m1 = a1 * a2; % coeffwise product, 从数组到矩阵的隐式转换
a1 = m1 * m2; % matrix product, 从矩阵到数组的隐式转换
a2 = a1 + m1.array(); % 禁止混合数组和矩阵
m2 = a1.matrix() + m1; % 如果需要混合运算,则需要显式转换
ArrayWrapper<Matrix4f> m1a(m1); // m1a is an alias for m1.array(), they share the same coefficients
MatrixWrapper<Array44f> a1m(a1);
在以下的说明中,将使用*
来强调特定对象的应用范围和领域:
* linear algebra matrix and vector only
* array objects only
2.1 基本矩阵运算(Basic matrix manipulation)
2.1.1 构造函数
对象定义可以用以下方式,类会自动调用构造函数,构造类对象。
Vector4d v1;
Array3i v2(x, y, z); % 以x,y,z为元素构造类对象
Vector4d v3(x, y, z, w);
Matrix4f m1;
MatrixXf m2; % 构造一个空对象
2.1.2 初始化
Eigen重载了<<
运算符,可以直观的用数据为变量赋值,如下所示:
Vector3f v1;
v1 << x, y, z; % 将x,y,z按顺利赋值给对应的位置
ArrayXf v2(4);
v2 << 1, 2, 3, 4;
Matrix3f m1;
m1 << 1, 2, 3, % 按行赋值给矩阵
4, 5, 6,
7, 8, 9;
int rows=5, cols=5;
MatrixXf m(rows,cols);
m << (Matrix3f() << 1, 2, 3, 4, 5, 6, 7, 8, 9).finished(), % 以块的形式为矩阵赋值
MatrixXf::Zero(3,cols-3),
MatrixXf::Zero(rows-3,3),
MatrixXf::Identity(rows-3,cols-3);
cout << m;
输出为:
1 2 3 0 0
4 5 6 0 0
7 8 9 0 0
0 0 0 1 0
0 0 0 0 1
2.1.3 运行时可获取信息
使用类的优势之一就是提供了大量的成员函数,可以获取对象的信息,而Eigen中为矩阵类对象提供了丰富的接口:
vector.size(); % 获取向量的大小,即元素个数
vector.innerStride(); % 沿数据存储的方向移动一个位置,内存中需要移动的位置数
vector.data(); % 获取向量的数据
matrix.rows(); % 获取矩阵的行数
matrix.cols(); % 获取矩阵的列数
matrix.innerSize(); % 按列存储的矩阵返回其行数,接行存储的矩阵返回其列数
matrix.outerSize(); % 按列存储的矩阵返回其列数,按行存储的矩阵返回其行数
matrix.innerStride(); % 返回内部实际存储时相邻元素之间的指针增量
matrix.outerStride(); % 返回按逻辑顺序存储时的指针增量
matrix.data(); % 获取矩阵的数据指针
2.1.4 编译时对象类型信息
ObjectType::Scalar
ObjectType::RowsAtCompileTime
ObjectType::RealScalar
ObjectType::ColsAtCompileTime
ObjectType::Index
ObjectType::SizeAtCompileTime
2.1.5 调整大小
vector.resize(size); % 调整具有动态尺寸的向量大小
vector.resizeLike(other_vector); % 按other_vector的尺寸调整具有动态尺寸的向量大小
vector.conservativeResize(size);
matrix.resize(nb_rows, nb_cols); % 调整成具有指定行列数的矩阵
matrix.resize(Eigen::NoChange, nb_cols); % 只调整列
matrix.resize(nb_rows, Eigen::NoChange); % 只调整行
matrix.resizeLike(other_matrix); % 按other_matrix的大小调整矩阵
matrix.conservativeResize(nb_rows, nb_cols);
使用有风险,应用需要谨慎。
2.1.6 数据获取
带有范围检查的数据获取方式如下:
vector(i) vector.x()
vector[i] vector.y()
vector.z()
vector.w()
matrix(i,j)
不带有数据范围检查的获取方式:
vector.coeff(i)
vector.coeffRef(i)
matrix.coeff(i,j)
matrix.coeffRef(i,j)
2.1.7 赋值/Copy
object = expression;
object_of_float = expression_of_double.cast<float>();
如果可能,左侧为动态矩阵时,会被自动调整为与右侧相同的维数形式。
2.2 预定义矩阵(Predefined Matrices)
2.2.1 固定维数的矩阵和向量
typedef {
Matrix3f|Array33f} FixedXD;
FixedXD x;
x = FixedXD::Identity(); % 构造一个单位阵临时对象,并赋值给x
x = FixedXD::Zero(); % 构造一个元素全是0的临时对象,并赋值给x
x = FixedXD::Ones(); % 构造一个元素全是1的临时对象,并赋值给x
x = FixedXD::Constant(value); % 构造一个元素全是value的临时对象,并赋值给x
x = FixedXD::Random(); % 构造一个元素是随机值的临时对象,并赋值给x
x = FixedXD::LinSpaced(size, low, high);