Eigen相關介紹
Eigen是一個高層次的C ++庫,有效支持線性代數,矩陣和矢量運算,數值分析及其相關的算法。
最近在用Matlab處理圖像,現在要做的是將其用C++語言進行翻譯,由於要進行大量的矩陣計算,就研究了一下可以進行矩陣計算的開源庫,詳細的介紹可以參照http://my.oschina.net/cvnote/blog/165340,我從中選擇了Eigen進行了一番學習,現在對裏面一些基礎知識做一下小結。以下內容可以看做它官方在線文檔的一個學習筆記,粗略看看還是感覺很強大的,而且由於只包含頭文件,方便跨平臺使用,打算去使用一下。詳細內容可以參照官方文檔:http://eigen.tuxfamily.org/index.php?title=Main_Page 。
Dense matrix and array manipulation
The Matrix class
創建語法:Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime, int Options, int MaxRowsAtCompileTime, int MaxColsAtCompileTime >, 其中Scalar是矩陣類型,有int、float、double、complex float、complex double等。
RowsAtCompileTime和ColsAtCompileTime是編譯時創建數組的大小。Eigen還定義了許多convenience typedefs 比如
Typedef Matrix<float, 4, 4> Matrix4f;
Options可以定義一些矩陣的屬性,不如存儲順序是按列存儲還是按行存儲;
MaxRowsAtCompileTime 和MaxColsAtCompileTime 是規定矩陣的最大範圍,以防動態分配(下面介紹)時分配過大。
向量是一種特殊的矩陣,也有很多convenience typedef,如
Typedef Matrix<float, 3, 1> Vector3f;
這種定義方式得到的是列向量,數據是按列存儲,還可以定義行向量:
Typedef Matrix<int, 1, 2> RowVector2i;
矩陣大小也可以不在編譯時設置,而是動態設定,如
Matrix(double, Dynamic, Dynamic)
向量也是如此:
Matrix(int, Dynamic, 1)
定義時可以對其進行初始化:
MatrixXf a(10, 15); //定義了一個10*15的矩陣
Vector2d a(5.0, 6.0);
矩陣變量允許下標訪問,不過是採用括號,例如定義一個變量
Matri3dx m = Matrix3d::Random(); //生成一個3*3的隨機矩陣。
訪問第二行第三個元素,可以使用m(1, 2)來進行訪問。
rows(), cols(), size()函數可以用以返回矩陣的行數、列數和元素個數。
Resize()函數可以用來改變矩陣或者向量的大小,但對於行數和列數固定的矩陣或向量進行修改會導致錯誤產生。
對於編譯時未規定數組,如果在定義時兩個矩陣長度不同,也可以進行賦值操作,只不過會將被複制的矩陣size改變,如
MatrixXf a(2, 2); MatrixXf b(3, 3);
b = a;
則此時b是一個2*2的矩陣。
需要說明的是,如果聲明一個矩陣用固定大小的方式(e.g. Matrix4f),效率比用動態動態聲明(MatrixXf a(4, 4))高很多。
Matrix and Vector Arithmetic
定義了矩陣相加、相減、相乘等操作
需要注意的是,在做矩陣計算是,用的是循環語句,所以是串行計算的方式。
Eigen還提供了一些常用的變換函數:transpose(),轉置操作:conjugata(),共軛操作;adjoint(),共軛轉置操作;需要注意的是這些是非原位操作,例如a = [1, 2;, 3, 4] 那麼a = a.transpose() 得到的a爲[1, 2, 2, 4],這樣就產生了所粗。原位轉置操作可以使用transposeInPlace() 函數。
矩陣乘法是一個特殊的運算,爲了防止上述的錯誤,矩陣與矩陣相乘都會使用臨時變量,例如m = m* m; 在實際編譯的時候是tem = m* m; m = tem; 如果在乘法中你可以確定不會有錯誤的產生,那麼可以使用noaliasd()函數來避免產生臨時變量,例如:c.noalias() += a * b;
Eigen還提供了dot()點乘函數和cross()叉乘函數。其中dot可以針對任意兩個長度相等的向量,而叉乘只能是兩個三維向量,例如Vector3d v(1, 2, 3); Vector3d w(0, 1, 2); 那麼v.dot(w) 得到的結果是8(等價於v.adjoint() * w),v.corss(w)得到的結果是(1;-2;1)。
Eigen提供了一些基礎的算法,如sum(),求所有元素和;prod(),求所有元素之積;mean(),求元素平均值;minCoeff(),最小元素;maxCoeff(),最大元素;trace(),求跡等。下面給出一個實例:
上述例子不僅求出了最小值,也求得了最小值所在的位置,類似於Matlab中find的用法。
The Array class and coefficient-wise operations
Array也是一個模板類,可以跟Matrix進行對比,聲明一個Array對象的方式與Matrix相似:
Array<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>
參數與Matrix一致,這裏不多做介紹,詳細可看官方文檔。Array也定義了許多conventional typedef,如
Array的賦值、加法、減法也與Matrix類似,需要說明的是Array相乘,這個操作類似於Matlab中的".*",對應元素做計算,所以兩個Array相乘,只能是大小相同的Array。
Array提供了一些操作函數,abs(),求每個元素的絕對值;sqrt(),求每個元素的算術平方根;a.min(b),求a和b中每個位置較小的元素。
Matrix和Array可以進行互換,利用matrix中的Array()函數和Array中的Matrix()函數,下面給出實例
需要說明的是,將array表達式賦值給Matrix變量是允許的;cwiseProduct()函數允許Matrix直接進行點對點乘法,而不用轉換至Array。
Block Operations
利用block()函數,可以從Matrix中取出一個小矩陣來進行處理,使用的語法爲matrix.block(i, j, p, q); 和 matrix.block<p, q>(i, j)。其中i,j是block左上角元素位於矩陣中的位置,p、q是block的大小,如果是小塊的話,那麼採用第二種方法,在編譯時就固定p、q,速度會比第一種方法快一些。
需要特別注意的是,block利用非常靈活,原因在於它不僅僅可以是右值表達式,它也可以作爲左值對matrix進行修改。同樣Array也有block函數,如下面實例
Block函數可以從矩陣中提取一塊,特殊的如提取一整行或者一整列,如想達到如此操作,可以使用row()函數和col()函數,已達到更高效率。這兩個函數很類似與Matlab中":"操作符。
如果想在矩陣中操作角落的block,Eigen還提供了一些特殊的函數:
最後一列如果在操作大小固定的小block時,在編譯時提供size,可以達到更快的效果。同樣對於vector,也有類似的操作函數:
Advanced Initialization
簡單初始一個矩陣的方法是利用逗號表達式,初始順序是從左到右、從上到下。可以一個元素一個元素的初始矩陣,也可以一次用一個矩陣去初始大矩陣的一部分,如
同樣利用上面講的block去初始矩陣的一部分:
Eigen提供了一些函數來初始特殊的矩陣,如Zero()用以初始全領的矩陣,其size可以通過參數或者命名空間設置,如Array33f::Zero(), ArrayXf::Zero(3), ArrayXXf::Zero(3, 4);Constant(rows, cols, value),用以初始元素相等的矩陣;Random(),用以初始一個隨機矩陣;Identity(),用以初始一個單位陣,但這個函數只針對Matrix類型,不針對Array類型。LinSpaced(size, low, high)是一個特殊的函數,功能類似Matlab中的冒號用法,如下面實例:
Eigen也提供了一個函數來進行初始設置,如setZero(), MatrixBase::setIdentity(), DenseBase::setLinSpaced()等,如我們要生成一個矩陣[0II0][0II0],可以有下面幾種方法:
上面介紹的例子中,一些方法如Zero(), Constant()等用以初始一個矩陣,我們可以認爲這都是生成一個臨時矩陣用以初始化,但事實是他們返回了一個所謂expression objects,而這不會引起多餘的開銷。例如我們想令一個2×32×3的矩陣行互換,即左乘一個
[0110][0110]
。
上述的finished()是必要的,因爲要獲取一個實際的matrix來進行乘法計算,而不是一個用於初始某個矩陣的臨時矩陣,它表明要在這個矩陣形成之後在用以做乘法計算。
Reductions,Visitors and Broadcasting
Reductions可以看做一個作用在matrix或者vector上的函數,返回結果是一個值,比如之前介紹的sum(), prod(), trace(), minCoeff()等,這裏再介紹一類Reductions—Norm computations。Eigen提供了範數類函數,norm(),如l2l2範數,squaredNorm(), lplp範數,lpNnorm<p>(),這裏的p可以是1或者無窮。
Reductions還有一類波爾類型,即一個矩陣可以對其中每一個元素進行大小比較。all(),如果每個元素都爲真,則爲真;any(),如果有一個元素爲真,返回真;count(),爲真元素的個數,如下面的例子所述:
在使用maxCoeff()和minCoeff()函數是,可以尋求最大元素和最小元素,但如果我們想返回其位置,則需要給定相關參數,而參數的類型我們要使用Index類型,例如:
Reductions還可以部分使用,即返回的不是一個值,而是一組值,可以看做是一種降維操作,所使用的函數爲colwise(), rowwise()。例如
Mat.colwise()理解爲分別去看矩陣的每一列,然後再作用maxCoeff()函數,即求每一列的最大值。
需要注意的是,colwise返回的是一個行向量(列方向降維),rowwise返回的是一個列向量(行方向降維)。
下面說一下Broadcasting,他與Reductions的部分使用類似,但是他作用的類型是vector,相當於把vector按照某種方式進行擴充,再與矩陣進行計算。如
Mat.colwise() += v;是核心語句,相當於[13216792]+[01010101][12693172]+[00001111]
經過上面介紹,我們來一個實例:
m.colwise() – v得到的是 [−10218447−1][−12147084−1],再將得到的矩陣取列,再求l2l2範數的平方,即(m.colwise()-v).colwise().squaredNorm()得到的是[15053250][15053250],最後再求這一列的最小值,並記錄位置。輸出時m.col(index)便將最小的那一列輸出。
Interfacing with Raw Buffers: the Map class
Map類型,給用戶一種選擇:在已經存在的矩陣或者向量中,不必拷貝對象,而是直接在該對象的內存上進行操作。創建語句如下:
Map<Matrix<typename Scalar, int RowsAtCompileTime, int ColsAtCompileTime>>;
由此可見Map需要一個模板參數,還要確保給予一個指向該類型的指針,如
Map<MatrixXf> mf(pf, rows, cols);
其中pf是有一個float指針。
Map還可以接受兩個參量,即Map<typename MatrixType, int MapOptions, tyepname StrideType>,MapOptions可以有Aligned和Unaligned兩個選擇;StrideType是對如何具體選擇數據的一個選擇,看下面兩個實例
上面實例解釋了在創建Map的時候,如何決定內存的存儲順序;下面的實例將重點解釋StrideType
class Eigen::Stride< _OuterStrideAtCompileTime, _InnerStrideAtCompileTime >,這是stride的聲明語法,有兩個int類型的參數:OuterStride是按行存儲矩陣每行之間的增量或者按列存儲矩陣每列之間的增量,InnerStreide是按行存儲矩陣每列之間的增量或者按列存儲矩陣每行之間的增量。具體增量可以編譯時給定,也可以運行時給定,即設置爲Dynamic。上面的例子中,MatrixXi是按列存儲,運行時stride的第一個參數是8,即每列之間相差8個位置;第二個參數是2,即每行直接相差2個位置;所以該矩陣存儲爲
⎡⎣⎢array[0]array[2]array[4]array[8]array[10]array[12]array[16]array[18]array[20]⎤⎦⎥[array[0]array[8]array[16]array[2]array[10]array[18]array[4]array[12]array[20]]
下面給一個使用Map變量的實例
需要注意的是,這裏m2,m2map和m2mapconst指向的是同一塊內存。
同樣,Map也可以改變指向的內存,利用New語句,方法如下:
這裏v改變的只是指向內存的位置,並沒有爲此新開闢內存,而且在聲明v的時候,應該已經有存在的內存區域被指向,即不會使用如下語句:
Map<Matrix3f> A(NULL);
Aliasing
混淆往往出現在賦值號左右都用同一變量的時候,如mat = mat.transpose();解決方法之一是使用eval()函數,看下面兩個實例
如果有xxxInplace()函數,用這個函數可達到最高效的效果,如transposeInPlace()
通常情況下,如果元素的表達只與該元素自己有關,那麼一般不會引起混淆,如語句:mat = (2 * mat – MatrixXf::Identity(2, 2)).array().square();
矩陣乘法是個特殊的操作,編譯器會默認爲其做避免混淆的處理,儘管這會引起額外的開銷。如果你確定不會引起混淆,可以使用noalias()函數來避免額外開銷。
總結一下:
- 對於單個元素操作,如單個元素成績或者加減操作一般不會引起混淆。
- 兩個矩陣相乘時會做避免混淆的處理,如果你能確定不產生混淆,可以使用noalias()函數來避免額外開銷。
- 其他情況下,都認爲沒有混淆存在,爲了避免混淆產生,使用eval()函數或者xxxInPlace()函數,後者效率更高。
Storage orders
Matrix 創建的矩陣默認是按列存儲,如果想修改可以在創建矩陣的時候加入參數,如Matrix<int, 3, 4, ColMajor> Acolmajor; 或者 Matrix<int, 3, 4, RowMajor> Arowmajor;
在選擇存儲順序的時候,要有如下考慮:
- 你的使用者希望你用哪種方式存儲,這可能跟其他處理庫所要求的存儲順序有關。
- 與你所處理的實際算法有關。
- Eigen庫在處理按列存儲的矩陣時會更加高效。