圖像的膨脹與腐蝕


此文原文http://www.cnblogs.com/slysky/archive/2011/10/16/2214015.html#2619948

腐蝕

  把結構元素B平移a后得到Ba,若Ba包含於X,我們記下這個a點,所有滿足上述條件的a點組成的集合稱做XB腐蝕(Erosion)的結果。用公式表示為:E(X)={a| Ba X}=X B,如圖1所示。

 

                                      

                             圖1

X是被處理的對象,B是結構元素。不難知道,對於任意一個在陰影部分的點aBa 包含於X,所以XB腐蝕的結果就是那個陰影部分。陰影部分在X的范圍之內,且比X小,就象X被剝掉了一層似的,這就是為什么叫腐蝕的原因。

值得注意的是,上面的B是對稱的,即B的對稱集Bv=B,所以XB腐蝕的結果和X Bv腐蝕的結果是一樣的。如果B不是對稱的,讓我們看下圖2,就會發現XB腐蝕的結果和X Bv腐蝕的結果不同。

 

                                   

                                圖2

1和圖2都是示意圖,讓我們來看看實際上是怎樣進行腐蝕運算的。

在圖3中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),中間是結構元素B,那個標有origin的點是中心點,即當前處理元素的位置,我們在介紹模板操作時也有過類似的概念。腐蝕的方法是,拿B的中心點和X上的點一個一個地對比,如果B上的所有點都在X的范圍內,則該點保留,否則將該點去掉;右邊是腐蝕后的結果。可以看出,它仍在原來X的范圍內,且比X包含的點要少,就象X被腐蝕掉了一層。

 

                         

                              圖3

4為原圖,圖5為腐蝕后的結果圖,能夠很明顯地看出腐蝕的效果。

                                      

                               圖4

                                                       

                              圖5

 下面的這段程序,實現了上述的腐蝕運算,針對的都是黑色點。參數中有一個BOOL變量,為真時,表示在水平方向進行腐蝕運算,即結構元素B ;否則在垂直方向上進行腐蝕運算,即結構元素B

腐蝕源碼
腐蝕源碼

BOOL Erosion(HWND hWnd,BOOL Hori)

{

       DWORD                             OffBits,BufSize;

LPBITMAPINFOHEADER    lpImgData;

       LPSTR                   lpPtr;

       HLOCAL                  hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                            lpTempPtr;

       HDC                      hDc;

       HFILE                    hf;

       LONG                    x,y;

       unsigned char              num;

       int                        i;

//為了處理方便,仍采用256級灰度圖,不過只用調色板中0和255兩項

if( NumColors!=256){  

           MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",

"Error Message",MB_OK|MB_ICONEXCLAMATION);

return FALSE;

}

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

//BufSize為緩沖區大小

       BufSize=OffBits+bi.biHeight*LineBytes;

       //為新的緩沖區分配內存

       if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

{

            MessageBox(hWnd,"Error alloc memory!","Error Message",

MB_OK|MB_ICONEXCLAMATION);

return FALSE;

    }

     lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);    

       lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

       //拷貝頭信息和位圖數據     

       memcpy(lpTempImgData,lpImgData,BufSize);

       if(Hori)

       {   

//在水平方向進行腐蝕運算

              for(y=0;y<bi.biHeight;y++){

                     //lpPtr指向原圖數據,lpTempPtr指向新圖數據

                     lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes)+1;

                     lpTempPtr=(char*)lpTempImgData+

(BufSize-LineBytes-y*LineBytes)+1;

                     for(x=1;x<bi.biWidth-1;x++){ 

//注意為防止越界,x的范圍從1到寬度-2

                            num=(unsigned char)*lpPtr;

                            if (num==0){  //因為腐蝕掉的是黑點,所以只對黑點處理

                                   *lpTempPtr=(unsigned char)0;  //先置成黑點

                                   for(i=0;i<3;i++){

                                          num=(unsigned char)*(lpPtr+i-1);

                                          if(num==255){ 

//自身及上下鄰居中若有一個不是黑點,則將該點腐

//蝕成白點

                                                 *lpTempPtr=(unsigned char)255;

                                                 break;

                                          }

                                   }

                            }

//原圖中就是白點的,新圖中仍是白點

                            else *lpTempPtr=(unsigned char)255;  

                            //指向下一個象素

                            lpPtr++; 

                            lpTempPtr++;

                     }

              }

       }

