二值圖像的腐蝕和膨脹

 二值圖像的腐蝕和膨脹圖像數字處理中應用相當廣泛,代碼處理也很簡單,只不過一些資料在介紹腐蝕和膨脹原理時,用一些形態學、集合上的概念和術語,搞得也有些”高深莫測“了。

    從圖像處理角度看,二值圖像的腐蝕和膨脹就是將一個小型二值圖(結構元素,一般爲3*3大小)在一個大的二值圖上逐點移動並進行比較,根據比較的結果作出相應處理而已。以二值圖的骨架爲黑色點爲例:

    作圖像腐蝕處理時,如果結構元素中的所有黑色點與它對應的大圖像素點完全相同,該點爲黑色,否則爲白色。

    作圖像膨脹處理時,如果結構元素中只要有一個及以上黑色點與它對應的大圖像素點相同,該點爲黑色,否則爲白色。也就是說,如果結構元素中的所有黑色點與它對應的大圖像素點沒有一個相同,該點爲白色,否則爲黑色。結構元素中的所有黑色點與它對應的大圖像素點沒有一個相同,說明大圖的這些像素點都是白色的,假如二值圖的骨架爲白色點,這個對黑色骨架二值圖的膨脹處理恰好是對白色骨架二值圖的腐蝕處理。同理,對黑色骨架二值圖的腐蝕處理也就是對白色骨架的膨脹處理。

    根據這個道理,我們完全可以把對黑色骨架和白色骨架分別所作的腐蝕和膨脹處理代碼統一起來,使得原來所需要的四段處理代碼變成二段甚至一段處理代碼。

    下面是一個對32位像素格式二值圖像數據的腐蝕和膨脹處理的全部代碼:

  1. //---------------------------------------------------------------------------   
  2.   
  3. // 定義ARGB像素結構   
  4. typedef union  
  5. {  
  6.     ARGB Color;  
  7.     struct  
  8.     {  
  9.         BYTE Blue;  
  10.         BYTE Green;  
  11.         BYTE Red;  
  12.         BYTE Alpha;  
  13.     };  
  14. }ARGBQuad, *PARGBQuad;  
  15. //---------------------------------------------------------------------------   
  16.   
  17. // 獲取二值圖像data的字節圖數據map,骨架像素是否爲黑色   
  18. VOID GetDataMap(CONST BitmapData *data, BitmapData *map, BOOL blackPixel)  
  19. {  
  20.     // 字節圖邊緣擴展1字節,便於處理data的邊緣像素   
  21.     map->Width = data->Width + 2;  
  22.     map->Height = data->Height + 2;  
  23.     map->Stride = map->Width;  
  24.     map->Scan0 = (void*)new char[map->Stride * map->Height + 1];// +1防最末字節越界   
  25.     BYTE *ps = (BYTE*)data->Scan0;  
  26.     BYTE *pd0 = (BYTE*)map->Scan0;  
  27.     BYTE *pd = pd0 + map->Stride;  
  28.     BYTE *pt = pd;  
  29.     INT srcOffset = data->Stride - data->Width * sizeof(ARGBQuad);  
  30.     UINT x, y;  
  31.   
  32.     // 如果骨架像素爲黑色,獲取異或字節圖   
  33.     if (blackPixel)  
  34.     {  
  35.         for (y = 0; y < data->Height; y ++, ps += srcOffset)  
  36.         {  
  37.             *pd ++ = *ps ^ 255;  
  38.             for (x = 0; x < data->Width; x ++, ps += sizeof(ARGBQuad))  
  39.                 *pd ++ = *ps ^ 255;  
  40.             *pd ++ = *(ps - sizeof(ARGBQuad)) ^ 255;  
  41.         }  
  42.   
  43.     }  
  44.     // 否則,獲取正常字節圖   
  45.     else  
  46.     {  
  47.         for (y = 0; y < data->Height; y ++, *pd ++ = *(ps - sizeof(ARGBQuad)), ps += srcOffset)  
  48.         {  
  49.             for (x = 0, *pd ++ = *ps; x < data->Width; x ++, *pd ++ = *ps, ps += sizeof(ARGBQuad));  
  50.         }  
  51.     }  
  52.     ps = pd - map->Stride;  
  53.     for (x = 0; x < map->Width; x ++, *pd0 ++ = *pt ++, *pd ++ = *ps ++);  
  54. }  
  55. //---------------------------------------------------------------------------   
  56.   
  57. // 按結構元素模板templet製作字節掩碼數組masks   
  58. // templet低3字節的低3位對應結構元素,如下面的結構元素:   
  59. //   水平     垂直     十字     方形     其它   
  60. //   ○ ○ ○    ○ ● ○    ○ ● ○    ● ● ●    ○ ● ○   
  61. //   ● ● ●    ○ ● ○    ● ● ●    ● ● ●    ● ● ●   
  62. //   ○ ○ ○    ○ ● ○    ○ ● ○    ● ● ●    ○ ● ●   
  63. // 用templet分別表示爲:0x000700, 0x020202, 0x020702, 0x070707, 0x020703   
  64. VOID GetTempletMasks(DWORD templet, DWORD masks[])  
  65. {  
  66.     for (INT i = 2; i >= 0; i --, templet >>= 8)  
  67.     {  
  68.         masks[i] = 0;  
  69.         for (UINT j = 4; j; j >>= 1)  
  70.         {  
  71.             masks[i] <<= 8;  
  72.             if (templet & j) masks[i] |= 1;  
  73.         }  
  74.     }  
  75. }  
  76. //---------------------------------------------------------------------------   
  77.   
  78. VOID Erosion_Dilation(BitmapData *data, DWORD templet, BOOL blackPixel)  
  79. {  
  80.     BitmapData map;  
  81.     GetDataMap(data, &map, blackPixel);  
  82.   
  83.     PARGBQuad pd = (PARGBQuad)data->Scan0;  
  84.     BYTE *ps = (BYTE*)map.Scan0 + map.Stride;  
  85.     INT width = (INT)data->Width;  
  86.     INT height = (INT)data->Height;  
  87.     INT dstOffset = data->Stride - width * sizeof(ARGBQuad);  
  88.     INT value = blackPixel? 0 : 255;  
  89.     INT x, y;  
  90.   
  91.     if (templet == 0x0700)  // 水平結構元素單獨處理,可提高處理速度   
  92.     {  
  93.         for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)  
  94.         {  
  95.             for (x = 0; x < width; x ++, pd ++, ps ++)  
  96.             {  
  97.                 if (*(DWORD*)ps & 0x010101)  
  98.                     pd->Blue = pd->Green = pd->Red = value;  
  99.             }  
  100.         }  
  101.     }  
  102.     else  
  103.     {  
  104.         DWORD masks[3];  
  105.         GetTempletMasks(templet, masks);  
  106.   
  107.         for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)  
  108.         {  
  109.             for (x = 0; x < width; x ++, pd ++, ps ++)  
  110.             {  
  111.                 if (*(DWORD*)(ps - map.Stride) & masks[0] ||  
  112.                     *(DWORD*)ps & masks[1] ||  
  113.                     *(DWORD*)(ps + map.Stride) & masks[2])  
  114.                     pd->Blue = pd->Green = pd->Red = value;  
  115.             }  
  116.         }  
  117.     }  
  118.   
  119.     delete map.Scan0;  
  120. }  
  121. //---------------------------------------------------------------------------   
  122.   
  123. // 二值圖膨脹。參數:二值圖數據,結構元素模板,是否黑色像素骨架   
  124. FORCEINLINE  
  125. VOID Dilation(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)  
  126. {  
  127.     Erosion_Dilation(data, templet, blackPixel);  
  128. }  
  129. //---------------------------------------------------------------------------   
  130.   
  131. // 二值圖腐蝕。參數:二值圖數據,結構元素模板,是否黑色像素骨架   
  132. FORCEINLINE  
  133. VOID Erosion(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)  
  134. {  
  135.     Erosion_Dilation(data, templet, !blackPixel);  
  136. }  
  137. //---------------------------------------------------------------------------  

    本文的二值圖像的腐蝕和膨脹處理代碼有以下特點:

    1、可使用任意的3*3結構元素進行處理。

    2、可對黑色或者白色骨架二值圖進行處理。

    3、在複製字節圖時對邊界像素作了擴展,以便對二值圖的邊界處理。

    4、沒有采用結構元素逐點比較的作法,而是使用結構元素掩碼,每次對三個像素進行邏輯運算,特別是對水平結構元素的單獨處理,大大提高了處理效率。

    5、上面的代碼雖然針對的是32位像素格式的二值圖,但稍作修改即可適應24位或者8位像素格式二值圖像數據。其實,對於32位像素格式的二值圖,改用下面的處理代碼可提高圖像處理速度(因其使用位運算一次性對像素的R、G、B分量進行了賦值):

  1. //---------------------------------------------------------------------------   
  2.   
  3. VOID _Dilation(BitmapData *data, BitmapData *map, DWORD templet)  
  4. {  
  5.     PARGBQuad pd = (PARGBQuad)data->Scan0;  
  6.     BYTE *ps = (BYTE*)map->Scan0 + map->Stride;  
  7.     INT width = (INT)data->Width;  
  8.     INT height = (INT)data->Height;  
  9.     INT dstOffset = data->Stride - width * sizeof(ARGBQuad);  
  10.     INT x, y;  
  11.   
  12.     if (templet == 0x0700)  // 水平結構元素單獨處理,可提高處理速度   
  13.     {  
  14.         for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)  
  15.         {  
  16.             for (x = 0; x < width; x ++, pd ++, ps ++)  
  17.                 if (*(DWORD*)ps & 0x010101)  
  18.                     pd->Color &= 0xff000000;  
  19.         }  
  20.     }  
  21.     else  
  22.     {  
  23.         DWORD masks[3];  
  24.         GetTempletMasks(templet, masks);  
  25.   
  26.         for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)  
  27.         {  
  28.             for (x = 0; x < width; x ++, pd ++, ps ++)  
  29.                 if (*(DWORD*)(ps - map->Stride) & masks[0] ||  
  30.                     *(DWORD*)ps & masks[1] ||  
  31.                     *(DWORD*)(ps + map->Stride) & masks[2])  
  32.                     pd->Color &= 0xff000000;  
  33.         }  
  34.     }  
  35. }  
  36. //---------------------------------------------------------------------------   
  37.   
  38. VOID _Erosion(BitmapData *data, BitmapData *map, DWORD templet)  
  39. {  
  40.     PARGBQuad pd = (PARGBQuad)data->Scan0;  
  41.     BYTE *ps = (BYTE*)map->Scan0 + map->Stride;  
  42.     INT width = (INT)data->Width;  
  43.     INT height = (INT)data->Height;  
  44.     INT dstOffset = data->Stride - width * sizeof(ARGBQuad);  
  45.     INT x, y;  
  46.   
  47.     if (templet == 0x0700)  // 水平結構元素單獨處理,可提高處理速度   
  48.     {  
  49.         for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)  
  50.         {  
  51.             for (x = 0; x < width; x ++, pd ++, ps ++)  
  52.                 if (*(DWORD*)ps & 0x010101)  
  53.                     pd->Color |= 0x00ffffff;  
  54.         }  
  55.     }  
  56.     else  
  57.     {  
  58.         DWORD masks[3];  
  59.         GetTempletMasks(templet, masks);  
  60.   
  61.         for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)  
  62.         {  
  63.             for (x = 0; x < width; x ++, pd ++, ps ++)  
  64.                 if (*(DWORD*)(ps - map->Stride) & masks[0] ||  
  65.                     *(DWORD*)ps & masks[1] ||  
  66.                     *(DWORD*)(ps + map->Stride) & masks[2])  
  67.                     pd->Color |= 0x00ffffff;  
  68.         }  
  69.     }  
  70. }  
  71. //---------------------------------------------------------------------------   
  72.   
  73. // 二值圖膨脹。參數:二值圖數據,結構元素模板,是否黑色像素骨架   
  74. VOID Dilation(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)  
  75. {  
  76.     BitmapData map;  
  77.     GetDataMap(data, &map, blackPixel);  
  78.     if (blackPixel)  
  79.         _Dilation(data, &map, templet);  
  80.     else  
  81.         _Erosion(data, &map, templet);  
  82.     delete map.Scan0;  
  83. }  
  84. //---------------------------------------------------------------------------   
  85.   
  86. // 二值圖腐蝕。參數:二值圖數據,結構元素模板,是否黑色像素骨架   
  87. VOID Erosion(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)  
  88. {  
  89.     Dilation(data, templet, !blackPixel);  
  90. }  
  91. //---------------------------------------------------------------------------  

    下面是使用BCB2007和GDI+位圖對黑色骨架二值圖進行的開運算處理,也可看作是對白色骨架二值圖的閉運算處理(先腐蝕後膨脹爲開運算;先膨脹後腐蝕爲閉運算):

  1. //---------------------------------------------------------------------------   
  2.   
  3. // 圖像數據data灰度同時二值化,threshold閥值   
  4. VOID GrayAnd2Values(BitmapData *data, BYTE threshold)  
  5. {  
  6.     PARGBQuad p = (PARGBQuad)data->Scan0;  
  7.     INT offset = data->Stride - data->Width * sizeof(ARGBQuad);  
  8.   
  9.     for (UINT y = 0; y < data->Height; y ++, (BYTE*)p += offset)  
  10.     {  
  11.         for (UINT x = 0; x < data->Width; x ++, p ++)  
  12.         {  
  13.             if (((p->Blue * 29 + p->Green * 150 + p->Red * 77 + 128) >> 8) < threshold)  
  14.                 p->Color &= 0xff000000;  
  15.             else  
  16.                 p->Color |= 0x00ffffff;  
  17.   
  18.         }  
  19.     }  
  20. }  
  21. //---------------------------------------------------------------------------   
  22.   
  23. // 鎖定GDI+位位圖掃描線到data   
  24. FORCEINLINE  
  25. VOID LockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)  
  26. {  
  27.     Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());  
  28.     bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite,  
  29.         PixelFormat32bppARGB, data);  
  30. }  
  31. //---------------------------------------------------------------------------   
  32.   
  33. // GDI+位圖掃描線解鎖   
  34. FORCEINLINE  
  35. VOID UnlockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)  
  36. {  
  37.     bmp->UnlockBits(data);  
  38. }  
  39. //---------------------------------------------------------------------------   
  40.   
  41. void __fastcall TForm1::Button1Click(TObject *Sender)  
  42. {  
  43.     Gdiplus::Bitmap *bmp =  new Gdiplus::Bitmap(L"d:\\source1.jpg");  
  44.     Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);  
  45.     g->DrawImage(bmp, 0, 0);  
  46.     BitmapData data;  
  47.     LockBitmap(bmp, &data);  
  48.   
  49.     GrayAnd2Values(&data, 128);  
  50.     Erosion(&data, 0x020702);  
  51.     Dilation(&data, 0x020702);  
  52.   
  53.     UnlockBitmap(bmp, &data);  
  54.     g->DrawImage(bmp, data.Width, 0);  
  55.     delete g;  
  56.     delete bmp;  
  57. }  
  58. //---------------------------------------------------------------------------  

    下面是使用本文代碼所作的各種處理效果圖,結構元素爲“十”字形:

    左上原圖,右上二值圖,左中腐蝕圖,右中膨脹圖,左下開運算圖,右下閉運算圖。這是對黑色骨架二值圖而言,如果是白色骨架二值圖,中間和下邊的左右處理效果就是相反的了。

發佈了37 篇原創文章 · 獲贊 3 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章