腐蚀、膨胀以及开闭操作等形态学处理

http://blog.csdn.net/u011021773/article/details/53065120
	上一篇我讲过什么叫做卷积,而且提到了卷积与滤波其实不完全相同,因为部分滤波是非线性的,比方说中值滤波,就是取每个局部地区的中位数,也就是中间大小的数。如果展开了类比,如果取局部最大值或者局部最小值会是什么样的呢?大值滤波?小值滤波?
哈哈,其实取大值滤波的官方学名叫做膨胀,小值滤波叫做腐蚀。一句话概括,膨胀就是取局部最大值的滤波,腐蚀就是取局部最小值的滤波。 
话不多讲,来上一段膨胀的代码:
Mat MyDilate(Mat src, int KernelSize=3,int step=1)
{
	Mat dst(src.rows,src.cols,src.type()); 
	src.copyTo(dst);
	//半个核的大小,用以计算滤波中心
	int KernelHalfSize = (KernelSize - 1) / 2;
	if (0 == KernelHalfSize)
	{
		return dst;
	}
	int channels = src.channels();
	//膨胀是求最大值的滤波
	for (int c = 0; c < channels; c++)
	{
		for (int col = 0; col < src.cols; col+=step)
		{
			for (int row = 0; row < src.rows; row+=step)
			{
				int max = 0;
				for (int kc = col-KernelHalfSize; kc < col+KernelHalfSize; kc ++)
				{
					for (int kr = row- KernelHalfSize; kr < row+KernelHalfSize; kr ++)
					{
						if (src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c] > max)
						{
							//如果是彩色图
							if (3 == channels)
							{
								//这里求三个数的中位数是为了解决滤波边界的问题,
								//如果超出界限的就选边界那一点的值,因为是求最大值所以某个点多算几次也没有关系
								max = src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c];
							}
							//如果是灰度图
							else if (1 == channels)
							{
								max = src.at<uchar>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1));
							}
						}
					}
				}
				if (3 == channels)
				{
					dst.at<Vec3b>(row,col)[c] = max;
				}
				//如果是灰度图
				else if (1 == channels)
				{
					dst.at<uchar>(row,col) = max;
				}
			}
		}
	}
	return dst;
}


	左边为原图,右边为膨胀后的图。这幅图我采用的核的大小为7*7,步长为5,可以看见原本该一起被刮掉的区域现在只有一个个白点被抹掉了,这是因为步长太大很多区域没有被处理,所以只有少部分黑色没有了,变成了白点。 
腐蚀就是跟膨胀相反的操作啦,把求最大值改成求最小值就OK了,代码如下:
Mat MyErode(Mat src, int KernelSize = 3, int step = 1)
{
	Mat dst(src.rows, src.cols, src.type());
	src.copyTo(dst);
	//半个核的大小,用以计算滤波中心
	int KernelHalfSize = (KernelSize - 1) / 2;
	if (0 == KernelHalfSize)
	{
		return dst;
	}
	int channels = src.channels();
	//腐蚀是求最小值的滤波
	for (int c = 0; c < channels; c++)
	{
		for (int col = 0; col < src.cols; col += step)
		{
			for (int row = 0; row < src.rows; row += step)
			{
				int min = 255;
				for (int kc = col - KernelHalfSize; kc < col + KernelHalfSize; kc++)
				{
					for (int kr = row - KernelHalfSize; kr < row + KernelHalfSize; kr++)
					{
						if (src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c] < min)
						{
							//如果是彩色图
							if (3 == channels)
							{
								//这里求三个数的中位数是为了解决滤波边界的问题,
								//如果超出界限的就选边界那一点的值,因为是求最大值所以某个点多算几次也没有关系
								min = src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c];
							}
							//如果是灰度图
							else if (1 == channels)
							{
								min = src.at<uchar>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1));
							}
						}
					}
				}
				if (3 == channels)
				{
					dst.at<Vec3b>(row, col)[c] = min;
				}
				//如果是灰度图
				else if (1 == channels)
				{
					dst.at<uchar>(row, col) = min;
				}
			}
		}
	}
	return dst;
}
效果如图:
	
