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