Eigen矩阵库入门:机器人数学运算的标配工具
前面把网络通信和序列化聊完了。最后两篇进入数学库板块——Eigen。
做机器人开发,矩阵运算是家常便饭。坐标变换要矩阵乘法,卡尔曼滤波要矩阵求逆,SLAM里的图优化要稀疏矩阵运算。这些运算如果你自己写,不仅慢还容易出错。Eigen是C++社区最流行的线性代数库,几乎所有机器人项目都在用。
面试的时候,如果你能熟练地说出Eigen的用法,再结合项目里的例子,面试官会觉得你是真正写过机器人代码的。
Eigen的基本用法
Eigen是一个纯头文件库,不需要编译安装,包含头文件就能用:
#include <Eigen/Dense>
#include <iostream>
using namespace Eigen;
int main() {
// 创建矩阵
Matrix3d m; // 3x3的double矩阵
m << 1, 2, 3,
4, 5, 6,
7, 8, 9;
cout << m << endl;
// 向量
Vector3d v(1, 2, 3);
cout << "v = " << v.transpose() << endl;
// 矩阵乘法
Vector3d result = m * v;
cout << "m * v = " << result.transpose() << endl;
// 转置
cout << "m^T = " << m.transpose() << endl;
// 求逆
cout << "m^-1 = " << m.inverse() << endl;
return 0;
}
Eigen的命名规则很直观:MatrixXd表示任意大小的double矩阵(X代表动态大小,d代表double),Matrix3d是固定的3x3矩阵,Vector4f是4维float向量。
动态大小和固定大小的选择:小矩阵(3x3、4x4这种)用固定大小,编译器可以做更多的优化;大矩阵用动态大小,运行时决定尺寸。
在机器人开发中的常见运算
机器人学里最常用的矩阵运算,Eigen都支持得很好:
// 1. 旋转矩阵(3x3)
Matrix3d R;
R = AngleAxisd(0.5, Vector3d::UnitZ()); // 绕Z轴旋转0.5弧度
// 2. 坐标变换
Vector3d point_body(1.0, 0.0, 0.0); // 机体坐标系下的点
Vector3d point_world = R * point_body; // 转换到世界坐标系
// 3. 最小二乘问题(Ax = b)
MatrixXd A(10, 3);
VectorXd b(10);
// ... 填充A和b
Vector3d x = A.colPivHouseholderQr().solve(b);
// 4. 特征值分解
Matrix3d covariance;
// ... 计算协方差矩阵
SelfAdjointEigenSolver<Matrix3d> solver(covariance);
Vector3d eigenvalues = solver.eigenvalues();
Matrix3d eigenvectors = solver.eigenvectors();
最小二乘在机器人里用得特别多。比如标定传感器的时候,你需要从多组观测数据中求解传感器的外参。这就是一个最小二乘问题。
特征值分解在SLAM里常用。比如对点云做PCA(主成分分析),提取平面的法向量,就是求协方差矩阵的最小特征值对应的特征向量。
// 点云PCA示例
vector<Vector3d> points = {/* 从点云获取 */};
// 计算质心
Vector3d centroid = Vector3d::Zero();
for (const auto& p : points) centroid += p;
centroid /= points.size();
// 计算协方差矩阵
Matrix3d covariance = Matrix3d::Zero();
for (const auto& p : points) {
Vector3d diff = p - centroid;
covariance += diff * diff.transpose();
}
covariance /= points.size();
// 特征值分解
SelfAdjointEigenSolver<Matrix3d> solver(covariance);
// 最小特征向量就是平面法向量
Vector3d normal = solver.eigenvectors().col(0);
Eigen的常见坑
用Eigen的时候有几个容易踩的坑,面试偶尔会提到。
第一个是auto的陷阱。Eigen用表达式模板,很多运算返回的是"表达式对象"而不是矩阵。如果你用auto接收,表达式可能引用已经被销毁的临时对象:
MatrixXd A(3, 3), B(3, 3);
// ... 初始化A和B
auto result = A * B; // 危险!result不是矩阵,是一个乘法表达式
// 如果A或B在result被求值之前被修改或销毁,结果就不对
MatrixXd result2 = A * B; // 正确:赋值给MatrixXd会立即求值
第二个是行优先和列优先。Eigen默认是列优先存储(和Fortran、MATLAB一样)。如果你的代码需要和行优先的库交互(比如某些图像库),需要指定存储顺序:
Matrix<double, 3, 3, RowMajor> rowMatrix; // 行优先
Matrix<double, 3, 3, ColMajor> colMatrix; // 列优先(默认)
Eigen的性能
Eigen的性能在线性代数库里算很好的。它用了表达式模板(Expression Templates)技术,在编译期优化矩阵运算,避免临时对象的创建。
// 这个表达式在编译期被优化
// 不会创建a*b的临时矩阵
Vector3d result = a.cross(b) + c;
对于大矩阵运算,Eigen会调用底层的BLAS/LAPACK库(如果你安装了的话),性能接近Fortran级别的数值计算库。
在嵌入式机器人上,Eigen也跑得动。你可以关掉一些不需要的特性来减小编译产物:
#define EIGEN_NO_MALLOC // 禁止动态内存分配
#define EIGEN_DONT_VECTORIZE // 关掉SIMD(如果硬件不支持)
#include <Eigen/Core>
面试中的关键考点
"Eigen的固定大小和动态大小矩阵有什么区别?"固定大小(如Matrix3d)在栈上分配,编译期确定大小,性能更好。动态大小(如MatrixXd)在堆上分配,运行时确定大小,更灵活。小矩阵用固定大小,大矩阵用动态大小。
"Eigen的表达式模板是什么?"一种编译期优化技术。Eigen不立即计算矩阵运算的结果,而是构建一个表达式树,在最终赋值的时候一次性计算。避免了中间临时对象的创建和内存分配。
"Eigen和OpenCV的Mat有什么区别?"Eigen是通用的线性代数库,支持各种矩阵分解和数学运算。OpenCV的Mat是专门为图像处理设计的,有丰富的图像操作API。在机器人项目里,两者经常配合使用——Eigen做数学运算,OpenCV处理图像。
再补充一个面试加分点:Eigen的调试技巧。在调试矩阵运算时,经常需要打印矩阵内容来检查结果是否正确。Eigen重载了<<运算符,可以直接cout << matrix输出矩阵。但在GDB里调试时,直接print matrix看不到矩阵内容,需要用print matrix.data()来查看底层数据数组。另外如果遇到Eigen编译报错信息特别长(模板错误的特点),可以用-ftemplate-depth=10限制模板嵌套深度来缩短错误信息,快速定位问题。面试时如果你能提到这些调试Eigen的实用技巧,面试官会觉得你确实动手写过不少Eigen代码。
给正在准备面试的你
Eigen是机器人开发的必备工具。面试的时候,不要求你把Eigen的API全背下来,但基本的矩阵创建、运算、分解要会用。
建议你写一个小项目练手——比如用Eigen实现一个简单的卡尔曼滤波器,或者实现一个坐标变换的工具类。面试的时候能拿出来讲,比干巴巴地说"我会用Eigen"有说服力得多。
下篇是Eigen的进阶篇——四元数、变换矩阵与机器人坐标运算。这些是机器人学里的核心数学工具,面试里经常被问到。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。
「机器人软件开发面试·从入门到精通」连载系列 上一篇:第60篇 序列化与反序列化——protobuf在机器人通信中的应用 下一篇预告:第62篇 Eigen进阶——四元数、变换矩阵与机器人坐标运算
有任何问题欢迎评论区留言,我会尽量回复。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)