2016-07-17 12:34:21 qq_29540745 阅读数 14905

图像的腐蚀与膨胀

一、原理:

⑴ 图像形态学处理的概念
        数字图像处理中的形态学处理是指将数字形态学作为工具从图像中提取对于表达和描绘区域形状有用处的图像分量,比如边界、骨架以及凸壳,还包括用于预处理或后处理的形态学过滤、细化和修剪等。图像形态学处理中我们感兴趣的主要是二值图像。

⑵ 二值图像的逻辑运算
        逻辑运算尽管本质上很简单,但对于实现以形态学为基础额图像处理算法是一种有力的补充手段。在图像处理中用到的主要逻辑运算是:与、或和非(求补),它们可以互相组合形成其他逻辑运算。

⑶ 膨胀和腐蚀

        膨胀和腐蚀这两种操作是形态学处理的基础,许多形态学算法都是以这两种运算为基础的。

定义结构元素B为:

1 1
1 0
图像元素与结构元素相乘,从而求得右下角元素值
(i-1,j+1) (i,j+1)
(i-1,j) 所求此点(i,j)

① 膨胀
⑴ 用结构元素B,扫描图像A的每一个像素
⑵ 用结构元素与其覆盖的二值图像做“或”操作
⑶ 如果有一个元素为0,结果图像的该像素为0。否则为255

② 腐蚀
         对Z中的集合A和B,B对A进行腐蚀的整个过程如下: 
⑴ 用结构元素B,扫描图像A的每一个像素
⑵ 用结构元素与其覆盖的二值图像做“与”操作
⑶ 如果都为0,结果图像的该像素为0。否则为255

腐蚀处理的结果是使原来的二值图像减小一圈。

二、我再加一个轮廓提取,非常简单的方法:用的是9X9的模板;

(i-1,j+1) (i,j+1) (i+1,j+1)
(i-1,j) 所求此点(i,j) (i+1,j)
(i-1,j-1) (i,j-1) (i+1,j_1)
三、代码

#include<opencv2/opencv.hpp>
#include<iostream>
using  namespace cv;
using namespace std;

Mat srcImage, grayImage, binarygray, erosion, dilation, outline;


static void g_erosion(int, void*);
static void g_dilation(int, void*);
static void g_outline(int, void*);
static void ShowHelpText();