else{ 

//在垂直方向進行腐蝕運算

              for(y=1;y<bi.biHeight-1;y++){ //注意為防止越界,y的范圍從1到高度-2

                     //lpPtr指向原圖數據,lpTempPtr指向新圖數據

                     lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);

                     lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);

                     for(x=0;x<bi.biWidth;x++){

                            num=(unsigned char)*lpPtr;

                            if (num==0){ //因為腐蝕掉的是黑點,所以只對黑點處理

                                   *lpTempPtr=(unsigned char)0; //先置成黑點

                                   for(i=0;i<3;i++){

                                          num=(unsigned char)*(lpPtr+(i-1)*LineBytes);

                                          if(num==255){

//自身及上下鄰居中若有一個不是黑點,則將該點腐

//蝕成白點

                                                 *lpTempPtr=(unsigned char)255;

                                                 break;

                                          }

                                   }

                            }

//原圖中就是白點的,新圖中仍是白點

                            else *lpTempPtr=(unsigned char)255;

                            //指向下一個象素

                            lpPtr++;

                            lpTempPtr++;

                     }

              }

       }

    if(hBitmap!=NULL)

           DeleteObject(hBitmap);

       hDc=GetDC(hWnd);     

       //產生新的位圖

       hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

                                         NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS);

       //起不同的結果文件名

       if(Hori)

              hf=_lcreat("c:\\herosion.bmp",0);

       else

              hf=_lcreat("c:\\verosion.bmp",0);

       _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); 

       _lwrite(hf,(LPSTR)lpTempImgData,BufSize);

       _lclose(hf);

       //釋放內存及資源

ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

膨脹

  膨脹(dilation)可以看做是腐蝕的對偶運算,其定義是:把結構元素B平移a后得到Ba,若Ba擊中X,我們記下這個a點。所有滿足上述條件的a點組成的集合稱做XB膨脹的結果。用公式表示為:D(X)={a | BaX}=X B,如圖6所示。圖6X是被處理的對象,B是結構元素,不難知道,對於任意一個在陰影部分的點aBa擊中X,所以XB膨脹的結果就是那個陰影部分。陰影部分包括X的所有范圍,就象X膨脹了一圈似的,這就是為什么叫膨脹的原因。

  同樣,如果B不是對稱的,XB膨脹的結果和X Bv膨脹的結果不同。

    讓我們來看看實際上是怎樣進行膨脹運算的。在圖7中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),中間是結構元素B。膨脹的方法是,拿B的中心點和X上的點及X周圍的點一個一個地對,如果B上有一個點落在X的范圍內,則該點就為黑;右邊是膨脹后的結果。可以看出,它包括X的所有范圍,就象X膨脹了一圈似的。

                                           

                               圖6

                        

                            圖7

8為圖4膨脹后的結果圖,能夠很明顯的看出膨脹的效果。

                                     

                              圖8

下面的這段程序,實現了上述的膨脹運算,針對的都是黑色點。參數中有一個BOOL變量,為真時,表示在水平方向進行膨脹運算,即結構元素B ;否則在垂直方向上進行膨脹運算,即結構元素B

 

腐蝕源碼
膨脹源碼

BOOL Dilation(HWND hWnd,BOOL Hori)

{

       DWORD                             OffBits,BufSize;

LPBITMAPINFOHEADER    lpImgData;

       LPSTR                   lpPtr;

       HLOCAL                  hTempImgData;

       LPBITMAPINFOHEADER    lpTempImgData;

       LPSTR                     lpTempPtr;

       HDC                     hDc;

       HFILE                    hf;

       LONG                    x,y;

       unsigned char              num;

       int                        i;

//為了處理的方便,仍采用256級灰度圖,不過只調色板中0和255兩項

if( NumColors!=256){  

            MessageBox(hWnd,"Must be a mono bitmap with grayscale palette!",

"Error Message",MB_OK|MB_ICONEXCLAMATION);

return FALSE;

}

OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);

//BufSize為緩沖區大小

       BufSize=OffBits+bi.biHeight*LineBytes;

//為新的緩沖區分配內存

       if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)

    {

           MessageBox(hWnd,"Error alloc memory!","Error Message",

MB_OK|MB_ICONEXCLAMATION);

return FALSE;

    }

     lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);    

       lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);

       //拷貝頭信息和位圖數據     

       memcpy(lpTempImgData,lpImgData,BufSize);

       if(Hori)

       {   

//在水平方向進行膨脹運算

              for(y=0;y<bi.biHeight;y++){

                     //lpPtr指向原圖數據,lpTempPtr指向新圖數據

                     lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes)+1;

                     lpTempPtr=(char*)lpTempImgData+

(BufSize-LineBytes-y*LineBytes)+1;

                     for(x=1;x<bi.biWidth-1;x++){ 

//注意為防止越界,x的范圍從1到寬度-2

                            num=(unsigned char)*lpPtr;

//原圖中是黑點的,新圖中肯定也是,所以要考慮的是那些原圖

//中的白點,看是否有可能膨脹成黑點

                            if (num==255){

                                   *lpTempPtr=(unsigned char)255; //先置成白點

                                   for(i=0;i<3;i++){ 

                                          num=(unsigned char)*(lpPtr+i-1);

//只要左右鄰居中有一個是黑點,就膨脹成黑點

                                          if(num==0){

*lpTempPtr=(unsigned char)0;

                                                 break;

                                          }

                                   }

                            }

//原圖中就是黑點的,新圖中仍是黑點

                            else *lpTempPtr=(unsigned char)0;

                            //指向下一個象素

                            lpPtr++;

                            lpTempPtr++;

                     }

              }

       }

       else{

//在垂直方向進行腐蝕運算

              for(y=1;y<bi.biHeight-1;y++){ //注意為防止越界,y的范圍從1到高度-2

              lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes);

                     lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes);

                     for(x=0;x<bi.biWidth;x++){

                            num=(unsigned char)*lpPtr;

                            if (num==255){

                                   *lpTempPtr=(unsigned char)255;

                                   for(i=0;i<3;i++){

                                          num=(unsigned char)*(lpPtr+(i-1)*LineBytes);

//只要上下鄰居中有一個是黑點,就膨脹成黑點

                                          if(num==0){

                                                 *lpTempPtr=(unsigned char)0;

                                                 break;

                                          }

                                   }

                            }

                            else *lpTempPtr=(unsigned char)0;

                            lpPtr++;

                            lpTempPtr++;

                     }

              }

       }

    if(hBitmap!=NULL)

           DeleteObject(hBitmap);

       hDc=GetDC(hWnd);     

       //產生新的位圖

       hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,

