訪問Mat圖像中每個像素的值

今天百度搜資料還搜到了自己的。。。《訪問圖像中每個像素的值》,這是之前寫的了,用的也是2.0的風格IplImage*格式,不太適用後來Mat的格式,特此重寫一篇。

以下例子源自《The OpenCV Tutorials --Release 2.4.2》2.2 How to scan images, lookup tables and time measurement with OpenCV


圖像容器Mat

還是先看Mat的存儲形式。Mat和Matlab裏的數組格式有點像,但一般是二維向量,如果是灰度圖,一般存放<uchar>類型;如果是RGB彩色圖,存放<Vec3b>類型。
單通道灰度圖數據存放格式:

多通道的圖像中,每列並列存放通道數量的子列,如RGB三通道彩色圖:

注意通道的順序反轉了:BGR。通常情況內存足夠大的話圖像的每一行是連續存放的,也就是在內存上圖像的所有數據存放成一行,這中情況在訪問時可以提供很大方便。可以用 isContinuous()函數來判斷圖像數組是否爲連續的。

訪問圖像中的像素


高效的方法:C操作符[ ]

最快的是直接用C風格的內存訪問操作符[]來訪問:
  1. Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     int channels = I.channels();  
  6.     int nRows = I.rows ;  
  7.     int nCols = I.cols* channels;  
  8.     if (I.isContinuous())  
  9.     {  
  10.         nCols *= nRows;  
  11.         nRows = 1;  
  12.     }  
  13.     int i,j;  
  14.     uchar* p;  
  15.     for( i = 0; i < nRows; ++i)  
  16.     {  
  17.         p = I.ptr<uchar>(i);  
  18.         for ( j = 0; j < nCols; ++j)  
  19.         {  
  20.             p[j] = table[p[j]];  
  21.         }  
  22.     }  
  23.     return I;  
  24. }  
注意:書中這段代碼是有問題的,前面寫成了 
  1. int nRows = I.rows * channels;  
  2. int nCols = I.cols;  
一般情況 isContinous爲true,運行不會出錯,但你可以註釋掉那個if,會有訪問越界的問題。
這種訪問形式就是在每行定義一個指針,然後在內存上直接連續訪問。如果整個數組在內存上都是連續存放的,那麼只需要定義一個指針就可以訪問所有的數據!如單通道的灰度圖訪問方式如下:
  1. uchar* p = I.data;  
  2. for( unsigned int i =0; i < ncol*nrows; ++i)  
  3.     *p++ = table[*p];  

安全的方法:迭代器iterator

相比用指針直接訪問可能出現越界問題,迭代器絕對是非常安全的方法:
  1. Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     const int channels = I.channels();  
  6.     switch(channels)  
  7.     {  
  8.     case 1:  
  9.         {  
  10.             MatIterator_<uchar> it, end;  
  11.             for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)  
  12.                 *it = table[*it];  
  13.             break;  
  14.         }  
  15.     case 3:  
  16.         {  
  17.             MatIterator_<Vec3b> it, end;  
  18.             for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)  
  19.             {  
  20.                 (*it)[0] = table[(*it)[0]];  
  21.                 (*it)[1] = table[(*it)[1]];  
  22.                 (*it)[2] = table[(*it)[2]];  
  23.             }  
  24.         }  
  25.     }  
  26.     return I;  
  27. }  
這裏我們只定義了一個迭代器,用了一個for循環,這是因爲在OpenCV裏迭代器會訪問每一列然後自動跳到下一行,不用管在內存上是否isContinous。另外要注意的是在三通道圖像中我們定義的是 <Vec3b>格式的迭代器,如果定義成uchar,則只能訪問到B即藍色通道的值。
這種方式雖然安全,但是挺慢的,一會兒就知道了。

更慢的方法:動態地址計算

這種方法在需要連續掃描所有點的應用時並不推薦,因爲它更實用與隨機訪問。這種方法最基本的用途是訪問任意的某一行某一列:
  1. Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)  
  2. {  
  3.     // accept only char type matrices  
  4.     CV_Assert(I.depth() != sizeof(uchar));  
  5.     const int channels = I.channels();  
  6.     switch(channels)  
  7.     {  
  8.     case 1:  
  9.         {  
  10.             forint i = 0; i < I.rows; ++i)  
  11.                 forint j = 0; j < I.cols; ++j )  
  12.                     I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];  
  13.             break;  
  14.         }  
  15.     case 3:  
  16.         {  
  17.             Mat_<Vec3b> _I = I;  
  18.   
  19.             forint i = 0; i < I.rows; ++i)  
  20.                 forint j = 0; j < I.cols; ++j )  
  21.                 {  
  22.                     _I(i,j)[0] = table[_I(i,j)[0]];  
  23.                     _I(i,j)[1] = table[_I(i,j)[1]];  
  24.                     _I(i,j)[2] = table[_I(i,j)[2]];  
  25.                 }  
  26.                 I = _I;  
  27.                 break;  
  28.         }  
  29.     }  
  30.     return I;  
  31. }  
因爲這種方法是爲隨機訪問設計的,所以真的是奇慢無比。。。

減小顏色空間 color space reduction

現在來介紹下上述函數對每個元素的操作,也就是用table更改像素值。這裏其實是做了個減小顏色空間的操作,這在一些識別之類的應用中會大大降低運算複雜度。類如uchar類型的三通道圖像,每個通道取值可以是0~255,於是就有 256*256個不同的值。我們可以通過定義:
0~9 範圍的像素值爲 0
10~19 範圍的像素值 爲 10
20~29 範圍的像素值爲 20
。。。。。。
着這樣的操作將顏色取值降低爲 26*26*26 種情況。這個操作可以用一個簡單的公式:

來實現,因爲C++中int類型除法操作會自動截餘。 類如 Iold=14; Inew=(Iold/10)*10=(14/10)*10=1*10=10;
在處理圖像像素時,每個像素需要進行一遍上述計算也需要一定的時間花銷。但我們注意到其實只有 0~255 種像素,即只有256種情況。進一步可以把256種計算好的結果提前存在表中 table 中,這樣每種情況不需計算直接從 table 中取結果即可。
  1. int divideWith=10;   
  2. uchar table[256];  
  3. for (int i = 0; i < 256; ++i)  
  4.     table[i] = divideWith* (i/divideWith);  
於是table[i]存放的是值爲i的像素減小顏色空間的結果,這樣也就可以理解上述方法中的操作:
  1. p[j] = table[p[j]];  

LUT : Look up table

OpenCV 很聰明的有個 LUT 函數就是針對這種 Look up talbe 的操作:
  1. Mat lookUpTable(1, 256, CV_8U);  
  2. uchar* p = lookUpTable.data;  
  3. forint i = 0; i < 256; ++i)  
  4.     p[i] = table[i];  
  5. for (int i = 0; i < times; ++i)  
  6.     LUT(I, lookUpTable, J);  

算法計時

爲了驗證幾種方法的效率,可以用一個簡單的計時和輸出:
  1. double t;  
  2. t = (double)getTickCount();  
  3. t = 1000*((double)getTickCount() - t)/getTickFrequency();  
  4. t /= times;  

實驗結果


原圖:


降低顏色空間結果:


算法時間:


更清楚的時間對比表:



發佈了24 篇原創文章 · 獲贊 38 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章