加微信:cv3d007,備注:SLAM,拉你入群。文末附行業細分群。
Eigen:基于線性代數的C ++模板庫,主要用于矩陣,向量,數值求解器和相關算法。SLAM中常用的Ceres、G2O等項目均是基于Eigen庫。
Eigen庫的優點:
支持整數、浮點數、復數,使用模板編程,可以為特殊的數據結構提供矩陣操作。
OpenCV自帶到Eigen的接口。
支持逐元素、分塊、和整體的矩陣操作。
支持使用Intel MKL加速部分功能。
支持多線程,對稀疏矩陣支持良好。
支持常用幾何運算,包括旋轉矩陣、四元數、矩陣變換、角軸等等。
即使不做SLAM,在3D視覺中,當處理大量數學運算時,我們也會用到Eigen庫,它幫我們優化了性能。在安裝完成Eigen庫后,開始接下來的學習。
Eigen庫的核心類是 Matrix,由6個參數構成:
Matrix< typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime, int Options = 0, // 默認(無需更改) int MaxRowsAtCompileTime = RowsAtCompileTime, // 默認(最大行數,提前知道極限) int MaxColsAtCompileTime = ColsAtCompileTime // 默認(最大列數,提前知道極限)>
其中:
前三個參數:需要我們指定
后三個參數:默認即可,無需指定
因為經常需要實例化一些方陣、向量,因此Eigen庫也提供了很多直接使用的模板(利用C++的關鍵字:typedef),例如 Matrix4f 是 的float型矩陣:
typedef Matrix<float, 4, 4> Matrix4f;
還有例如列向量:Vector3f ,其本質也是 Matrix 類:
typedef Matrix< float, 3, 1 > Vector3f;
行向量RowVector:
typedef Matrix<int, 1, 2> RowVector2i;
靜態-動態-矩陣
靜態矩陣:矩陣是靜態的,即編譯時候就知道運行結果,例如Matrix3d:表示元素類型為double大小為3*3的矩陣變量,其大小在編譯時就知道。
動態矩陣:有時候運行完之后,才可以知道,這里使用MatrixXd:表示任意大小的元素類型為double的矩陣變量,其大小只有在運行被賦值之后才能知道;
數據類型
Eigen中的矩陣類型一般都是用類似MatrixNX來表示,可以根據該名字來判斷其大小(2,3,4,或X,意思Dynamic)和數據類型,比如:
d:表示double類型
f:表示float類型
i:表示整數
c:表示復數;
舉例:Matrix2f,表示的是一個維的,其每個元素都是float類型。
矩陣構造
默認構造,分配了大小和內存空間,但沒有初始化矩陣元素(里面的數值是隨機的,不能使用):
Matrix3f a; // 3*3的元素,其中還有一個float[9]數組,其中的元素沒有初始化;MatrixXf b; // 動態大小的矩陣,目前的大小是0*0,它的元素數組完全沒有分配。
對于動態數組,你也可以直接分配大小(失去作用了),同樣沒有初始化矩陣元素:
MatrixXf a(10, 15);// 10x15動態矩陣,數組內存已經分配,但是沒有初始化;VectorXf b(30); // 大小為30的向量,數組內存已經分配,但是元素沒有初始化。
或者更通用的:
Matrix< float, 3, 1 > Vector3f_def;
矩陣初始化
在構造完后,我們需要對元素進行初始化,常用的是直接賦值:
Eigen::Matrix3f m; m << 1, 2, 3, 4, 5, 6, 7, 8, 9;
它是逐行寫入的,這只適用于較小的矩陣:
Eigen::MatrixXd m(3,3);m <<1,2,3, 4,5,6, 7,8,9;
對于向量,還可以在構造的時候初始化:
Vector3d v(1, 2, 3);Vector3d w(1, 0, 0);
還有一些特殊函數,函數:
MatrixXf::Zero(3,4); // 將矩陣3行4列初始化為0 MatrixXf::Ones(3,3); // 將矩陣3行3列初始化為1 Vector3f::Ones(); // 將3行的縱向量初始化為1 MatrixXi::Identity(3,3); // 單位矩陣 Matrix3d::Random(); // 隨機矩陣
當前矩陣的行數、列數、大小可以通過rows()、cols()和size()來獲取。遍歷Eigen矩陣時最好通過rows和cols來限制訪問范圍,索引的方法如下:
1、矩陣訪問按照先行索引、后列索引方式進行,索引下標從0開始(與Matlab不同);
2、矩陣元素的訪問可以通過**”( )”操作符完成。例如m(2, 3)**,矩陣m的第2行第3列元素;
3、針對向量還提供”**[ ]”操作符,注意矩陣則不可**如此使用。
resize:不同于matlab、Python,對于動態矩陣雖然可以通過resize()函數來動態修改矩陣的大小,但是需要說明的是,在Eigen中:
不能用:固定大小的矩陣是不能使用resize()來修改矩陣的大小;
數據會變:resize()函數會析構掉原來的數據,變為0.,因此最好使用:conservativeResize()函數
大小修改:使用”=”操作符操作動態矩陣時,如果左右兩邊的矩陣大小不等,則左邊的動態矩陣的大小會被修改為右邊的大小。
利用block()函數,可以從Matrix中取出一個小矩陣來進行處理,使用的語法為:
matrix.block<p,q>(i,j);
例如:
Eigen::MatrixXf m(4, 4);m << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16;cout << "Block in the middle" << endl;cout << m.block<2, 2>(1, 1) << endl << endl;for (int i = 1; i <= 3; ++i){ cout << "Block of size " << i << "x" << i << endl; cout << m.block(0, 0, i, i) << endl << endl;}// Output is:// Block in the middle// 6 7// 10 11// Block of size 1x1// 1// Block of size 2x2// 1 2// 5 6// Block of size 3x3// 1 2 3// 5 6 7// 9 10 11
單獨的列和行是塊的特殊情況。Eigen提供了可以輕松解決它們的方法:.col()和.row():
Eigen::MatrixXi m(2, 2);m << 1, 2, 3, 4;cout << m.col(0) << endl;// 1 3
Eigen幫我們重載了,直接運算:
Vector3d v(1, 2, 3);Vector3d w(1, 0, 0);cout << v + w << endl;
除法:通常我們是除以標量。對于矩陣除法,我們是求它的逆,再轉換為矩陣乘法。因此較為簡單:
Vector3d v(1, 2, 3);Vector3d r = v / 3;cout << r << endl;
矩陣乘法:*
乘法,標量非常簡單:
cout << v * 2 << endl;v *= 2; // 原地操作
Matrix2d mat;mat << 1, 2, 3, 4;Vector2d u(-1, 1), v(2, 0);// 矩陣乘法 乘以矩陣std::cout << "Here is mat*mat: " << mat * mat << std::endl;// 矩陣乘法 乘以向量std::cout << "Here is mat*u: " << mat * u << std::endl;// 轉置之后,再矩陣乘法std::cout << "Here is u^T*mat: " << u.transpose() * mat << std::endl;// 轉置之后,向量的矩陣乘法std::cout << "Here is u^T*v: " << u.transpose() * v << std::endl;std::cout << "Here is u*v^T: " << u * v.transpose() << std::endl;// 矩陣乘法std::cout << "Let's multiply mat by itself" << std::endl;mat = mat * mat;std::cout << "Now mat is mat: " << mat << std::endl;//Output is:// Here is mat*mat:// 7 10// 15 22// Here is mat*u:// 1// 1// Here is u^T*mat:// 2 2// Here is u^T*v:// -2// Here is u*v^T:// -2 -0// 2 0// Let's multiply mat by itself// Now mat is mat:// 7 10// 15 22
補充:轉置
向量、矩陣的乘法,因為需要size一致,因此需要用到轉置:
MatrixXcf a = MatrixXcf::Random(2, 2); //MatrixXcf 為復數矩陣cout << "Here is the matrix a " << a << endl;// 矩陣轉置cout << "Here is the matrix a^T " << a.transpose() << endl;// 共軛矩陣cout << "Here is the conjugate of a " << a.conjugate() << endl;// 共軛轉置矩陣cout << "Here is the matrix a^* " << a.adjoint() << endl;
需要說明的是,在Eigen中,對于自身的操作,都有專門的函數,例如對自身的轉置:
a.transposeInPlace(); // 直接在a上操作
點乘和叉乘
Vector3d v(1, 2, 3);Vector3d w(0, 1, 2);// 點乘cout << "Dot product: " << v.dot(w) << endl;// 叉乘cout << "Cross product: " << v.cross(w) << endl;// 點成結果Dot product: 8 // 1 * 0 + 2 * 1 + 3 * 2=8 Cross product: 1 // 2 * 2 - 1 * 3 = 1-2 // 3 * 0 - 1 * 2 = -2 1 // 1 * 1 - 0 * 2 = 1
在Eigen中,向量的叉乘只支持三維的向量,這是因為叉乘通常用于計算方向、夾角等,它的計算規則如下:
// Eigen also provides some reduction operations to reduce a given matrix or vector to a single value// such as the sum (computed by sum()), product (prod()), or the maximum (maxCoeff()) and minimum (minCoeff()) of all its coefficients.Eigen::Matrix2d mat;mat << 1, 2, 3, 4;//元素和,元素乘積,元素均值,最小系數,最大系數,蹤cout << "Here is mat.sum(): " << mat.sum() << endl;cout << "Here is mat.prod(): " << mat.prod() << endl;cout << "Here is mat.mean(): " << mat.mean() << endl;cout << "Here is mat.minCoeff(): " << mat.minCoeff() << endl;cout << "Here is mat.maxCoeff(): " << mat.maxCoeff() << endl;cout << "Here is mat.trace(): " << mat.trace() << endl;// 可以返回元素位置Matrix3f m = Matrix3f::Random();std::ptrdiff_t i, j; // std::ptrdiff_t 是二個指針相減結果的有符號整數類型float minOfM = m.minCoeff(&i, &j);cout << "Here is the matrix m: " << m << endl;cout << "Its minimum coefficient (" << minOfM << ") is at position (" << i << "," << j << ") ";RowVector4i v = RowVector4i::Random();int maxOfV = v.maxCoeff(&i);cout << "Here is the vector v: " << v << endl;cout << "Its maximum coefficient (" << maxOfV << ") is at position " << i << endl;// Output is:// Here is mat.sum(): 10// Here is mat.prod(): 24// Here is mat.mean(): 2.5// Here is mat.minCoeff(): 1// Here is mat.maxCoeff(): 4// Here is mat.trace(): 5// Here is the matrix m:// -0.444451 0.257742 0.904459// 0.10794 -0.270431 0.83239// -0.0452059 0.0268018 0.271423// Its minimum coefficient (-0.444451) is at position (0,0)
Array類提供了通用數組。此外,Array類提供了一種執行逐系數運算的簡便方法,該運算可能沒有線性代數含義,例如將常數添加到數組中的每個系數或按系數乘兩個數組。
注:Eigen計算三角函數等,Matrix并不支持,需要通過.array() 轉換到Array類,再計算!
m1.array().atan();
常見數據類型
Array<float,Dynamic,1> ArrayXfArray<float,3,1> Array3fArray<double,Dynamic,Dynamic> ArrayXXdArray<double,3,3> Array
常見操作:
// 逐元素操作Vectorized operations on each element independently // Eigen // Matlab //注釋 R = P.cwiseProduct(Q); // R = P .* Q //逐元素乘法 R = P.array() * s.array(); // R = P .* s //逐元素乘法(s為標量) R = P.cwiseQuotient(Q); // R = P ./ Q //逐元素除法 R = P.array() / Q.array(); // R = P ./ Q //逐元素除法 R = P.array() + s.array(); // R = P + s //逐元素加法(s為標量) R = P.array() - s.array(); // R = P - s //逐元素減法(s為標量) R.array() += s; // R = R + s //逐元素加法(s為標量) R.array() -= s; // R = R - s //逐元素減法(s為標量) R.array() < Q.array(); // R < Q //逐元素比較運算 R.array() <= Q.array(); // R <= Q //逐元素比較運算 R.cwiseInverse(); // 1 ./ P //逐元素取倒數 R.array().inverse(); // 1 ./ P //逐元素取倒數 R.array().sin() // sin(P) //逐元素計算正弦函數 R.array().cos() // cos(P) //逐元素計算余弦函數 R.array().pow(s) // P .^ s //逐元素計算冪函數 R.array().square() // P .^ 2 //逐元素計算平方 R.array().cube() // P .^ 3 //逐元素計算立方 R.cwiseSqrt() // sqrt(P) //逐元素計算平方根 R.array().sqrt() // sqrt(P) //逐元素計算平方根 R.array().exp() // exp(P) //逐元素計算指數函數 R.array().log() // log(P) //逐元素計算對數函數 R.cwiseMax(P) // max(R, P) //逐元素計算R和P的最大值 R.array().max(P.array()) // max(R, P) //逐元素計算R和P的最大值 R.cwiseMin(P) // min(R, P) //逐元素計算R和P的最小值 R.array().min(P.array()) // min(R, P) //逐元素計算R和P的最小值 R.cwiseAbs(P) // abs(P) //逐元素計算R和P的絕對值 R.array().abs() // abs(P) //逐元素計算絕對值 R.cwiseAbs2() // abs(P.^2) //逐元素計算平方 R.array().abs2() // abs(P.^2) //逐元素計算平方 (R.array() < s).select(P,Q); // (R < s ? P : Q) //根據R的元素值是否小于s,選擇P和Q的對應元素 R = (Q.array()==0).select(P,A) // R(Q==0) = P(Q==0) R(Q!=0) = P(Q!=0) //根據Q中元素等于零的位置選擇P中元素 R = P.unaryExpr(ptr_fun(func)) // R = arrayfun(func, P) // 對P中的每個元素應用func函數
對于Eigen,它適合一個簡單的數值計算庫,并沒有什么實用技巧。其實大多數時候,你只需要利用Google和百度去查詢你需要的操作即可!對于更多的操作,可以參考:Eigen 常用函數查詢,對比MatLab操作 。
目前工坊已經建立了3D視覺方向多個社群,包括SLAM、工業3D視覺、自動駕駛方向,細分群包括:[工業方向]三維點云、結構光、機械臂、缺陷檢測、三維測量、TOF、相機標定、綜合群;[SLAM方向]多傳感器融合、ORB-SLAM、激光SLAM、機器人導航、RTK|GPS|UWB等傳感器交流群、SLAM綜合討論群;[自動駕駛方向]深度估計、Transformer、毫米波|激光雷達|視覺攝像頭傳感器討論群、多傳感器標定、自動駕駛綜合群等。[三維重建方向]NeRF、colmap、OpenMVS等。除了這些,還有求職、硬件選型、視覺產品落地等交流群。大家可以添加小助理微信: cv3d007,備注:加群+方向+學校|公司, 小助理會拉你入群。
現在人工智能大熱,如果想了解人工智能或者自詡為專家,那不得不學習OpenCV。OpenCV 是一個基于BSD許可(開源)發行的跨平臺計算機視覺庫,可實現人工智能領域內很多核心的圖像處理和計算機視覺方面的通用算法。值得自豪的是,目前全球OpenCV開發總部已經遷移到中國,里面也凝聚了不少國人的心血。
在Linux下安裝opencv是一件很簡單的事情,本以為在win10下也不復雜,結果還是碰到了一些坑,這跟微軟的生態不無關系。先是下載免費的Visual Studio 2015 社區版,占用十幾G的空間,結果還是浪費了不少時間才通過編譯opencv,這是后話暫且不提。本文只是講講得如何在Win10下面,利用MINGW編譯通過opencv-4.2.0版本。
MINGW是Windows下面的一個類Linux編譯器,按照官網介紹,就是“MinGW,是Minimalist GNUfor Windows的縮寫。它是一個可自由使用和自由發布的Windows特定頭文件和使用GNU工具集導入庫的集合,允許你在GNU/Linux和Windows平臺生成本地的Windows程序而不需要第三方C運行時(C Runtime)庫。”而且簡單易操作。
先做些準備工作,這個步驟挺重要,否則會走很多彎路:
1.下載最新版的CMAKE,安裝好后運行命令看版本: