Eigen 用法总结

总结 Eigen 的常见用法。

矩阵和向量基础(Matrix and Vector)

快速导航(Quick Reference Guide)

Dense Matrix:http://eigen.tuxfamily.org/dox/group__QuickRefPage.html

Sparse Matrix:http://eigen.tuxfamily.org/dox/group__SparseQuickRefPage.html

这两个链接概括了稠密矩阵(即普通常用的矩阵)和稀疏矩阵的几乎所有的常见操作。

常见函数

Eigen::Vector3d axis;
// 下面这几个函数也存在于普通矩阵中,其实是因为矩阵和向量都是来自 Eigen::Matrix<type, row, col> 类型。
axis.setRandom(); // 随机设置数值
axis.setZero(); // 全设为 0

从常数赋值一个矩阵

MatrixXd mat(3,2);
mat << 1.0 << 2.0 << 3.0 
    << 4.1 << 5.2 << 6.3; // 最常见的常数赋值,不过此时必须要像这样分成两行

MatrixXd mat2 = (Eigen::MatrixXd(3,2) << 1.0 << 2.0 << 3.0 
                 << 4.1 << 5.2 << 6.3).finished(); // 非要写成一行的话,结尾要用 finished() 函数

将矩阵或者向量的格式从 double 转换成 float

Eigen::MatrixXd d;                       // Matrix of doubles.
Eigen::MatrixXf f = d.cast<float>();   // Matrix of floats,注意最后的括号

Eigen::Map

Eigen::Map 的作用是将一个已有的 C 数组映射为一个 Eigen 的向量或者矩阵。它的优点是:

  • 可以使用 Eigen 向量和矩阵的各种操作函数;
  • 不会额外分配空间。即,它并非拷贝,而是依然使用已有数组的空间。

Eigen::Map 对象的构建是这样:

Map<Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime> >

如果用 Eigen 预定义的诸如 MatrixXf 之类的话就更简洁了。

Eigen::Map 的构造函数有很多种,不过都至少需要一个 C 数据指针作为输入,具体参见官方文档中 Eigen::Map 的定义

官方文档上的一个例子,将一个一维数组映射为矩阵:

int array[9];
for(int i = 0; i < 9; ++i) array[i] = i;
cout << Eigen::Map<Eigen::Matrix3i>(array) << endl;

注意转换后的矩阵默认依然是 column-major 列优先的(可以在构建时修改,具体参见参考文档)。例如上面的矩阵就是

0 3 6
1 4 7
2 5 8

旋转阵和欧式变换阵

Angle-Axis 形式的旋转阵:绕某个轴旋转一定角度

使用 angle-axis 表达方式的旋转矩阵形式更加容易。

// 绕 Z 轴转 45 度(默认方向是逆时针)
Eigen::AngleAxisd rotAngleAxis(M_PI/4, Eigen::Vector3d(0,0,1));
Eigen::Vector3d v(1,0,0);
// 如果只是要旋转一个已有的向量,可以直接使用 AngleAxis 变量左乘向量,无需转换成标准形式,因为它重载了乘法符号。
Eigen::Vector3d vecRotated = rotAngleAxis * v;
// 将 angle-axis 形式转换成标准的旋转阵
Eigen::Matrix3d rotMat = rotAngleAxis.toRotationMatrix(); 
// 当然,肯定可以用标准的旋转阵乘以一个向量,结果是一样的
vecRotated = rotMat * v;

// 也可以将普通的旋转矩阵转换成 AngleAxis 形式
Eigen::AngleAxisd rotAngleAxis2(rotMat); // 默认的构造函数就可以转换
// 这样可以获取角度和旋转轴
std::cout << rotAngleAxis2.angle() << std::endl << rotAngleAxis2.axis() << std::endl;

四元数(Quaternion)

  • Eigen 官方文档:https://eigen.tuxfamily.org/dox/classEigen_1_1QuaternionBase.html
  • 有关四元数的用法可以参考 OpenGL 的一个教程:第十七课:旋转

四元数的构建方式有多种。可以直接赋值 xyzw 这几个成员,也可以从欧拉角得到,或者从 angle-axis 表达式得到,当然也可以暴力的直接从标准的旋转阵直接转换得到。

/// 四元数的各种创建方法
///------------------------------------------------
// 直接给四元数四个成员赋值当然可以,不过这显然是用处最小的。
Quaternionf q(w, x, y, z); // 注意 Eigen 中它的参数顺序是 wxyz

// 已知欧拉角或者 Angle-Axis 形式来创建四元数。其实还是先将欧拉角转换成 Angle-Axis 形式再转换的
float roll = 1.5707f, pitch = 0, yaw = 0.707f;    
Quaternionf q1;
q1 = AngleAxisf(roll, Vector3f::UnitX())
    * AngleAxisf(pitch, Vector3f::UnitY())
    * AngleAxisf(yaw, Vector3f::UnitZ()); // 直接赋值就可以转换(Eigen 中多种格式之间都可以这样转换)
std::cout << q1.coeffs() << std::endl; // coeffs() 返回的是一个 Vector4f
AngleAxisf aa(roll, Vector3f::UnitX());
Quaternionf q2(aa); // 作为构造函数的参数也可以

