文章目录
Eigen
- Eigen是可以用来进行线性代数、矩阵、向量操作等运算的C++库,它里面包含了很多算法。它的License是MPL2。它支持多平台。
- Eigen采用源码的方式提供给用户使用,在使用时只需要包含Eigen的头文件即可进行使用。之所以采用这种方式,是因为Eigen采用模板方式实现,由于模板函数不支持分离编译,所以只能提供源码而不是动态库的方式供用户使用。
- 头文件
- Eigen 部分
#include <Eigen/Core>
- 稠密矩阵的代数运算(逆,特征值等)
#include <Eigen/Dense>
- Eigen 部分
Matrix
基础:
- 矩阵的定义:Eigen中关于矩阵类的模板函数中,共有六个模板参数,常用的只有前三个。其前三个参数分别表示矩阵元素的类型、行数和列数。
- Eigen中所有向量和矩阵都是Eigen::Matrix,它是一个模板类。它的前三个参数为:数据类型,行,列。
- 如:
Eigen::Matrix<float, 2, 3> matrix_23;
- 例如 Vector3d 实质上是 Eigen::Matrix<double, 3, 1>,即三维向量。
- 如:
- 如果不确定矩阵大小,可以使用动态大小的矩阵,矩阵定义时可以使用Dynamic来表示矩阵的行列数为未知,如:
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic > matrix_dynamic;
- Eigen中无论是矩阵、数组、向量,无论是静态矩阵还是动态矩阵都提供默认构造函数,也就是定义这些数据结构时都可以不用提供任何参数,其大小均由运行时来确定。
- 矩阵的构造函数中只提供行列数、元素类型的构造参数,而不提供元素值的构造,对于比较小的、固定长度的向量提供初始化元素的定义。
- 例如:
Eigen::Matrix3d matrix_33=Eigen::Matrix3d::Zero();
- 矩阵类型:Eigen中的矩阵类型一般都是用类似MatrixXXX来表示,可以根据该名字来判断其数据类型,比如”d”表示double类型,”f”表示float类型,”i”表示整数,”c”表示复数;Matrix2f,表示的是一个2*2维的,其每个元素都是float类型。
- 数据存储:Matrix创建的矩阵默认是按列存储,Eigen在处理按列存储的矩阵时会更加高效。如果想修改可以在创建矩阵的时候加入参数,如:
Matrix<int,3, 4, ColMajor> Acolmajor;
Matrix<int,3, 4, RowMajor> Arowmajor;
- 动态矩阵和静态矩阵:动态矩阵是指其大小在运行时确定,静态矩阵是指其大小在编译时确定。
- MatrixXd:表示任意大小的元素类型为double的矩阵变量,其大小只有在运行时被赋值之后才能知道。
- Matrix3d:表示元素类型为double大小为3*3的矩阵变量,其大小在编译时就知道。
- 矩阵元素的访问:在矩阵的访问中,行索引总是作为第一个参数,Eigen中矩阵、数组、向量的下标都是从0开始。矩阵元素的访问可以通过”()”操作符完成。例如:
m(2, 3)
既是获取矩阵m的第2行第3列元素。- 针对向量还提供”[]”操作符,注意矩阵则不可如此使用。
- 设置矩阵的元素:在Eigen中重载了”<<”操作符,通过该操作符即可以一个一个元素的进行赋值,也可以一块一块的赋值。另外也可以使用下标进行赋值。如:matrix_23 << 1, 2, 3, 4, 5, 6;
- 重置矩阵大小:当前矩阵的行数、列数、大小可以通过rows()、cols()和size()来获取,对于动态矩阵可以通过resize()函数来动态修改矩阵的大小。注意:
- (1)、固定大小的矩阵是不能使用resize()来修改矩阵的大小;
- (2)、resize()函数会析构掉原来的数据,因此调用resize()函数之后将不能保证元素的值不改变;
- (3)、使用”=”操作符操作动态矩阵时,如果左右两边的矩阵大小不等,则左边的动态矩阵的大小会被修改为右边的大小。
- 如何选择动态矩阵和静态矩阵:对于小矩阵(一般大小小于16)使用固定大小的静态矩阵,它可以带来比较高的效率;对于大矩阵(一般大小大于32)建议使用动态矩阵。注意:如果特别大的矩阵使用了固定大小的静态矩阵则可能会造成栈溢出的问题。
- 矩阵块的操作,有两种使用方法:
matrix.block(i,j, p, q)
: 表示返回从矩阵(i, j)
开始,每行取p
个元素,每列取q
个元素所组成的临时新矩阵对象,原矩阵的元素不变;matrix.block<p,q>(i, j)
: 其中<p, q>
可理解为一个p
行q
列的子矩阵,该定义表示从原矩阵中第(i, j)
开始,获取一个p行q列的子矩阵,返回该子矩阵组成的临时矩阵对象,原矩阵的元素不变;
- 向量块的操作:
- 获取向量的前n个元素:
vector.head(n);
- 获取向量尾部的n个元素:
vector.tail(n);
- 获取从向量的第i个元素开始的n个元素:
vector.segment(i,n);
- 获取向量的前n个元素:
算术运算
- Eigen中算术运算重载了C++的
+ - ×
- 矩阵的运算:提供加减的一元操作符
”-”、+=、-=
;二元操作符+/-
,表示两矩阵相加(矩阵中对应元素相加/减,返回一个临时矩阵);- 一元操作符-表示对矩阵取负(矩阵中对应元素取负,返回一个临时矩阵);
- 组合操作法+=或者-=表示(对应每个元素都做相应操作);
- 矩阵还提供与标量(单一数字)的乘除操作,表示每个元素都与该标量进行乘除操作;
- 求矩阵的转置、共轭矩阵、伴随矩阵:可以通过成员函数
transpose()、conjugate()、adjoint()
来完成。注意:这些函数返回操作后的结果,而不会对原矩阵的元素进行直接操作,如果要让原矩阵进行转换,则需要使用响应的InPlace函数,如transpoceInPlace()等; - 矩阵相乘、矩阵向量相乘:使用操作符*,共有
*
和*=
两种操作符;
矩阵运算
- 随机矩阵:
matrix_33 = Eigen::Matrix3d::Random();
- 转置:
matrix_33.transpose()
- 各个元素之和:
matrix_33.sum()
- 迹:
matrix_33.trace()
- 数乘:
10*matrix_33
- 逆:
matrix_33.inverse()
- 行列式:
matrix_33.determinant()
特征值+解方程
- 特征值+特征向量求解:
// 实对称矩阵可以保证对角化成功 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigen_solver ( matrix_33.transpose()*matrix_33 ); cout << "Eigen values = \n" << eigen_solver.eigenvalues() << endl; cout << "Eigen vectors = \n" << eigen_solver.eigenvectors() << endl;
解方程:
- 我们求解 matrix_NN * x = v_Nd 这个方程,N的大小在前边的宏里定义,它由随机数生成。
- 方法一:直接求逆自然是最直接的,但是求逆运算量大
clock_t time_stt = clock(); // 计时 Eigen::Matrix<double,MATRIX_SIZE,1> x = matrix_NN.inverse()*v_Nd; double cost_time = 1000* (clock() - time_stt)/(double)CLOCKS_PER_SEC; cout <<"time use in normal inverse is " << cost_time << "ms"<< endl;
- 方法二:
time_stt = clock(); x = matrix_NN.colPivHouseholderQr().solve(v_Nd); double cost_time = 1000* (clock() - time_stt)/(double)CLOCKS_PER_SEC; cout <<"time use in Qr decomposition is " << cost_time << "ms"<< endl;
矩阵容器 aligned_allocator
问题:
- 如果STL容器中的元素是Eigen库数据结构,如这里定义一个vector容器,元素是Matrix4d
std::vector<Eigen::Matrix4d>
- 这是错误的,编译不会出错,只有在运行的时候出错。
解决:
- 只需定义为如下:
std::vector<Eigen::Matrix4d,Eigen::aligned_allocator<Eigen::Matrix4d>>;
原因:
- 其实上述的这段代码才是标准的定义容器方法,只是我们一般情况下定义容器的元素都是C++中的类型,所以可以省略。
- 在C++11标准中,aligned_allocator管理C++中的各种数据类型的内存方法是一样的,可以不需要着重写出来。
- 但是在Eigen管理内存和C++11中的方法是不一样的,所以需要单独强调元素的内存分配和管理。
norm,normlize,normalized
norm()
- 对于Vector,norm返回的是向量的二范数,即: ∣ ∣ x ∣ ∣ 2 = ∑ i = 1 N x i 2 {||x||_2 = \sqrt{\sum_{i=1}^N x_i^2}} ∣∣x∣∣2=∑i=1Nxi2
- 对于 Matrix,norm返回的是矩阵的 弗罗贝尼乌斯范数(
Frobenius Norm
),即:
∣ ∣ A ∣ ∣ F = ∑ i = 1 m ∑ i = j n ∣ a i j ∣ 2 {||A||_F = \sqrt{\sum_{i=1}^m\sum_{i=j}^n |a_{ij}|^2}} ∣∣A∣∣F=∑i=1m∑i=jn∣aij∣2
normalize()
- normalize()其实就是把自身的各元素除以它的范数。返回值为void。
normalized()
- 而normalized()与normalize()类似,只不过normalize()是对自身上做修改,而normalized()返回的是一个新的Vector/Matrix,并不改变原有的矩阵。
Quaternion
核心构造
1、默认构造函数使四元数未初始化
Eigen::Quaternion< Scalar_, Options_ >::Quaternion ()
2、构造并初始化四元素: w + x i ⃗ + y j ⃗ + z k ⃗ {w+x\vec i+y\vec j+z\vec k} w+xi+yj+zk。
Eigen::Quaternion< Scalar_, Options_ >::Quaternion (const Scalar & w,const Scalar & x,const Scalar & y,const Scalar & z )
- 注意参数的顺序:首先是实部的
w
系数,而在内部系统按以下顺序存储:[x, y, z, w]
3、一个4D向量构造
Eigen::Quaternion< Scalar_, Options_ >::Quaternion ( const MatrixBase< Derived > & other)
- 一个 4D 向量表达式,以 [x, y, z, w] 的顺序表示四元数系数。
4、从角轴 aa 构造并初始化一个四元数
Eigen::Quaternion< Scalar_, Options_ >::Quaternion ( const AngleAxisType & aa)
成员函数
FromTwoVectors
-
函数:Eigen::Quaterniond::FromTwoVectors(v1,v2)
-
返回: R ∗ V 1 = V 2 R*V_1 = V_2 R∗V1=V2
-
Quaternion< Scalar, Options > Eigen::Quaternion< Scalar_, Options_ >::FromTwoVectors ( const MatrixBase< Derived1 > & a,const MatrixBase< Derived2 > & b)
-
返回一个四元数,表示两个任意向量 a 和 b 之间的旋转。换句话说,构建的旋转表示将方向 a 的线发送到方向 b 的线的旋转,两条线都通过原点。
-
请注意,两个输入向量不必进行归一化,也不必具有相同的范数。
UnitRandom
Quaternion< Scalar, Options > Eigen::Quaternion< Scalar, Options >::UnitRandom
- SO (3) 上的随机单位定律分布
相关旋转
对旋转向量(轴角)赋值的三大种方法
-
0、下面三个变量作为下面演示的中间变量
AngleAxisd t_V(M_PI / 4, Vector3d(0, 0, 1)); Matrix3d t_R = t_V.matrix(); Quaterniond t_Q(t_V)
-
1、使用旋转的角度和旋转轴向量(此向量为单位向量)来初始化角轴
AngleAxisd V1(M_PI / 4, Vector3d(0, 0, 1))
- 以(0,0,1)为旋转轴,旋转45度
-
2、使用旋转矩阵转旋转向量的方式
- 2.1 使用旋转向量的fromRotationMatrix()函数来对旋转向量赋值(注意此方法为旋转向量独有,四元数没有)
AngleAxisd V2; V2.fromRotationMatrix(t_R); cout << "Rotation_vector2" << endl << V2.matrix() << endl;
- 2.2 直接使用旋转矩阵来对旋转向量赋值
AngleAxisd V3; V3 = t_R; cout << "Rotation_vector3" << endl << V3.matrix() << endl;
- 2.3 使用旋转矩阵来对旋转向量进行初始化
AngleAxisd V4(t_R); cout << "Rotation_vector4" << endl << V4.matrix() << endl;
- 2.1 使用旋转向量的fromRotationMatrix()函数来对旋转向量赋值(注意此方法为旋转向量独有,四元数没有)
-
3、使用四元数来对旋转向量进行赋值
- 3.1 直接使用四元数来对旋转向量赋值
AngleAxisd V5; V5 = t_Q
- 3.2 使用四元数来对旋转向量进行初始化
AngleAxisd V6(t_Q);
- 3.1 直接使用四元数来对旋转向量赋值
对四元数赋值的三大种方法(注意Eigen库中的四元数前三维是虚部,最后一维是实部)
- 第一种输出四元数的方式
cout << Q1.coeffs() << endl;
- 1、使用旋转的角度和旋转轴向量(此向量为单位向量)来初始化四元数
即使用:q=[cos(A/2),n_x*sin(A/2),n_y*sin(A/2),n_z*sin(A/2)] // 例如 以(0,0,1)为旋转轴,旋转45度: A =M_PI / 4; Quaterniond Q1(cos((A)/2),0*sin((A)/2),0*sin((A)/2),1*sin(A)/2));
- 2、使用旋转矩阵转四元數的方式
- 直接使用旋转矩阵来对旋转向量赋值
Quaterniond Q2; Q2 = t_R;
- 使用旋转矩阵来对四元數进行初始化
Quaterniond Q3(t_R);
- 直接使用旋转矩阵来对旋转向量赋值
- 3、使用旋转向量对四元数来进行赋值
- 直接使用旋转向量对四元数来赋值
Quaterniond Q4; Q4 = t_V;
- 使用旋转向量来对四元数进行初始化
Quaterniond Q5(t_V);
- 直接使用旋转向量对四元数来赋值
对旋转矩阵赋值的三大种方法
- 1、使用旋转矩阵的函数来初始化旋转矩阵
Matrix3d R1=Matrix3d::Identity();
- 2、使用旋转向量转旋转矩阵来对旋转矩阵赋值
- 使用旋转向量的成员函数matrix()来对旋转矩阵赋值
Matrix3d R2; R2 = t_V.matrix();
- 使用旋转向量的成员函数toRotationMatrix()来对旋转矩阵赋值
Matrix3d R3; R3 = t_V.toRotationMatrix();
- 使用旋转向量的成员函数matrix()来对旋转矩阵赋值
- 3、 使用四元数转旋转矩阵来对旋转矩阵赋值
- 使用四元数的成员函数matrix()来对旋转矩阵赋值
Matrix3d R4; R4 = t_Q.matrix();
- 使用四元数的成员函数toRotationMatrix()来对旋转矩阵赋值
Matrix3d R5; R5 = t_Q.toRotationMatrix();
- 使用四元数的成员函数matrix()来对旋转矩阵赋值