[Eigen中文文档] Matrix类

简介: 在Eigen中,所有矩阵和向量都是Matrix模板类的对象。向量只是行数或者列数为1的特殊矩阵。

文档总目录

英文原文链接

在Eigen中,所有矩阵和向量都是Matrix模板类的对象。向量只是行数或者列数为1的特殊矩阵。

template<typename Scalar_, int Rows_, int Cols_, int Options_, int MaxRows_, int MaxCols_>
class Eigen::Matrix< Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_ >

Matrix前三个模板参数

Matrix类有6个模板参数,但现在了解前3个参数就足够了,剩下的3个参数有默认值,以后再讨论他们。

Matrix类3个强制模板参数是:

Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>
  • Scalar是标量的类型,例如系数的类型。如果你想要一个float类型的矩阵,那么你可以在这里填写float。有关所有支持的标量类型以及如何添加新类型,请参阅标量类型
  • RowsAtCompileTimeColsAtCompileTime是矩阵的行数和列数,这是在编译时就知道的。如果在编译时不知道矩阵大小,请参阅下文“动态的特殊值”一节。

我们提供了很多常用的类型。例如,Matrix4f4*4float矩阵,这在Eigen中定义为:

typedef Matrix<float, 4, 4> Matrix4f;

下文会讨论这些常用类型的定义。

向量

如上所述,在Eigen中,向量是一种特殊的矩阵,把只有1列的矩阵叫做列向量,通常把列向量称为向量。行数为1的矩阵叫做行向量

例如,Vector3f是有3个float元素的列向量。在Eigen中这样定义:

typedef Matrix<float, 3, 1> Vector3f;

也提供了行向量的类型定义,如下表示有2个int元素的行向量:

typedef Matrix<int, 1, 2> RowVector2i;

动态的特殊值

当然,Eigen不局限于处理编译时维度已知的矩阵。模板参数RowsAtCompileTimeColsAtCompileTime可以是一个动态值,这表明在编译时矩阵大小是未知的,必须当作运行时的变量进行处理。在Eigen的术语中,这叫做动态大小;在编译期间就知道大小叫做固定大小。例如,MatrixXddouble类型的动态大小矩阵,定义如下:

typedef Matrix<double, Dynamic, Dynamic> MatrixXd;

类似的,定义int类型的动态大小向量VectorXi如下:

typedef Matrix<int, Dynamic, 1> VectorXi;

也可以定义一个固定行数和一个动态列数的矩阵,如下:

Matrix<float, 3, Dynamic>

构造函数

默认的构造函数始终可用,它不执行任何动态内存分配,也不初始化矩阵元素。例如:

Matrix3f a;
MatrixXf b;
  • a是一个3*3的矩阵,实际上是一个float[9]的数组,而且没有初始化。
    • b是一个动态大小的矩阵,它的初始大小是0*0,而且没有开辟内存。

也有指定大小的构造函数。对于矩阵来说,第一个参数总是行,第二个参数是列,对于向量来说,只需要传入向量的大小即可。构造函数按照给定的大小开辟内存,但不会初始化内存。

MatrixXf a(10,15);
VectorXf b(30);
  • a是一个动态大小为10*15的矩阵,开辟了内存但是没有初始化。
  • b是一个动态大小为30的向量,同样开辟了内存但是没有初始化。

为了提供动态大小和固定大小矩阵统一的API,可以向固定大小矩阵的构造函数传递大小,虽然这是无效的,但是合法的:

Matrix3f a(3,3);

这并没有任何操作。

Matrix3f 的定义是 typedef Matrix<float, 3, 3> Matrix3f; ,如果传入构造函数的大小不符合该定义就会报错,如Matrix3f a(2, 3);

矩阵和向量也可以从列表初始化。在C++11之前,此功能仅限于固定大小且不超过 4 的向量,如:

Vector2d a(5.0, 6.0);
Vector3d b(5.0, 6.0, 7.0);
Vector4d c(5.0, 6.0, 7.0, 8.0);

如果使用C++11编译,则可以通过列表来初始化任意固定大小的列或行向量,如:

