OpenCV4学习笔记(29)——图像的各种形态学操作

今天要整理的笔记内容是关于图像形态学的操作。

图像形态学,也叫做数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论,在图像处理中占有相当重要的地位。

在图像处理中,对于图像形态学的应用相当的广泛,主要应用在对于图像的预处理操作当中,尤其是在对二值图像的预处理和分析方面,图像形态学是二值图像分析的重要分支学科。

图像形态学能够从图像中提取出对于表达和描绘区域形状有意义、或者是我们所感兴趣的区域形状的图像分量,使后续的处理工作对于整幅图像而言更具有针对性,能够抓住目标对象最为本质(最具区分能力—most discrimination)的形状特征,例如某一目标范围、边界、连通区域等。

图像形态学主要分为:膨胀(dilate)、腐蚀(erode)、开运算(open)、闭运算(colse)、黑帽(blackHat)、顶帽(topHat)、形态学梯度(gradient)、击中击不中(hit and miss)这些操作。下面逐一来进行简要的整理。

1.膨胀(dilate)

膨胀(dilate)是图像形态学中最基础的两个操作之一,形态学中的其它操作都是基于两种基础操作(另一种是腐蚀(erode),下面再做介绍)做进一步拓展而得到的。

膨胀(dilate)通俗易懂地讲,我们可以把它看成最大值滤波,即用结构元素内的最大值去替换锚点的像素值。这里所用到的结构元素,和对图像进行卷积操作时所使用的卷积核有些类似,也可以把它理解成形态学操作中的卷积核,只不过在形态学中被称为结构元素,而且结构元素的形状选择会更加的丰富。

对图像进行膨胀操作后,由于相当于对图像进行了一个最大值滤波,所以对于灰度图像和彩色图像而言,图像的整体亮度会变亮;对于二值图像而言就是它的白色区域会变大,黑色区域会减少。

当对于二值图像进行膨胀操作时,可以用来桥接不同的连通域,也就是可以把一些原本属于同一对象的、但是经过二值化后又分为不同连通域的区域进行相连,使他们能够再次属于同一个区域。

OpenCV中提供了dilate()这个API进行图像的膨胀操作,其参数介绍如下:

第一个参数src:输入的多通道或单通道图;输入二值图像进行操作时,必须是黑色背景、白色前景;
第二个参数dst:输出图像,类型与通道数目必须跟输入保持一致;
第三个参数kernel:结构元素,需要事先定义;
第四个参数anchor:结构元素的锚点,默认为结构元素的中心位置;
第五个参数iterations:迭代次数,即进行几次操作;
第六个参数borderType:边缘填充方式,使用默认值即可;
第七个参数borderValue:边缘填充值,使用默认值即可。

下面是代码演示:

	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));		//获取结构元素
	Mat image_dilate;
	dilate(image, image_dilate, element, Point(-1, -1), 1);			//膨胀
	imshow("image_dilate", image_dilate);

注意在进行形态学操作前,都需要先定义一个结构元素,这个结构元素很大程度影响了形态学操作的效果。OpenCV中使用getStructuringElement()这个API来进行结构元素的定义。其参数如下:
第一个参数shape:结构元素的类型,常见的有矩形、圆形、十字交叉;需要用MORPH_RECT这种格式来定义;

第二个参数ksize:结构元素的尺寸;

第三个参数archor:结构元素的锚点,默认下是选择其中心位置为锚点。

这样,就在OpenCV中实现了对图像的膨胀操作,效果如下:
在这里插入图片描述

从图中可以很明显的看出,右图在经过膨胀操作后,其整体亮度是升高了,尤其是猫的眼睛里都泛着白亮的光芒。

2.腐蚀(erode)
腐蚀(erode)是图像形态学中最基础的两个操作之一,形态学中的其它操作都是基于膨胀(dilate)和腐蚀(erode)两种基础操作做进一步拓展而得到的。

