腐蝕、膨脹以及開閉操作等形態學處理

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);
}




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