int main()
{
	system("color 3f");
	ShowHelpText();

	srcImage = imread("D://vvoo//cell.jpg");
	cvtColor(srcImage, grayImage, CV_RGB2GRAY);

	int threshold;
	cout << "input threshold: " << endl;
	cin >> threshold;

	//二值化
	binarygray = Mat::zeros(grayImage.rows, grayImage.cols, grayImage.type());
	{
		for (int i = 0; i <grayImage.rows; i++)
		{
			for (int j = 0; j < grayImage.cols; j++)
			{
				if (grayImage.data[i*grayImage.step + j] > threshold)
				{
					binarygray.data[i*binarygray.step + j] = 255;
				}
				else
				{
					binarygray.data[i*binarygray.step + j] = 0;
				}
			}
		}
	}
	//腐蚀
	g_erosion(0, 0);
	//膨胀
	g_dilation(0, 0);
	//轮廓提取
	g_outline(0, 0);

	imshow("原图", srcImage);
	imshow("binarygray", binarygray);

	waitKey(0);
	return 0;
}
static void g_erosion(int, void*)
{
	erosion = Mat::zeros(binarygray.rows, binarygray.cols, binarygray.type());
	{
		for (int i = 1; i < binarygray.rows; i++)
		{
			for (int j = 1; j < binarygray.cols; j++)
			{
				if (binarygray.data[(i - 1)*binarygray.step + j] + binarygray.data[(i - 1)*binarygray.step + j + 1] + binarygray.data[i*binarygray.step + j + 1] == 0)
				{
					erosion.data[i*erosion.step + j] = 0;
				}
				else
				{
					erosion.data[i*erosion.step + j] = 255;
				}
			}

		}

	}
	imshow("erosion_1", erosion);
}
static void g_dilation(int, void*)
{
	dilation = Mat::zeros(binarygray.rows, binarygray.cols, binarygray.type());

	for (int i = 1; i < binarygray.rows; i++)
	{
		for (int j = 1; j < binarygray.cols; j++)
		{
			if (binarygray.data[(i - 1)*binarygray.step + j] == 0 || binarygray.data[(i - 1)*binarygray.step + j - 1] == 0 || binarygray.data[i*binarygray.step + j + 1] == 0)
			{
				dilation.data[i*dilation.step + j] = 0;
			}
			else
			{
				dilation.data[i*dilation.step + j] = 255;
			}
		}

	}

	imshow("dilation_1", dilation);
}
static void g_outline(int, void*)
{
	outline = Mat::zeros(binarygray.rows, binarygray.cols, binarygray.type());

	for (int i = 1; i < binarygray.rows; i++)
	{
		for (int j = 1; j < binarygray.cols; j++)
		{
			if (binarygray.data[i*binarygray.step + j + 1] + binarygray.data[(i - 1)*binarygray.step + j]
				+ binarygray.data[i*binarygray.step + j - 1] + binarygray.data[(i - 1)*binarygray.step + j - 1]
				+ binarygray.data[(i + 1)*binarygray.step + j - 1] + binarygray.data[(i + 1)*binarygray.step + j]
				+ binarygray.data[(i - 1)*binarygray.step + j + 1] + binarygray.data[(i + 1)*binarygray.step + j + 1] == 2040)
			{
				outline.data[i*erosion.step + j] = 255;
			}
			if (binarygray.data[i*binarygray.step + j + 1] + binarygray.data[(i - 1)*binarygray.step + j]
				+ binarygray.data[i*binarygray.step + j - 1] + binarygray.data[(i - 1)*binarygray.step + j - 1]
				+ binarygray.data[(i + 1)*binarygray.step + j - 1] + binarygray.data[(i + 1)*binarygray.step + j]
				+ binarygray.data[(i - 1)*binarygray.step + j + 1] + binarygray.data[(i + 1)*binarygray.step + j + 1] == 0)
			{
				outline.data[i*erosion.step + j] = 255;
			}
		}


	}
	imshow("outline", outline);
}
static void ShowHelpText()
{
	cout << "\n\n本程序涉及到:"<<"腐蚀(erosion)、膨胀(dilation)、轮廓提取(outline)。\n\n" << endl;
}
四、运行结果




五、调用Opencv的erode()函数和dilate()函数实现腐蚀和膨胀功能

1)erode函数,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

看一下函数原型:

 void erode(
  InputArray src,
  OutputArray dst,
  InputArray kernel,
  Point anchor=Point(-1,-1),
  int iterations=1,
  int borderType=BORDER_CONSTANT,
  const Scalar& borderValue=morphologyDefaultBorderValue()
 );

参数原型

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
  • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。(具体看上文中浅出部分dilate函数的第三个参数讲解部分)
  • 第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
  • 第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
  • 第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
  • 第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

同样的,使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。

2)dilate函数原型

函数原型:

C++: void dilate(
  InputArray src,
  OutputArray dst,
  InputArray kernel,
  Point anchor=Point(-1,-1),
  int iterations=1,
  int borderType=BORDER_CONSTANT,
  const Scalar& borderValue=morphologyDefaultBorderValue() 
);

参数详解:

  • 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
  • 第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
  • 第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。

我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵。其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:

矩形: MORPH_RECT

    • 交叉形: MORPH_CROSS
    • 椭圆形: MORPH_ELLIPSE

而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。

3)代码实现

#include<opencv2/opencv.hpp>
#include<iostream>
using  namespace cv;
using namespace std;