膨胀和腐蚀是相对而言的,既然膨胀可以被看作是最大值滤波,那么同样的,腐蚀就可以看成是最小值滤波,即用结构元素内的最小值替换锚点像素值。

对图像进行腐蚀操作后,对于灰度图像和彩色图像而言其整体亮度变暗,而对于二值图像而言就是黑色区域变大、白色区域减少了。

腐蚀(erode)操作可以消除二值图像中微小的连通域干扰,因为它能将一些比较小的连通组件给腐蚀掉,使它也变成黑色背景;或者是用来分离连通域,如果有一些原本并不属于同一物体的像素点经过二值化后被划分为同一个连通域,就可以通过腐蚀操作来断开两个不同物体之间的连接,使其分为两个不同的连通域。

OpenCV中提供了erode()这个API进行腐蚀操作,其参数如下:
第一个参数src:输入的多通道或单通道图;输入二值图像进行操作时,必须是黑色背景、白色前景;
第二个参数dst:输出图像,类型与通道数目必须跟输入保持一致;
第三个参数kernel:结构元素,需要事先定义;
第四个参数anchor:结构元素的锚点,默认为结构元素的中心位置;
第五个参数iterations:迭代次数,即进行几次操作;
第六个参数borderType:边缘填充方式,使用默认值即可;
第七个参数borderValue:边缘填充值,使用默认值即可。

代码演示:

	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));		//获取结构元素
	Mat image_erode;
	erode(image, image_erode, element, Point(-1, -1), 1);			//腐蚀
	imshow("image_erode", image_erode);

具体效果如下图:
在这里插入图片描述
从膨胀和腐蚀操作后的两幅图像对比,很明显的能够看出腐蚀后的图像整体地变暗了,而且暗像素也增多了,尤其是眼珠部分特别的明显。

3.开运算(open)

图像的开操作,就是对图像先进行腐蚀操作,然后再进行膨胀操作。第一步腐蚀操作可以消除部分亮像素点,然后又通过第二步膨胀操作来将整体的亮像素点恢复到原来区域范围。当然了由于先进行了腐蚀操作,所以图像整体的灰度值是会降低的。

开操作对于二值图像来说可以消除掉一些小的干扰连通域,而不影响整体的大的连通域,解决图像二值化之后噪点过多的问题。还可以用来在纤细点处分离物体,将细小的连接处腐蚀掉再恢复原来的大的连通域。还可以消除连通域的毛刺,在平滑较大连通域边界的同时并不明显改变其面积。

而且,对二值图像进行开操作后剩下的连通域,其形状会逐渐趋向于所使用的结构元素,所以当我们想保留二值图像中的某些连通组件时,可以使用与连通组件形状相似的结构元素。例如可以使用与要保留的水平或竖直方向的直线类似的结构元素,来进行开操作,最终结果就可以将这个直线保留下来,达到对目标形状的提取。

OpenCV中提供了morphologyEx()这个API进行各种形态学操作,其参数如下:

第一个参数src:输入的进行形态学操作的图像;
第二个参数dst:输出结果图像;
第三个参数op:所选择的形态学操作;
第四个参数kernel:结构元素;
第五个参数anchor:结构元素的锚点;
第六个参数iterations:进行操作的迭代次数;
第七个参数borderType:边缘填充方式,使用默认值即可;
第八个参数borderValue:边缘填充值,使用默认值即可;

当使用开操作时,参数op我们使用MORPH_OPEN,具体代码如下:

	Mat image1 = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\fill.png");
	Mat image_gaus, image_binary, image_gray;
	cvtColor(image1, image_gray, COLOR_BGR2GRAY);
	GaussianBlur(image_gray, image_gaus, Size(), 1, 1);
	adaptiveThreshold(image_gray, image_binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 35, 10);
	imshow("image_binary", image_binary);
	Mat image_open;
	Mat kernel = getStructuringElement(MORPH_RECT, Size(20, 1), Point(-1, -1));
	morphologyEx(image_binary, image_open, MORPH_OPEN, kernel, Point(-1, -1), 1, 0);
	imshow("image_open", image_open);

