OpenCV優化:圖像的遍歷4種方式

OpenCV優化:圖像的遍歷4種方式

咱們在實際應用中對圖像進行的操做,每每並非將圖像做爲一個總體進行操做,而是對圖像中的全部點或特殊點進行運算,因此遍歷圖像就顯得很重要,如何高效的遍歷圖像是一個很值得探討的問題。架構

1、遍歷圖像的4種方式:at<typename>(i,j)

Mat類提供了一個at的方法用於取得圖像上的點,它是一個模板函數,能夠取到任何類型的圖像上的點。下面咱們經過一個圖像處理中的實際來講明它的用法。函數

在實際應用中,咱們不少時候須要對圖像降色彩,由於256*256*256實在太多了,在圖像顏色聚類或彩色直方圖時,咱們須要用一些表明性的顏色代替豐富的色彩空間,咱們的思路是將每一個通道的256種顏色用64種代替,即將原來256種顏色劃分64個顏色段,每一個顏色段取中間的顏色值做爲表明色。優化

複製代碼

 1 void colorReduce(Mat& image,int div) 2 { 3     for(int i=0;i<image.rows;i++) 4     { 5         for(int j=0;j<image.cols;j++) 6         { 7             image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0]/div*div+div/2; 8             image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1]/div*div+div/2; 9             image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2]/div*div+div/2;10         }11     }12 }

複製代碼

image

經過上面的例子咱們能夠看出,at方法取圖像中的點的用法:spa

image.at<uchar>(i,j):取出灰度圖像中i行j列的點。3d

image.at<Vec3b>(i,j)[k]:取出彩色圖像中i行j列第k通道的顏色點。其中uchar,Vec3b都是圖像像素值的類型,不要對Vec3b這種類型感受懼怕,其實在core裏它是經過typedef Vec<T,N>來定義的,N表明元素的個數,T表明類型。指針

更簡單一些的方法:OpenCV定義了一個Mat的模板子類爲Mat_,它重載了operator()讓咱們能夠更方便的取圖像上的點。code

Mat_<uchar> im=image;blog

im(i,j)=im(i,j)/div*div+div/2;索引

2、高效一點:用指針來遍歷圖像

上面的例程中能夠看到,咱們實際喜歡把原圖傳進函數內,可是在函數內咱們對原圖像進行了修改,而將原圖做爲一個結果輸出,不少時候咱們須要保留原圖,這樣咱們須要一個原圖的副本。內存

