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代表doubleMatrix3d是固定的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; // ... 计算协方差矩阵 SelfAdjointEigenSolverMatrix3d solver(covariance); Vector3d eigenvalues solver.eigenvalues(); Matrix3d eigenvectors solver.eigenvectors();最小二乘在机器人里用得特别多。比如标定传感器的时候你需要从多组观测数据中求解传感器的外参。这就是一个最小二乘问题。特征值分解在SLAM里常用。比如对点云做PCA主成分分析提取平面的法向量就是求协方差矩阵的最小特征值对应的特征向量。// 点云PCA示例 vectorVector3d 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(); // 特征值分解 SelfAdjointEigenSolverMatrix3d 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一样。如果你的代码需要和行优先的库交互比如某些图像库需要指定存储顺序Matrixdouble, 3, 3, RowMajor rowMatrix; // 行优先 Matrixdouble, 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-depth10限制模板嵌套深度来缩短错误信息快速定位问题。面试时如果你能提到这些调试Eigen的实用技巧面试官会觉得你确实动手写过不少Eigen代码。给正在准备面试的你Eigen是机器人开发的必备工具。面试的时候不要求你把Eigen的API全背下来但基本的矩阵创建、运算、分解要会用。建议你写一个小项目练手——比如用Eigen实现一个简单的卡尔曼滤波器或者实现一个坐标变换的工具类。面试的时候能拿出来讲比干巴巴地说我会用Eigen有说服力得多。下篇是Eigen的进阶篇——四元数、变换矩阵与机器人坐标运算。这些是机器人学里的核心数学工具面试里经常被问到。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第60篇 序列化与反序列化——protobuf在机器人通信中的应用 下一篇预告第62篇 Eigen进阶——四元数、变换矩阵与机器人坐标运算有任何问题欢迎评论区留言我会尽量回复。