1. 程式人生 > >opencv學習--- 影象中像的訪問與遍歷

opencv學習--- 影象中像的訪問與遍歷

***好記性不如爛筆頭**** 轉自:https://www.cnblogs.com/yssongest/p/4708259.html

我們在實際應用中對影象進行的操作,往往並不是將影象作為一個整體進行操作,而是對影象中的所有點或特殊點進行運算,所以遍歷影象就顯得很重要,如何高效的遍歷影象是一個很值得探討的問題。

一、遍歷影象的4種方式:at<typename>(i,j)
       Mat類提供了一個at的方法用於取得影象上的點,它是一個模板函式,可以取到任何型別的影象上的點。下面我們通過一個影象處理中的實際來說明它的用法。
       在實際應用中,我們很多時候需要對影象降色彩,因為256*256*256實在太多了,在影象顏色聚類或彩色直方圖時,我們需要用一些代表性的顏色代替豐富的色彩空間,我們的思路是將每個通道的256種顏色用64種代替,即將原來256種顏色劃分64個顏色段,每個顏色段取中間的顏色值作為代表色。

注意:image.at<Vec3b>(ROW,COL),如果對應某個點Point A,   則是image.at<Vec3b>(A.y,A.x)[0],順序是反的。

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通道的顏色點。其中uchar,Vec3b都是影象畫素值的型別,不要對Vec3b這種型別感覺害怕,其實在core裡它是通過typedef Vec<T,N>來定義的,N代表元素的個數,T代表型別。

更簡單一些的方法:OpenCV定義了一個Mat的模板子類為Mat_,它過載了operator()讓我們可以更方便的取影象上的點。Mat_<uchar> im=image;                                                                                                                                          im(i,j)=im(i,j)/div*div+div/2;                                                                                                                                                   

 二、高效一點:用指標來遍歷影象                                                                                                                                                            上面的例程中可以看到,我們實際喜歡把原圖傳進函式內,但是在函式內我們對原影象進行了修改,而將原圖作為一個結果輸出,很多時候我們需要保留原圖,這樣我們需要一個原圖的副本。                                                                                               

void colorReduce(const Mat& image,Mat& outImage,int div)
{
    // 建立與原影象等尺寸的影象
    outImage.create(image.size(),image.type());
    int nr=image.rows;
    // 將3通道轉換為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行資料的指標:image.ptr<uchar>(i)。 

值得說明的是:程式中將三通道的資料轉換為1通道,在建立在每一行資料元素之間在記憶體裡是連續儲存的,每個畫素三通道畫素按順序儲存。也就是一幅影象資料最開始的三個值,是最左上角的那畫素的三個通道的值。但是這種用法不能用在行與行之間,因為影象在 OpenCV 裡的儲存機制問題,行與行之間可能有空白單元。這些空白單元對影象來說是沒有意思的,只是為了在某些架構上能夠更有效率,比如 intel MMX 可以更有效的處理那種個數是4或8倍數的行。但是我們可以申明一個連續的空間來儲存影象,這個話題引入下面最為高效的遍歷影象的機制。

三、更高效的方法                                                                                                                                                                                     上面已經提到過了,一般來說影象行與行之間往往儲存是不連續的,但是有些影象可以是連續的,Mat提供了一個檢測影象是否連續的函式isContinuous()。當影象連通時,我們就可以把影象完全展開,看成是一行。                                                         

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返回影象一行畫素元素的個數(包括空白元素),image.elemSize()返回一個影象畫素的大小。                                                                                                                          &image.at<uchar>(i,j)=image.data+i*image.step+j*image.elemSize();

四、用迭代器來遍歷                                                                                                                                                                        下面的方法可以讓我們來為影象中的畫素宣告一個迭代器:                                                                                      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開始。                                                                                上面4種方法中,第3種方法的效率最高!                                                                                                                                         

五、影象的鄰域操作                                                                                                                                                                                  很多時候,我們對影象處理時,要考慮它的鄰域,比如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)。

void ImgFilter2d(const Mat &image,Mat& result)
{
    result.create(image.size(),image.type());
    int nr=image.rows;
    int nc=image.cols*image.channels();
    for(int i=1;i<nr-1;i++)
    {
        const uchar* up_line=image.ptr<uchar>(i-1);//指向上一行
        const uchar* mid_line=image.ptr<uchar>(i);//當前行
        const uchar* down_line=image.ptr<uchar>(i+1);//下一行
        uchar* cur_line=result.ptr<uchar>(i);
        for(int j=1;j<nc-1;j++)
        {
            cur_line[j]=saturate_cast<uchar>(5*mid_line[j]-mid_line[j-1]-mid_line[j+1]-
                up_line[j]-down_line[j]);
        }
    }
    // 把影象邊緣畫素設定為0
    result.row(0).setTo(Scalar(0));
    result.row(result.rows-1).setTo(Scalar(0));
    result.col(0).setTo(Scalar(0));
    result.col(result.cols-1).setTo(Scalar(0));
}

上面的程式有以下幾點需要說明:

1,staturate_cast<typename>是一個型別轉換函式,程式裡是為了確保運算結果還在uchar範圍內。

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

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

六、影象的算術運算                                                                                                                                                                                 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個通道分開考慮。         

vector<Mat> planes;
// 將image分為三個通道影象儲存在planes中
split(image,planes);
planes[0]+=image2;
// 將planes中三幅影象合為一個三通道影象
merge(planes,result);