C++好用的算法庫(一)——Eigen

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()函數來避免額外開銷。

    總結一下:

  1. 對於單個元素操作,如單個元素成績或者加減操作一般不會引起混淆。
  2. 兩個矩陣相乘時會做避免混淆的處理,如果你能確定不產生混淆,可以使用noalias()函數來避免額外開銷。
  3. 其他情況下,都認爲沒有混淆存在,爲了避免混淆產生,使用eval()函數或者xxxInPlace()函數,後者效率更高。

Storage orders

    Matrix 創建的矩陣默認是按列存儲,如果想修改可以在創建矩陣的時候加入參數,如Matrix<int, 3, 4, ColMajor> Acolmajor; 或者 Matrix<int, 3, 4, RowMajor> Arowmajor;

    在選擇存儲順序的時候,要有如下考慮:

  1. 你的使用者希望你用哪種方式存儲,這可能跟其他處理庫所要求的存儲順序有關。
  2. 與你所處理的實際算法有關。
  3. Eigen庫在處理按列存儲的矩陣時會更加高效。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章