OpenCV2学习笔记(一)
Mat - 圖像的容器
在對(duì)圖像進(jìn)行處理時(shí),首先需要將圖像載入到內(nèi)存中,而Mat就是圖像在內(nèi)存中的容器,管理著圖像在內(nèi)存中的數(shù)據(jù)。Mat是C++ 的一個(gè)類,由于OpenCV2中引入了內(nèi)存自動(dòng)管理機(jī)制,所以不必手動(dòng)的為Mat開(kāi)辟內(nèi)存空間以及手動(dòng)的釋放內(nèi)存。Mat中包含的數(shù)據(jù)主要由兩個(gè)部分構(gòu)成:矩陣頭(矩陣尺寸、存儲(chǔ)方法、存儲(chǔ)地址等信息)和一個(gè)指向存儲(chǔ)圖像所有像素值的矩陣(根據(jù)所選的存儲(chǔ)方法不同的矩陣可以是不同的維數(shù))的指針。
在圖像處理中,對(duì)圖像的處理不可能是在一個(gè)函數(shù)中完成的,這就需要在不同的函數(shù)間傳遞Mat。同時(shí),圖像處理的計(jì)算量是很大,除非萬(wàn)不得已就不要去傳遞比較大的Mat。這就要求使用某種機(jī)制來(lái)實(shí)現(xiàn)Mat的快速傳遞。Mat中主要有矩陣頭和一個(gè)指向矩陣的指針,矩陣頭是一個(gè)常數(shù)值,但是矩陣保存了圖像所有的像素值,通常會(huì)比矩陣頭大幾個(gè)數(shù)量級(jí),因此傳遞Mat是主要的消耗是在矩陣復(fù)制上。為了解決這個(gè)問(wèn)題,OpenCV中引入了計(jì)數(shù)機(jī)制。每個(gè)Mat都有自己的信息頭,但是共享同一個(gè)矩陣,也就是在傳遞Mat時(shí),只復(fù)制矩陣頭和指向矩陣的指針。
1: Mat a,c ; 2: a = imread("d:\\test.jpg",1) ; 3: Mat b(a) ; //拷貝構(gòu)造函數(shù) 4: a = c ; //復(fù)制運(yùn)算符上面代碼中3個(gè)Mat對(duì)象a,b,c指向同一個(gè)矩陣,由于都指向了同一個(gè)矩陣,某一個(gè)對(duì)象對(duì)矩陣進(jìn)行操作時(shí)也會(huì)影響到其他對(duì)象讀取到的矩陣。
多個(gè)對(duì)象同時(shí)使用一個(gè)矩陣,那么當(dāng)不需要該矩陣時(shí),誰(shuí)來(lái)負(fù)責(zé)清理?簡(jiǎn)單的回答是,最后一個(gè)使用它的對(duì)象。通過(guò)引用計(jì)數(shù)機(jī)制,無(wú)論什么時(shí)候Mat對(duì)象的信息頭被復(fù)制了,都會(huì)增加矩陣的引用次數(shù)加1;反之,當(dāng)一個(gè)Mat的信息頭被釋放后,引用計(jì)數(shù)就會(huì)被減1;當(dāng)計(jì)數(shù)被減到0時(shí),矩陣就會(huì)被釋放。
當(dāng)然,有些時(shí)候還是需要拷貝矩陣本身的,這時(shí)候可以使用clone和 copyTo。通過(guò)clone和copyTo創(chuàng)建的Mat,都有自己的矩陣,修改其中一個(gè)的矩陣不會(huì)對(duì)其他的造成影響。
?
訪問(wèn)像素的三種方法
對(duì)圖像像素值的訪問(wèn)是圖像處理最基本的要求,在OpenCV中提供了三種方式來(lái)訪問(wèn)圖像的像素值。
矩陣在內(nèi)存中的存儲(chǔ)
首先來(lái)看一下圖像像素值在內(nèi)存中的保存方式。前面提到,像素值是以矩陣的方式保存的,矩陣的大小取決于圖像采用的顏色模型,確切的說(shuō)是圖像的通道數(shù)。如果是灰度圖像,矩陣是這樣的:
矩陣的每一個(gè)元素代表一個(gè)像素 值。而對(duì)多通道圖像來(lái)說(shuō),一個(gè)像素值需要多個(gè)矩陣元素來(lái)存儲(chǔ),矩陣中的列會(huì)包含多個(gè)子列,其子列數(shù)和通道數(shù)目相等。以常見(jiàn)的RGB模型來(lái)說(shuō):
而且,如果內(nèi)存比較大,圖像中的各行各列就可以一行一行的連接起來(lái),形成一個(gè)長(zhǎng)行。連續(xù)存儲(chǔ)有助于提升圖像的掃描速度,使用iscontinuous來(lái)判斷矩陣是否是連續(xù)存儲(chǔ)的。
顏色空間縮減
如果矩陣元素存儲(chǔ)的是單通道像素,使用8位無(wú)符號(hào)來(lái)保存每個(gè)元素,那么像素可能有256個(gè)不同的值。如果是三通道的話,就會(huì)用一千六百多種顏色。如此多的顏色在有些時(shí)候不是必須的,而且會(huì)對(duì)算法的性能造成嚴(yán)重的影響。在這種情況下,最常用的做法就是顏色空間的縮減,也就是將現(xiàn)有的顏色空間進(jìn)行映射,以獲得較少的顏色數(shù)。例如:顏色值0到9映射為0,10到19映射為10,以此類推。
以簡(jiǎn)單顏色空間縮減為例,使用OpenCV提供的三種方式來(lái)遍歷圖像像素。將各個(gè)顏色值映射關(guān)系存儲(chǔ)到表中,在對(duì)格像素的顏色值進(jìn)行處理時(shí),直接進(jìn)行查表。下面是對(duì)映射表的初始化:
1: ? 2: uchar table[256] ; 3: int divideWith = 10; 4: for(int i = 0 ; i < 256 ; i ++) 5: table[i] = (uchar) ( divideWith * (i / divideWith));這里將各個(gè)像素的顏色值整除以10,然后再乘以10,這樣會(huì)像上面所說(shuō)的將0到9的顏色值映射為0,10到19的顏色值映射為10,以此類推。
指針遍歷圖像 Efficient Way
1: Mat& scanImageWithPointer(Mat &img , const uchar * const table) 2: { 3: CV_Assert(img.depth () == sizeof(uchar)); 4: ? 5: int channels = img.channels() ; 6: ? 7: int rows = img.rows * channels; 8: int cols = img.cols ; 9: ? 10: if(img.isContinuous()) { 11: cols *= rows ; 12: rows = 1 ; 13: } 14: ? 15: uchar * p ; 16: for(int i = 0 ; i < rows ; i ++){ 17: p = img.ptr<uchar>(i); 18: for(int j = 0 ; j < cols ; j ++){ 19: p[j] = table[p[j]] ; 20: } 21: } 22: return img ; 23: }首先使用斷言,只處理使用8位無(wú)符號(hào)數(shù)保存元素值的矩陣。然后在取出舉證的行數(shù)和列數(shù),如果是多通道的話矩陣是有子列的,用通道數(shù)乘以矩陣的行數(shù)作為最終遍歷時(shí)行數(shù)。另外,調(diào)用isContinuous來(lái)判斷矩陣在內(nèi)存中是不是連續(xù)存儲(chǔ)的。p = img.ptr<uchar>(i); 來(lái)獲取每一行開(kāi)始處的指針,然后遍歷至改行的末尾。如果是連續(xù)存儲(chǔ)的,就只需要獲取一次每行的開(kāi)始指針,一路遍歷下去即可。
Mat中的data字段會(huì)返回指向矩陣第一行第一列的指針,通過(guò)可以使用該字段來(lái)檢查圖像是否被載入成功了。當(dāng)矩陣是連續(xù)存儲(chǔ)時(shí),也可以通過(guò)data來(lái)遍歷整個(gè)圖像。
1: uchar * p = img.data ; 2: for(unsigned int i = 0 ; i < img.rows * img.cols * img.channels() ; i ++) 3: *p ++ = table[*p] ;但是這種代碼可讀性差,并且進(jìn)一步操作困難,其在性能上的表現(xiàn)并不明顯的優(yōu)于上面的方法。
迭代遍歷圖像 Safe Method
1: Mat& scanImageWithIterator(Mat &img,const uchar * const table) 2: { 3: CV_Assert(img.depth () == sizeof(uchar)); 4: ? 5: const int channels = img.channels() ; 6: ? 7: switch (channels){ 8: case 1: 9: { 10: MatIterator_<uchar> it,end ; 11: end = img.end<uchar>() ; 12: for(it = img.begin<uchar>(); it != end ; it ++) { 13: *it = table[*it] ; 14: } 15: break ; 16: } 17: case 2: 18: { 19: MatIterator_<Vec3b> it,end ; 20: end = img.end<Vec3b>() ; 21: for(it = img.begin<Vec3b>(); it != end ; it ++) { 22: (*it)[0] = table[(*it)[0]] ; 23: (*it)[1] = table[(*it)[1]] ; 24: (*it)[2] = table[(*it)[2]] ; 25: } 26: break ; 27: } 28: } 29: return img ; 30: } 有兩種方式來(lái)獲取圖像矩陣的迭代器 1: cv::MatIterator_<cv::Vec3b> it = image.begin<cv::Vec3b>(); 2: cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();同樣的獲取圖像的常量迭代器也有兩種方式 1: cv::MatConstIterator_<cv::Vec3b> it = image.begin<cv::Vec3b>(); 2: cv::Mat_<cv::Vec3b>::const_iterator end = image.end<cv::Vec3b>(); 另外需要注意的就是,對(duì)于多通道圖像(例如三通道),每列中有3個(gè)uchar元素,也就是一個(gè)有3個(gè)元素的vector,在OpenCV中使用Vec3b來(lái)命名。如果要訪問(wèn)第n個(gè)子列,只需要使用[]來(lái)操作就可以了。而且,OpenCV迭代中掃過(guò)一行的所有列后會(huì)自動(dòng)的跳到下一列,所以在RGB中如果使用uchar來(lái)進(jìn)行迭代不實(shí)用Vec3b的話只能獲取到B通道的值。at<>遍歷圖像
這種方法不推薦用來(lái)遍歷圖像,它主要用來(lái)獲取或更改圖像的中隨機(jī)元素的。基本用途是用來(lái)訪問(wèn)特定的矩陣元素(知道行數(shù)和列數(shù))
1: Mat& scanImageWithAt(Mat& img,const uchar * const table) 2: { 3: CV_Assert(img.depth () == sizeof(uchar)); 4: const int channels = img.channels() ; 5: ? 6: switch (channels){ 7: case 1: 8: { 9: for (int i = 0 ; i < img.rows ; i ++) 10: for(int j = 0 ; j < img.cols ; j ++) 11: img.at<uchar>(i,j) = table[img.at<uchar>(i,j)] ; 12: break ; 13: } 14: case 2: 15: { 16: Mat_<Vec3b> I = img ; 17: for(int i = 0 ; i < I.rows ; i ++){ 18: for(int j = 0 ; j < I.cols ; j ++){ 19: I(i,j)[0] = table[I(i,j)[0]] ; 20: I(i,j)[1] = table[I(i,j)[1]] ; 21: I(i,j)[2] = table[I(i,j)[2]] ; 22: } 23: } 24: img = I ; 25: break ; 26: } 27: } 28: return img ; 29: } 在Mat種保存矩陣元素的數(shù)據(jù)類型是很重要的,Mat類中也提供了很多的模板方法來(lái)處理不同的矩陣元素類型,這種靈活性卻會(huì)使得簡(jiǎn)單的代碼復(fù)雜化,因此就有了Mat_模板類來(lái)簡(jiǎn)化操作。也就是說(shuō)如果知道了Mat的矩陣元素的數(shù)據(jù)類型,就可以將其轉(zhuǎn)換為Mat_模板類來(lái)簡(jiǎn)化代碼。 前面提到,at<>主要是用來(lái)訪問(wèn)矩陣的隨機(jī)元素的,下面使用該方法來(lái)給一張圖像添加椒鹽噪聲。 1: //n添加椒鹽噪聲的個(gè)數(shù) 2: void salt(Mat& img,int n) 3: { 4: for(int k = 0 ; k < n ; k ++) { 5: int i = rand() % img.rows ; 6: int j = rand() % img.cols ; 7: 8: if(img.channels() == 1){ 9: img.at<uchar>(i,j) = 255 ; 10: }else if(img.channels() == 3){ 11: img.at<Vec3b>(i,j)[0] = 255 ; 12: img.at<Vec3b>(i,j)[1] = 255 ; 13: img.at<Vec3b>(i,j)[2] = 255 ; 14: } 15: } 16: }lenna圖片添加椒鹽噪聲后
這里要提下,在使用imshow將Mat顯示到命名窗口時(shí),需要調(diào)用waitkey()這個(gè)函數(shù)下,不然的話在命名窗口顯示不出來(lái)。
LUT
在圖像處理中,對(duì)圖像的所有像素重新映射是很常見(jiàn)的,在OpenCV中提供一個(gè)函數(shù)來(lái)實(shí)現(xiàn)該該操作,不需要去掃描整個(gè)圖像,operation on array :LUT。
1: Mat lookupTable(1,256,CV_8S); 2: uchar *p = lookupTable.data ; 3: for(int i = 0 ; i < 256 ; i ++) 4: p[i] = table[i] ; 5: ? 6: Mat result ; 7: LUT(img,lookupTable,result) ;void LUT(InputArray src, InputArray lut, OutputArray dst)?轉(zhuǎn)載于:https://www.cnblogs.com/wangguchangqing/p/3660623.html
總結(jié)
以上是生活随笔為你收集整理的OpenCV2学习笔记(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 第15章-输入/输出 --- 理解Jav
- 下一篇: sdut 2153:Clockwise(