数字图像处理(六)形态学处理之腐蚀、膨胀、开运算、闭运算

本篇博客是自己的理解,如有错误,欢迎指正

注意,本文所有例子都是24位真彩色图,并不是真正意义上的二值单通道图像。图像下载地址

https://download.csdn.net/download/hjxu2016/10436834

二值图像形态学运算时图像形态学运算的基础。二值图像形态学运算的过程就是在图像中移动结构元素,将结构元素与其下面重叠部分的图像进行交、并等集合运算。为了确定元素中的参照位置,一般把进行形态学运算时的结构元素的参考点称为原点,且远点可以选择在结构元素之中,也可以选择在结构元素之外。

二值图像的形态学处理的基本运算有腐蚀、膨胀、开运算、闭运算,击中与击不中、骨架抽取等。

一. 图像腐蚀

就像土壤侵蚀一样,这个操作会把前景物体的边界腐蚀掉(但是前景仍然是白色)

简单的讲,就是黑的吃白的。至于怎么吃,涉及结构元素以及原点的设计。先看解释


再看结构元素的定义


记录一下步骤

为了防止越界,不处理最左边、最右边、最上和最下边的元素。从第2行、第2列开始检查源图像中的像素点,如果当点对应结构元素中为白色的那些点钟有一个不是白色,则将目标图像中对应的像素点赋值成白色。

将目标图像中的当前点先赋值成黑色,如果源图像中对应结构元素中为白色的那些点中只要有一个不是白色,则将目标图像中的当前点赋值成白色。

在这里,我仅将原点赋值,结果也差不多

先看3*3的结构元素,且所有值都为1,也就是说,只要结构元素对应的像素点有一个值不是白色,为0,那么原点就赋值成黑色0.

第一步:在ResourceView资源视图中,添加Menu子菜单:(注意ID号)

第二步:打开类向导(Ctrl+W),为点运算每个ID菜单添加相应的功能处理函数,如下图所示:选择类CImageProcessingView,选择ID号。

void CImageProcessingView::OnXtxFs()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("形态学-腐蚀1!", MB_OK, 0);
	//int kelnel[3][3];//定义3*3的结构元素
	/*结构元素
	{	{1,1,1}
		{1,1,1}
		{1,1,1}
	}
	*/
	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y, i, j, val,xx,yy;
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 255;//这里我给val赋值为255,如果发现结构元素对应的像素有是0的点,就赋值成0
			for (j = 0; j < 3; j++)
			{
				yy = y + j - 1;
				for (i = 0; i < 3; i++)
				{
					xx = x + i - 1;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] == 0)
					{
						val = 0;
						break;
					}
				}
				if (val == 0) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num +1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}

	fwrite(ImageSize, m_nImage, 1, fpw);

	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level = 400;
	Invalidate();
}

结果如下所示,是不是感觉腐蚀过头了。

我们再换一个2*2的核,且所有值为1看看结果

void CImageProcessingView::OnXtxFs2()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("形态学-腐蚀1!", MB_OK, 0);
	//int kelnel[2][2];//定义3*3的结构元素
	/*结构元素
	{	{1,1,1}
	{1,1,1}
	}
	*/
	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y, i, j, val, xx, yy;
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 255;
			for (j = 0; j < 2; j++)//这边是2*2
			{
				yy = y + j - 1;
				for (i = 0; i < 2; i++)
				{
					xx = x + i - 1;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] == 0)
					{
						val = 0;
						break;
					}
				}
				if (val == 0) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}

	fwrite(ImageSize, m_nImage, 1, fpw);

	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level = 400;
	Invalidate();
}

结果如下,粗细还可以


在看一下特殊结构元素

void CImageProcessingView::OnXtxFs3()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("形态学-腐蚀1!", MB_OK, 0);
	//int kelnel[3][3] = {	{0,1,0},
	//						{1,1,1},
	//						{0,1,0} };//定义3*3的结构元素
	int kelnel[3][3] = {{ 1,0,1 },
						{ 0,0,0 },
						{ 1,0,1 } };//定义3*3的结构元素

	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y, i, j, val, xx, yy;
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 255;
			for (j = 0; j < 3; j++)
			{
				yy = y + j - 1;
				for (i = 0; i < 3; i++)
				{
					xx = x + i - 1;
					if (kelnel[j][i]) continue;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] == 0)
					{
						val = 0;
						break;
					}
				}
				if (val == 0) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}

	fwrite(ImageSize, m_nImage, 1, fpw);

	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level = 400;
	Invalidate();
}

所以,结构元素的设计是会影响腐蚀结果的。

一. 图像膨胀

我认为膨胀和腐蚀正好相反,简单的讲,就是白的吃黑的。至于怎么吃,涉及结构元素以及原点的设计。


记录一下步骤

为了防止越界,不处理最左边、最右边、最上和最下边的元素。从第2行、第2列开始检查源图像中的像素点,如果当点对应结构元素中为白色的那些点中只要有一个是白色,则将目标图像中的当前点赋值成白色。

代码,建立类向导