Vector2i a(1, 2);                      // 包含 {1, 2} 的列向量
Matrix<int, 5, 1> b {
   1, 2, 3, 4, 5};   // 包含 {1, 2, 3, 4, 5} 的行向量
Matrix<int, 1, 5> c = {
   1, 2, 3, 4, 5}; // 包含 {1, 2, 3, 4, 5} 的列向量

初始化固定大小或者运行时大小固定的矩阵和向量时,必须将元素按照行进行分组,编译器会使用该分组一行一行的初始化矩阵,如下:

MatrixXi a {
         // 构造一个 2*2 的矩阵
      {
   1, 2},     // 第一行
      {
   3, 4}      // 第二行
};
Matrix<double, 2, 3> b {
    // 构造一个 2*3 的矩阵
      {
   2, 3, 4},
      {
   5, 6, 7},
};

对于列或者行向量,允许隐式的转置。这意味着可以使用一个行分组对一个列向量初始化。

VectorXd a {
   {
   1.5, 2.5, 3.5}};             // 包含3个元素的列向量,行向量隐式转换成列向量
RowVectorXd b {
   {
   1.0, 2.0, 3.0, 4.0}};     // 包含4个元素的行向量,本来就是行向量

访问元素

在Eigen中主要的元素访问与修改方法是重载括号运算符。对于矩阵,行索引总是优先传递的。对于向量,只需要传递一个索引,索引从0开始。如下:

#include <iostream>
#include <Eigen/Dense>

int main()
{
   
  Eigen::MatrixXd m(2,2);
  m(0,0) = 3;
  m(1,0) = 2.5;
  m(0,1) = -1;
  m(1,1) = m(1,0) + m(0,1);
  std::cout << "Here is the matrix m:\n" << m << std::endl;
  Eigen::VectorXd v(2);
  v(0) = 4;
  v(1) = v(0) - 1;
  std::cout << "Here is the vector v:\n" << v << std::endl;
}

输出:

Here is the matrix m:
  3  -1
2.5 1.5
Here is the vector v:
4
3

请注意 m(index) 不只用于向量,也可用于普通矩阵,这意味在元素数组中访问是基于索引的。但是,索引取决于矩阵的存储顺序。在Eigen中,矩阵默认列优先进行存储,也可以更改为行优先,详情查阅 存储顺序

运算符[]同样也被重载用于基于索引的向量访问,但C++不允许运算符[]传入多个参数。所以Eigen限制了运算符[]只能用于vector,因为C++语言的笨拙,会把 matrix[i,j] 编译成 matrix[j]

逗号初始化

可以使用所谓的逗号初始化语法初始化矩阵和向量。如下:

Matrix3f m;
m << 1, 2, 3,
     4, 5, 6,
     7, 8, 9;
std::cout << m;

输出:

1 2 3
4 5 6
7 8 9

重置大小

矩阵的当前大小可以通过 rows()cols()size() 来检索。这些方法分别返回行数、列数和元素个数。调整动态大小矩阵的大小可以使用 resize() 方法。

#include <iostream>
#include <Eigen/Dense>

int main()
{
   
  Eigen::MatrixXd m(2,5);
  m.resize(4,3);
  std::cout << "The matrix m is of size "
            << m.rows() << "x" << m.cols() << std::endl;
  std::cout << "It has " << m.size() << " coefficients" << std::endl;
  Eigen::VectorXd v(2);
  v.resize(5);
  std::cout << "The vector v is of size " << v.size() << std::endl;
  std::cout << "As a matrix, v is of size "
            << v.rows() << "x" << v.cols() << std::endl;
}

输出:

The matrix m is of size 4x3
It has 12 coefficients
The vector v is of size 5
As a matrix, v is of size 5x1

如果矩阵的大小没有改变,那么 resize() 方法是空操作。否则,该方法会破坏当前的矩阵,矩阵的元素可能会改变。如果你不想改变矩阵的系数,可以使用resize()的变体 conservativeResize()

