1. 程式人生 > >學習OpenCV3——第四章:影象和大型陣列型別

學習OpenCV3——第四章:影象和大型陣列型別

一、動態可變的儲存

本章,我們將進入大型陣列型別的世界,他們之中最主要的當屬cv::Mat,這個結構可以視為是OpenCV所有C++實現的核心,OpenCV所有主要函式都或是cv::Mat類的成員,或是將cv::Mat作為引數,或是返回一個cv::Mat型別。很少有函式和這三者都沒有關係。

cv::Mat類用於表示任意維度的稠密陣列。在本章中,“稠密”表示該陣列的所有部分都有一個值儲存,即使這個值為0。對於大多數影象來說,都是以稠密陣列的形式儲存的;與此相對的當然還有稀疏陣列,在spare array中被實現,稀疏陣列中只有非0的數值會被儲存。從結論上來說,如果陣列的很多地方都是0,那麼稀疏陣列會非常節約記憶體。但是在陣列比較稠密的時候,稀疏陣列反而會浪費大量記憶體。一個常用的使用稀疏陣列比稠密陣列好的例子是,統計直方圖的大部分數值都為0,我們沒有必要儲存所有值為0的部分。

1、cv::Mat類N維稠密陣列         cv::Mat類可以作為任意維度的陣列使用,其資料可以看做是以按照柵格掃描順序儲存的n維陣列。這意味著在一維陣列中,元素是按順序排列的;而在一個二維陣列中,資料按行組織,每一行也按順序排列,對於三維陣列來說,所有的通道都被行填充,每一個通道同樣按順序排列。         所有的矩陣都包含:

  • 一個表示它所包含陣列型別的元素flag
  • 一個表示其維度的元素dims
  • 分別表示行和列的數目的元素rows和cols(在dims大於2的時候無效)
  • 一個表示資料真正儲存位置的data指標
  • 一個表示該記憶體區域有多少個引用的refcount元素,類似於cv::Ptr<>的引用計數器

2、建立一個數組 

       資料型別包括CV_{8U,16S,16U,32S,32F,64F}C{1,2,3} 。例如:CVC_32FC3 表示一個三通道的32位浮點數資料。

常見的構造方式有:

// 先宣告,再建立資料
cv::Mat m;
// Create data area for 3 rows and 10 columns of 3-channel 32-bit floats
m.create( 3, 10, CV_32FC3 );
// Set the values in the 1st channel to 1.0, the 2nd to 0.0, and the 3rd to 1.0
m.setTo( cv::Scalar( 1.0f, 0.0f, 1.0f ) );

等效於:
// 宣告同時建立
cv::Mat m( 3, 10, CV_32FC3, cv::Scalar( 1.0f, 0.0f, 1.0f ) );

表4-1是一個完整的cv:Mat建構函式的列表,有很多,但有些並不常用,這個表格在你需要的時候用來查詢會幫助到你。

表4-1:cv::Mat的建構函式(非複製構造)
建構函式 說明
cv::Mat; 預設建構函式
cv::Mat( int rows, int cols, int type ); 指定型別的二維陣列
cv::Mat(int rows, int cols, int type,const Scalar& s); 指定型別的二維陣列,並指定初始值
cv::Mat(int rows, int cols, int type,void* data, size_t step=AUTO_STEP); 指定型別的二維陣列,並指定預先儲存的資料
cv::Mat( cv::Size sz, int type ); 指定型別的二維陣列(大小由sz決定)
cv::Mat(cv::Size sz,int type, const Scalar& s); 指定型別的二維陣列,並指定初始化值(大小由sz決定)
cv::Mat(cv::Size sz, int type,void* data, size_t step=AUTO_STEP); 指定型別的二維陣列,並指定預先儲存的資料(大小由sz決定)
cv::Mat(int ndims, const int* sizes,int type); 指定型別的多維陣列
cv::Mat(int ndims, const int* sizes,int type, const Scalar& s); 指定型別的多維陣列,並指定初始化值
cv::Mat(int ndims, const int* sizes,int type, void* data,size_t step=AUTO_STEP); 指定型別的多維陣列,並指定預先儲存的資料
表4-2:cv::Mat從其他cv::Mat複製資料的建構函式
建構函式 描述
cv::Mat( const Mat& mat ); 複製建構函式
cv::Mat(const Mat& mat,const cv::Range& rows,const cv::Range& cols); 只從指定的行列中複製資料的複製建構函式
cv::Mat(const Mat& mat,const cv::Rect& roi); 只從感興趣的去榆中複製資料的複製建構函式
cv::Mat(const Mat& mat,const cv::Range* ranges); 服務於n維陣列的,從而泛化的感興趣區域中複製資料的複製建構函式
cv::Mat( const cv::MatExpr& expr ); 從其他矩陣的線性代數表述中聲稱新矩陣的複製建構函式
表4-4:cv::Mat模板建構函式
建構函式 描述
cv::Mat(const cv::Vec& vec,bool copyData=true); 構造一個如同cv::Vec所指定的資料型別為T、大小為n的一維陣列
cv::Mat(const cv::Matx& vec,bool copyData=true); 構造一個如同cv::Matx所指定的資料型別為T、大小為m*n的二維陣列
cv::Mat(const std::vector& vec,bool copyData=true); 構造STL的vector所指定的資料型別為T、大小為vector元素數的一維陣列
表4-5:構造cv::Mat的靜態方法
函式 描述
cv::Mat::zeros( rows, cols, type ); 構造一個大小為rows*cols、資料型別為type指定型別的、數值全為0的矩陣
cv::Mat::ones( rows, cols, type ); 構造一個大小為rows*cols、資料型別為type指定型別的、數值全為1的矩陣
cv::Mat::eye( rows, cols, type ); 構造一個大小為rows*cols、資料型別為type指定型別的單位矩陣

