天天看點

OpenCV通路Mat中每個像素的值

在《OpenCV 2 Computer Vision Application Programming Cookbook》看到的例子,非常不錯,算是對之前的文章<通路Mat圖像中每個像素的值>的回顧和補充。

Color Reduce

還是使用經典的Reduce Color的例子,即對圖像中的像素表達進行量化。如常見的RGB24圖像有256×256×256中顔色,通過Reduce Color将每個通道的像素減少8倍至256/8=32種,則圖像隻有32×32×32種顔色。假設量化減少的倍數是N,則代碼實作時就是簡單的value/N*N,通常我們會再加上N/2以得到相鄰的N的倍數的中間值,最後圖像被量化為(256/N)×(256/N)×(256/N)種顔色。

OpenCV通路Mat中每個像素的值

方法零:.ptr和[]操作符

Mat最直接的通路方法是通過.ptr<>函數得到一行的指針,并用[]操作符通路某一列的像素值。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using .ptr and []  
  2. void colorReduce0(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols * image.channels(); // total number of elements per line  
  5.       for (int j=0; j
  6.           uchar* data= image.ptr(j);  
  7.           for (int i=0; i
  8.                   data[i]= data[i]/div*div + div/2;  
  9.             }                    
  10.       }  
  11. }  

方法一:.ptr和指針操作

除了[]操作符,我們可以移動指針*++的組合方法通路某一行中所有像素的值。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using .ptr and * ++   
  2. void colorReduce1(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols * image.channels(); // total number of elements per line  
  5.       for (int j=0; j
  6.           uchar* data= image.ptr(j);  
  7.           for (int i=0; i
  8.                  *data++= *data/div*div + div/2;  
  9.             } // end of row                   
  10.       }  
  11. }  

方法二:.ptr、指針操作和取模運算

方法二和方法一的通路方式相同,不同的是color reduce用模運算代替整數除法

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using .ptr and * ++ and modulo  
  2. void colorReduce2(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols * image.channels(); // total number of elements per line  
  5.       for (int j=0; j
  6.           uchar* data= image.ptr(j);  
  7.           for (int i=0; i
  8.                   int v= *data;  
  9.                   *data++= v - v%div + div/2;  
  10.             } // end of row                   
  11.       }  
  12. }  

方法三:.ptr、指針運算和位運算

由于進行量化的單元div通常是2的整次方,是以所有的乘法和除法都可以用位運算表示。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using .ptr and * ++ and bitwise  
  2. void colorReduce3(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols * image.channels(); // total number of elements per line  
  5.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  6.       // mask used to round the pixel value  
  7.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  8.       for (int j=0; j
  9.           uchar* data= image.ptr(j);  
  10.           for (int i=0; i
  11.             *data++= *data&mask + div/2;  
  12.             } // end of row                   
  13.       }  
  14. }  

方法四:指針運算

方法四和方法三量化處理的方法相同,不同的是用指針運算代替*++操作。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // direct pointer arithmetic  
  2. void colorReduce4(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols * image.channels(); // total number of elements per line  
  5.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  6.       int step= image.step; // effective width  
  7.       // mask used to round the pixel value  
  8.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  9.       // get the pointer to the image buffer  
  10.       uchar *data= image.data;  
  11.       for (int j=0; j
  12.           for (int i=0; i
  13.             *(data+i)= *data&mask + div/2;  
  14.             } // end of row                   
  15.             data+= step;  // next line  
  16.       }  
  17. }  

方法五:.ptr、*++、位運算以及image.cols * image.channels()

這種方法就是沒有計算nc,基本是個充數的方法。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using .ptr and * ++ and bitwise with image.cols * image.channels()  
  2. void colorReduce5(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  5.       // mask used to round the pixel value  
  6.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  7.       for (int j=0; j
  8.           uchar* data= image.ptr(j);  
  9.           for (int i=0; i
  10.             *data++= *data&mask + div/2;  
  11.             } // end of row                   
  12.       }  
  13. }  

方法六:連續圖像

Mat提供了isContinuous()函數用來檢視Mat在記憶體中是不是連續存儲,如果是則圖檔被存儲在一行中。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using .ptr and * ++ and bitwise (continuous)  
  2. void colorReduce6(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols * image.channels(); // total number of elements per line  
  5.       if (image.isContinuous())  {  
  6.           // then no padded pixels  
  7.           nc= nc*nr;   
  8.           nr= 1;  // it is now a 1D array  
  9.        }  
  10.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  11.       // mask used to round the pixel value  
  12.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  13.       for (int j=0; j
  14.           uchar* data= image.ptr(j);  
  15.           for (int i=0; i
  16.             *data++= *data&mask + div/2;  
  17.             } // end of row                   
  18.       }  
  19. }  

方法七:continuous+channels

與方法六基本相同,也是充數的。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using .ptr and * ++ and bitwise (continuous+channels)  
  2. void colorReduce7(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols ; // number of columns  
  5.       if (image.isContinuous())  {  
  6.           // then no padded pixels  
  7.           nc= nc*nr;   
  8.           nr= 1;  // it is now a 1D array  
  9.        }  
  10.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  11.       // mask used to round the pixel value  
  12.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  13.       for (int j=0; j
  14.           uchar* data= image.ptr(j);  
  15.           for (int i=0; i
  16.             *data++= *data&mask + div/2;  
  17.             *data++= *data&mask + div/2;  
  18.             *data++= *data&mask + div/2;  
  19.             } // end of row                   
  20.       }  
  21. }  