(LONG)CBM_INIT,

(LPSTR)lpTempImgData+

sizeof(BITMAPINFOHEADER)+

                                         NumColors*sizeof(RGBQUAD),

(LPBITMAPINFO)lpTempImgData,

DIB_RGB_COLORS);

       //起不同的結果文件名

       if(Hori)

              hf=_lcreat("c:\\hdilation.bmp",0);

       else

              hf=_lcreat("c:\\vdilation.bmp",0);

       _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); 

       _lwrite(hf,(LPSTR)lpTempImgData,BufSize);

       _lclose(hf);

       //釋放內存及資源

      ReleaseDC(hWnd,hDc);

       LocalUnlock(hTempImgData);

       LocalFree(hTempImgData);

       GlobalUnlock(hImgData);

       return TRUE;

}

  腐蝕運算和膨脹運算互為對偶的,用公式表示為(X B)c=(Xc B),即X B腐蝕后的補集等於X的補集被B膨脹。這句話可以形象的理解為:河岸的補集為河面,河岸的腐蝕等價於河面的膨脹。你可以自己舉個例子來驗證一下這個關系。在有些情況下,這個對偶關系是非常有用的。例如:某個圖象處理系統用硬件實現了腐蝕運算,那么不必再另搞一套膨脹的硬件,直接利用該對偶就可以實現了。

開運算

先腐蝕后膨脹稱為開(open),即OPEN(X)=D(E(X))

讓我們來看一個開運算的例子(見圖9)

                                 

                              圖9

  在圖9上面的兩幅圖中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),右邊是結構元素B,下面的兩幅圖中左邊是腐蝕后的結果;右邊是在此基礎上膨脹的結果。可以看到,原圖經過開運算后,一些孤立的小點被去掉了。一般來說,開運算能夠去除孤立的小點,毛刺和小橋(即連通兩塊區域的小點),而總的位置和形狀不變。這就是開運算的作用。要注意的是,如果B是非對稱的,進行開運算時要用B的對稱集Bv膨脹,否則,開運算的結果和原圖相比要發生平移。圖10和圖11能夠說明這個問題。

                                

                         圖10B膨脹后,結果向左平移了

                                                     

                             11   Bv膨脹后位置不變

 

10是用B膨脹的,可以看到,OPEN(X)向左平移了。圖11是用Bv膨脹的,可以看到,總的位置和形狀不變。

12為圖6.11經過開運算后的結果。

                                       

                                         圖12

先膨脹后腐蝕稱為閉(close),即CLOSE(X)=E(D(X))

讓我們來看一個閉運算的例子(見圖13)

                                       

                               圖13

在圖13上面的兩幅圖中,左邊是被處理的圖象X(二值圖象,我們針對的是黑點),右邊是結構元素B,下面的兩幅圖中左邊是膨脹后的結果,右邊是在此基礎上腐蝕的結果可以看到,原圖經過閉運算后,斷裂的地方被彌合了。一般來說,閉運算能夠填平小湖(即小孔),彌合小裂縫,而總的位置和形狀不變。這就是閉運算的作用。同樣要注意的是,如果B是非對稱的,進行閉運算時要用B的對稱集Bv膨脹,否則,閉運算的結果和原圖相比要發生平移。

14為圖4經過閉運算后的結果。

                                           

                               圖14

閉運算的源程序可以很容易的根據上面的膨脹,腐蝕程序得到,這里就不給出了。

  你大概已經猜到了,開和閉也是對偶運算,的確如此。用公式表示為(OPEN(X))c=CLOSE((Xc)),或者(CLOSE(X))c =OPEN((Xc))。即X 開運算的補集等於X的補集的閉運算,或者X 閉運算的補集等於X的補集的開運算。這句話可以這樣來理解:在兩個小島之間有一座小橋,我們把島和橋看做是處理對象X,則X的補集為大海。如果漲潮時將小橋和島的外圍淹沒(相當於用尺寸比橋寬大的結構元素對X進行開運算),那么兩個島的分隔,相當於小橋兩邊海域的連通(Xc做閉運算)

 

本文說明:本文中用的事例是把黑點看做的‘1’,把白點看做了‘0’,但是我們在opencv中使用時是把黑點看做‘0’,把白點看做’1‘的,所以膨脹擴張的是白的區域,腐蝕縮小的也是白色的區域

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM