Eigen3 教程基础篇(一)

参考

Eigen3 主页,Eigen3 官网教程

矩阵的本质,通过多种矩阵的应用去感受矩阵本质

3Blue1Brown 的线性代数,用可视化方法来表现线性代数的特性,强推

如何理解复数和虚数,有动画方便理解复数的意义

相关文章

Eigen3 教程基础篇(二)

前言

本文主要内容基于 Eigen3 官网 3.3.9 版本教程,对相关数学知识和特殊需要注意的知识点进行简单的拓展。需要学习不同版本教程可以在官网主页左侧的 Documentation 选择版本。

本地测试代码的 Eigen3 版本是 3.3.7,环境是 ubuntu20.04。

linux 中可以通过下面指令查看本地 Eigen3 版本:

pkg-config --modversion eigen3

Eigen3 基础数据类型和操作

矩阵的核心是向量和线性代数,而线性代数的核心是向量加法和标量乘法。

Matrix 矩阵类

Matrix 其实是个类模板,有 6 个参数,前 3 个强制参数,后 3 个默认参数。

Eigen::Matrix< Scalar_, Rows_, Cols_, Options_, MaxRows_, MaxCols_ >
  • Scalar_ 数据类型,可以是 double,float,int,或者复数 std::complex<float> 等。

  • Rows_ 矩阵行数,或者动态行数 Dynamic。动态矩阵在编译期间矩阵尺寸可以是未知的。

  • Cols_ 矩阵列数,或者动态列数 Dynamic。动态矩阵在编译期间矩阵尺寸可以是未知的。

  • Options_ 配置,有存储顺序配置 storage order:行优先 RowMajor 和 列优先 ColMajor。默认是列优先存储。另外有对齐配置:自动对齐 AutoAlign 和不对齐 DontAlign。默认是自动对齐。

  • MaxRows_ 最大行数,默认是 Rows_。当 Rows_ 是 Dynamic 时候,用于限制动态矩阵最大行数。

  • MaxCols_ 最大列数,默认是 Cols_。当 Cols_ 是 Dynamic 时候,用于限制动态矩阵最大列数。

存储配置

矩阵有行列,是维数的,但是内存地址是一维连续的。存储顺序就是指矩阵系数在内存上是一行一行存储,还是一列一列存储。

void TestStorageOrder()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  Eigen::Matrix<int, 3, 3, Eigen::RowMajor> rowmajorm;
  rowmajorm << 0, 1, 2,
               3, 4, 5,
               6, 7, 8;
  ROS_INFO_STREAM("矩阵:" << std::endl << rowmajorm);

  int* rowmajorm_data = rowmajorm.data(); // 获取矩阵系数数据存储地址
  ROS_INFO_STREAM("行优先存储在内存上数据分布:");
  for (size_t i = 0; i < rowmajorm.size(); i++) {
    ROS_INFO_STREAM("index[" << i << "]:" << rowmajorm_data[i]);
  }

  Eigen::Matrix<int, 3, 3, Eigen::ColMajor> colmajorm;
  colmajorm = rowmajorm;
  int* colmajorm_data = colmajorm.data();
  ROS_INFO_STREAM("列优先存储在内存上数据分布:");
  for (size_t i = 0; i < colmajorm.size(); i++) {
    ROS_INFO_STREAM("index[" << i << "]:" << colmajorm_data[i]);
  }
}

对齐配置

对齐配置一般都选择 AutoAlign 自动对齐,只有内存受限或者特定硬件情况才需要修改为不对齐。另外 Eigen3 的部分功能要求内存对齐,例如动态矩阵,如果没有对齐会导致崩溃。

void TestAlign()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  Eigen::Matrix<int, 3, 3, Eigen::AutoAlign> autoalignm;
  autoalignm.Random(3, 3);
  ROS_INFO_STREAM("自动对齐矩阵:" << std::endl << autoalignm);

  Eigen::Matrix<int, 3, 3, Eigen::DontAlign> dontalignm;
  dontalignm = autoalignm;
  ROS_INFO_STREAM("不对齐矩阵:" << std::endl << dontalignm);
}

固定矩阵和动态矩阵

一般对于小于 16 个系数的矩阵,用固定矩阵效率更高。

对于不确定大小或系数大于 16 的矩阵,用动态矩阵效率更高。

矩阵的系数访问只能通过 () 括号表达式访问,矩阵赋值可以用逗号表达式赋值具体系数,动态矩阵能够自动适应右值矩阵的大小。

void TestDynamic()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> dynamicm(2, 3); // 行列初始化
  dynamicm.Random(2, 3);
  ROS_INFO_STREAM("动态矩阵, 随机赋值:" << std::endl << dynamicm);

  dynamicm << 1.2, 2.3, 3.4, // 逗号表达式赋值
              4.5, 5.6, 6.7;
  ROS_INFO_STREAM("动态矩阵,赋值:" << std::endl << dynamicm);

  ROS_INFO_STREAM("动态矩阵,系数访问[0,2]:" << dynamicm(0,2)); // 矩阵系数访问方式
  dynamicm(0,2) = 233;
  ROS_INFO_STREAM("动态矩阵,系数访问[0,2]:" << dynamicm(0,2));

  Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> dynamicm44(4, 4);
  ROS_INFO_STREAM("动态矩阵4x4,初始化:" << std::endl << dynamicm44);
  dynamicm44 = dynamicm; // 4x4 矩阵自动适应 2x3 矩阵
  ROS_INFO_STREAM("动态矩阵4x4,自适应大小:" << std::endl << dynamicm44);
}