#define WINDOWN_NAME_1 "原图"
#define WINDOWN_NAME_2 "腐蚀图"
#define WINDOWN_NAME_3 "膨胀图"

int main()
{
	Mat srcImage = imread("D://vvoo//cell.jpg");

	//获取自定义核
	Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
	Mat out_erosion, out_dilate;

	//进行膨胀操作
	erode(srcImage, out_erosion, element);
	dilate(srcImage, out_dilate, element);

	imshow(WINDOWN_NAME_1, srcImage);
	imshow(WINDOWN_NAME_2, out_erosion);
	imshow(WINDOWN_NAME_3, out_dilate);

	waitKey(0);
	return 0;

}

4)运行结果



和自己写的比较下比较一下,差别比较大,主要是因为结构元素大小的关系,我的是2*2,Opencv是15*15的。

我也是初学者,欢迎纠正!



六、参考资料

1.system("color 3f");//输出窗口和字体颜色可变化,3代表窗口颜色(绿色),f代表窗口里字体颜色(白色)

全部颜色为:


2.图像腐蚀、膨胀、细化基本原理

3. 形态学图像处理(一): 膨胀与腐蚀

2018-10-30 15:56:28 u013230291 阅读数 5029

腐蚀的原理:

二值图像前景物体为1,背景为0.假设原图像中有一个前景物体,那么我们用一个结构元素去腐蚀原图的过程是这样的:遍历原图像的每一个像素,然后用结构元素的中心点对准当前正在遍历的这个像素,然后取当前结构元素所覆盖下的原图对应区域内的所有像素的最小值,用这个最小值替换当前像素值。由于二值图像最小值就是0,所以就是用0替换,即变成了黑色背景。从而也可以看出,如果当前结构元素覆盖下,全部都是背景,那么就不会对原图做出改动,因为都是0.如果全部都是前景像素,也不会对原图做出改动,因为都是1.只有结构元素位于前景物体边缘的时候,它覆盖的区域内才会出现0和1两种不同的像素值,这个时候把当前像素替换成0就有变化了。因此腐蚀看起来的效果就是让前景物体缩小了一圈一样。对于前景物体中一些细小的连接处,如果结构元素大小相等,这些连接处就会被断开。

膨胀的原理:

二值图像前景物体为1,背景为0.假设原图像中有一个前景物体,那么我们用一个结构元素去膨胀原图的过程是这样的:遍历原图像的每一个像素,然后用结构元素的中心点对准当前正在遍历的这个像素,然后取当前结构元素所覆盖下的原图对应区域内的所有像素的最大值,用这个最大值替换当前像素值。由于二值图像最大值就是1,所以就是用1替换,即变成了白色前景物体。从而也可以看出,如果当前结构元素覆盖下,全部都是背景,那么就不会对原图做出改动,因为都是0.如果全部都是前景像素,也不会对原图做出改动,因为都是1.只有结构元素位于前景物体边缘的时候,它覆盖的区域内才会出现0和1两种不同的像素值,这个时候把当前像素替换成1就有变化了。因此膨胀看起来的效果就是让前景物体胀大了一圈一样。对于前景物体中一些细小的断裂处,如果结构元素大小相等,这些断裂的地方就会被连接起来。

下篇文章附上底层源码
原文:https://blog.csdn.net/woainishifu/article/details/60778033

