1. 程式人生 > >Boost的動態多維陣列-multi_array

Boost的動態多維陣列-multi_array

簡短的例子

以下是使用 multi_array 的一個簡短例子:

  1. #include "boost/multi_array.hpp"
  2. #include <cassert>
  3. int main () {
  4.   // 建立一個 3 x 4 x 2 的 3D 陣列
  5.   typedef boost::multi_array<double, 3> array_type;
  6.   typedef array_type::index index;
  7.   array_type myarray(boost::extents[3][4][2]);
  8.   // 賦值到陣列的元素
  9.   int values = 0;
  10.   for(index i = 0; i != 3; ++i)
  11.     for(index j = 0; j != 4; ++j)
  12.       for(index k = 0; k != 2; ++k)
  13.         myarray[i][j][k] = values++;
  14.   // 校驗元素值
  15.   int verify = 0;
  16.   for(index i = 0; i != 3; ++i)
  17.     for(index j = 0; j != 4; ++j)
  18.       for(index k = 0; k != 2; ++k)
  19.         assert(myarray[i][j][k] == verify++);
  20.   return 0;
  21. }

上面的程式碼可以告訴我們這樣一些資訊:

  • boost::multi_array是一個模板類,第一個模板引數指定元素資料型別;第二個模板引數是一個數值,指出陣列維度。
  • multi_array::index型別定義用於描述資料索引,一般來說它就是int
  • boost:: extents的連續多個[]操作用於指明每個維度的大小(另外,也可以使用boost::array來指明維度大小,如boost:: array<int,3> dims={3,4,2}; array_type myarray(dims);)
  • boost::multi_array的讀寫介面和原生多維陣列相同

把連續記憶體適配成多維陣列

有時我們需要把已有的一段連續記憶體(比如一維陣列)當作多維陣列使用,Boost.MultiArray提供了multi_array_ref和const_multi_array_ref類用於把原始記憶體塊適配成多維陣列。

