【Eigen教程02】深入Eigen矩阵引擎:模板参数、内存布局与基础操作指南
- 一、eigen核心模板类
- 二、完整类型体系
- [三、固定大小 vs 动态大小](#三、固定大小 vs 动态大小)
- 四、基础操作
-
- a) 创建和初始化 创建和初始化)
- b) 访问元素 (operator(), operator[]) 访问元素 (operator(), operator[]))
- c) 基本算术运算 基本算术运算)
- d) 大小和类型信息 大小和类型信息)
- e) 转置、共轭、伴随 转置、共轭、伴随)
- f) 归约操作 归约操作)
- g) 分块操作 分块操作)
- [五、 重要注意事项](#五、 重要注意事项)
原创作者:郑同学的笔记
原文链接:https://zhengjunxue.blog.csdn.net/article/details/148478757
一、eigen核心模板类
所有类型均基于 Matrix 模板类:
cpp
Matrix<Scalar, RowsAtCompileTime, ColsAtCompileTime, Options>
- Scalar:元素类型(float, double, int, complex等)
- Rows/Cols:行/列数(Dynamic 表示动态大小)
- Options:位字段组合(存储顺序+对齐方式)
Options 详解
-
- 存储顺序 (Storage Order)
| 标志值 | 说明 | 内存布局示例 (2x3 矩阵) |
|---|---|---|
| ColMajor (默认) | 列优先 | [a11, a21, a12, a22, a13, a23] |
| RowMajor = 0x1 | 行优先 | [a11, a12, a13, a21, a22, a23] |
选择建议:
默认用 ColMajor(兼容BLAS/LAPACK)
与C/C++数组交互时可用 RowMajor
bash
Eigen 与 C/C++ 数组交互时为何推荐使用 RowMajor?
核心原因:内存布局匹配
C/C++ 多维数组采用 行优先存储(Row-Major Order):
cpp
int c_array[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
内存实际存储顺序:
1, 2, 3, 4, 5, 6(先存第一行,再存第二行)
-
- 内存对齐 (Alignment)
| 标志值 | 说明 | 适用场景 |
|---|---|---|
| AutoAlign (默认) | 自动对齐 | 固定尺寸矩阵自动启用SIMD优化 |
| DontAlign = 0x80 | 禁用对齐 | 特殊内存需求或嵌入式系统 |
- Options 组合示例
cpp
#include <iostream>
#include <Eigen/Eigen>
using namespace Eigen;
using namespace std;
int main() {
// 默认选项:列优先 + 自动对齐 (等价于 Options=0)
Matrix<float, 3, 3> mat1;
// 显式指定行优先 + 自动对齐
Matrix<double, Dynamic, Dynamic, RowMajor> mat2(100, 100);
// 列优先 + 禁用对齐 (0x0 | 0x80 = 0x80)
Matrix<int, 4, 4, ColMajor | DontAlign> mat3;
// 行优先 + 禁用对齐 (0x1 | 0x80 = 0x81)
Matrix<float, 1, Dynamic, RowMajor | DontAlign> vec;
cout << "Output signal length: " << endl;
return 0;
}
- 错误:static_assert failed: 'INVALID_MATRIX_TEMPLATE_PARAMETERS'
错误的代码
cpp
Matrix<float, Dynamic, 1, RowMajor | DontAlign> vec;
这个错误表明问题出在 Matrix<float, Dynamic, 1, RowMajor | DontAlign> vec; 的定义上。在 Eigen 库中,对于列向量(列数为1),指定 RowMajor 存储顺序是无效的,因为列向量的存储顺序只能是列优先(ColMajor)。
应该更正为
cpp
// 行优先 + 禁用对齐 (0x1 | 0x80 = 0x81)
Matrix<float, 1, Dynamic, RowMajor | DontAlign> vec;
- 下面是一个演示Eigen中行优先(Row-major)和列优先(Column-major)存储的完整示例代码
cpp
#include <iostream>
#include <Eigen/Eigen>
using namespace Eigen;
using namespace std;
int main() {
// 创建3x2矩阵
Eigen::MatrixXd matrix(3, 2);
matrix << 1, 2,
3, 4,
5, 6;
std::cout << "原始矩阵:\n" << matrix << "\n\n";
// 默认列优先存储 (内存布局: 1,3,5,2,4,6)
Eigen::MatrixXd col_major = matrix;
std::cout << "列优先存储 (默认):\n";
std::cout << "内存顺序: ";
for (int i = 0; i < col_major.size(); ++i) {
std::cout << *(col_major.data() + i) << " ";
}
std::cout << "\n\n";
// 行优先存储 (显式指定模板参数)
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> row_major = matrix;
std::cout << "行优先存储:\n";
std::cout << "内存顺序: ";
for (int i = 0; i < row_major.size(); ++i) {
std::cout << *(row_major.data() + i) << " ";
}
std::cout << "\n";
return 0;
}
输出
bash
原始矩阵:
1 2
3 4
5 6
列优先存储 (默认):
内存顺序: 1 3 5 2 4 6
行优先存储:
内存顺序: 1 2 3 4 5 6
二、完整类型体系
| 类型定义 | 等效模板 | 元素类型 | 大小 | 典型用途 |
|---|---|---|---|---|
| MatrixXd | Matrix<double,Dynamic,Dynamic> | double | 动态 | 通用矩阵 |
| Matrix4f | Matrix<float,4,4> | float | 4x4固定 | 3D变换矩阵 |
| Vector3d | Matrix<double,3,1> | double | 固定列向量 | 3D坐标 |
| RowVectorXi | Matrix<int,1,Dynamic> | int | 行动态 | 行向量 |
| ArrayXXcf | Array<complex,Dynamic,Dynamic> | 复数float | 动态数组 | 信号处理 |
| Quaternionf | 专用四元数类型 | float | 4元素 | 旋转表示 |
三、固定大小 vs 动态大小
| 特性 | 固定大小 (e.g., Matrix4f) | 动态大小 (e.g., MatrixXf) |
|---|---|---|
| 内存分配 | 栈空间 | 堆空间(默认16字节对齐) |
| 大小检查 | 编译时 | 运行时 |
| 性能 | 无分配开销,编译器完全优化 | 有分配开销,循环边界需运行时检查 |
| 最大尺寸 | 受栈大小限制 | 受系统内存限制 |
| 典型用途 | 小矩阵(<16x16),变换矩阵 | 大矩阵,用户定义尺寸 |
四、基础操作
a) 创建和初始化
cpp
#include <iostream>
#include <Eigen/Eigen>
using namespace Eigen;
using namespace std;
int main()
{
// 1. 默认构造函数 (元素未初始化!)
Vector3f v1; // 3x1 float 列向量 (元素是垃圾值)
Matrix2d m1; // 2x2 double 矩阵 (元素是垃圾值)
// 2. 逗号初始化 (非常重要且常用)
Vector3f v2; v2 << 1.0f, 2.0f, 3.0f; // 初始化列向量 [1, 2, 3]^T
RowVector3d rv; rv << 4.0, 5.0, 6.0; // 初始化行向量 [4, 5, 6]
Matrix3f m2; m2 << 1, 2, 3,
4, 5, 6,
7, 8, 9; // 初始化矩阵
// 3. 固定大小类型的构造函数初始化
Vector2d v3(1.5, 2.5); // 直接构造 [1.5, 2.5]^T
Vector4i v4(1, 2, 3, 4); // [1, 2, 3, 4]^T
// 4. 动态大小对象的构造 (必须指定大小)
VectorXd v5(5); // 创建大小为 5 的 double 列向量 (元素未初始化)
MatrixXf m3(2, 3); // 创建 2x3 的 float 矩阵 (元素未初始化)
// 5. 特殊值初始化
Matrix3d m4 = Matrix3d::Zero(); // 全零矩阵
MatrixXd m5 = MatrixXd::Zero(4, 4); // 动态大小全零矩阵
Vector3f v6 = Vector3f::Ones(); // 全一向量 [1,1,1]^T
Matrix3f m6 = Matrix3f::Identity(); // 单位矩阵
MatrixXf m7 = MatrixXf::Random(3, 3); // 3x3 随机矩阵 (元素在 [-1,1])
Vector4i v7 = Vector4i::Constant(10); // [10,10,10,10]^T
//Matrix2f m8 = Matrix2f::LinSpaced(4, 0.0f, 1.0f).reshaped(2, 2); // 创建线性间隔向量并重塑 (更复杂)
// 修正:创建线性间隔向量并手动重塑为矩阵
VectorXf linSpacedVec = Eigen::VectorXf::LinSpaced(4, 0.0f, 1.0f); // 创建线性间隔向量
Matrix2f m8;
m8 << linSpacedVec(0), linSpacedVec(1), // 将线性间隔向量重塑为矩阵
linSpacedVec(2), linSpacedVec(3);
return 0;
}
b) 访问元素 (operator(), operator[])
-
矩阵: 使用 operator(row, col)。索引从 0 开始。
-
向量: 使用 operator 或 operator(index) (对于列向量 index 是行索引,对于行向量 index 是列索引)。推荐 operator[] 用于向量元素访问。
-
数组: 访问方式与矩阵/向量相同。
cpp
Vector3f v(1.0f, 2.0f, 3.0f);
float x = v[0]; // x = 1.0f (推荐方式)
float y = v(1); // y = 2.0f
Matrix2d m;
m(0, 0) = 3.0; m(0, 1) = 2.5;
m(1, 0) = -1.0; m(1, 1) = m(0, 0) + m(0, 1); // m(1,1) = 5.5
double elem = m(1, 0); // elem = -1.0
c) 基本算术运算
-
+, -: 矩阵/向量/数组的加减法(要求维度匹配)。
-
*: 含义取决于操作数:
- matrix * matrix: 矩阵乘法。
- matrix * vector: 矩阵-向量乘法。
- scalar * matrix/vector/array: 标量乘法。
- array * array: 逐元素乘法 (Hadamard product)。
-
/: 标量除法 (matrix/scalar, vector/scalar, array/scalar), 或数组的逐元素除法 (array / array)。
-
=: 赋值。 Eigen 使用表达式模板,赋值操作会触发实际计算并复制结果。
-
**+=, -=, =, /=: **复合赋值运算符。对于矩阵/向量, = 仅用于标量乘法 (matrix = scalar)。对于数组,= 和 /= 可用于逐元素操作 (array1 *= array2)。
cpp
// 向量运算
Vector3f a(1, 2, 3), b(4, 5, 6);
Vector3f c = a + b; // [5, 7, 9]^T
Vector3f d = a - b; // [-3, -3, -3]^T
Vector3f e = 2 * a; // [2, 4, 6]^T
Vector3f f = b / 2; // [2, 2.5, 3]^T
float dot = a.dot(b); // 点积 (1*4 + 2*5 + 3*6 = 32)
Vector3f cross = a.cross(b); // 叉积 (计算略)//a×b=((2×6−3×5),−(1×6−3×4),(1×5−2×4))
// 矩阵运算
Matrix2d A, B;
A << 1, 2,
3, 4;
B << 5, 6,
7, 8;
Matrix2d C = A + B; // [ [6, 8], [10, 12] ]
Matrix2d D = A - B; // [ [-4, -4], [-4, -4] ]
Matrix2d E = 3 * A; // [ [3,6], [9,12] ]
Matrix2d F = A * B; // 标准矩阵乘法 [ [1*5+2*7, 1*6+2*8], [3*5+4*7, 3*6+4*8] ] = [ [19, 22], [43, 50] ]
Matrix2d G = A.array() * B.array(); // 逐元素乘法 [ [1*5, 2*6], [3*7, 4*8] ] = [ [5, 12], [21, 32] ] (需要.array()转换)
// 数组逐元素运算
Array3f arr1, arr2;
arr1 << 1, 2, 3;
arr2 << 4, 5, 6;
Array3f arr3 = arr1 * arr2; // [4, 10, 18] (逐元素乘)
Array3f arr4 = arr1 / arr2; // [0.25, 0.4, 0.5] (逐元素除)
Array3f arr5 = arr1.sqrt(); // [1, sqrt(2), sqrt(3)] (逐元素平方根)
Array3f arr6 = arr1.exp(); // [exp(1), exp(2), exp(3)] (逐元素指数)