这里进一步解释一下,如果矩阵的大小没有改变, resize() 不执行内存分配并且保持矩阵元素值不变,如果矩阵的大小改变了(行数、列数、元素数至少任意一个改变) ,数据被重新分配并且丢失矩阵所有值初始化为0,如下:

#include <iostream>
#include <Eigen/Dense>

int main()
{
    
Eigen::MatrixXd m{
    {
    1,2,3,4},{
    5,6,7,8},{
    9,10,11,12},{
    13,14,15,16}};
std::cout << "Before resize, the matrix m is: \n" << m << std::endl;
m.resize(3, 4);
std::cout << "After resize, the matrix m is: \n" << m << std::endl;
}

输出:

Before resize, the matrix m is: 
1  2  3  4
5  6  7  8
9 10 11 12
13 14 15 16
After resize, the matrix m is: 
0 0 0 0
0 0 0 0
0 0 0 0

为了 API 统一,所有这些方法在固定大小的矩阵上仍然可用。当然,实际上无法调整固定大小的矩阵。尝试将固定大小更改为实际不同的值将触发断言失败,但代码在语法上是合法的,如下:

#include <iostream>
#include <Eigen/Dense>

int main()
{
   
  Eigen::Matrix4d m;
  m.resize(4,4); // no operation
  std::cout << "The matrix m is of size "
            << m.rows() << "x" << m.cols() << std::endl;
}

输出:

The matrix m is of size 4x4

赋值和重置大小

赋值是使用操作符 = 将一个矩阵复制到另一个矩阵的操作。Eigen会自动调整 = 左边的矩阵大小以便与和 = 右边的矩阵大小相匹配,例如:

MatrixXf a(2,2);
std::cout << "a is of size " << a.rows() << "x" << a.cols() << std::endl;
MatrixXf b(3,3);
a = b;
std::cout << "a is now of size " << a.rows() << "x" << a.cols() << std::endl;

输出:

a is of size 2x2
a is now of size 3x3

当然,如果 = 左边的矩阵大小是固定大小,重置大小是不被允许的。

如果不想这种自动调整大小的操作发生,可以禁用它,详情参考this page

固定大小与动态大小

什么时候应该使用固定大小(例如Matrix4f),什么时候应该使用动态大小(例如MatrixXf)?

简单的答案是:尽可能对非常小的矩阵使用固定大小,对较大的矩阵必须使用动态尺寸。对于小矩阵,尤其是小于(大约)16 的矩阵,使用固定尺寸对性能非常有利,因为它允许Eigen避免动态内存分配和展开循环。在内部,固定大小的Eigen矩阵是一个普通数组,例如:

Matrix4f mymatrix

等价于

float mymatrix[16]

所以这没有没有运行成本。而动态矩阵需要在堆中分配空间,例如:

MatrixXf mymatrix(rows,columns)

等价于

float *mymatrix = new float[rows*columns] ,

除此之外,MatrixXf 对象还存储了其行数和列数。

使用固定大小的限制就是要求在编译的时候就知道大小,而且对于大矩阵,例如大于32的矩阵,使用固定大小的矩阵带来的优势可以忽略不计。更糟糕的是,试图在函数内部使用固定大小的矩阵创建一个非常大的矩阵可能会导致栈溢出,因为Eigen会尝试将数组自动分配为局部变量,而这通常是在栈上完成的。最后,根据情况,当使用动态大小时, Eigen也可以更积极地尝试向量化(使用 SIMD 指令),请参阅矢量化

可选模板参数

在开始的时候,我们提及Matrix类有6个模板参数,但是,目前只讨论了前三个,剩下的三个参数是可供选择的。下面是完整的模板参数:

