从图像处理角度看,二值图像的腐蚀和膨胀就是将一个小型二值图(结构元素,一般为3*3大小)在一个大的二值图上逐点移动并进行比較,依据比較的结果作出对应处理而已。以二值图的骨架为黑色点为例:
作图像腐蚀处理时,假设结构元素中的全部黑色点与它相应的大图像素点全然同样,该点为黑色,否则为白色。
作图像膨胀处理时,假设结构元素中仅仅要有一个及以上黑色点与它相应的大图像素点同样,该点为黑色,否则为白色。也就是说,假设结构元素中的全部黑色点与它相应的大图像素点没有一个同样,该点为白色,否则为黑色。结构元素中的全部黑色点与它相应的大图像素点没有一个同样,说明大图的这些像素点都是白色的,假如二值图的骨架为白色点,这个对黑色骨架二值图的膨胀处理恰好是对白色骨架二值图的腐蚀处理。同理,对黑色骨架二值图的腐蚀处理也就是对白色骨架的膨胀处理。
依据这个道理,我们全然能够把对黑色骨架和白色骨架分别所作的腐蚀和膨胀处理代码统一起来,使得原来所须要的四段处理代码变成二段甚至一段处理代码。
以下是一个对32位像素格式二值图像数据的腐蚀和膨胀处理的所有代码:
- //---------------------------------------------------------------------------
- // 定义ARGB像素结构
- typedef union
- {
- ARGB Color;
- struct
- {
- BYTE Blue;
- BYTE Green;
- BYTE Red;
- BYTE Alpha;
- };
- }ARGBQuad, *PARGBQuad;
- //---------------------------------------------------------------------------
- // 获取二值图像data的字节图数据map,骨架像素是否为黑色
- VOID GetDataMap(CONST BitmapData *data, BitmapData *map, BOOL blackPixel)
- {
- // 字节图边缘扩展1字节,便于处理data的边缘像素
- map->Width = data->Width + 2;
- map->Height = data->Height + 2;
- map->Stride = map->Width;
- map->Scan0 = (void*)new char[map->Stride * map->Height + 1];// +1防最末字节越界
- BYTE *ps = (BYTE*)data->Scan0;
- BYTE *pd0 = (BYTE*)map->Scan0;
- BYTE *pd = pd0 + map->Stride;
- BYTE *pt = pd;
- INT srcOffset = data->Stride - data->Width * sizeof(ARGBQuad);
- UINT x, y;
- // 假设骨架像素为黑色,获取异或字节图
- if (blackPixel)
- {
- for (y = 0; y < data->Height; y ++, ps += srcOffset)
- {
- *pd ++ = *ps ^ 255;
- for (x = 0; x < data->Width; x ++, ps += sizeof(ARGBQuad))
- *pd ++ = *ps ^ 255;
- *pd ++ = *(ps - sizeof(ARGBQuad)) ^ 255;
- }
- }
- // 否则,获取正常字节图
- else
- {
- for (y = 0; y < data->Height; y ++, *pd ++ = *(ps - sizeof(ARGBQuad)), ps += srcOffset)
- {
- for (x = 0, *pd ++ = *ps; x < data->Width; x ++, *pd ++ = *ps, ps += sizeof(ARGBQuad));
- }
- }
- ps = pd - map->Stride;
- for (x = 0; x < map->Width; x ++, *pd0 ++ = *pt ++, *pd ++ = *ps ++);
- }
- //---------------------------------------------------------------------------
- // 按结构元素模板templet制作字节掩码数组masks
- // templet低3字节的低3位相应结构元素,如以下的结构元素:
- // 水平 垂直 十字 方形 其他
- // ○ ○ ○ ○ ● ○ ○ ● ○ ● ● ● ○ ● ○
- // ● ● ● ○ ● ○ ● ● ● ● ● ● ● ● ●
- // ○ ○ ○ ○ ● ○ ○ ● ○ ● ● ● ○ ● ●
- // 用templet分别表示为:0x000700, 0x020202, 0x020702, 0x070707, 0x020703
- VOID GetTempletMasks(DWORD templet, DWORD masks[])
- {
- for (INT i = 2; i >= 0; i --, templet >>= 8)
- {
- masks[i] = 0;
- for (UINT j = 4; j; j >>= 1)
- {
- masks[i] <<= 8;
- if (templet & j) masks[i] |= 1;
- }
- }
- }
- //---------------------------------------------------------------------------
- VOID Erosion_Dilation(BitmapData *data, DWORD templet, BOOL blackPixel)
- {
- BitmapData map;
- GetDataMap(data, &map, blackPixel);
- PARGBQuad pd = (PARGBQuad)data->Scan0;
- BYTE *ps = (BYTE*)map.Scan0 + map.Stride;
- INT width = (INT)data->Width;
- INT height = (INT)data->Height;
- INT dstOffset = data->Stride - width * sizeof(ARGBQuad);
- INT value = blackPixel? 0 : 255;
- INT x, y;
- if (templet == 0x0700) // 水平结构元素单独处理,可提高处理速度
- {
- for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)
- {
- for (x = 0; x < width; x ++, pd ++, ps ++)
- {
- if (*(DWORD*)ps & 0x010101)
- pd->Blue = pd->Green = pd->Red = value;
- }
- }
- }
- else
- {
- DWORD masks[3];
- GetTempletMasks(templet, masks);
- for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)
- {
- for (x = 0; x < width; x ++, pd ++, ps ++)
- {
- if (*(DWORD*)(ps - map.Stride) & masks[0] ||
- *(DWORD*)ps & masks[1] ||
- *(DWORD*)(ps + map.Stride) & masks[2])
- pd->Blue = pd->Green = pd->Red = value;
- }
- }
- }
- delete map.Scan0;
- }
- //---------------------------------------------------------------------------
- // 二值图膨胀。參数:二值图数据,结构元素模板,是否黑色像素骨架
- FORCEINLINE
- VOID Dilation(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)
- {
- Erosion_Dilation(data, templet, blackPixel);
- }
- //---------------------------------------------------------------------------
- // 二值图腐蚀。參数:二值图数据,结构元素模板,是否黑色像素骨架
- FORCEINLINE
- VOID Erosion(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)
- {
- Erosion_Dilation(data, templet, !blackPixel);
- }
- //---------------------------------------------------------------------------
本文的二值图像的腐蚀和膨胀处理代码有下面特点:
1、可使用随意的3*3结构元素进行处理。
2、可对黑色或者白色骨架二值图进行处理。
3、在复制字节图时对边界像素作了扩展,以便对二值图的边界处理。
4、没有採用结构元素逐点比較的作法,而是使用结构元素掩码,每次对三个像素进行逻辑运算,特别是对水平结构元素的单独处理,大大提高了处理效率。
5、上面的代码尽管针对的是32位像素格式的二值图,但稍作改动就可以适应24位或者8位像素格式二值图像数据。事实上,对于32位像素格式的二值图,改用以下的处理代码可提高图像处理速度(因其使用位运算一次性对像素的R、G、B分量进行了赋值):
- //---------------------------------------------------------------------------
- VOID _Dilation(BitmapData *data, BitmapData *map, DWORD templet)
- {
- PARGBQuad pd = (PARGBQuad)data->Scan0;
- BYTE *ps = (BYTE*)map->Scan0 + map->Stride;
- INT width = (INT)data->Width;
- INT height = (INT)data->Height;
- INT dstOffset = data->Stride - width * sizeof(ARGBQuad);
- INT x, y;
- if (templet == 0x0700) // 水平结构元素单独处理,可提高处理速度
- {
- for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)
- {
- for (x = 0; x < width; x ++, pd ++, ps ++)
- if (*(DWORD*)ps & 0x010101)
- pd->Color &= 0xff000000;
- }
- }
- else
- {
- DWORD masks[3];
- GetTempletMasks(templet, masks);
- for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)
- {
- for (x = 0; x < width; x ++, pd ++, ps ++)
- if (*(DWORD*)(ps - map->Stride) & masks[0] ||
- *(DWORD*)ps & masks[1] ||
- *(DWORD*)(ps + map->Stride) & masks[2])
- pd->Color &= 0xff000000;
- }
- }
- }
- //---------------------------------------------------------------------------
- VOID _Erosion(BitmapData *data, BitmapData *map, DWORD templet)
- {
- PARGBQuad pd = (PARGBQuad)data->Scan0;
- BYTE *ps = (BYTE*)map->Scan0 + map->Stride;
- INT width = (INT)data->Width;
- INT height = (INT)data->Height;
- INT dstOffset = data->Stride - width * sizeof(ARGBQuad);
- INT x, y;
- if (templet == 0x0700) // 水平结构元素单独处理,可提高处理速度
- {
- for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)
- {
- for (x = 0; x < width; x ++, pd ++, ps ++)
- if (*(DWORD*)ps & 0x010101)
- pd->Color |= 0x00ffffff;
- }
- }
- else
- {
- DWORD masks[3];
- GetTempletMasks(templet, masks);
- for (y = 0; y < height; y ++, (BYTE*)pd += dstOffset, ps += 2)
- {
- for (x = 0; x < width; x ++, pd ++, ps ++)
- if (*(DWORD*)(ps - map->Stride) & masks[0] ||
- *(DWORD*)ps & masks[1] ||
- *(DWORD*)(ps + map->Stride) & masks[2])
- pd->Color |= 0x00ffffff;
- }
- }
- }
- //---------------------------------------------------------------------------
- // 二值图膨胀。參数:二值图数据,结构元素模板,是否黑色像素骨架
- VOID Dilation(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)
- {
- BitmapData map;
- GetDataMap(data, &map, blackPixel);
- if (blackPixel)
- _Dilation(data, &map, templet);
- else
- _Erosion(data, &map, templet);
- delete map.Scan0;
- }
- //---------------------------------------------------------------------------
- // 二值图腐蚀。參数:二值图数据,结构元素模板,是否黑色像素骨架
- VOID Erosion(BitmapData *data, DWORD templet, BOOL blackPixel = TRUE)
- {
- Dilation(data, templet, !blackPixel);
- }
- //---------------------------------------------------------------------------
以下是使用BCB2007和GDI+位图对黑色骨架二值图进行的开运算处理,也可看作是对白色骨架二值图的闭运算处理(先腐蚀后膨胀为开运算;先膨胀后腐蚀为闭运算):
- //---------------------------------------------------------------------------
- // 图像数据data灰度同一时候二值化,threshold阀值
- VOID GrayAnd2Values(BitmapData *data, BYTE threshold)
- {
- PARGBQuad p = (PARGBQuad)data->Scan0;
- INT offset = data->Stride - data->Width * sizeof(ARGBQuad);
- for (UINT y = 0; y < data->Height; y ++, (BYTE*)p += offset)
- {
- for (UINT x = 0; x < data->Width; x ++, p ++)
- {
- if (((p->Blue * 29 + p->Green * 150 + p->Red * 77 + 128) >> 8) < threshold)
- p->Color &= 0xff000000;
- else
- p->Color |= 0x00ffffff;
- }
- }
- }
- //---------------------------------------------------------------------------
- // 锁定GDI+位位图扫描线到data
- FORCEINLINE
- VOID LockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
- {
- Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());
- bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite,
- PixelFormat32bppARGB, data);
- }
- //---------------------------------------------------------------------------
- // GDI+位图扫描线解锁
- FORCEINLINE
- VOID UnlockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
- {
- bmp->UnlockBits(data);
- }
- //---------------------------------------------------------------------------
- void __fastcall TForm1::Button1Click(TObject *Sender)
- {
- Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap(L"d:\\source1.jpg");
- Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
- g->DrawImage(bmp, 0, 0);
- BitmapData data;
- LockBitmap(bmp, &data);
- GrayAnd2Values(&data, 128);
- Erosion(&data, 0x020702);
- Dilation(&data, 0x020702);
- UnlockBitmap(bmp, &data);
- g->DrawImage(bmp, data.Width, 0);
- delete g;
- delete bmp;
- }
- //---------------------------------------------------------------------------
以下是使用本文代码所作的各种处理效果图,结构元素为“十”字形:
左上原图,右上二值图,左中腐蚀图,右中膨胀图,左下开运算图,右下闭运算图。这是对黑色骨架二值图而言,假设是白色骨架二值图,中间和下边的左右处理效果就是相反的了。