數字圖像可看做一個數值矩陣, 其中的每一個元素表明一個像素點,以下圖所示:html
OpenCV 中,用 Mat 來表示該數值矩陣,它是很關鍵的一種數據結構,由於 OpenCV 中的大部分函數都和 Mat 有關:數據結構
有的是 Mat 的成員函數;有的把 Mat 做爲參數;還有的將 Mat 做爲返回值函數
Mat,在 OpenCV 中表示的是 N 維稠密矩陣,與稠密矩陣相對的是稀疏矩陣(只存儲非零的像素值),後者經常使用於直方圖處理中,OpenCV 中對應爲 cv::SparseMatspa
以下所示:第一個爲稠密矩陣的存儲方式,存儲全部的像素數值;第二個爲稀疏矩陣的存儲方式,只存儲非零的像素值.net
$\quad \begin{bmatrix} 0 & 2 & 0 \\ 1 & 0 & 1 \\ 0 & 2 & 0 \end{bmatrix} $ $\quad \begin{bmatrix} & 2 & \\ 1 & & 1 \\ & 2 & \end{bmatrix} $指針
當 N=1 時,全部像素存儲爲一行;當 N=2 時,全部像素按照一行行的順序排列;當 N=3 時,全部像素按照一面面的順序排列,其中一行行的像素構成一個平面。code
下圖左,爲灰度圖的存儲方式;圖右,爲 RGB 圖像的存儲方式,注意其存儲順序爲 BGR (Blue->Green->Red)htm
Mat 類包含兩部分,一是 矩陣頭 (matrix header),二是 矩陣指針 (pointer to matrix),部分矩陣頭以下:blog
int flags; // signaling the contents of the matrix int dims; // dimensions int rows, cols; // rows and columns MatSize size; // MatStep step; //
矩陣指針以下,指向包含全部像素值的矩陣索引
uchar* data; // pointer to the data
Mat 類中的賦值算子 "=" 和 拷貝構造函數,涉及的是淺拷貝,所以,當執行這兩個操做時,僅僅是複製了矩陣頭。
若是想要深拷貝,達到複製圖像矩陣的目的,應使用 clone() 或 copyTo() 函數,以下圖所示 (摘自參考資料 -- 4):
下面是簡單的驗證,將矩陣 m3 經過 copyTo() 函數複製給 m1,而 m2 是經過 m1 直接賦值的,兩者指向的是一樣的數據。所以,若是改變了 m1,則 m2 對應的矩陣數值,也會進行相應的改變。
Mat m1(3, 3, CV_32FC1, Scalar(1.1f) ); cout << "m1 = " << endl << " " << m1 << endl << endl;
// using assign operator Mat m2 = m1; cout << "m2 = " << endl << " " << m2 << endl << endl; Mat m3(3, 3, CV_32FC1, Scalar(3.3f) ); m3.copyTo(m1); cout << "m1 = " << endl << " " << m1 << endl << endl; cout << "m2 = " << endl << " " << m2 << endl << endl;
在建立 Mat 以前,首先了解 Mat 中元素的數據類型,其格式爲 CV_{8U, 16S, 16U, 32S, 32F, 64F}C{1, 2, 3} 或 CV_{8U, 16S, 16U, 32S, 32F, 64F}C(n)
第一個 {} 內數據表示的意義以下:
CV_8U - 8-bit 無符號整數 ( 0..255 ) CV_8S - 8-bit 有符號整數 ( -128..127 ) CV_16U - 16-bit 無符號整數 ( 0..65535 ) CV_16S - 16-bit 有符號整數 ( -32768..32767 ) CV_32S - 32-bit 有符號整數 ( -2147483648..2147483647 ) CV_32F - 32-bit 浮點數 ( -FLT_MAX..FLT_MAX, INF, NAN ) CV_64F - 64-bit 浮點數 ( -DBL_MAX..DBL_MAX, INF, NAN )
第二個 {} 內的數據 或 (n),表示的是圖像矩陣的通道數,CV_8UC3 則等價於 CV_8UC(3),表示的數據類型爲:3通道8位無符號整數
建立一個 3 行 5 列,3 通道 32 位,浮點型的矩陣,通道 1, 2, 3 的值分別爲 1.1f,2.2f,3.3f
Mat m(3, 5, CV_32FC3, Scalar(1.1f, 2.2f, 3.3f) ); cout << "m = " << endl << " " << m << endl << endl;
輸出的矩陣以下:
使用 Mat() + create() + setTo(),也能夠構建如上的數值矩陣
Mat m;
// Create data area for 3 rows and 10 columns of 3-channel 32-bit floats m.create(3,5,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(Scalar(1.1f, 2.2f,3.3f)); cout << "m = " << endl << " " << m << endl << endl;
單位矩陣 (ones),對角矩陣 (eye),零矩陣 (zeros),以下所示:
// 單位矩陣 Mat O = Mat::ones(3, 3, CV_32F); cout << "O = " << endl << " " << O << endl << endl; // 零矩陣 Mat Z = Mat::zeros(3, 3, CV_8UC1); cout << "Z = " << endl << " " << Z << endl << endl; // 對角矩陣 Mat E = Mat::eye(3, 3, CV_64F); cout << "E = " << endl << " " << E << endl << endl;
經常使用來遍歷 Mat 元素的基本函數爲 at<>(),其中 <> 內的數據類型,取決於 Mat 中元素的數據類型,兩者的對應關係以下:
CV_8U -- Mat.at<uchar>(y,x) CV_8S -- Mat.at<schar>(y,x) CV_16U -- Mat.at<ushort>(y,x) CV_16S -- Mat.at<short>(y,x) CV_32S -- Mat.at<int>(y,x) CV_32F -- Mat.at<float>(y,x) CV_64F -- Mat.at<double>(y,x)
簡單的遍歷以下,使用了 Qt 的 qDebug() 來顯示輸出
Mat m1 = Mat::eye(10, 10, CV_32FC1);
// use qDebug() qDebug() << "Element (3,3) is : " << m1.at<float>(3,3); Mat m2 = Mat::eye(10, 10, CV_32FC2);
// use qDebug()
qDebug() << "Element (3,3) is " << m2.at<cv::Vec2f>(3,3)[0] << "," << m2.at<cv::Vec2f>(3,3)[1];
注意:at<>() 函數中 () 內,行索引號在前,列索引號在後,也即 (y, x)
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table) { // accept only char type matrices CV_Assert(I.depth() == CV_8U); int channels = I.channels(); int nRows = I.rows; int nCols = I.cols * channels; if (I.isContinuous()) { nCols *= nRows; nRows = 1; } int i,j; uchar* p; for(i=0; i<nRows; ++i) { p = I.ptr<uchar>(i); for (j = 0; j<nCols; ++j) { p[j] = table[p[j]]; } } return I; }
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table) { // accept only char type matrices CV_Assert(I.depth() == CV_8U); const int channels = I.channels(); switch(channels) { case 1: { MatIterator_<uchar> it, end; for(it=I.begin<uchar>(), end=I.end<uchar>(); it!=end; ++it) *it = table[*it]; break; } case 3: { MatIterator_<Vec3b> it, end; for(it=I.begin<Vec3b>(), end=I.end<Vec3b>(); it!=end; ++it) { (*it)[0] = table[(*it)[0]]; (*it)[1] = table[(*it)[1]]; (*it)[2] = table[(*it)[2]]; } } } return I; }
比較上面兩種方法的耗時,可以使用以下代碼來進行計算:
double t = (double)getTickCount(); // do something ... t = ((double)getTickCount() - t)/getTickFrequency(); qDebug() << "Times passed in seconds: " << t << endl; // using qDebug()
1. <Learning OpenCV3> chapter 4
2. OpenCV Tutorials / The Core Functionality (core module) / Mat - The Basic Image Container
3. OpenCV Tutorials / The Core Functionality (core module) / How to scan images, lookup tables and time measurement with OpenCV