这里我选用的依然是7 * 7步长为1的模板,可以看到黑色的字明显变粗,中间的小点也都变大,有一块黑色区域的中间的白点几乎消失了,这就是膨胀的效果,消除亮色区域,增大暗色区域。接着我们还是试一试调整一下步长,我把步长调整为2,看看效果图。
可以看见黑色大军还是向外扩展了,但是由于有步长限制了,不能每个点都占领到,所以出现了一个个的小黑点,只能得到点的胜利,不能得到面的胜利。同样,核的大小决定了膨胀的力度,如果核特别大黑色的字将特别粗,可能粗成一个坨。 有了腐蚀和膨胀的基础了,开闭操作就很好理解了,所谓的开操作就是先腐蚀后膨胀,闭操作就是先膨胀后腐蚀,效果如图:
以上两张图分别是开操作和闭操作,核的大小为7*7,步长为1。
开闭操作的意义在哪里呢?
通过观察我们发现开操作的效果和腐蚀很像,都是消除了白色的杂质,这是因为开操作是先腐蚀,后膨胀。通过一次腐蚀就已经将白色杂质消除了,但是此时字也变得很粗了,想要让他还保持原来的面积,就需要再把它膨胀回来,而一些白点会被消除。比方说左侧黑色圆圈中间的白点,还有去字中间的白色缝隙。闭操作是先膨胀后腐蚀,原本的黑字先变粗后变细了,因为黑点已经被消除了,所以再膨胀的时候原来的黑点不会再出现了,但是大面积的黑线依然存在,并且经历了一次变细又变粗的过程,这样画面上就只剩下黑色的字了,而旁边墨水洒落留下的黑点全部都消失了。
为什么黑色圆圈和去字的中间都不是黑色,也不是白色,而是灰色呢?这点开始我也非常奇怪,赶紧检查了程序,发现程序没有问题,0和1的问题不存在有中间值。后来观察图片发现原来图片是经过了有损压缩的,所有的颜色边缘都是渐变的,所以出现了灰色的边缘,而经过开操作的放大就显得非常明显了。
最后再总结一下:
腐蚀就是求局部最小值的滤波操作,目的是去除亮色杂质。 
膨胀就是求局部最大值的滤波操作,目的是去除暗色杂质。 
开操作是先腐蚀后膨胀的过程,目的是去除亮色杂质并保持重要部分面积不变。 
闭操作是先膨胀后腐蚀的过程,目的是去除暗色杂质并保持重要部分面积不变。
最后贴上我的全部代码,以供参考。
#include "iostream"

#include "imgproc.hpp"
#include "highgui.h"
#include "opencv.hpp"

using namespace std;
using namespace cv;

