學習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建構函式的列表,有很多,但有些並不常用,這個表格在你需要的時候用來查詢會幫助到你。
建構函式 | 說明 |
---|---|
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); | 指定型別的多維陣列,並指定預先儲存的資料 |
建構函式 | 描述 |
---|---|
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 ); | 從其他矩陣的線性代數表述中聲稱新矩陣的複製建構函式 |
建構函式 | 描述 |
---|---|
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元素數的一維陣列 |
函式 | 描述 |
---|---|
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<>()函式可用的資料型別:
示例 | 描述 |
---|---|
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++;
}