OpenCV2学习笔记(一)
Mat - 圖像的容器
在對圖像進行處理時,首先需要將圖像載入到內存中,而Mat就是圖像在內存中的容器,管理著圖像在內存中的數據。Mat是C++ 的一個類,由于OpenCV2中引入了內存自動管理機制,所以不必手動的為Mat開辟內存空間以及手動的釋放內存。Mat中包含的數據主要由兩個部分構成:矩陣頭(矩陣尺寸、存儲方法、存儲地址等信息)和一個指向存儲圖像所有像素值的矩陣(根據所選的存儲方法不同的矩陣可以是不同的維數)的指針。
在圖像處理中,對圖像的處理不可能是在一個函數中完成的,這就需要在不同的函數間傳遞Mat。同時,圖像處理的計算量是很大,除非萬不得已就不要去傳遞比較大的Mat。這就要求使用某種機制來實現Mat的快速傳遞。Mat中主要有矩陣頭和一個指向矩陣的指針,矩陣頭是一個常數值,但是矩陣保存了圖像所有的像素值,通常會比矩陣頭大幾個數量級,因此傳遞Mat是主要的消耗是在矩陣復制上。為了解決這個問題,OpenCV中引入了計數機制。每個Mat都有自己的信息頭,但是共享同一個矩陣,也就是在傳遞Mat時,只復制矩陣頭和指向矩陣的指針。
1: Mat a,c ; 2: a = imread("d:\\test.jpg",1) ; 3: Mat b(a) ; //拷貝構造函數 4: a = c ; //復制運算符上面代碼中3個Mat對象a,b,c指向同一個矩陣,由于都指向了同一個矩陣,某一個對象對矩陣進行操作時也會影響到其他對象讀取到的矩陣。
多個對象同時使用一個矩陣,那么當不需要該矩陣時,誰來負責清理?簡單的回答是,最后一個使用它的對象。通過引用計數機制,無論什么時候Mat對象的信息頭被復制了,都會增加矩陣的引用次數加1;反之,當一個Mat的信息頭被釋放后,引用計數就會被減1;當計數被減到0時,矩陣就會被釋放。
當然,有些時候還是需要拷貝矩陣本身的,這時候可以使用clone和 copyTo。通過clone和copyTo創建的Mat,都有自己的矩陣,修改其中一個的矩陣不會對其他的造成影響。
?
訪問像素的三種方法
對圖像像素值的訪問是圖像處理最基本的要求,在OpenCV中提供了三種方式來訪問圖像的像素值。
矩陣在內存中的存儲
首先來看一下圖像像素值在內存中的保存方式。前面提到,像素值是以矩陣的方式保存的,矩陣的大小取決于圖像采用的顏色模型,確切的說是圖像的通道數。如果是灰度圖像,矩陣是這樣的:
矩陣的每一個元素代表一個像素 值。而對多通道圖像來說,一個像素值需要多個矩陣元素來存儲,矩陣中的列會包含多個子列,其子列數和通道數目相等。以常見的RGB模型來說:
而且,如果內存比較大,圖像中的各行各列就可以一行一行的連接起來,形成一個長行。連續存儲有助于提升圖像的掃描速度,使用iscontinuous來判斷矩陣是否是連續存儲的。
顏色空間縮減
如果矩陣元素存儲的是單通道像素,使用8位無符號來保存每個元素,那么像素可能有256個不同的值。如果是三通道的話,就會用一千六百多種顏色。如此多的顏色在有些時候不是必須的,而且會對算法的性能造成嚴重的影響。在這種情況下,最常用的做法就是顏色空間的縮減,也就是將現有的顏色空間進行映射,以獲得較少的顏色數。例如:顏色值0到9映射為0,10到19映射為10,以此類推。
以簡單顏色空間縮減為例,使用OpenCV提供的三種方式來遍歷圖像像素。將各個顏色值映射關系存儲到表中,在對格像素的顏色值進行處理時,直接進行查表。下面是對映射表的初始化:
1: ? 2: uchar table[256] ; 3: int divideWith = 10; 4: for(int i = 0 ; i < 256 ; i ++) 5: table[i] = (uchar) ( divideWith * (i / divideWith));這里將各個像素的顏色值整除以10,然后再乘以10,這樣會像上面所說的將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位無符號數保存元素值的矩陣。然后在取出舉證的行數和列數,如果是多通道的話矩陣是有子列的,用通道數乘以矩陣的行數作為最終遍歷時行數。另外,調用isContinuous來判斷矩陣在內存中是不是連續存儲的。p = img.ptr<uchar>(i); 來獲取每一行開始處的指針,然后遍歷至改行的末尾。如果是連續存儲的,就只需要獲取一次每行的開始指針,一路遍歷下去即可。
Mat中的data字段會返回指向矩陣第一行第一列的指針,通過可以使用該字段來檢查圖像是否被載入成功了。當矩陣是連續存儲時,也可以通過data來遍歷整個圖像。
1: uchar * p = img.data ; 2: for(unsigned int i = 0 ; i < img.rows * img.cols * img.channels() ; i ++) 3: *p ++ = table[*p] ;但是這種代碼可讀性差,并且進一步操作困難,其在性能上的表現并不明顯的優于上面的方法。
迭代遍歷圖像 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: } 有兩種方式來獲取圖像矩陣的迭代器 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>(); 另外需要注意的就是,對于多通道圖像(例如三通道),每列中有3個uchar元素,也就是一個有3個元素的vector,在OpenCV中使用Vec3b來命名。如果要訪問第n個子列,只需要使用[]來操作就可以了。而且,OpenCV迭代中掃過一行的所有列后會自動的跳到下一列,所以在RGB中如果使用uchar來進行迭代不實用Vec3b的話只能獲取到B通道的值。at<>遍歷圖像
這種方法不推薦用來遍歷圖像,它主要用來獲取或更改圖像的中隨機元素的。基本用途是用來訪問特定的矩陣元素(知道行數和列數)
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種保存矩陣元素的數據類型是很重要的,Mat類中也提供了很多的模板方法來處理不同的矩陣元素類型,這種靈活性卻會使得簡單的代碼復雜化,因此就有了Mat_模板類來簡化操作。也就是說如果知道了Mat的矩陣元素的數據類型,就可以將其轉換為Mat_模板類來簡化代碼。 前面提到,at<>主要是用來訪問矩陣的隨機元素的,下面使用該方法來給一張圖像添加椒鹽噪聲。 1: //n添加椒鹽噪聲的個數 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顯示到命名窗口時,需要調用waitkey()這個函數下,不然的話在命名窗口顯示不出來。
LUT
在圖像處理中,對圖像的所有像素重新映射是很常見的,在OpenCV中提供一個函數來實現該該操作,不需要去掃描整個圖像,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)?轉載于:https://www.cnblogs.com/wangguchangqing/p/3660623.html
總結
以上是生活随笔為你收集整理的OpenCV2学习笔记(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 第15章-输入/输出 --- 理解Jav
- 下一篇: sdut 2153:Clockwise(