複製代碼

 1 void colorReduce(const Mat& image,Mat& outImage,int div) 2 { 3     // 建立與原圖像等尺寸的圖像 4     outImage.create(image.size(),image.type()); 5     int nr=image.rows; 6     // 將3通道轉換爲1通道 7     int nl=image.cols*image.channels(); 8     for(int k=0;k<nr;k++) 9     {10         // 每一行圖像的指針11         const uchar* inData=image.ptr<uchar>(k);12         uchar* outData=outImage.ptr<uchar>(k);13         for(int i=0;i<nl;i++)14         {15             outData[i]=inData[i]/div*div+div/2;16         }17     }18 }

複製代碼

從上面的例子中能夠看出,取出圖像中第i行數據的指針:image.ptr<uchar>(i)。

值得說明的是:程序中將三通道的數據轉換爲1通道,在創建在每一行數據元素之間在內存裏是連續存儲的,每一個像素三通道像素按順序存儲。也就是一幅圖像數據最開始的三個值,是最左上角的那像素的三個通道的值。

可是這種用法不能用在行與行之間,由於圖像在OpenCV裏的存儲機制問題,行與行之間可能有空白單元。這些空白單元對圖像來講是沒有意思的,只是爲了在某些架構上可以更有效率,好比intel MMX能夠更有效的處理那種個數是4或8倍數的行。可是咱們能夠申明一個連續的空間來存儲圖像,這個話題引入下面最爲高效的遍歷圖像的機制。

3、更高效的方法

上面已經提到過了,通常來講圖像行與行之間每每存儲是不連續的,可是有些圖像能夠是連續的,Mat提供了一個檢測圖像是否連續的函數isContinuous()。當圖像連通時,咱們就能夠把圖像徹底展開,當作是一行。

複製代碼

 1 void colorReduce(const Mat& image,Mat& outImage,int div) 2 { 3     int nr=image.rows; 4     int nc=image.cols; 5     outImage.create(image.size(),image.type()); 6     if(image.isContinuous()&&outImage.isContinuous()) 7     { 8         nr=1; 9         nc=nc*image.rows*image.channels();10     }11     for(int i=0;i<nr;i++)12     {13         const uchar* inData=image.ptr<uchar>(i);14         uchar* outData=outImage.ptr<uchar>(i);15         for(int j=0;j<nc;j++)16         {17             *outData++=*inData++/div*div+div/2;18         }19     }20 }

複製代碼

用指針除了用上面的方法外,還能夠用指針來索引固定位置的像素:

image.step返回圖像一行像素元素的個數(包括空白元素),image.elemSize()返回一個圖像像素的大小。

&image.at<uchar>(i,j)=image.data+i*image.step+j*image.elemSize();

4、還有嗎?用迭代器來遍歷。

下面的方法可讓咱們來爲圖像中的像素聲明一個迭代器:

MatIterator_<Vec3b> it;

Mat_<Vec3b>::iterator it;

若是迭代器指向一個const圖像,則能夠用下面的聲明:

MatConstIterator<Vec3b> it; 或者

Mat_<Vec3b>::const_iterator it;

下面咱們用迭代器來簡化上面的colorReduce程序:

複製代碼

 1 void colorReduce(const Mat& image,Mat& outImage,int div) 2 { 3     outImage.create(image.size(),image.type()); 4     MatConstIterator_<Vec3b> it_in=image.begin<Vec3b>(); 5     MatConstIterator_<Vec3b> itend_in=image.end<Vec3b>(); 6     MatIterator_<Vec3b> it_out=outImage.begin<Vec3b>(); 7     MatIterator_<Vec3b> itend_out=outImage.end<Vec3b>(); 8     while(it_in!=itend_in) 9     {10         (*it_out)[0]=(*it_in)[0]/div*div+div/2;11         (*it_out)[1]=(*it_in)[1]/div*div+div/2;12         (*it_out)[2]=(*it_in)[2]/div*div+div/2;13         it_in++;14         it_out++;15     }16 }

複製代碼

若是你想從第二行開始,則能夠從image.begin<Vec3b>()+image.rows開始。

上面4種方法中,第3種方法的效率最高!

5、圖像的鄰域操做

不少時候,咱們對圖像處理時,要考慮它的鄰域,好比3*3是咱們經常使用的,這在圖像濾波、去噪中最爲常見,下面咱們介紹若是在一次圖像遍歷過程當中進行鄰域的運算。

下面咱們進行一個簡單的濾波操做,濾波算子爲[0 –1 0;-1 5 –1;0 –1 0]。

它可讓圖像變得尖銳,而邊緣更加突出。核心公式即:sharp(i.j)=5*image(i,j)-image(i-1,j)-image(i+1,j

)-image(i,j-1)-image(i,j+1)。

複製代碼

 1 void ImgFilter2d(const Mat &image,Mat& result) 2 { 3     result.create(image.size(),image.type()); 4     int nr=image.rows; 5     int nc=image.cols*image.channels(); 6     for(int i=1;i<nr-1;i++) 7     { 8         const uchar* up_line=image.ptr<uchar>(i-1);//指向上一行 9         const uchar* mid_line=image.ptr<uchar>(i);//當前行10         const uchar* down_line=image.ptr<uchar>(i+1);//下一行11         uchar* cur_line=result.ptr<uchar>(i);12         for(int j=1;j<nc-1;j++)13         {14             cur_line[j]=saturate_cast<uchar>(5*mid_line[j]-mid_line[j-1]-mid_line[j+1]-15                 up_line[j]-down_line[j]);16         }17     }18     // 把圖像邊緣像素設置爲019     result.row(0).setTo(Scalar(0));20     result.row(result.rows-1).setTo(Scalar(0));21     result.col(0).setTo(Scalar(0));22     result.col(result.cols-1).setTo(Scalar(0));23 }

複製代碼

image

上面的程序有如下幾點須要說明:

1,staturate_cast<typename>是一個類型轉換函數,程序裏是爲了確保運算結果還在uchar範圍內。

2,row和col方法返回圖像中的某些行或列,返回值是一個Mat。

3,setTo方法將Mat對像中的點設置爲一個值,Scalar(n)爲一個灰度值,Scalar(a,b,c)爲一個彩色值。

6、圖像的算術運算

Mat類把不少算數操做符都進行了重載,讓它們來符合矩陣的一些運算,若是+、-、點乘等。

下面咱們來看看用位操做和基本算術運算來完成本文中的colorReduce程序,它更簡單,更高效。

將256種灰度階降到64位實際上是拋棄了二進制最後面的4位,因此咱們能夠用位操做來作這一步處理。

首先咱們計算2^8降到2^n中的n:int n=static_cast<int>(log(static_cast<double>(div))/log(2.0));

而後能夠獲得mask,mask=0xFF<<n;

用下面簡直的語句就能夠獲得咱們想要的結果:

result=(image&Scalar(mask,mask,mask))+Scalar(div/2,div/2,div/2);

不少時候咱們須要對圖像的一個通訊單獨進行操做,好比在HSV色彩模式下,咱們就常常把3個通道分開考慮。

1 vector<Mat> planes;2 // 將image分爲三個通道圖像存儲在p
相關文章
相關標籤/搜索