上述代码中,读入的是一张填空题的图像:
在这里插入图片描述
在经过了转灰度、高斯模糊、自适应阈值分割等一系列的预处理后,得到一张以题目为前景的二值图像。再使用和填空处直线相似的结构元素来进行开操作,最终将这些直线保留下来,而去掉了其他的文字。效果如下:
在这里插入图片描述
可见,开操作虽然是图像形态学的基本操作,但是灵活运用的话也能达到一些奇妙的用途。

4.闭运算(colse)

图像的闭操作和开操作相反,它是先进行膨胀操作,再进行腐蚀操作,最后结果就是整体的灰度值会变大。

闭操作可以用来填充二值图像中的孔洞区域,先膨胀后将孔洞连接起来然后再腐蚀回原来的大小,并形成完整的闭合连通域。
闭操作还可以用来消除图像中小于结构元素的黑色区域。

代码演示如下:

	Mat image2 = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\morph3.png");
	Mat image_gaus2, image_binary2, image_gray2;
	cvtColor(image2, image_gray2, COLOR_BGR2GRAY);
	GaussianBlur(image_gray2, image_gaus2, Size(), 1, 1);
	adaptiveThreshold(image_gray2, image_binary2, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 25, 10);
	imshow("image_binary2", image_binary2);
	Mat image_close2;
	Mat kernel1 = getStructuringElement(MORPH_ELLIPSE, Size(15, 15), Point(-1, -1));
	morphologyEx(image_binary2, image_close2, MORPH_CLOSE, kernel1, Point(-1, -1), 1, 0);
	imshow("image_close2", image_close2);

效果图:
在这里插入图片描述
可见,将左边的原图像进行闭操作后,中间那些“甜甜圈”都被填充起来了,这就是闭操作的孔洞填充效果。

5.黑帽(blackHat)

图像形态学中的黑帽操作(blackHat),是利用闭操作后的图像来减去原图像。而闭操作的主要效果就是将图像中的孔洞区域给填充上,那么用闭操作后的图像、也就是填充后的图像去减掉原始图像,得到的差值不就是在闭操作中被填上的那些孔洞区域嘛。所以黑帽(blackHat)操作能够用来提取图像中的孔洞区域,或者是背景中的小黑点。

其代码演示如下:

	Mat image3 = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\morph3.png");
	Mat image_gaus, image_binary, image_gray;
	cvtColor(image3, image_gray, COLOR_BGR2GRAY);
	GaussianBlur(image_gray, image_gaus, Size(), 1, 1);
	adaptiveThreshold(image_gray, image_binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 35, 10);
	imshow("image_binary", image_binary);
	Mat image_blackhat;
	Mat kernel3 = getStructuringElement(MORPH_RECT, Size(15, 15), Point(-1, -1));
	morphologyEx(image_binary, image_blackhat, MORPH_BLACKHAT, kernel3, Point(-1, -1), 1, 0);
	imshow("image_tophat", image_blackhat);

效果图:
在这里插入图片描述

6.顶帽(topHat)
图像形态学的顶帽操作,是使用原图减去原图进行开操作后的图像。开操作的效果是消除了一些微小的连通域,再用原图像去相减,那么得到的就是这些被消除掉了的连通域。所以顶帽(topHat)操作有利于提取图像中的微小区域。

其代码如下:

	Mat image3 = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\fill.png");
	Mat image_gaus, image_binary, image_gray;
	cvtColor(image3, image_gray, COLOR_BGR2GRAY);
	GaussianBlur(image_gray, image_gaus, Size(), 1, 1);
	adaptiveThreshold(image_gray, image_binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 35, 10);
	imshow("image_binary", image_binary);
	Mat image_tophat;
	Mat kernel3 = getStructuringElement(MORPH_RECT, Size(19, 1), Point(-1, -1));
	morphologyEx(image_binary, image_tophat, MORPH_TOPHAT, kernel3, Point(-1, -1), 1, 0);
	imshow("image_tophat", image_tophat);