// 已知旋转阵创建四元数
Matrix3f rot = Matrix3f::Identity();
Quaternion q3;
q3 = rot; // 赋值可以,不过必须要像这样分两行写,而不能写成一行 `Quaternion q2 = rot`
Quaternion q4(rot); // 作为构造函数的参数也可以。Eigen 中很多变量创建都是类似,不能在创建时直接赋值,但是作为参数传入构造函数可以

/// 四元数转换成其他形式
///------------------------------------------------
// 四元数转换回 Angle-Axis 形式
AngleAxisf aa1;
aa1 = q; // 赋值是可以的,不过必须要像这样分两行写,而不能写成一行 `AngleAxisf aa = q`。
AngleAxisf aa2(q); // 作为构造函数的参数是可以的

// 四元数转换成标准旋转阵
Matrix3f rot1 = q.toRotationMatrix();
// 从标准旋转阵中得到欧拉角。这里,0,1,2 依次对应绕 X,Y,Z 轴的旋转角度,即,rot1 = Rx * Ry * Rz。这几个值可以互换位置的,例如(2,1,0),得到的 angles 中的角度就表示 rot1 = Rz‘ * Ry’ * Rx'。注意,显然不同的顺序得到的旋转角是不同的。
Vector3f angles = rot1.eulerAngles(0, 1, 2); 

/// 四元数用法
///------------------------------------------------
// 旋转一个向量
Vector3f v(1.0f, 2.0f, 3.5f);
Vector3f v_rotated = q*v; // 注意数学上是 qvq^{-1},Eigen 重载了 * 运算符

欧式变换矩阵(Enclidean Transformation Matrix)

欧式变换阵即包含了旋转和平移的 4x4 的矩阵。

Eigen::Isometry3d T = Eigen::Isometry3d::Identity(); // 虽然称为 3d ,实质上是 4*4 的矩阵
T.rotate ( rotationAxis ); // 按照 angle-axis 记录的旋转阵进行旋转

T.pretranslate ( Eigen::Vector3d ( 1,3,4 ) ); // 在变换阵的”左边“添加一个平移
// 下面是在变换阵的”右边“增加一个平移。
// 一定注意在左边和右边增加添加平移的区别。左边增加平移的话,最终的 4x4 变换阵的右上角就是平移向量 t(即不会受到旋转的影响)。而右边增加平移则不同了,它会受到旋转的影响。此时的变换阵右上角的位置是 Rt 而不是 t,相当于是平移向量也被旋转了。
T.translate( Eigen::Vector3d ( 1,3,4 ) ); 

cout << "Transform matrix = \n" << T.matrix() << endl; // matrix() 函数获取 4x4 的标准形式矩阵
// 可以直接将 T 应用于变换一个三维向量
Eigen::Vector3d vTransformed = T * v; // 相当于 R*v+t,注意这里的 v 是 Vector3d 即可,无需齐次

数组(Array)

https://eigen.tuxfamily.org/dox/group__TutorialArrayClass.html

Eigen 中还定义了一个普通数组(Array)的类。数组和矩阵的区别是,矩阵就是通常意义下的线性代数中的矩阵,常用来进行各种线性运算,例如加减乘等,这些运算都有一套特殊的规则的,例如矩阵乘法。而数组就是普通的一组数据,它通常用于进行对应项(coefficient-wise)操作,例如两个数组的对应项分别相乘或者相除,等。

数组类的名字是 ArrayXf,ArrayXd(一维数组)和 ArrayXXf,ArrayXXd(二维数组)。当然也有提前定义的诸如 Array2f,Array33d等标注了数组的维度。

实际上,如果你已经定义了一个矩阵,然后想进行类似数组的 coefficient-wise 操作时,其实无需显示的定义 Array 变量,因为 Eigen 已经提供了 Array 转 Matrix 的显式转换 matrix(),以及 Matrix 转 Array 的转换函数 array()。另外,通过赋值也可以进行两种类型的直接转换。例如:

  MatrixXf m(2,2);
  MatrixXf n(2,2);
  MatrixXf result(2,2);
  m << 1,2,
       3,4;
  n << 5,6,
       7,8;
  result = m * n; // 注意这是矩阵相乘,不是对应项相乘
  result = m.array() * n.array(); // 相乘结果还是 Array,通过赋值转换成 Matrix
  result = m.cwiseProduct(n); // 这个是 Matrix 类中定义的对应项相乘的函数,结果和上面相同
  result = m.array() + 4; // 转换成 Array 后再在每个项加上 4
  result = (m.array() + 4).matrix() * m; // 先转换成 Array 操作后再转换回矩阵操作

Matrix 类中也定义了一些函数用于对应项操作,例如上面例子中的 cwiseProduct()用于计算两个矩阵对应项相乘。不过这种函数很少。而使用 Array 则可以进行几乎所有常见的对应项操作,例如它还有类似 sqrt(), abs(), max(), min()等多种数学操作函数。因此,如果想对 Matrix 做对应项操作,最好还是使用 array() 函数转换成 Array 然后操作,最后再用赋值或者 matrix() 函数转换回来。

Search

    Table of Contents