OpenCV?通過Mat遍歷圖像的方法匯總
我們在實際應(yīng)用中對圖像進(jìn)行的操作,往往并不是將圖像作為一個整體進(jìn)行操作,而是對圖像中的所有點或特殊點進(jìn)行運(yùn)算,所以遍歷圖像就顯得很重要,如何高效的遍歷圖像是一個很值得探討的問題。
Color Reduce
還是使用經(jīng)典的Reduce Color的例子,即對圖像中的像素表達(dá)進(jìn)行量化。如常見的RGB24圖像有256×256×256中顏色,通過Reduce Color將每個通道的像素減少8倍至256/8=32種,則圖像只有32×32×32種顏色。假設(shè)量化減少的倍數(shù)是N,則代碼實現(xiàn)時就是簡單的value/N*N,通常我們會再加上N/2以得到相鄰的N的倍數(shù)的中間值,最后圖像被量化為(256/N)×(256/N)×(256/N)種顏色。
并對圖像降色彩后的彩色直方圖進(jìn)行統(tǒng)計。
方法一、直接對圖像像素修改.at<typename>(i,j)
Mat類提供了一個at的方法用于取得圖像上的點,它是一個模板函數(shù),可以取到任何類型的圖像上的點。
void colorReduce(Mat& image,int div) { for(int i=0;i<image.rows;i++) { for(int j=0;j<image.cols;j++) { image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0]/div*div+div/2; image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1]/div*div+div/2; image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2]/div*div+div/2; } } }
通過上面的例子我們可以看出,at方法取圖像中的點的用法:
image.at<uchar>(i,j):取出灰度圖像中i行j列的點。
image.at<Vec3b>(i,j)[k]:取出彩色圖像中i行j列第k通道的顏色點,k=[0,1,2],分別代表B,G,R。
其中uchar,Vec3b都是圖像像素值的類型,不要對Vec3b這種類型感覺害怕,其實在core里它是通過typedef Vec<T,N>來定義的,N代表元素的個數(shù),T代表類型。
更簡單一些的方法:OpenCV定義了一個Mat的模板子類為Mat_,它重載了operator()讓我們可以更方便的取圖像上的點。
Mat_<uchar> im=image; im(i,j)=im(i,j)/div*div+div/2;
二、用指針.ptr<uchar>(k)來遍歷輸入圖像,數(shù)組[]生成輸出圖像
上面的例程中可以看到,我們實際喜歡把原圖傳進(jìn)函數(shù)內(nèi),但是在函數(shù)內(nèi)我們對原圖像進(jìn)行了修改,而將原圖作為一個結(jié)果輸出,很多時候我們需要保留原圖,這樣我們需要一個原圖的副本。
void colorReduce(const Mat& image,Mat& outImage,int div) { // 創(chuàng)建與原圖像等尺寸的圖像 outImage.create(image.size(),image.type()); int nr=image.rows; // 將3通道轉(zhuǎn)換為1通道 int nl=image.cols*image.channels(); for(int k=0;k<nr;k++) { // 每一行圖像的指針 const uchar* inData=image.ptr<uchar>(k); uchar* outData=outImage.ptr<uchar>(k); for(int i=0;i<nl;i++) { outData[i]=inData[i]/div*div+div/2; } } }
從上面的例子中可以看出,取出圖像中第i行數(shù)據(jù)的指針:image.ptr<uchar>(i)。
值得說明的是:程序中將3通道的數(shù)據(jù)轉(zhuǎn)換為1通道,在建立在每一行數(shù)據(jù)元素之間在內(nèi)存里是連續(xù)存儲的,每個像素三通道像素按順序存儲。也就是一幅圖像數(shù)據(jù)最開始的三個值,是最左上角的那像素的三個通道的值。
但是這種用法不能用在行與行之間,因為圖像在OpenCV里的存儲機(jī)制問題,行與行之間可能有空白單元。這些空白單元對圖像來說是沒有意思的,只是為了在某些架構(gòu)上能夠更有效率,比如intel MMX可以更有效的處理那種個數(shù)是4或8倍數(shù)的行。但是我們可以申明一個連續(xù)的空間來存儲圖像,這個話題引入下面最為高效的遍歷圖像的機(jī)制。
三、用指針.ptr<uchar>(k)來遍歷輸入圖像,指針方式生成輸出圖像
與上述方法二遍歷圖像的方法相同,而生成輸出圖像的方式從數(shù)組換成了指針的方式。因此只需改動一句話。
void colorReduce(const Mat& image,Mat& outImage,int div) { // 創(chuàng)建與原圖像等尺寸的圖像 outImage.create(image.size(),image.type()); int nr=image.rows; // 將3通道轉(zhuǎn)換為1通道 int nl=image.cols*image.channels(); for(int k=0;k<nr;k++) { // 每一行圖像的指針 const uchar* inData=image.ptr<uchar>(k); uchar* outData=outImage.ptr<uchar>(k); for(int i=0;i<nl;i++) { *outData++ = *inData++ / div*div + div / 2; } } }
四、用指針.ptr<uchar>(k)來遍歷輸入圖像,指針方式結(jié)合位運(yùn)算生成輸出圖像
與上述方法遍歷圖像的方法相同,而生成輸出圖像的方式從加減乘除基本四則運(yùn)算的方式換成了位運(yùn)算的方式。
這里特別需要注意的是,位運(yùn)算的優(yōu)先級是低于乘除加減的,所以一定要在位運(yùn)算加括號。
void colorReduce(const Mat& image, Mat& outImage, int div) { // 創(chuàng)建與原圖像等尺寸的圖像 outImage.create(image.size(), image.type()); int nr = image.rows; // 將3通道轉(zhuǎn)換為1通道 int nl = image.cols*image.channels(); //對數(shù)換底公式log a(b) = log b/log a int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0)); // mask used to round the pixel value e.g. for div=16, mask= 0xF0 uchar mask = 0xFF << n; for (int k = 0; k<nr; k++) { // 每一行圖像的指針 const uchar* inData = image.ptr<uchar>(k); uchar* outData = outImage.ptr<uchar>(k); for (int i = 0; i<nl; i++) { //進(jìn)行位運(yùn)算時要注意加括號,位運(yùn)算優(yōu)先級低于+-*/ *outData++ = (*inData++ & mask) + div / 2; } } }
五、用指針.ptr<uchar>(k)來遍歷輸入圖像,指針方式結(jié)合取模運(yùn)算生成輸出圖像
與上述方法遍歷圖像的方法相同,而生成輸出圖像的方式從位運(yùn)算的方式換成了取模運(yùn)算的方式。
void colorReduce(const Mat& image, Mat& outImage, int div) { // 創(chuàng)建與原圖像等尺寸的圖像 outImage.create(image.size(), image.type()); int nr = image.rows; // 將3通道轉(zhuǎn)換為1通道 int nl = image.cols*image.channels(); int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0)); // mask used to round the pixel value e.g. for div=16, mask= 0xF0 uchar mask = 0xFF << n; for (int k = 0; k<nr; k++) { // 每一行圖像的指針 const uchar* inData = image.ptr<uchar>(k); uchar* outData = outImage.ptr<uchar>(k); for (int i = 0; i<nl; i++) { int Data = *inData++; *outData++ = Data - Data%div + div / 2; } } }
六、連續(xù)圖像isContinuous()函數(shù)方法。
上面已經(jīng)提到過了,一般來說圖像行與行之間往往存儲是不連續(xù)的,但是有些圖像可以是連續(xù)的,Mat提供了一個檢測圖像是否連續(xù)的函數(shù)isContinuous()。當(dāng)圖像連通時,我們就可以把圖像完全展開,看成是一行。
void colorReduce(const Mat& image,Mat& outImage,int div) { int nr=image.rows; int nc=image.cols; outImage.create(image.size(),image.type()); if(image.isContinuous()&&outImage.isContinuous()) { nr=1; nc=nc*image.rows*image.channels(); } for(int i=0;i<nr;i++) { const uchar* inData=image.ptr<uchar>(i); uchar* outData=outImage.ptr<uchar>(i); for(int j=0;j<nc;j++) { *outData++=*inData++/div*div+div/2; } } }
用指針除了用上面的方法外,還可以用指針來索引固定位置的像素:
image.step返回圖像一行像素元素的個數(shù)(包括空白元素),image.elemSize()返回一個圖像像素的大小。
image.at<uchar>(i,j)=image.data+i*image.step+j*image.elemSize();
七、迭代器Mat_iterator方法。
下面的方法可以讓我們來為圖像中的像素聲明一個迭代器:
MatIterator_<Vec3b> it; Mat_<Vec3b>::iterator it;
如果迭代器指向一個const圖像,則可以用下面的聲明:
MatConstIterator<Vec3b> it; 或者 Mat_<Vec3b>::const_iterator it;
下面我們用迭代器來簡化上面的colorReduce程序:
void colorReduce(const Mat& image,Mat& outImage,int div) { outImage.create(image.size(),image.type()); MatConstIterator_<Vec3b> it_in=image.begin<Vec3b>(); MatConstIterator_<Vec3b> itend_in=image.end<Vec3b>(); MatIterator_<Vec3b> it_out=outImage.begin<Vec3b>(); MatIterator_<Vec3b> itend_out=outImage.end<Vec3b>(); while(it_in!=itend_in) { (*it_out)[0]=(*it_in)[0]/div*div+div/2; (*it_out)[1]=(*it_in)[1]/div*div+div/2; (*it_out)[2]=(*it_in)[2]/div*div+div/2; it_in++; it_out++; } }
如果你想從第二行開始,則可以從
image.begin<Vec3b>()+image.rows
開始。
上面7種方法中,第4種方法的效率最高!
到此這篇關(guān)于OpenCV 通過Mat遍歷圖像的幾種方法的文章就介紹到這了,更多相關(guān)OpenCV遍歷圖像內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++利用vector創(chuàng)建二維數(shù)組的幾種方法總結(jié)
這篇文章主要介紹了c++利用vector創(chuàng)建二維數(shù)組的幾種方法總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11詳解MFC/C++調(diào)用易語言的整數(shù)型和文本型與VS2010互動
在本篇文章里我們給大家分享了MFC/C++調(diào)用易語言的整數(shù)型和文本型與VS2010互動相關(guān)知識點內(nèi)容,有興趣的朋友們可以參考下。2018-11-11C語言 數(shù)據(jù)結(jié)構(gòu)之中序二叉樹實例詳解
這篇文章主要介紹了C語言 數(shù)據(jù)結(jié)構(gòu)之中序二叉樹實例詳解的相關(guān)資料,需要的朋友可以參考下2017-01-01