2012-11-05 17:45:37 qq917141931 阅读数 409
转自:http://blog.csdn.net/maozefa/article/details/7060342
        二值图像的腐蚀和膨胀图像数字处理中应用相当广泛,代码处理也很简单,只不过一些资料在介绍腐蚀和膨胀原理时,用一些形态学、集合上的概念和术语,搞得也有些”高深莫测“了。

    从图像处理角度看,二值图像的腐蚀和膨胀就是将一个小型二值图(结构元素,一般为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; data->Height; ++, ps += srcOffset)  
  36.          
  37.             *pd ++ *ps 255;  
  38.             for (x 0; data->Width; ++, ps += sizeof(ARGBQuad))  
  39.                 *pd ++ *ps 255;  
  40.             *pd ++ *(ps sizeof(ARGBQuad)) 255;  
  41.          
  42.   
  43.      
  44.     // 否则,获取正常字节图  
  45.     else  
  46.      
  47.         for (y 0; data->Height; ++, *pd ++ *(ps sizeof(ARGBQuad)), ps += srcOffset)  
  48.          
  49.             for (x 0, *pd ++ *ps; data->Width; ++, *pd ++ *ps, ps += sizeof(ARGBQuad));  
  50.          
  51.      
  52.     ps pd map->Stride;  
  53.     for (x 0; map->Width; ++, *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 2; >= 0; --, templet >>= 8)  
  67.      
  68.         masks[i] 0;  
  69.         for (UINT 4; 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? 255;  
  89.     INT x, y;  
  90.   
  91.     if (templet == 0x0700)  // 水平结构元素单独处理,可提高处理速度  
  92.      
  93.         for (y 0; height; ++, (BYTE*)pd += dstOffset, ps += 2)  
  94.          
  95.             for (x 0; width; ++, 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; height; ++, (BYTE*)pd += dstOffset, ps += 2)  
  108.          
  109.             for (x 0; width; ++, 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; height; ++, (BYTE*)pd += dstOffset, ps += 2)  
  15.          
  16.             for (x 0; width; ++, 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; height; ++, (BYTE*)pd += dstOffset, ps += 2)  
  27.          
  28.             for (x 0; width; ++, 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; height; ++, (BYTE*)pd += dstOffset, ps += 2)  
  50.          
  51.             for (x 0; width; ++, 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; height; ++, (BYTE*)pd += dstOffset, ps += 2)  
  62.          
  63.             for (x 0; width; ++, 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 (PARGBQuad)data->Scan0;  
  7.     INT offset data->Stride data->Width sizeof(ARGBQuad);  
  8.   
  9.     for (UINT 0; data->Height; ++, (BYTE*)p += offset)  
  10.      
  11.         for (UINT 0; data->Width; ++, ++)  
  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. //---------------------------------------------------------------------------  

    下面是使用本文代码所作的各种处理效果图,结构元素为“十”字形:

    左上原图,右上二值图,左中腐蚀图,右中膨胀图,左下开运算图,右下闭运算图。这是对黑色骨架二值图而言,如果是白色骨架二值图,中间和下边的左右处理效果就是相反的了。

    如有错误或者建议,请来信指导

2011-12-10 22:17:28 maozefa 阅读数 16803
    二值图像的腐蚀和膨胀图像数字处理中应用相当广泛,代码处理也很简单,只不过一些资料在介绍腐蚀和膨胀原理时,用一些形态学、集合上的概念和术语,搞得也有些”高深莫测“了。

    从图像处理角度看,二值图像的腐蚀和膨胀就是将一个小型二值图(结构元素,一般为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;
}
//---------------------------------------------------------------------------

    下面是使用本文代码所作的各种处理效果图,结构元素为“十”字形:

    左上原图,右上二值图,左中腐蚀图,右中膨胀图,左下开运算图,右下闭运算图。这是对黑色骨架二值图而言,如果是白色骨架二值图,中间和下边的左右处理效果就是相反的了。

    如有错误或者建议,请来信指导:maozefa@hotmail.com

 

2019-05-07 16:11:20 weixin_43887189 阅读数 811

形态学操作

图像形态学操作是基于形状的一系列图像处理操作的合集,主要针对二值图像(二值图像前景物体为1,背景为0)进行处理。
形态学有四个基本操作:腐蚀,膨胀,开,闭。
本文主要记录膨胀和腐蚀。
原图

膨胀

膨胀跟卷积操作类似。
假设有图像A任意形状的内核B(与卷积不同的是,B可以是线、矩形、圆形或者十字等形状。但是通常B为正方形或者圆形),结构元素B在A上面移动遍历所有像素点。B的内核中心点被定义为锚点,进行膨胀操作时,将内核 B划过图像,将内核 B覆盖区域的最大像素值提取,并代替锚点位置的像素。由于二值图像最大值就是1,所以就是用1替换,即变成了白色前景物体。显然,这一最大化操作将会导致图像中的亮区开始扩展。因此膨胀看起来的效果就是让前景物体胀大了一圈一样。对于前景物体中一些细小的断裂处,如果结构元素大小相等,这些断裂的地方就会被连接起来。于是图像高亮部分被进行“领域扩张”,效果图拥有比原图更大的高亮区域。

膨胀处理

腐蚀

腐蚀跟膨胀操作刚好相反。
腐蚀看起来的效果就是让前景物体缩小了一圈一样。对于前景物体中一些细小的连接处,如果结构元素大小相等,这些连接处就会被断开。
以与膨胀相同的图像作为样本,我们使用腐蚀操作。从下面的结果图我们看到亮区(背景)变细,而黑色区域(字母)则变大了。
腐蚀操作

源码实现
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "highgui.h"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/// 全局变量
Mat src, erosion_dst, dilation_dst;

int erosion_elem = 0;
int erosion_size = 0;
int dilation_elem = 0;
int dilation_size = 0;
int const max_elem = 2;
int const max_kernel_size = 21;

/** Function Headers */
void Erosion( int, void* );
void Dilation( int, void* );

/** @function main */
int main( int argc, char** argv )
{
  /// Load 图像
  src = imread( argv[1] );

  if( !src.data )
  { return -1; }

  /// 创建显示窗口
  namedWindow( "Erosion Demo", CV_WINDOW_AUTOSIZE );
  namedWindow( "Dilation Demo", CV_WINDOW_AUTOSIZE );
  cvMoveWindow( "Dilation Demo", src.cols, 0 );

  /// 创建腐蚀 Trackbar
  createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo",
                  &erosion_elem, max_elem,
                  Erosion );

  createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo",
                  &erosion_size, max_kernel_size,
                  Erosion );

  /// 创建膨胀 Trackbar
  createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo",
                  &dilation_elem, max_elem,
                  Dilation );

  createTrackbar( "Kernel size:\n 2n +1", "Dilation Demo",
                  &dilation_size, max_kernel_size,
                  Dilation );

  /// Default start
  Erosion( 0, 0 );
  Dilation( 0, 0 );

  waitKey(0);
  return 0;
}