d) 大小和类型信息
-
.rows(), .cols(): 获取矩阵/向量/数组的行数和列数。
-
.size(): 获取向量/数组中元素的个数。对于矩阵,返回 .rows() * .cols()。
-
.resize(rows, cols): 改变动态大小矩阵/向量/数组的维度(固定大小对象调用此方法会导致断言错误)。这会丢弃原有数据!如果需要保留数据改变大小,请使用 conservativeResize()(更昂贵)。
-
.setZero(), .setOnes(), .setIdentity(), .setRandom(): 将对象设置为对应特殊值(原地修改)。
-
static 方法如 MatrixXd::Identity(rows, cols) 用于创建动态大小的特殊矩阵。
cpp
MatrixXd dynMat(2, 3); // 2x3 动态矩阵
int rows = dynMat.rows(); // 2
int cols = dynMat.cols(); // 3
int numElems = dynMat.size(); // 6
dynMat.resize(3, 2); // 现在变成 3x2, 内容未定义
dynMat.setZero(); // 设为 3x2 零矩阵
VectorXf dynVec(4);
dynVec.setOnes(); // [1,1,1,1]^T
e) 转置、共轭、伴随
-
.transpose(): 返回矩阵/向量的转置(对于向量,行向量转置成列向量,反之亦然)。注意: 原地转置使用 .transposeInPlace()。避免在复杂表达式中使用 A = A.transpose(),使用 A.transposeInPlace() 或 A = A.adjoint()(如果涉及复数)。
-
.conjugate(): 返回逐元素共轭复数(对实数无影响)。
-
.adjoint(): 返回伴随矩阵(即共轭转置 conjugate().transpose())。对于实数矩阵等同于 .transpose()。
cpp
Matrix2cd matC; // 2x2 复数 double 矩阵
matC << std::complex<double>(1,2), std::complex<double>(3,4),
std::complex<double>(5,6), std::complex<double>(7,8);
Matrix2cd trans = matC.transpose(); // [[1+2i, 5+6i], [3+4i, 7+8i]]
Matrix2cd conj = matC.conjugate(); // [[1-2i, 3-4i], [5-6i, 7-8i]]
Matrix2cd adj = matC.adjoint(); // [[1-2i, 5-6i], [3-4i, 7-8i]] (共轭转置)
RowVector3d r(1, 2, 3);
Vector3d c = r.transpose(); // 行向量转置成列向量 [1,2,3]^T
f) 归约操作
- .sum(): 所有元素的和。
- .prod(): 所有元素的乘积。
- .mean(): 所有元素的平均值。
- .minCoeff(): 最小元素的值。
- .maxCoeff(): 最大元素的值。
- .trace(): 矩阵的迹 (主对角线元素之和)。
- .norm(): 向量的 L2 范数(欧几里得长度),或矩阵的 Frobenius 范数(所有元素平方和的平方根)。
- .squaredNorm(): L2 范数的平方(或 Frobenius 范数的平方)。
.lpNorm<p>():计算 Lp 范数 (例如 .lpNorm<1>() 是 L1 范数,.lpNorm<Infinity>()是 L∞ 范数)。- .all(): 是否所有元素都为 true (非零)。
- .any(): 是否有任何一个元素为 true (非零)。
- .count(): 等于 true (非零) 的元素个数。
cpp
Vector3f v(1, -2, 3);
float s = v.sum(); // 1 + (-2) + 3 = 2
float p = v.prod(); // 1 * (-2) * 3 = -6
float m = v.mean(); // 2 / 3 ≈ 0.666...
float minVal = v.minCoeff(); // -2
float maxVal = v.maxCoeff(); // 3
float l2 = v.norm(); // sqrt(1^2 + (-2)^2 + 3^2) = sqrt(14) ≈ 3.74
float l2sq = v.squaredNorm(); // 14
float l1 = v.lpNorm<1>(); // |1| + |-2| + |3| = 6
Matrix2f mat;
mat << 1, 2,
3, 4;
float trace = mat.trace(); // 1 + 4 = 5
float frob = mat.norm(); // sqrt(1^2 + 2^2 + 3^2 + 4^2) = sqrt(30) ≈ 5.477
g) 分块操作
-
Eigen 提供了强大的方法来提取或操作矩阵/向量的子块。
-
.block(i, j, p, q): 返回一个从 (i, j) 开始的 p x q 子块的可读写视图。
-
.topLeftCorner(p, q), .topRightCorner(p, q), .bottomLeftCorner(p, q), - .bottomRightCorner(p, q): 返回对应角落的 p x q 子块视图。
-
.topRows(q), .bottomRows(q), .leftCols(p), .rightCols(p): 返回顶部/底部 q 行或左侧/右侧 p 列的视图。 -
.head(n), .tail(n): 对于向量,返回前 n 个或后 n 个元素的视图。
-
.segment(i, n): 对于向量,返回从索引 i 开始的 n 个元素的视图。
-
InPlace 变体: 许多块操作有对应的 ...InPlace() 版本,可以直接在原始对象上操作该块(例如 mat1.topRows(2) = mat2; 是赋值给视图)。
-
.col(0) 直接引用首列
-
middleCols(1,2) 引用中间两列(索引1开始,长度2)
cpp
Matrix4f M = Matrix4f::Random();
// 提取 2x2 子块,从位置 (1,1) 开始
Matrix2f block = M.block<2,2>(1,1); // 固定大小版本 (如果大小在编译时已知更高效)
MatrixXf dynBlock = M.block(1,1,2,2); // 动态大小版本
// 提取第一行
RowVector4f firstRow = M.row(0);
// 提取第三列
Vector4f thirdCol = M.col(2);
// 操作左上角 3x3 子块
M.topLeftCorner(3,3) *= 2; // 原地将左上角 3x3 块乘以 2
VectorXf longVec(10);
VectorXf first3 = longVec.head(3); // 前3个元素
VectorXf last4 = longVec.tail(4); // 后4个元素
VectorXf middle5 = longVec.segment(2,5); // 从索引2开始的5个元素
cpp
Eigen::VectorXi v(3);
v << 1, 2, 3;
Eigen::MatrixXi m1(3, 2);
m1 << 4, 5,
6, 7,
8, 9;
Eigen::MatrixXi m2(3, 2);
m2 << 10, 11,
12, 13,
14, 15;
Eigen::MatrixXi result(3, 5);
// 使用块操作直接赋值,避免额外拷贝
result.col(0) = v; // 第0列 = VectorXi
result.middleCols(1, 2) = m1; // 第1-2列 = 第一个MatrixXi
result.middleCols(3, 2) = m2; // 第3-4列 = 第二个MatrixXi
五、 重要注意事项
-
表达式模板 (Expression Templates): Eigen 大量使用表达式模板来优化性能。像 C = A * B + D 这样的表达式通常会被优化成一个循环,避免创建临时矩阵。理解这点有助于避免性能陷阱(如不必要的临时对象)和正确使用 .eval()(强制表达式求值)。
-
混叠 (Aliasing): 在赋值操作 A = A * B 中,如果 A 和 B 是同一个矩阵(或共享数据),计算结果会出错。Eigen 默认会检测简单混叠(如 mat = mat.transpose()),但对于更复杂的情况(如 A = A * A),需要使用 A = A * A.eval() 或更好的 A = A.square()(如果存在)来避免。使用 .noalias() 可以明确告诉 Eigen 没有混叠(如 C.noalias() = A * B)。
-
固定大小 vs. 动态大小: 固定大小的矩阵/向量(在编译时知道维度)允许 Eigen 进行更积极的优化(如循环展开、使用栈内存)。尽可能使用固定大小。动态大小的对象使用堆内存分配。
-
内存对齐 (Memory Alignment): 为了使用 SIMD 指令获得最佳性能,Eigen 对象(特别是固定大小向量和矩阵)需要内存对齐。使用 EIGEN_MAKE_ALIGNED_OPERATOR_NEW 宏重载 new 运算符或使用 std::aligned_allocator 在 STL 容器中存储 Eigen 对象来确保对齐。
-
编译时间: Eigen 是一个纯头文件库,大量使用模板。复杂的表达式可能导致编译时间较长。合理组织代码和使用前向声明有时有帮助。