如,可以如下修改前例的程式碼,在這個程式碼裡使用multi_array_ref代替multi_array,它把double buf[24]適配成了“double A[3][4][2]”:

  1. /* 原來的程式碼
  2.   typedef boost::multi_array<double, 3> array_type;
  3.   typedef array_type::index index;
  4.   array_type myarray(boost::extents[3][4][2]);
  5. */
  6.   typedef boost::multi_array_ref<double, 3> array_type;
  7.   typedef array_type::index index;
  8.   double buf[24]; //連續記憶體塊
  9.   array_type myarray(buf, boost::extents[3][4][2]); //把buf適配成3 x 4 x 2 的3D陣列
  10. ...
  11. Boost.MultiArray是一個動態多維陣列庫。它實現了一個通用、與標準庫的容器一致的介面,並且具有與C++中內建的多維陣列一樣的介面和行為。 子檢視

    子 檢視,在影象處理中定義為從一個大的圖片(如100x100的圖片)中取一小塊(如左上角25,25到右下角75,75的50x50區域),把它看作一個 新的圖片,這個新的圖片即為子檢視(可以參考本站GIL庫教程)。多維陣列的概念與此相似,作為聰明的你肯定已經知道是怎麼一回事了。下面是生成子檢視的 例子,還是以前例程式碼為基礎,從3x4x2的myarray陣列中產生一個2x2x2的子檢視,其中第二維以2步進:

    1. ...
    2.   typedef array_type::index_range range;
    3.   array_type::array_view<3>::type myview =
    4.     myarray[ boost::indices[range(1,3)][range(0,4,2)][range(0,2)] ];
    5.   for (array_type::index i = 0; i != 2; ++i)
    6.     for (array_type::index j = 0; j != 2; ++j)
    7.       for (array_type::index k = 0; k != 2; ++k)
    8.     assert(myview[i][j][k] == myarray[i+1][j*2][k]);

    上面的程式碼我們總結出的資訊是:

    • multi_array的子檢視型別由它的array_view::type型別定義確定,這個array_view是個模板類,其引數指定了檢視的維度。
    • multi_array的[]操作除了接受整數來模擬原生陣列以外,還接受boost::indices物件的[]操作所返回的東東(這個東東是index_gen型別,我們只管用,不予深究),向multi_array的[]操作傳入這個東東可以得到一個子檢視。
    • multi_array有個index_range型別,用於指定索引的起始、終止和步進值(預設是1)。boost::indices物件的[]操作接受這個型別的資料才能生成指定區域的子檢視。
    • 子檢視的用法和多維陣列一樣

    boost::indices物件的[]操作除了接受index_range,還可以接受整型數值,這樣我們可以生成一個比原有陣列的維度更少的子檢視(也稱為切片)。

    下面的程式碼取3維陣列myarray的X軸為1的一個2維切片(假設把myarray看成一個3維空間,3個維度3x4x2分別對應於ZxYxX三個軸)

    1. ...
    2.   typedef array_type::index_range range;
    3.   array_type::array_view<2>::type myview =
    4.     myarray[ boost::indices[range(1,3)][range(0,4,2)][1] ];
    5.   for (array_type::index i = 0; i != 2; ++i)
    6.     for (array_type::index j = 0; j != 2; ++j)
    7.         assert(myview[i][j] == myarray[i+1][j*2][1]);

  12. 儲存的順序

    每 個數組類都提供了接受一個儲存順序引數的建構函式。這在與某些跟標準C的陣列儲存順序不同的遺留程式碼進行介面時非常有用,如 FORTRAN. 可選的值有 c_storage_order, fortran_storage_order, 和 general_storage_order.

    c_storage_order 是預設值,它將以C陣列相同的順序在記憶體中儲存各元素,即從後往前儲存各個維度。

    fortran_storage_order 則以 FORTRAN 的順序在記憶體中儲存各個元素:從第一個維度到最後一個。注意,在使用這個引數時,陣列的索引仍然保持為從零起計。

    例子

    1. typedef boost::multi_array<double,3> array_type;
    2. array_type A(boost::extents[3][4][2],boost::fortran_storage_order);
    3. call_fortran_function(A.data());

    general_storage_order 允許你定製在記憶體中儲存各個維度的順序,以及各個維度是按升序還是降序來儲存。

    例子

    1. typedef boost::general_storage_order<3> storage;
    2. typedef boost::multi_array<int,3> array_type;
    3. // 先儲存最後一個維度,然後是第一個維度,最後是中間
    4. array_type::size_type ordering[] = {2,0,1};
    5. // 以降序儲存第一個維度(維度0)
    6. bool ascending[] = {false,true,true};
    7. array_type A(extents[3][4][2],storage(ordering,ascending)); 

    改變陣列的形狀

    Boost.MultiArray 陣列提供了整形操作。只要維數和元素數量保持不變,陣列的形狀即維度的大小改變。

    例子

    1. ...
    2.   boost::array<int,3> dims = {2, 3, 4};
    3.   myarray.reshape( dims );
    4.   //現在myarray是2x3x4的陣列了

    改變陣列的大小

    和 標準庫的容器一樣,boost::multi_array類也提供了保留元素的調整大小操作。維數必須保持一致,但每個維度的長度可以按需要增加或減少。 當一個數組被擴大時,原有元素將被複制到新的記憶體中,然後舊記憶體中的元素將被析構。而陣列中的新元素將是預設構造的。但是,如果新陣列的某些維度的大小是 縮短的,則有些元素將不再可用。

    例子

    1. myarray.resize(boost::extents[1][2][3]);

  13. 使用multi_array做影象格式轉換

    本站有AGGCImg的影象處理教程,AGG庫傾向於向量繪圖,CImg傾向於影象處理。我們可以考慮雙劍合壁,共同來生成我們要的影象。可是它們的內部資料格式卻不完全相同:

    AGG的內部格式是 color buf[y][x][v]
    CImg的內部格式是 color buf[v][y][x]
    其中color為單通道顏色值、v代表顏色通道(如RGB三色)、x,y是座標。我們這裡讓CImg的z軸為1,即二維影象。

    我們得找個方法可以方便地互相轉換,這裡我們選用multi_array來做這件事(另,GIL也是一個不錯的候選方案,見本站GIL教程)。

    1. #include "boost/multi_array.hpp" // multi_array
    2. #include "cimg.h" // CImg
    3. #include <agg_pixfmt_rgb.h> //後面全是AGG的
    4. #include <agg_scanline_u.h>
    5. #include <agg_renderer_scanline.h>
    6. #include <../font_win32_tt/agg_font_win32_tt.h>
    7. #include <agg_font_cache_manager.h>
    8. #include <agg_conv_bspline.h>
    9. #include <agg_path_storage.h>
    10. #include <agg_conv_curve.h>
    11. #include <agg_conv_transform.h>
    12. #include <agg_ellipse.h>
    13. #include <agg_trans_single_path.h>
    14. using cimg_library::CImg;
    15. int main () {
    16.     // AGG畫圖,寫了一行字
    17.     char buf[200][300][3];
    18.     agg::rendering_buffer rbuf(
    19.             (unsigned char*)buf,
    20.             300, 200,
    21.             300*3);
    22.     agg::pixfmt_rgb24 pixf(rbuf);
    23.     agg::renderer_base<agg::pixfmt_rgb24> renb(pixf);
    24.     typedef agg::font_engine_win32_tt_int16 fe_type;
    25.     typedef agg::font_cache_manager<fe_type> fcman_type;
    26.     renb.clear(agg::rgba(0.5,0.5,1));
    27.     fe_type font(::GetDC(0));
    28.     fcman_type font_manager(font);
    29.     font.height(32.0);
    30.     font.flip_y(true);
    31.     font.hinting(true);
    32.     if(!font.create_font("Comic Sans MS",agg::glyph_ren_outline)) return -1;
    33.     //座標轉換管道
    34.     typedef agg::conv_curve<
    35.         fcman_type::path_adaptor_type
    36.     > cc_pa_type;
    37.     cc_pa_type ccpath(font_manager.path_adaptor());
    38.     typedef agg::conv_transform<cc_pa_type,
    39.         agg::trans_single_path> ct_cc_pa_type;
    40.     agg::trans_single_path trans_path;
    41.     ct_cc_pa_type ctpath(ccpath, trans_path);
    42.     agg::path_storage ps;
    43.     ps.move_to(20,100);
    44.     ps.line_rel(80,50);
    45.     ps.line_rel(100,-100);
    46.     ps.line_rel(100,100);
    47.     agg::conv_bspline<agg::path_storage> cb_ps(ps);
    48.     trans_path.add_path(cb_ps);
    49.     agg::rasterizer_scanline_aa<> ras;
    50.     agg::scanline_u8 sl;
    51.     double x=0, y=0;
    52.     for(const wchar_t *p = L"http://www.cpp-prog.com"; *p; p++)
    53.     {
    54.         const agg::glyph_cache* gc = font_manager.glyph(*p);
    55.         if(gc)
    56.         {
    57.             font_manager.init_embedded_adaptors(gc, x, y);
    58.             ras.add_path(ctpath);
    59.             x += gc->advance_x;
    60.             y += gc->advance_y;
    61.         }
    62.     }
    63.     agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(1,0,0));
    64.     // 定義CImg
    65.     CImg<unsigned char> img(300,200,1,3);
    66.     // 用multi_array把AGG影象資料轉成CImg
    67.     typedef boost::multi_array_ref<unsigned char,3> array_type;
    68.     // AGG->多維陣列
    69.     /**
    70.     * AGG排列是[y][x][v], CImg是[v][y][x]
    71.     * 設y=2, x=1, v=0(C語言預設順序)
    72.     * 則AGG的[2][1][0]對應CImg就是[0][2][1]
    73.     */
    74.     typedef boost::general_storage_order<3> storage;
    75.     array_type::size_type ordering[] = {0,2,1};
    76.     bool ascending[] = {true,true,true};
    77.     array_type array_agg((unsigned char*)buf,boost::extents[3][200][300],storage(ordering,ascending));
    78.     // 把AGG影象資料賦值給CImg,multi_array_ref內部做轉換工作
    79.     array_type(img.data, boost::extents[3][200][300]) = array_agg;
    80.     // CImg影象處理
    81.     img.blur(6).noise(10).erode(4);
    82.     // 把CImg處理過的影象又傳給AGG
    83.     array_agg = array_type(img.data, boost::extents[3][200][300]);
    84.     // AGG在此基礎上畫圖
    85.     agg::render_scanlines_aa_solid(ras,sl,renb,agg::rgba(1,1,0));
    86.     // 給CImg顯示
    87.     array_type(img.data, boost::extents[3][200][300]) = array_agg;
    88.     img.erode(2);
    89.     img.display("view");  
    90.     return 0;
    91. }

    顯示效果: