【OpenCV 4开发详解】形态学应用
重磅干货,第一时间送达
经过几个月的努力,小白终于完成了市面上第一本OpenCV 4入门书籍《OpenCV 4开发详解》。为了更让小伙伴更早的了解最新版的OpenCV 4,小白与出版社沟通,提前在公众号上连载部分内容,请持续关注小白。 |
图像形态学腐蚀可以将细小的噪声区域去除,但是会将图像主要区域的面积缩小,造成主要区域的形状发生改变;图像形态学膨胀可以扩充每一个区域的面积,填充较小的空洞,但是同样会增加噪声的面积。根据两者的特性将图像腐蚀和膨胀适当的结合,便可以既去除图像中的噪声,又不缩小图像中主要区域的面积;既填充了较小的空洞,又不增加噪声所占的面积。因此,本节中将介绍如何利用不同顺序的图像腐蚀和膨胀实现图像的开运算、闭运算、形态学梯度、顶帽运算、黑帽运算以及击中击不中变换等操作。
开运算
图像开运算可以去除图像中的噪声,消除较小连通域,保留较大连通域,同时能够在两个物体纤细的连接处将两个物体分离,并且在不明显改变较大连通域的面积的同时能够平滑连通域的边界。开运算是图像腐蚀和膨胀操作的结合,首先对图像进行腐蚀,消除图像中的噪声和较小的连通域,之后通过膨胀运算弥补较大连通域因腐蚀而造成的面积减小。图6-22给出了图像开运算的三个阶段,图6-22中左侧图像是待开运算的原图像,中间的图像是利用3×3矩形结构元素对原图像进行腐蚀后的图像,通过结果可以看到较小的连通域已经被去除,但是较大的连通域也在边界区域产生了较大的面积缩减,之后对腐蚀后的图像进行膨胀运算,得到图6-22中右侧图像。通过结果可以看出,膨胀运算弥补了腐蚀运算造成的边界面积缩减,使得开运算的结果只去除了较小的连通域,保留了较大的连通域。
图6-22 图像开运算三个阶段
开运算是对图像腐蚀和膨胀的组合,OpenCV 4没有提供只用于图像开运算的函数,而是提供了图像腐蚀和膨胀运算不同组合形式的morphologyEx()函数,以实现图像的开运算、闭运算、形态学提取、顶帽运算、黑帽运算以及击中击不中变换,该函数的函数原型在代码清单6-15中给出。
代码清单6-15 morphologyEx()函数原型
1.void cv::morphologyEx(InputArray src,
2. OutputArray dst,
3. int op,
4. InputArray kernel,
5. Point anchor = Point(-1,-1),
6. int iterations = 1,
7. int borderType = BORDER_CONSTANT,
8. const Scalar & borderValue = morphologyDefaultBorderValue()
9. )
src:输入图像,图像的通道数可以是任意的,但是图像的数据类型必须是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。 dst:形态学操作后的输出图像,与输入图像具有相同的尺寸和数据类型。 op:形态学操作类型的标志,可以选择的标志及含义在表6-6中给出。 kernel:结构元素,可以自己生成,也可以用getStructuringElement()函数生成。 anchor:中心点在结构元素中的位置,默认参数为结构元素的几何中心点 iterations:处理的次数 borderType:像素外推法选择标志,取值范围在表3-5中给出。默认参数为BORDER_DEFAULT,表示不包含边界值倒序填充。 borderValue:使用边界不变外推法时的边界值。
该函数根据结构元素对输入图像进行多种形态学操作,在处理多通道图像时每个通道独立进行处理。函数的第一个参数为待形态学处理的图像,图像通道数可以是任意的,但是图像的数据类型必须是CV_8U,CV_16U,CV_16S,CV_32F或CV_64F之一。函数第二个参数为形态学处理后的输出图像,与输入图像具有相同的尺寸和数据类型。函数第三个参数是形态学操作类型的选择标志,可以选择的形态学操作类型有开运算、闭运算、形态学梯度、顶帽运算、黑帽运算以及击中击不中变换,详细的参数在表6-6给出。函数第四个和第五个参数都是与结构元素相关的参数,第四个参数为结构元素,使用的结构元素尺寸越大效果越明显,第四个参数为结构元素的中心位置,第五个参数的默认值为Point(-1,-1),表示结构元素的几何中心处为结构元素的中心点。函数第六个参数是使用结构元素处理的次数,处理次数越多效果越明显。函数第七个参数是图像像素外推法的选择标志,第八个参数为使用边界不变外推法时的边界值,这两个参数对图像中主要部分的形态学操作没有影响,因此在多数情况下使用默认值即可。
表6-6 morphologyEX()函数中形态学操作类型标志可选参数及含义
标志参数 | 简记 | 作用 |
---|---|---|
MORPH_ERODE | 0 | 图像腐蚀 |
MORPH_DILATE | 1 | 图像膨胀 |
MORPH_OPEN | 2 | 开运算 |
MORPH_CLOSE | 3 | 闭运算 |
MORPH_GRADIENT | 4 | 形态学梯度 |
MORPH_TOPHAT | 5 | 顶帽运算 |
MORPH_BLACKHAT | 6 | 黑帽运算 |
MORPH_HITMISS | 7 | 击中击不中运算 |
该函数实现了多种形态学操作,对于函数的使用方法将在介绍该函数涉及到的所有形态学操作相关概念后在代码清单6-16中给出。
闭运算
图像闭运算可以去除连通域内的小型空洞,平滑物体轮廓,连接两个临近的连通域。开运算是图像腐蚀和膨胀操作的结合,首先对图像进行膨胀,填充连通域内的小型空洞,扩大连通域的边界,将临近的两个连通域连接,之后通过腐蚀运算减少由膨胀运算引起的连通域边界的扩大以及面积的增加。图6-23给出了图像闭运算的三个阶段,图6-23中左侧图像是待闭运算的原图像,中间的图像是利用3×3矩形结构元素对原图像进行膨胀后的图像,通过结果可以看到较大连通域内的小型空洞已经被填充,同时临近的两个连通域也连接了在一起,但是连通域的边界明显扩张,整体的面积增加,之后对膨胀后的图像进行腐蚀运算,得到图6-22中右侧图像。通过结果可以看出,腐蚀运算能够消除连通域因膨胀运算带来的面积增长,但是图像中依然存在较大的面积增长,主要是因为连通域膨胀后,有较大区域在图像的边缘区域,而图像边缘区域的形态学操作结果与图像的边缘外推方法有着密切的关系,因此采用默认外推方法时,边缘的连通域不会被腐蚀掉,从而产生图6-23右侧的结果。
图6-23 图像闭运算三个阶段
闭运算是对图像膨胀和腐蚀的组合,OpenCV 4提供的morphologyEx()函数可以选择闭运算参数MORPH_CLOSE实现图像的闭运算,该函数的函数原型已经在代码清单6-15中给出,函数的使用方式将在介绍该函数涉及到的所有形态学操作相关概念后在代码清单6-16中给出。
形态学梯度
形态学梯度能够描述目标的边界,根据图像腐蚀和膨胀与原图之间的关系计算得到,形态学梯度可以分为基本梯度、内部梯度和外部梯度。基本梯度是原图像膨胀后图像和腐蚀后图像间的差值图像,内部梯度图像是原图像和腐蚀后图像间的差值图像,外部梯度是膨胀后图像和原图像间的差值图像。图6-24给出了计算形态学基本梯度的三个阶段,图6-24中左侧图像是原图像利用3×3矩形结构元素进行膨胀后的图像,中间的图像是原图像利用3×3矩形结构元素进行腐蚀后的图像,右侧图像是左侧图像和中间图像的差值。
图6-24 形态学梯度计算的三个阶段
OpenCV 4提供的morphologyEx()函数可以选择闭运算参数MORPH_GRADIENT实现图像的基本梯度,如果需要计算图像的内部梯度或者外部梯度,需要自己通过程序实现。morphologyEx()函数的函数原型已经在代码清单6-15中给出,函数的使用方式将在介绍该函数涉及到的所有形态学操作相关概念后在代码清单6-16中给出。
顶帽运算
图像顶帽运算是原图像与开运算结果之间的差值,往往用来分离比邻近点亮一些的斑块,因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域。顶帽运算先对图像进行开运算,之后从原图像中减去开运算计算的结果,在图6-25中给出了计算顶帽运算的三个阶段,图6-25中左侧图像是原图像,中间的图像是利用3×3矩形结构元素对原图像进行开运算后的图像,右侧图像是左侧原与中间开运算结果图像之间的差值,即为原图像顶帽运算的结果。
图6-25 图像顶帽运算的三个阶段
OpenCV 4提供的morphologyEx()函数可以选择顶帽运算的参数MORPH_TOPHAT实现图像的闭运算,该函数的函数原型已经在代码清单6-15中给出,函数的使用方式将在介绍该函数涉及到的所有形态学操作相关概念后在代码清单6-16中给出。
黑帽运算
图像黑帽运算是与图像顶帽运算相对应的形态学操作,与顶帽运算相反,黑帽运算是原图像与闭运算结果之间的差值,往往用来分离比邻近点暗一些的斑块。顶帽运算先对图像进行开运算,之后从原图像中减去开运算计算的结果,在图6-26中给出了计算顶帽运算的三个阶段,图6-26中左侧图像是利用3×3矩形结构元素对原图像进行闭运算后的图像,中间的图像是原图像,右侧图像是左侧闭运算结果图像与中间原图像之间的差值,即为原图像黑帽运算的结果。
图6-26 图像黑帽运算的三个阶段
OpenCV 4提供的morphologyEx()函数可以选择黑帽运算的参数MORPH_BLACKHAT实现图像的闭运算,该函数的函数原型已经在代码清单6-15中给出,函数的使用方式将在介绍该函数涉及到的所有形态学操作相关概念后在代码清单6-16中给出。
击中击不中变换
击中击不中变换是比图像腐蚀要求更加苛刻的一种形态学操作,图像腐蚀只需要图像能够将结构元素中所有非0元素包含即可,但是击中击不中变换要求原图像中需要存在与结构元素一模一样的结构,即结构元素中非0元素也需要同时被考虑。如图6-27中所示,如果用中间的结构元素对左侧图像进行腐蚀,那么将会得到图6-24中所示的腐蚀计算结果,而用中间结构元素对左侧图像进行击中击不中变换结果为图6-27右侧所示,因为结构元素的中心位置为0,而在原图像中符合这种结构的位置只在图像中的中心处,因此击中击不中变换的结果与图像腐蚀结果具有极大的差异。但是在使用矩形结构元素时,击中击不中变换与图像的腐蚀结果相同。
图6-27 图像击中击不中变换结果
OpenCV 4提供的morphologyEx()函数可以选择击中击不中变换参数MORPH_HITMISS实现图像的击中击不中变换,该函数的函数原型已经在代码清单6-15中给出,函数的使用方式在介绍该函数涉及到的所有形态学操作相关概念后在代码清单6-16中给出。程序中构建了用于介绍形态学多种操作原理的原图像,之后用3×3矩形结构元素分别对原图像进行开运算、闭运算、形态学梯度、顶帽运算、黑帽运算以及击中击不中变换等操作,验证morphologyEx()函数处理结果与理论处理结果是否相同,处理结果在图6-28给出。其中需要注意的是由于进行击中击不中变换使用的结构元素与原理介绍时不同,因此程序中的击中击不中变换结果与图6-27不同。此外,为了验证多种形态学操作处理图像的效果,程序中读取一张灰度图像,对图像进行二值化后分别进行多种形态学操作,灰度图像和二值化后的图像如图6-29所示,形态学操作处理后的图像在图6-30给出。
代码清单6-16 myMorphologyApp.cpp形态学操作应用
1.#include <opencv2\opencv.hpp>
2.#include <iostream>
3.#include <vector>
4.
5.using namespace cv;
6.using namespace std;
7.
8.int main()
9.{
10. //用于验证形态学应用的二值化矩阵
11. Mat src = (Mat_<uchar>(9, 12) << 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
12. 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
13. 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
14. 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
15. 0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0,
16. 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
17. 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 255, 0,
18. 0, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0,
19. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
20. namedWindow("src", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
21. imshow("src", src);
22. //3×3矩形结构元素
23. Mat kernel = getStructuringElement(0, Size(3, 3));
24.
25. //对二值化矩阵进行形态学操作
26. Mat open, close,gradient,tophat,blackhat,hitmiss;
27.
28. //对二值化矩阵进行开运算
29. morphologyEx(src, open, MORPH_OPEN, kernel);
30. namedWindow("open", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
31. imshow("open", open);
32.
33. //对二值化矩阵进行闭运算
34. morphologyEx(src, close, MORPH_CLOSE, kernel);
35. namedWindow("close", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
36. imshow("close", close);
37.
38. //对二值化矩阵进行梯度运算
39. morphologyEx(src, gradient, MORPH_GRADIENT, kernel);
40. namedWindow("gradient", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
41. imshow("gradient", gradient);
42.
43. //对二值化矩阵进行顶帽运算
44. morphologyEx(src, tophat, MORPH_TOPHAT, kernel);
45. namedWindow("tophat", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
46. imshow("tophat", tophat);
47.
48. //对二值化矩阵进行黑帽运算
49. morphologyEx(src, blackhat, MORPH_BLACKHAT, kernel);
50. namedWindow("blackhat", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
51. imshow("blackhat", blackhat);
52.
53. //对二值化矩阵进行击中击不中变换
54. morphologyEx(src, hitmiss, MORPH_HITMISS, kernel);
55. namedWindow("hitmiss", WINDOW_NORMAL); //可以自由调节显示图像的尺寸
56. imshow("hitmiss", hitmiss);
57.
58. //用图像验证形态学操作效果
59. Mat coin = imread("coin.png",IMREAD_GRAYSCALE);
60. imshow("coin", coin);
61. threshold(coin, coin, 130, 255, THRESH_BINARY);
62. imshow("二值化后的coin", coin);
63.
64. //5×5矩形结构元素
65. Mat kernel_coin = getStructuringElement(0, Size(5, 5));
66. Mat open_coin, close_coin, gradient_coin;
67. Mat tophat_coin, blackhat_coin, hitmiss_coin;
68.
69. //对图像进行开运算
70. morphologyEx(coin, open_coin, MORPH_OPEN, kernel_coin);
71. imshow("open_coin", open_coin);
72.
73. //对图像进行闭运算
74. morphologyEx(coin, close_coin, MORPH_CLOSE, kernel_coin);
75. imshow("close_coin", close_coin);
76.
77. //对图像进行梯度运算
78. morphologyEx(coin, gradient_coin, MORPH_GRADIENT, kernel_coin);
79. imshow("gradient_coin", gradient_coin);
80.
81. //对图像进行顶帽运算
82. morphologyEx(coin, tophat_coin, MORPH_TOPHAT, kernel_coin);
83. imshow("tophat_coin", tophat_coin);
84.
85. //对图像进行黑帽运算
86. morphologyEx(coin, blackhat_coin, MORPH_BLACKHAT, kernel_coin);
87. imshow("blackhat_coin", blackhat_coin);
88.
89. //对图像进行击中击不中变换
90. morphologyEx(coin, hitmiss_coin, MORPH_HITMISS, kernel_coin);
91. imshow("hitmiss_coin", hitmiss_coin);
92.
93. waitKey(0);
94. return 0;
95.}
图6-28 myMorphologyApp.cpp程序中验证形态学操作的处理结果
图6-29 myMorphologyApp.cpp程序中灰度图像及二值化后的图像
图6-30 myMorphologyApp.cpp程序中图像形态学操作后的结果