这里使用了上面演示开操作时的图像和结构元素,所以结果就是将那些填空题的文字部分保留了下来,效果图如下:
在这里插入图片描述

7.形态学梯度(gradient)

图像的形态学梯度跟图像卷积计算出来的梯度有本质不同,形态学梯度有助于获得连通域的边缘与轮廓,实现图像轮廓或者边缘提取。

形态学梯度可以分为:基本梯度、内梯度、外梯度。

基本梯度:使用原图像膨胀后的图像,减去原图像腐蚀后的图像,就得到了原图像的基本梯度。
内梯度:使用原图像减去将原图像腐蚀后的图像,就得到原图像的内梯度。
外梯度:使用将原图像膨胀后的图像,减去原图像,就得到原图像的外梯度。

代码如下:

	Mat image4 = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\cat.jpg");
	resize(image4, image4, Size(400, 300));
	Mat image_gaus, image_binary, image_gray;
	cvtColor(image4, image_gray, COLOR_BGR2GRAY);
	GaussianBlur(image_gray, image_gaus, Size(), 1, 1);
	adaptiveThreshold(image_gray, image_binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 35, 25);
	imshow("image_binary", image_binary);
	Mat image_grad1, image_grad2, image_grad3;
	Mat kernel3 = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	//基本梯度:膨胀后图像 - 腐蚀后图像
	morphologyEx(image_binary, image_grad1, MORPH_GRADIENT, kernel3, Point(-1, -1), 1, 0);
	imshow("image_grad1", image_grad1);
	//内梯度:输入图像 - 腐蚀后图像
	morphologyEx(image_binary, image_grad2, MORPH_ERODE, kernel3, Point(-1, -1), 1, 0);
	subtract(image_binary, image_grad2, image_grad2);
	imshow("image_grad2", image_grad2);
	//外梯度:膨胀后图像 - 原图
	morphologyEx(image_binary, image_grad3, MORPH_DILATE, kernel3, Point(-1, -1), 1, 0);
	subtract(image_grad3, image_binary, image_grad3);
	imshow("image_grad3", image_grad3);

梯度效果图:
在这里插入图片描述
其中,左上图为原图像的二值图像,右上为原图像的基本梯度,左下为原图像的内梯度,右下为原图像的外梯度。

我们还可以基于形态学梯度来实现图像二值化。例如当我们要进行OCR识别时,首先需要进行图像二值化,但是对于拍摄或光照不好的文本图像进行简单二值化很难达到比较好的效果,这时候可以尝试利用形态学梯度来实现图像二值化,这种方式往往比简单的二值化对文本图像有更好的分割效果。

其主要步骤如下:
1、求图像形态学梯度;
2、将梯度图转灰度图;
3、全局阈值二值化;
4、形态学操作提取ROI;
5、轮廓分析。