注意,矩阵行列初始化的法并不会默认把系数初始化为 0!

矩阵系数访问不能通过 [] 索引来访问。

调整矩阵大小

resize 只能调整动态矩阵大小,如果大小无变化,那么没有任何操作。如果尺寸发生改变,原有数据会清除,但不是清零!

如果调整大小的时候想要保留原有的数据,请使用 conservativeResize()。

void TestResize()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> m3x3(3, 3); // 行列初始化
  m3x3.Random(3, 3);
  ROS_INFO_STREAM("矩阵3x3, 随机赋值:" << std::endl << m3x3);

  m3x3.resize(5, 5);
  ROS_INFO_STREAM("调整矩阵大小5x5:" << std::endl << m3x3);

  m3x3.conservativeResize(2, 2);
  ROS_INFO_STREAM("调整矩阵大小2x2:" << std::endl << m3x3);
}

预定义好的矩阵模类

MatrixNt 格式:N*N 方块矩阵,例如 MatrixXiMatrix<int, Dynamic, Dynamic>

MatrixXNt 格式:X*N 行动态列固定的矩阵,例如 MatrixX3iMatrix<int, Dynamic, 3>

MatrixNXt 格式:N*X 行固定列动态的矩阵,例如 Matrix4XdMatrix<double, 4, Dynamic>

VectorNt 格式:N 大小的列向量,例如 Vector2fMatrix<float, 2, 1>

RowVectorNt 格式:N 大小的行向量,例如 RowVector3d Matrix<double, 1, 3>

维数大小 N ∈ {2,3,4,X}。

数据类型 t ∈ {i,f,d,cf,cd}。分别是 int ,float,double,complex<float>(复数),complex<double>(复数)。

void TestDefaultTemplate()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  // Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic>
  Eigen::MatrixXi imx(3, 3);
  ROS_INFO_STREAM("int 动态矩阵 3x3:" << std::endl << imx);

  // Eigen::Matrix<float, 2, 2>
  Eigen::Matrix2f fm2x2;
  ROS_INFO_STREAM("float 固定矩阵 2x2:" << std::endl << fm2x2);

  // Eigen::Matrix<double, Eigen::Dynamic, 2>
  Eigen::MatrixX2d dmx2(3, 2);
  ROS_INFO_STREAM("double 动态行,2列矩阵 3x2:" << std::endl << dmx2);

  // Eigen::Matrix<double, 1, Eigen::Dynamic>
  Eigen::Matrix2Xd dm2x(2, 3);
  ROS_INFO_STREAM("double 2行,动态列矩阵 2x3:" << std::endl << dm2x);
}

200

矩阵运算

  • 矩阵加减运算要求操作数是相同大小的矩阵。

二元运算:a+ba-b;一元运算:-a;复合运算:a+=ba-=b

矩阵可以看做是多组向量,矩阵加法可以看做是多组向量分别计算。

A = \left[ \begin{matrix} 1 & -1 \\ 2 & 2 \\ \end{matrix} \right], B = \left[ \begin{matrix} 2 & -2 \\ 0 & -2 \\ \end{matrix} \right], C=A+B= \left[ \begin{matrix} 3 & -3 \\ 2 & 0 \\ \end{matrix} \right]

矩阵 A 看做是向量u [1, 2] 和向量v [-1, 2],矩阵 B 看做是向量w [2, 0] 和向量 a [-2, -2],A+B 看做是 u+w 和 v+a,分别得到向量 b [3, 2] 和向量 c [-3, 0]。如下图:

  • 标量乘除运算。

二元运算:矩阵m*标量x标量x*矩阵m矩阵m/标量x;复合运算:矩阵m *= 标量x矩阵m /= 标量x

矩阵的标量乘法可以看做是多组向量的标量乘法计算。

A = \left[ \begin{matrix} 3 & -2 \\ 1 & 1 \\ \end{matrix} \right], B=1.5*A= \left[ \begin{matrix} 3 & -3 \\ 2 & 0 \\ \end{matrix} \right]

矩阵 A 看做是向量d [3, 1] 和 e [-2, 1],1.5*A 看做 d 和 e 分别乘 1.5 得到向量f [4.5, 1.5] 和向量g [-3, 1.5]。如下图:

void TestBaseOperation()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  Eigen::Matrix3i m0;
  m0 << 2, 1, 3,
        6, 8, 0,
        0, 9, 12;
  ROS_INFO_STREAM("3x3 m0:" << std::endl << m0);

  Eigen::Matrix3i m1;
  m1 << 6, 1, 13,
        2, 0, 4,
        9, 11, 7;
  ROS_INFO_STREAM("3x3 m1:" << std::endl << m1);

  ROS_INFO_STREAM("m0+m1=" << std::endl << m0+m1);
  ROS_INFO_STREAM("3*m0=" << std::endl << 3*m0);
  ROS_INFO_STREAM("m1/2=" << std::endl << m1/2);
}