//求三个数的中位数
int mid(int a, int b, int c)
{
	int max = a;
	int min = b;
	if ((a <=b&&b<=c)|| (c <= b&&b <= a))
	{
		return b;
	}
	else if ((b <= a&&a <= c) || (c <= a&&a <= b))
	{
		return a;
	}
	else
	{
		return c;
	}
}
Mat MyDilate(Mat src, int KernelSize=3,int step=1)
{
	Mat dst(src.rows,src.cols,src.type()); 
	src.copyTo(dst);
	//半个核的大小,用以计算滤波中心
	int KernelHalfSize = (KernelSize - 1) / 2;
	if (0 == KernelHalfSize)
	{
		return dst;
	}
	int channels = src.channels();
	//膨胀是求最大值的滤波
	for (int c = 0; c < channels; c++)
	{
		for (int col = 0; col < src.cols; col+=step)
		{
			for (int row = 0; row < src.rows; row+=step)
			{
				int max = 0;
				for (int kc = col-KernelHalfSize; kc < col+KernelHalfSize; kc ++)
				{
					for (int kr = row- KernelHalfSize; kr < row+KernelHalfSize; kr ++)
					{
						if (src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c] > max)
						{
							//如果是彩色图
							if (3 == channels)
							{
								//这里求三个数的中位数是为了解决滤波边界的问题,
								//如果超出界限的就选边界那一点的值,因为是求最大值所以某个点多算几次也没有关系
								max = src.at<Vec3b>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1))[c];
							}
							//如果是灰度图
							else if (1 == channels)
							{
								max = src.at<uchar>(mid(kr, 0, src.rows-1), mid(kc, 0, src.cols-1));
							}
						}
					}
				}
				if (3 == channels)
				{
					dst.at<Vec3b>(row,col)[c] = max;
				}
				//如果是灰度图
				else if (1 == channels)
				{
					dst.at<uchar>(row,col) = max;
				}
			}
		}
	}
	return dst;
}
Mat MyErode(Mat src, int KernelSize = 3, int step = 1)
{
	Mat dst(src.rows, src.cols, src.type());
	src.copyTo(dst);
	//半个核的大小,用以计算滤波中心
	int KernelHalfSize = (KernelSize - 1) / 2;
	if (0 == KernelHalfSize)
	{
		return dst;
	}
	int channels = src.channels();
	//腐蚀是求最小值的滤波
	for (int c = 0; c < channels; c++)
	{
		for (int col = 0; col < src.cols; col += step)
		{
			for (int row = 0; row < src.rows; row += step)
			{
				int min = 255;
				for (int kc = col - KernelHalfSize; kc < col + KernelHalfSize; kc++)
				{
					for (int kr = row - KernelHalfSize; kr < row + KernelHalfSize; kr++)
					{
						if (src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c] < min)
						{
							//如果是彩色图
							if (3 == channels)
							{
								//这里求三个数的中位数是为了解决滤波边界的问题,
								//如果超出界限的就选边界那一点的值,因为是求最大值所以某个点多算几次也没有关系
								min = src.at<Vec3b>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1))[c];
							}
							//如果是灰度图
							else if (1 == channels)
							{
								min = src.at<uchar>(mid(kr, 0, src.rows - 1), mid(kc, 0, src.cols - 1));
							}
						}
					}
				}
				if (3 == channels)
				{
					dst.at<Vec3b>(row, col)[c] = min;
				}
				//如果是灰度图
				else if (1 == channels)
				{
					dst.at<uchar>(row, col) = min;
				}
			}
		}
	}
	return dst;
}
//开操作先腐蚀后膨胀
Mat MyOpen(Mat img, int KernelSize = 3, int step = 1)
{
	Mat ErodeImg = MyErode(img, KernelSize, step);
	Mat DilateImg = MyDilate(ErodeImg, KernelSize, step);
	return DilateImg;
}
//闭操作先膨胀后腐蚀
Mat MyClose(Mat img, int KernelSize = 3, int step = 1)
{
	Mat DilateImg = MyDilate(img, KernelSize, step);
	Mat ErodeImg = MyErode(DilateImg, KernelSize, step);
	return ErodeImg;
}
void main()
{
	Mat img=imread("..\\image\\original.png");
	
	Mat ErodeImg = MyErode(img,7);
	Mat DilateImg = MyDilate(img, 7);
	Mat OpenImg = MyOpen(img, 7);
	Mat CloseImg = MyClose(img, 7);
	imshow("original", img);
	imshow("ErodeImg", ErodeImg);
	imshow("DilateImg", DilateImg);
	imshow("OpenImg", OpenImg);
	imshow("CloseImg", CloseImg);
	waitKey(0);
	imwrite("..\\image\\ErodeImg7.png", ErodeImg);
	imwrite("..\\image\\DilateImg7.png", DilateImg);
	imwrite("..\\image\\OpenImg.png", OpenImg);
	imwrite("..\\image\\CloseImg.png", CloseImg);
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章