代码演示如下:

	Mat test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\55.jpg");
	resize(test_image, test_image, Size(300, 600));
	imshow("test_image", test_image);

	
	Mat kernel = getStructuringElement(MORPH_CROSS, Size(3, 3), Point(-1, -1));
	Mat grad_image;
	morphologyEx(test_image, grad_image, MORPH_GRADIENT, kernel, Point(-1, -1), 1, 0);
	imshow("grad_image", grad_image);

	
	Mat gray_image;
	cvtColor(grad_image, gray_image, COLOR_BGR2GRAY);
	imshow("gray_image", gray_image);


	Mat binary_image;
	threshold(gray_image, binary_image, 0, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("binary_image", binary_image);

	
	Mat close_kernel = getStructuringElement(MORPH_RECT, Size(2, 7), Point(-1, -1));
	Mat close_image;
	morphologyEx(binary_image, close_image, MORPH_CLOSE, close_kernel, Point(-1, -1), 1, 0);
	imshow("close_image", close_image);

	Mat open_kernel = getStructuringElement(MORPH_RECT, Size(5, 12), Point(-1, -1));
	Mat open_image;
	morphologyEx(close_image, open_image, MORPH_OPEN, open_kernel, Point(-1, -1), 1, 0);
	imshow("open_image", open_image);


	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(open_image, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
	for (int i = 0; i < contours.size(); i++)
	{
		float area = contourArea(contours[i]);
		if (area > 250 || area < 30)
		{
			continue;
		}
		Rect box = boundingRect(contours[i]);
		rectangle(test_image, box, Scalar(0, 0, 255), 1, 8, 0);
	}
	imshow("result", test_image);

效果图如下:
在这里插入图片描述
上图中,左边为原始图像,中间是原图像的形态学梯度,右边是通过形态学梯度图进行轮廓分析后提取出的文本轮廓。演示图中的文本提取效果并不是特别好,这主要是因为分析过程的参数调整问题,如果能够耐心地花时间去调试参数,就能够达到比较好的效果,将这张银行卡的卡号提取出来。但是如果使用普通阈值分割来作为预处理,再进行分析操作的话,会更难以达到这样的效果。

所以在有些场合下使用形态学梯度二值化来作为图像预处理的一部分,更能达到我们所需要的效果。

8.击中击不中(hit and miss)

图像形态学中的击中、击不中(hit and miss)操作,是先用击中结构元素射击图像,如果图像中有元素和击中结构元素完全匹配,就再使用击不中结构元素射击图像中除了该匹配元素外的其他区域,如果完全不匹配,就将该元素保留下来。否则,消除该元素。

击中击不中(hit and miss)操作相当于使用一个模板去对图像做匹配,将与模板相同的元素留下,不相同的则过滤。

可用于物体识别方面,将结构元素设计为目标物体的形状,再对原图像进行击中、击不中操作,可以达到保留目标物体,而过滤其他物体的效果。这就需要根据想保留或者去掉的部分来设计结构元素。

下面使用一张网格图来做演示:
在这里插入图片描述
我们使用和结点相似的结构元素来对这张图像进行击中击不中操作,最终希望将这些结点保留下来,而把网线过滤掉。
代码如下:

	Mat test_image = imread("D:\\opencv_c++\\opencv_tutorial\\data\\images\\cross.png");
	imshow("test_image", test_image);
	Mat gray_image, binary_image;
	cvtColor(test_image, gray_image, COLOR_BGR2GRAY);
	threshold(gray_image, binary_image, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	imshow("binary_image", binary_image);
	//应用示例:使用十字交叉结构元素,保留图像中的点,而将其他线段过滤掉
	Mat kernel = getStructuringElement(MORPH_CROSS, Size(11, 11), Point(-1, -1));
	Mat point_image;
	morphologyEx(binary_image, point_image, MORPH_HITMISS, kernel, Point(-1, -1), 1, 0);
	imshow("point_image", point_image);

最终的效果图:
在这里插入图片描述
可见,我们把图像二值化后,通过击中击不中操作,就把我们需要的结点区域给保留了下来。所以形态学的击中击不中(hit and miss)操作能够用来保留图像中我们所需要的元素。

总结:图像形态学常用的大致为以上这些操作,但是每一种操作在具体应用时,都需要根据具体需求来调整参数,尤其是结构元素的类型和尺寸。所以在调试参数的过程中,才是最最考验我们细心和耐心的时候。

今天的笔记就整理到这里啦,这是目前我写过得最长的一篇博客了,下午下了网课后就开始写。。。谢谢阅读啦~

PS:本人的注释比较杂,既有自己的心得体会也有网上查阅资料时摘抄下的知识内容,所以如有雷同,纯属我向前辈学习的致敬,如果有前辈觉得我的笔记内容侵犯了您的知识产权,请和我联系,我会将涉及到的博文内容删除,谢谢!

发布了36 篇原创文章 · 获赞 43 · 访问量 1796

猜你喜欢

转载自blog.csdn.net/weixin_45224869/article/details/104947645