表达式模板

运算并不会在 + - * / 时候计算,而是在最后返回赋值的时候。使用 eigen 进行复杂计算时候会执行更多优化操作。

VectorXf a(50), b(50), c(50), d(50);
a = 3*b + 4*c + 5*d;

实际计算效果如下:

for(int i = 0; i < 50; ++i)
  a[i] = 3*b[i] + 4*c[i] + 5*d[i];

  • 转置

矩阵转置是一个将矩阵的行和列互换的操作。如果原始矩阵是 A,其转置矩阵通常表示为A^T。

转置操作 transpose() 并不会实际执行,而是返回一个代理对象。在写入转置结果的才会计算转置的结果

void TestTranspose()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  Eigen::Matrix3i m;
  m << 2, 1, 3,
       6, 8, 0,
       0, 9, 3;
  ROS_INFO_STREAM("3x3 m:" << std::endl << m);

  Eigen::Matrix3i tm = m.transpose();
  ROS_INFO_STREAM("after transpose, tm:" << std::endl << tm);

  m.transposeInPlace();
  ROS_INFO_STREAM("m.transposeInPlace():" << std::endl << m);

}

不要执行m=m.transpose()操作!

  • 共轭,转置共轭

矩阵共轭(Conjugate of a matrix)通常指的是复数矩阵中的元素被各自的共轭复数所替换。对于一个复数矩阵 A,其共轭矩阵 A^* 是通过将 A 中的每个元素替换为其共轭复数。

复数用 a + bi 表达,其中 a 是实部,b 是虚部,i 是虚数单位,定义为 i^2=-1 

复数的出现完善数系,弥补了一些计算的空白。例如:

\sqrt{-4}=2iln(-5)=ln(5e^{i\pi})=ln5+lne^{i\pi}=ln5+i\pi 等等。

复数使得我们能够观察到系统在虚数部分的运作,进而推断实数部分的结果。

打个比如,一个系统仅存在一维数据(这个系统的实部只有1维信息)。我们很难直接观察并预测系统在实数部分的运行规律。如下图。

系统仅存在于一维世界

但如果计算扩展出一个新的维度,得到第二维的信息,如下图。我们能够有更充分的信息去分析,预测系统。

扩展出新的观察维度,橙色是虚部信息,蓝色是实部信息

共轭运算 conjugate(),转置共轭运算 adjoint()

void TestAdjoint()
{
  setlocale(LC_ALL, "zh_CN.UTF-8"); // 支持 ros 打印中文

  Eigen::Matrix<std::complex<float>, 2, 2> m;
  m << std::complex<float>(3, 2), std::complex<float>(5, 1),
       std::complex<float>(7, 3), std::complex<float>(4, 2);
  ROS_INFO_STREAM("2x2 complex m:" << std::endl << m);

  Eigen::Matrix<std::complex<float>, 2, 2> cm = m.conjugate();
  ROS_INFO_STREAM("after conjugate, cm:" << std::endl << cm);

  Eigen::Matrix<std::complex<float>, 2, 2> am = m.adjoint();
  ROS_INFO_STREAM("after adjoint, am:" << std::endl << am);

  m.adjointInPlace();
  ROS_INFO_STREAM("m.adjointInPlace(), m:" << std::endl << m);
}

同样不要执行m=m.adjoint()的操作。

  • 矩阵乘法

只有当第一个矩阵的列数与第二个矩阵的行数相等时,这两个矩阵才能相乘。

二元运算:a*b;复合运算:a*=b

矩阵乘法是一种变换,A*B=C表示矩阵B经过变换矩阵A后,得到矩阵C。

从几何上看,矩阵乘法可以看做是矩阵 B 的向量组,在以变换矩阵 A 向量组作为基向量的坐标系中的表示。这么说比较绕,看看下面的图解。

A = \left[ \begin{matrix} 2 & -1 \\ -1 & 3 \\ \end{matrix} \right], B= \left[ \begin{matrix} 1 & -2 \\ 1 & -1 \\ \end{matrix} \right], C=A*B= \left[ \begin{matrix} 1 & -3 \\ 2 & -1 \\ \end{matrix} \right]

矩阵 B 可以看做向量 u [1, 1] 和向量 v [-2, -1]

变换矩阵 A 可以看做由向量 i [2, -1]和向量 j [-1, 3] 构成的基向量坐标系

新的基向量坐标系用红蓝虚线表示:

以 i 和 j 作为基向量,矩阵 B 的向量组表示为:向量 uu [1, 1] 和向量 vv [-2, -1],下图紫色向量 :

而向量 uu 和 vv 用标准基向量表示则是 [1, 2] 和 [-3, -1],这正是矩阵 B 向量组经过变换矩阵 A 变换后的向量组。

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值