Matrix<typename Scalar,
       int RowsAtCompileTime,
       int ColsAtCompileTime,
       int Options = 0,
       int MaxRowsAtCompileTime = RowsAtCompileTime,
       int MaxColsAtCompileTime = ColsAtCompileTime>
  • Options是一个位域。这里以RowMajor为例,它指定了矩阵按照行优先进行存储。默认情况下,存储顺序是按照列存储的。例如 Matrix<float, 3, 3, RowMajor> 是指一个行优先的3*3矩阵。

    Options 是一个枚举,具体见 Eigen::StorageOptions

  • MaxRowsAtCompileTimeMaxColsAtCompileTime 是矩阵大小的上界,这让即使在编译时不知道矩阵的确切大小,但已知固定的上限,可以避免动态内存分配。例如,Matrix<float, Dynamic, Dynamic, 0, 3, 4>会在编译时使用一个大小为12float类型的数组,而不需要动态分配内存。

其他常用Matrix类型

Eigen 定义了以下 Matrix 类型:

类型 类型别名 示例
Matrix MatrixNt MatrixXi -> Matrix
Matrix MatrixXNt MatrixX3i -> Matrix
Matrix MatrixNXt Matrix4Xd -> Matrix
Matrix VectorNt Vector2f -> Matrix
Matrix RowVectorNt RowVector3d -> Matrix
  • N可以是2, 3, 4X(意思是Dynamic) 中的任何一个。
  • t可以是i(int)f(float)d(double)cf(complex<float>)cd(complex<double>) 中的任何一个。虽然这里只定义了五种类型,但并不意味只支持这五种。例如,还支持所有标准整数类型,请参阅标量类型
相关文章
|
存储 编译器
[Eigen中文文档] 深入了解 Eigen - 类层次结构
本页面介绍了Eigen类层次结构中 Core 类的设计及其相互关系。一般用户可能不需要关注这些细节,但对于高级用户和Eigen开发人员可能会有用。
308 0
|
C++
[Eigen中文文档] 按值将Eigen对象传递给函数
对于 Eigen,这一点更为重要:按值传递固定大小的可向量化 Eigen 对象不仅效率低下,而且可能是非法的或使程序崩溃! 原因是这些 Eigen 对象具有对齐修饰符,在按值传递时会不遵守这些修饰符。
179 0
|
存储 C语言 C++
|
存储 编译器
|
存储 算法 NoSQL
[Eigen中文文档] 稀疏矩阵操作
在许多应用中(例如,有限元方法),通常要处理非常大的矩阵,其中只有少数系数不为零。在这种情况下,可以通过使用仅存储非零系数的特殊表示来减少内存消耗并提高性能。这样的矩阵称为稀疏矩阵。
493 0
|
存储
[Eigen中文文档] 就地矩阵分解
从 Eigen 3.3 开始,LU、Cholesky 和 QR 分解可以就地操作,即直接在给定的输入矩阵内操作。当处理大矩阵时,或者当可用内存非常有限(嵌入式系统)时,此功能特别有用。
116 0
|
存储 索引
[Eigen中文文档] 扩展/自定义Eigen(三)
本页面针对非常高级的用户,他们不害怕处理一些Eigen的内部细节。在大多数情况下,可以通过使用自定义一元或二元函数避免使用自定义表达式,而极其复杂的矩阵操作可以通过零元函数(nullary-expressions)来实现,如前一页所述。 本页面通过示例介绍了如何在Eigen中实现新的轻量级表达式类型。它由三个部分组成:表达式类型本身、包含有关表达式编译时信息的特性类和评估器类,用于将表达式评估为矩阵。
151 1
|
存储
[Eigen中文文档] Reshape操作
从 Eigen3.4 开始,Eigen 发布了将矩阵或向量重塑为不同大小的便捷方法。所有的操作可以通过 DenseBase::reshaped(NRowsType,NColsType) 和 DenseBase::reshaped() 两个函数完成。这些函数并不直接改变原有的变量,而是返回一个重塑后的变量副本。
163 0
|
存储 编译器 调度
|
安全
[Eigen中文文档] 混叠
在 Eigen 中,混叠是指相同的矩阵(或数组或向量)出现在赋值操作符的左边和右边。如下表达式,mat = 2*mat 或者 mat = mat.transpose()。第一个表达式是没有问题的,但是第二个表达式,会出现不可预料的结果。这一节会解释什么是混叠,以及它的危害与处理方法。
129 0