/**  @function Erosion  */
void Erosion( int, void* )
{
  int erosion_type;
  if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
  else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
  else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }

  Mat element = getStructuringElement( erosion_type,
                                       Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                                       Point( erosion_size, erosion_size ) );

  /// 腐蚀操作
  erode( src, erosion_dst, element );//src原图像,erosion_dst输出图像,element服饰操作的内核,如果不指定,默认为一个简单的3X3矩阵。否则就需要我们利用getStructuringElement函数来明确指出它的形状。然后,我们还需要指定内核大小,以及 锚点 位置。不指定锚点位置,则默认锚点在内核中心位置。
  imshow( "Erosion Demo", erosion_dst );
}

/** @function Dilation */
void Dilation( int, void* )
{
  int dilation_type;
  if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
  else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
  else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }

  Mat element = getStructuringElement( dilation_type,
                                       Size( 2*dilation_size + 1, 2*dilation_size+1 ),
                                       Point( dilation_size, dilation_size ) );
  ///膨胀操作
  dilate( src, dilation_dst, element );
  imshow( "Dilation Demo", dilation_dst );
}

更改Trackbars的位置就会产生不一样的输出图像。
还可以通过增加第三个Trackbar来控制膨胀或腐蚀的次数。

没有更多推荐了,返回首页