方法八:Mat _iterator

真正有差別的方法來啦,用Mat提供的疊代器代替前面的[]操作符或指針,血統純正的官方方法~

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using Mat_ iterator   
  2. void colorReduce8(cv::Mat &image, int div=64) {  
  3.       // get iterators  
  4.       cv::Mat_::iterator it= image.begin();  
  5.       cv::Mat_::iterator itend= image.end();  
  6.       for ( ; it!= itend; ++it) {  
  7.         (*it)[0]= (*it)[0]/div*div + div/2;  
  8.         (*it)[1]= (*it)[1]/div*div + div/2;  
  9.         (*it)[2]= (*it)[2]/div*div + div/2;  
  10.       }  
  11. }  

方法九:Mat_ iterator 和位運算

把方法八中的乘除法換成位運算。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using Mat_ iterator and bitwise  
  2. void colorReduce9(cv::Mat &image, int div=64) {  
  3.       // div must be a power of 2  
  4.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  5.       // mask used to round the pixel value  
  6.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  7.       // get iterators  
  8.       cv::Mat_::iterator it= image.begin();  
  9.       cv::Mat_::iterator itend= image.end();  
  10.       for ( ; it!= itend; ++it) {  
  11.         (*it)[0]= (*it)[0]&mask + div/2;  
  12.         (*it)[1]= (*it)[1]&mask + div/2;  
  13.         (*it)[2]= (*it)[2]&mask + div/2;  
  14.       }  
  15. }  

方法十:MatIterator_

和方法八基本相同。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using MatIterator_   
  2. void colorReduce10(cv::Mat &image, int div=64) {  
  3.       cv::Mat_ cimage= image;  
  4.       cv::Mat_::iterator it=cimage.begin();  
  5.       cv::Mat_::iterator itend=cimage.end();  
  6.       for ( ; it!= itend; it++) {   
  7.         (*it)[0]= (*it)[0]/div*div + div/2;  
  8.         (*it)[1]= (*it)[1]/div*div + div/2;  
  9.         (*it)[2]= (*it)[2]/div*div + div/2;  
  10.       }  
  11. }  

方法十一:圖像坐标

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using (j,i)  
  2. void colorReduce11(cv::Mat &image, int div=64) {  
  3.       int nr= image.rows; // number of rows  
  4.       int nc= image.cols; // number of columns  
  5.       for (int j=0; j
  6.           for (int i=0; i
  7.                   image.at(j,i)[0]=     image.at(j,i)[0]/div*div + div/2;  
  8.                   image.at(j,i)[1]=     image.at(j,i)[1]/div*div + div/2;  
  9.                   image.at(j,i)[2]=     image.at(j,i)[2]/div*div + div/2;  
  10.             } // end of row                   
  11.       }  
  12. }  

方法十二:建立輸出圖像

之前的方法都是直接修改原圖,方法十二建立了輸出圖像,主要用于後面的時間對比。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // with input/ouput images  
  2. void colorReduce12(const cv::Mat &image, // input image   
  3.                  cv::Mat &result,      // output image  
  4.                  int div=64) {  
  5.       int nr= image.rows; // number of rows  
  6.       int nc= image.cols ; // number of columns  
  7.       // allocate output image if necessary  
  8.       result.create(image.rows,image.cols,image.type());  
  9.       // created images have no padded pixels  
  10.       nc= nc*nr;   
  11.       nr= 1;  // it is now a 1D array  
  12.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  13.       // mask used to round the pixel value  
  14.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  15.       for (int j=0; j
  16.           uchar* data= result.ptr(j);  
  17.           const uchar* idata= image.ptr(j);  
  18.           for (int i=0; i
  19.             *data++= (*idata++)&mask + div/2;  
  20.             *data++= (*idata++)&mask + div/2;  
  21.             *data++= (*idata++)&mask + div/2;  
  22.           } // end of row                   
  23.       }  
  24. }  

方法十三:重載操作符

Mat重載了+&等操作符,可以直接将兩個Scalar(B,G,R)資料進行位運算和數學運算。

[cpp]  view plain  copy  

OpenCV通路Mat中每個像素的值
OpenCV通路Mat中每個像素的值
  1. // using overloaded operators  
  2. void colorReduce13(cv::Mat &image, int div=64) {  
  3.       int n= static_cast<</span>int>(log(static_cast<</span>double>(div))/log(2.0));  
  4.       // mask used to round the pixel value  
  5.       uchar mask= 0xFF<<n; // e.g. for div=16, mask= 0xF0  
  6.       // perform color reduction  
  7.       image=(image&cv::Scalar(mask,mask,mask))+cv::Scalar(div/2,div/2,div/2);  
  8. }  

時間對比

通過疊代二十次取平均時間,得到每種方法是運算時間如下。

OpenCV通路Mat中每個像素的值

可以看到,指針*++通路和位運算是最快的方法;而不斷的計算image.cols*image.channles()花費了大量重複的時間;另外疊代器通路雖然安全,但性能遠低于指針運算;通過圖像坐标(j,i)通路時最慢的,使用重載操作符直接運算效率最高。

繼續閱讀