void CImageProcessingView::OnXtxPz()
{
	// TODO: 在此添加命令处理程序代码
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("形态学-膨胀1!", MB_OK, 0);
	//int kelnel[3][3] = {	{0,1,0},
	//						{1,1,1},
	//						{0,1,0} };//定义3*3的结构元素
	int kelnel[3][3] = { { 0,1,0 },
						{ 1,1,1 },
						{ 0,1,0 } };//定义3*3的结构元素

	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y, i, j, val, xx, yy;
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 0;//先赋值黑色,再赋值白色
			for (j = 0; j < 3; j++)
			{
				yy = y + j - 1;
				for (i = 0; i < 3; i++)
				{
					xx = x + i - 1;
					if (kelnel[j][i] == 0) continue;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] != 0)
					{
						val = 255;
						break;
					}
				}
				if (val == 255) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}

	fwrite(ImageSize, m_nImage, 1, fpw);

	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level = 400;
	Invalidate();
}

白的把黑的吃掉了


三. 图像开运算

开运算就是使用用一个结构元素,对目标图像先腐蚀,再膨胀。先黑的吃白的,再白的吃黑的,可以消除小的白色的点

void CImageProcessingView::OnXtxKys()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("形态学-开运算!", MB_OK, 0);
	//int kelnel[3][3] = {	{0,1,0},
	//						{1,1,1},
	//						{0,1,0} };//定义3*3的结构元素
	int kelnel[3][3] = { { 0,1,0 },
	{ 1,1,1 },
	{ 0,1,0 } };//定义3*3的结构元素

	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y, i, j, val, xx, yy;
	//先腐蚀
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 255;//先赋值成白色
			for (j = 0; j < 3; j++)
			{
				yy = y + j - 1;
				for (i = 0; i < 3; i++)
				{
					xx = x + i - 1;
					if (kelnel[j][i]) continue;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] == 0)//只要有一个不是白色,赋值成白色
					{
						val = 0;
						break;
					}
				}
				if (val == 0) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}
	//再膨胀
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 0;//先赋值成黑色
			for (j = 0; j < 3; j++)
			{
				yy = y + j - 1;
				for (i = 0; i < 3; i++)
				{
					xx = x + i - 1;
					if (kelnel[j][i] == 0) continue;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] != 0)//只要有一个是白色,赋值成白色
					{
						val = 255;
						break;
					}
				}
				if (val == 255) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}

	fwrite(ImageSize, m_nImage, 1, fpw);

	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level = 400;
	Invalidate();
}

四. 图像闭运算

开运算就是使用用一个结构元素,对目标图像先膨胀,再腐蚀。先白的吃黑的,再黑的吃白的,可以消除小的黑色的点

void CImageProcessingView::OnXtxBys()
{
	// TODO: 在此添加命令处理程序代码
	if (numPicture == 0)
	{
		AfxMessageBox("请输入一张图像", MB_OK, 0);
		return;
	}

	if (m_nBitCount != 24)
	{
		AfxMessageBox("输入图片不是24位", MB_OK, 0);
		return;
	}
	AfxMessageBox("形态学-闭运算!", MB_OK, 0);
	//int kelnel[3][3] = {	{0,1,0},
	//						{1,1,1},
	//						{0,1,0} };//定义3*3的结构元素
	int kelnel[3][3] = { { 0,1,0 },
	{ 1,1,1 },
	{ 0,1,0 } };//定义3*3的结构元素

	int num;//记录每一行需要填充的字节
	if (m_nWidth * 3 % 4 != 0)
	{
		num = 4 - m_nWidth * 3 % 4;
	}
	else
	{
		num = 0;
	}

	//打开临时的图片  
	FILE *fpo = fopen(BmpName, "rb");
	FILE *fpw = fopen(BmpNameLin, "wb+");
	fread(&bfh, sizeof(BITMAPFILEHEADER), 1, fpo);
	fread(&bih, sizeof(BITMAPINFOHEADER), 1, fpo);
	fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, fpw);
	fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, fpw);
	fread(m_pImage, m_nImage, 1, fpo);
	unsigned char *ImageSize;
	ImageSize = new unsigned char[m_nImage];

	int x, y, i, j, val, xx, yy;
	//先膨胀
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 0;//先赋值成黑色
			for (j = 0; j < 3; j++)
			{
				yy = y + j - 1;
				for (i = 0; i < 3; i++)
				{
					xx = x + i - 1;
					if (kelnel[j][i]) continue;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] != 0)//只要有一个是白色,则赋值成白色
					{
						val = 255;
						break;
					}
				}
				if (val == 255) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}
	//再腐蚀
	for (y = 1; y < m_nHeight - 1; y++)
	{
		for (x = 1; x < m_nWidth - 1; x++)
		{
			val = 255;//先赋值成白色
			for (j = 0; j < 3; j++)
			{
				yy = y + j - 1;
				for (i = 0; i < 3; i++)
				{
					xx = x + i - 1;
					if (kelnel[j][i] == 0) continue;
					if (m_pImage[(xx + yy*m_nWidth) * 3 + yy * num] == 0)//只要有一个不是白色,赋值成黑色
					{
						val = 0;
						break;
					}
				}
				if (val == 0) break;
			}
			ImageSize[(x + y*m_nWidth) * 3 + y*num] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 1] = unsigned char(val);
			ImageSize[(x + y*m_nWidth) * 3 + y*num + 2] = unsigned char(val);
		}
	}

	fwrite(ImageSize, m_nImage, 1, fpw);

	fclose(fpo);
	fclose(fpw);
	numPicture = 2;
	level = 400;
	Invalidate();
}


©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页