3、獨立獲取陣列元素

OpenCV有幾種不同的訪問矩陣的方法,這些方法均是為了方便對某一種資料型別進行訪問而設計。然而,最近的OpenCV版本投入大量的精力使其幾十不完全相同,也可以進行對比,並進行高效的訪問。訪問一個元素的兩種主要的方法是通過位置迭代器訪問。

  • 直接訪問是通過模板函式at<>()來實現的:
    //單通道----------------------------------
    cv::Mat m = cv::Mat::eye( 10, 10, 32FC1 );
    printf("Element (3,3) is %f\n",m.at<float>(3,3));
    
    //多通道----------------------------------
    cv::Mat m = cv::Mat::eye( 10, 10, 32FC2 );
    printf("Element (3,3) is (%f,%f)\n",m.at<cv::Vec2f>(3,3)[0],m.at<cv::Vec2f>(3,3)[1]);
    
    //構建一個複數陣列-------------------------
    cv::Mat m = cv::Mat::eye( 10, 10, cv::DataType<cv::Complexf>::type );
    printf("Element (3,3) is %f + i%f\n",m.at<cv::Complexf>(3,3).re,m.at<cv::Complexf>(3,3).im,);
    
    

     表4-6列舉了at<>()函式可用的資料型別:

表4-6:at<>()訪問器函式的變體
示例 描述
M.at<int>( i ); 整型陣列M中的元素
M.at<float>( i, j ); 浮點型陣列M中的元素
M.at<int>( pt ); 整型矩陣M中處於(pt.x, pt.y)的元素
M.at<float>( i, j, k ); 三維浮點型矩陣M中處於(i, j, k)位置的元素
M.at<uchar>( idx ); 無符號字元陣列M中位於idx[]所索引的n維位置的元素
  • 迭代器訪問

所有的迭代器都必須在陣列建立的時候宣告並且指定一個物件型別。下面有一個簡單的使用迭代器來計算三通道陣列中“最長”元素(一個三維向量域)的例子:

int sz[3] = { 4, 4, 4 };
cv::Mat m( 3, sz, CV_32FC3 ); // A three-dimensional array of size 4-by-4-by-4
cv::randu( m, -1.0f, 1.0f ); // fill with random numbers from -1.0 to 1.0

float max = 0.0f; // minimum possible value of L2 norm
cv::MatConstIterator<cv::Vec3f> it = m.begin();
while( it != m.end() ) 
{
    len2 = (*it)[0]*(*it)[0]+(*it)[1]*(*it)[1]+(*it)[2]*(*it)[2];
    if( len2 > max ) max = len2;
    it++;
}

還有一種形式的迭代器,儘管不像cv::MatIterator<>那樣將不連續的記憶體區域打包以同時處理多個數組的迭代。這個迭代器稱為

cv::NaryMatIterator,它只要求被迭代的陣列有相同的幾何結構(維度以及每一個維度的範圍)。

該迭代器不會返回一個用於迭代的單獨元素,而通過返回一堆陣列來進行N-ary迭代器操作,這些返回的陣列也稱為“面”。一個面表示輸入陣列有連續記憶體的部分(一般來說是一維或者二維的片段)。

示例4-1:按面進行多維數相加

const int n_mat_size = 5;
const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
cv::Mat n_mat( 3, n_mat_sz, CV_32FC1 );
cv::RNG rng;
rng.fill( n_mat, cv::RNG::UNIFORM, 0.f, 1.f );
const cv::Mat* arrays[] = { &n_mat, 0 };
cv::Mat my_planes[1];
cv::NAryMatIterator it( arrays, my_planes );

// On each iteration, it.planes[i] will be the current plane of the
// i-th array from 'arrays'.
float s = 0.f; // Total sum over all planes
int n = 0; // Total number of planes
for (int p = 0; p < it.nplanes; p++, ++it) 
{
    s += cv::sum(it.planes[0])[0];
    n++;
}

為了看到N-ary迭代器的實際效用,考慮將上個例子稍微拓展一下,變成兩個陣列相加(示例4-2)

const int n_mat_size = 5;
const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
cv::Mat n_mat0( 3, n_mat_sz, CV_32FC1 );
cv::Mat n_mat1( 3, n_mat_sz, CV_32FC1 );
cv::RNG rng;
rng.fill( n_mat0, cv::RNG::UNIFORM, 0.f, 1.f );
rng.fill( n_mat1, cv::RNG::UNIFORM, 0.f, 1.f );
const cv::Mat* arrays[] = { &n_mat0, &n_mat1, 0 };
cv::Mat my_planes[2];
cv::NAryMatIterator it( arrays, my_planes );

float s = 0.f; // Total sum over all planes in both arrays
int n = 0; // Total number of planes
for(int p = 0; p < it.nplanes; p++, ++it) 
{
    s += cv::sum(it.planes[0])[0];
    s += cv::sum(it.planes[1])[0];
    n++;
}