®.運行環境:Linux(RedHat+OpenCV3.0)
1.Mat的作用:
Mat類用於表示一個多維的單通道或者多通道的稠密數組。能夠用來保存實數或複數的向量、矩陣,灰度或彩色圖像,立體元素,點雲,張量以及直方圖(高維的直方圖使用SparseMat保存比較好)。簡而言之,Mat就是用來保存多維的矩陣的。
2.Mat的常見屬性:
- data:
uchar類型的指針,指向Mat數據矩陣的首地址。可以理解爲標示一個房屋的門牌號;
- dims:
Mat矩陣的維度,若Mat是一個二維矩陣,則dims=2,三維則dims=3,大多數情況下處理的都是二維矩陣,是一 個平面上的矩陣。
可以理解爲房屋是一個一層的平房,三維或更多維的則是多層樓房;
- rows:
Mat矩陣的行數。可理解爲房屋內房間行數;
- cols:
Mat矩陣的列數。可理解爲房屋內房間列數;
- size():
首先size是一個結構體,定義了Mat矩陣內數據的分佈形式,數值上有關係式:
image.size().width==image.cols; image.size().height==image.rows
可以理解爲房屋內房間的整體佈局,這其中包括了房間分別在行列上分佈的數量信息;
- channels():
Mat矩陣元素擁有的通道數。例如常見的RGB彩色圖像,channels==3;而灰度圖像只有一個灰度分量信息, channels==1。
可以理解爲每個房間內放有多少牀位,3通道的放了3張牀,單通道的放了1張牀;
- depth:
用來度量每一個像素中每一個通道的精度,但它本身與圖像的通道數無關!depth數值越大,精度越高。在 Opencv中,Mat.depth()得到的是一個0~6的數字,分別代表不同的位數,對應關係如下:
enum{CV_8U=0,CV_8S=1,CV_16U=2,CV_16S=3,CV_32S=4,CV_32F=5,CV_64F=6}
其中U是unsigned的意思,S表示signed,也就是有符號和無符號數。
可以理解爲房間內每張牀可以睡多少人,這個跟房間內有多少牀並無關係;
- elemSize:
elem是element(元素)的縮寫,表示矩陣中每一個元素的數據大小,如果Mat中的數據類型是CV_8UC1,那麼 elemSize==1;如果是CV_8UC3或CV_8SC3,那麼elemSize==3;如果是CV_16UC3或者CV_16SC3,那麼 elemSize==6;即elemSize是以8位(一個字節)爲一個單位,乘以通道數和8位的整數倍;
可以理解爲整個房間可以睡多少人,這個時候就得累計上房間內所有牀位數(通道)和每張牀的容納量了;
- elemSize1:
elemSize加上一個“1”構成了elemSize1這個屬性,1可以認爲是元素內1個通道的意思,這樣從命名上拆分後就很 容易解釋這個屬性了:表示Mat矩陣中每一個元素單個通道的數據大小,以字節爲一個單位,所以有:
eleSize1==elemSize/channels;
- step:
可以理解爲Mat矩陣中每一行的“步長”,以字節爲基本單位,每一行中所有元素的字節總量,是累計了一行中所 有元素、所有通道、所有通道的elemSize1之後的值;
- step1():
以字節爲基本單位,Mat矩陣中每一個像素的大小,累計了所有通道、所有通道的elemSize1之後的值,所以有:
step1==step/elemSize1;
- type:
Mat矩陣的類型,包含有矩陣中元素的類型以及通道數信息,type的命名格式爲CV_(位數)+(數據類型)+(通道數),所有取值如下:
3.矩陣的構造、初始化、釋放
注:1、在程序的最開始加上: using namespace cv; 2、把Mat改爲 cv::Mat (由於本人不會C++所有開始有點沒明白如何使用函數)
創建Mat類的方式:1.構造函數 2.create()函數創建對象3.從已有的數據源初始化
1.構造函數
Mat::Mat()
無參數構造方法;
Mat::Mat(int rows, int cols, int type)
創建行數爲 rows,列數爲col,類型爲type的圖像;
Mat::Mat(Size size, int type)
創建大小爲 size,類型爲type的圖像;
Mat::Mat(int rows, int cols, int type, const Scalar& s)
創建行數爲rows,列數爲col,類型爲type的圖像,並將所有元素初始化爲值s;
Mat::Mat(Size size, int type, const Scalar& s)
創建大小爲 size,類型爲type的圖像,並將所有元素初始化爲值s
Mat::Mat(const Mat& m)
將 m 賦值給新創建的對象,此處不會對圖像數據進行復制, m 和新對象共用圖像數據;
Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
創建行數爲 rows,列數爲col,類型爲type的圖像,此構造函數不創建圖像數據所需內存,而是直接使用data所指內存,圖像的行步長由step指定。
Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
創建大小爲 size,類型爲type的圖像,此構造函數不創建圖像數據所需內存,而是直接使用data所指內存,圖像的行步長由step指定。
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
創建的新圖像爲 m 的一部分,具體的範圍由 rowRange 和 colRange 指定,此構造函數也不進行圖像數據的複製操作,新圖像與m共用圖像數據;
Mat::Mat(const Mat& m, const Rect& roi)
創建的新圖像爲 m 的一部分,具體的範圍 roi 指定,此構造函數也不進行圖像數據的複製操作,新圖像與m共用圖像數據。
type的類型有CV_8UC1,CV_16SC1,…,CV_64FC4等。裏面的8U表示8位無符號整數,16S表示16位有符號整數,64F表示64位浮點數(即double類型);C後面的數表示通道數,例如C1表示一個通道的圖像,C4表示4個通道的圖像,以此類推。
如果你需要更多的通道數,需要用宏CV_8UC(n),例如:Mat M(3,2, CV_8UC(5));//創建行數爲3,列數爲2,通道數爲5的圖像
計算機視覺中,圖像的讀取是圖像處理的基礎,圖像就是一系列像素值,OpenCV使用數據結構cv::Mat來存儲圖像。cv::Mat是一個矩陣類,矩陣中每一個元素都代表一個像素,對於灰度圖像,像素用8位無符號數,0表示黑色,255表示白色。對於彩色像素而言,每個像素需要三位這樣的8位無符號數來表示,即三個通道(R,G,B),矩陣則依次存儲一個像素的三個通道的值,然後再存儲下一個像素點。
cv::Mat中,
cols代表圖像的寬度(圖像的列數),
rows代表圖像的高度(圖像的行數),
step代表以字節爲單位的圖像的有效寬度,
elemSize返回像素的大小,
channels()方法返回圖像的通道數,
total函數返回圖像的像素數。
像素的大小 = 顏色大小(字節)*通道數,
比如:
三通道short型矩陣(CV_16SC3)的大小爲2*3 = 6,
三通道Byte型矩陣(CV_8UC3)的大小爲1*3= 3,像素的channels方法返回圖像的通道數,total函數返回圖像的像素數。
RGB圖像的顏色數目是256*256*256,本文對圖像進行量化,縮減顏色數目到256的1/8(即32*32*32)爲目標,分別利用一下幾種方法實現,比較幾種方法的安全和效率。
方法一:使用Mat的成員函數ptr<>()
cv::Mat中提供ptr函數訪問任意一行像素的首地址,特別方便圖像的一行一行的橫向訪問,如果需要一列一列的縱向訪問圖像,就稍微麻煩一點。但是ptr訪問效率比較高,程序也比較安全,有越界判斷。
- int nl = image.rows; //行數
- int nc = image.cols * image.channels();
- for (int j = 0; j<nl; j++)
- {
- uchar* data = image.ptr<uchar>(j);
- for (int i = 0; i<nc; i++)
- {
- data[i] = data[i] / div*div + div / 2;
- }
- }
方法二:使用迭代器遍歷圖像
cv::Mat同樣有標準模板庫(STL),可以使用迭代器訪問數據。
用迭代器來遍歷圖像像素,可簡化過程降低出錯的機會,比較安全,不過效率較低;如果想避免修改輸入圖像實例cv::Mat,可採用const_iterator。iterator有兩種調用方法,cv::MatIterator_<cv::Vec3b>it;cv::Mat_<cv::Vec3b>::iterator it;中間cv::Vec3b是因爲圖像是彩色圖像,3通道,cv::Vec3b可以代表一個像素。
- cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
- cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
- for (; it != itend; ++it)
- {
- (*it)[0] = (*it)[0] / div*div + div / 2;
- (*it)[1] = (*it)[1] / div*div + div / 2;
- (*it)[2] = (*it)[2] / div*div + div / 2;
- }
方法三:使用Mat的成員函數at<>()
cv::Mat也是向量,可以使at方法取值,使用調用方法image.at<cv::Vec3b>(j,i),at方法方便,直接給i,j賦值就可以隨意訪問圖像中任何一個像素,其中j表示第j行,i表示該行第i個像素。但是at方法效率是這3中訪問方法中最慢的一個,所以如果遍歷圖像或者訪問像素比較多時,建議不要使用這個方法,畢竟程序的效率還是比程序的可讀性要重要的。下面是完整的調用方法,其運行時間在下面會介紹。
- for (int j = 0; j< image.rows; j++)
- {
- for (int i = 0; i< image.cols; i++)
- {
- image.at<cv::Vec3b>(j, i)[0] = image.at<cv::Vec3b>(j, i)[0] / div*div + div / 2;
- image.at<cv::Vec3b>(j, i)[1] = image.at<cv::Vec3b>(j, i)[1] / div*div + div / 2;
- image.at<cv::Vec3b>(j, i)[2] = image.at<cv::Vec3b>(j, i)[2] / div*div + div / 2;
- } // end of line
- }
注意:使用at函數時,應該知道矩陣元素的類型和通道數,根據矩陣元素類型和通道數來確定at函數傳遞的類型,使用的是Vec3b這個元素類型,他是一個包含3個unsigned char類型向量。之所以採用這個類型來接受at的返回值,是因爲,我們的矩陣im是3通道,類型爲unsigned char類型
完整實例:
- #include <iostream>
- #include < opencv.hpp>
- using namespace cv;
- using namespace std;
- int main()
- {
- //新建一個uchar類型的3通道矩陣
- Mat img(5, 3, CV_8UC3, Scalar(50,50,50));
- cout << img.rows << endl; //5
- cout << img.cols << endl; //3
- cout << img.channels() << endl; //3
- cout << img.depth() << endl; //CV_8U 0
- cout << img.dims << endl; //2
- cout << img.elemSize() << endl; //1 * 3,一個位置,三個通道的CV_8U
- cout << img.elemSize1() << endl; //1
- cout << img.size[0] << endl; //5
- cout << img.size[1] << endl; //3
- cout << img.step[0] << endl; //3 * ( 1 * 3 )
- cout << img.step[1] << endl; //1 * 3
- cout << img.step1(0) << endl; //3 * 3
- cout << img.step1(1) << endl; //3
- cout << img.total() << endl; //3*5
- //-------------------------------------- 地址運算 --------------------------------//
- for (int row = 0; row < img.rows; row++)
- {
- for (int col = 0; col < img.cols; col++)
- {
- //[row, col]像素的第 1 通道地址被 * 解析(blue通道)
- *(img.data + img.step[0] * row + img.step[1] * col) += 15;
- //[row, col]像素的第 2 通道地址被 * 解析(green通道)
- *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1()) += 15;
- //[row, col]像素的第 3 通道地址被 * 解析(red通道)
- *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) += 15;
- }
- }
- cout << img << endl;
- //-------------------------------------- Mat的成員函數at<>( ) --------------------------------//
- for (int row = 0; row < img.rows; row++)
- {
- for (int col = 0; col < img.cols; col++)
- {
- img.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
- }
- }
- cout << img << endl;
- //-------------------------------------- 使用Mat的成員函數ptr<>() --------------------------------//
- for (int row = 0; row < img.rows; row++)
- {
- // data 是 uchar* 類型的, m.ptr(row) 返回第 row 行數據的首地址
- // 需要注意的是該行數據是按順序存放的,也就是對於一個 3 通道的 Mat, 一個像素3個通道值, [B,G,R][B,G,R][B,G,R]...
- // 所以一行長度爲:sizeof(uchar) * m.cols * m.channels() 個字節
- uchar* data = img.ptr(row);
- for (int col = 0; col < img.cols; col++)
- {
- data[col * 3] = 50; //第row行的第col個像素點的第一個通道值 Blue
- data[col * 3 + 1] = 50; // Green
- data[col * 3 + 2] = 50; // Red
- }
- }
- cout << img << endl;
- Vec3b *pix(NULL);
- for (int r = 0; r < img.rows; r++)
- {
- pix = img.ptr<Vec3b>(r);
- for (int c = 0; c < img.cols; c++)
- {
- pix[c] = pix[c] * 2;
- }
- }
- cout << img << endl;
- //-------------------------------------- 使用Mat的成員函數ptr<>() --------------------------------//
- MatIterator_<Vec3b> it_im, itEnd_im;
- it_im = img.begin<Vec3b>();
- itEnd_im = img.end<Vec3b>();
- for(; it_im != itEnd_im; it_im++)
- {
- *it_im = (*it_im) * 2;
- }
- cout << img << endl;
- cvWaitKey();
- return 0;
- }