一、理论与概念讲解
1、开运算(Opening Operation)
开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。其数学表达式如下:
开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。
2、闭运算(Closing Operation)
先膨胀后腐蚀的过程称为闭运算(Closing Operation),其数学表达式如下:
闭运算能够排除小型黑洞(黑色区域)。效果图如下所示:
3、形态学梯度(MorphologicalGradient)
形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差,数学表达式如下:
对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓
4、顶帽(Top Hat)
顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与上文刚刚介绍的“开运算“的结果图之差,数学表达式如下:
因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。
5、黑帽(Black Hat)
黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:
黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以,黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:
二、示例
1、morphologyEx函数详解
上面我们已经讲到,morphologyEx函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。函数原型如下:
1 C++: void morphologyEx(
2 InputArray src,
3 OutputArray dst,
4 int op,
5 InputArraykernel,
6 Pointanchor=Point(-1,-1),
7 intiterations=1,
8 intborderType=BORDER_CONSTANT,
9 constScalar& borderValue=morphologyDefaultBorderValue() );
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
MORPH_OPEN – 开运算(Opening operation)
MORPH_CLOSE – 闭运算(Closing operation)
MORPH_GRADIENT -形态学梯度(Morphological gradient)
MORPH_TOPHAT - “顶帽”(“Top hat”)
MORPH_BLACKHAT - “黑帽”(“Black hat“)
(另有CV版本的标识符也可选择,如CV_MOP_CLOSE,CV_MOP_GRADIENT,CV_MOP_TOPHAT,CV_MOP_BLACKHAT,
这应该是OpenCV1.0系列版本遗留下来的标识符,和上面的“MORPH_OPEN”一样的效果。)
第四个参数,InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。
getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。关于getStructuringElement我们上篇文章中讲过了,这里为了大家参阅方便,再写一遍:
矩形: MORPH_RECT
交叉形: MORPH_CROSS
椭圆形: MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。
getStructuringElement函数相关的调用示例代码如下:
1 int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸
2
3 //获取自定义核
4 Mat element =getStructuringElement(MORPH_RECT,
5 Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),
6 Point(g_nStructElementSize, g_nStructElementSize ));
调用这样之后,我们便可以在接下来调用erode、dilate或morphologyEx函数时,kernel参数填保存getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。
第五个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
其中的这些操作都可以进行就地(in-place)操作。且对于多通道图像,每一个通道都是单独进行操作。
2、开运算
1 #include <opencv2/opencv.hpp>
2 #include<opencv2/highgui/highgui.hpp>
3 #include<opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 //载入原始图
10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
11 //创建窗口
12 namedWindow("【原始图】开运算");
13 namedWindow("【效果图】开运算");
14 //显示原始图
15 imshow("【原始图】开运算", image);
16 //定义核
17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
18 //进行形态学操作AC
19 morphologyEx(image,image, MORPH_OPEN, element);
20 //显示效果图
21 imshow("【效果图】开运算", image);
22 imwrite("开运算.jpg", image);
23 waitKey(0);
24
25 return 0;
26 }
效果展示:
原图 开运算
3、闭运算
1 #include <opencv2/opencv.hpp>
2 #include <opencv2/highgui/highgui.hpp>
3 #include<opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 //载入原始图
10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
11 //创建窗口
12 namedWindow("【原始图】闭运算");
13 namedWindow("【效果图】闭运算");
14 //显示原始图
15 imshow("【原始图】闭运算", image);
16 //定义核
17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
18 //进行形态学操作
19 morphologyEx(image,image, MORPH_CLOSE, element);
20 //显示效果图
21 imshow("【效果图】闭运算", image);
22 imwrite("闭运算.jpg", image);
23 waitKey(0);
24
25 return 0;
26 }
效果展示:
原图 闭运算
3、梯度
1 #include <opencv2/opencv.hpp>
2 #include<opencv2/highgui/highgui.hpp>
3 #include<opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 //载入原始图
10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
11 //创建窗口
12 namedWindow("【原始图】形态学梯度");
13 namedWindow("【效果图】形态学梯度");
14 //显示原始图
15 imshow("【原始图】形态学梯度", image);
16 //定义核
17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
18 //进行形态学操作
19 morphologyEx(image,image, MORPH_GRADIENT, element);
20 //显示效果图
21 imshow("【效果图】形态学梯度", image);
22 imwrite("梯度.jpg", image);
23 waitKey(0);
24
25 return 0;
26 }
效果展示:
原图 梯度
4、顶帽运算
1 #include <opencv2/opencv.hpp>
2 #include<opencv2/highgui/highgui.hpp>
3 #include<opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 //载入原始图
10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
11 //创建窗口
12 namedWindow("【原始图】顶帽运算");
13 namedWindow("【效果图】顶帽运算");
14 //显示原始图
15 imshow("【原始图】顶帽运算", image);
16 //定义核
17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
18 //进行形态学操作
19 morphologyEx(image,image, MORPH_TOPHAT, element);
20 //显示效果图
21 imshow("【效果图】顶帽运算", image);
22 imwrite("顶帽.jpg", image);
23 waitKey(0);
24
25 return 0;
26 }
效果展示:
原图 顶帽
6、黑帽运算
1 #include <opencv2/opencv.hpp>
2 #include <opencv2/highgui/highgui.hpp>
3 #include<opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 //载入原始图
10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
11 //创建窗口
12 namedWindow("【原始图】黑帽运算");
13 namedWindow("【效果图】黑帽运算");
14 //显示原始图
15 imshow("【原始图】黑帽运算", image);
16 //定义核
17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
18 //进行形态学操作
19 morphologyEx(image,image, MORPH_BLACKHAT, element);
20 //显示效果图
21 imshow("【效果图】黑帽运算", image);
22 imwrite("黑帽.jpg", image);
23 waitKey(0);
24
25 return 0;
26 }
效果展示:
原图 黑帽
7、腐蚀(morphologyEx调用版)
1 #include <opencv2/opencv.hpp>
2 #include <opencv2/highgui/highgui.hpp>
3 #include<opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 //载入原始图
10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
11 //创建窗口
12 namedWindow("【原始图】腐蚀");
13 namedWindow("【效果图】腐蚀");
14 //显示原始图
15 imshow("【原始图】腐蚀", image);
16 //定义核
17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
18 //进行形态学操作
19 morphologyEx(image,image, MORPH_ERODE, element);
20 //显示效果图
21 imshow("【效果图】腐蚀", image);
22 imwrite("腐蚀.jpg", image);
23 waitKey(0);
24
25 return 0;
26 }
效果展示:
原图 腐蚀
8、膨胀(morphologyEx调用版)示例
1 #include <opencv2/opencv.hpp>
2 #include<opencv2/highgui/highgui.hpp>
3 #include<opencv2/imgproc/imgproc.hpp>
4
5 using namespace cv;
6
7 int main( )
8 {
9 //载入原始图
10 Mat image = imread("1.jpg"); //工程目录下应该有一张名为1.jpg的素材图
11 //创建窗口
12 namedWindow("【原始图】膨胀");
13 namedWindow("【效果图】膨胀");
14 //显示原始图
15 imshow("【原始图】膨胀", image);
16 //定义核
17 Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));
18 //进行形态学操作
19 morphologyEx(image,image, MORPH_DILATE, element);
20 //显示效果图
21 imshow("【效果图】膨胀", image);
22 imwrite("膨胀.jpg", image);
23 waitKey(0);
24
25 return 0;
26 }
效果展示:
原图 膨胀
三、综合示例
1 #include <opencv2/opencv.hpp>
2 #include <opencv2/highgui/highgui.hpp>
3 #include <opencv2/imgproc/imgproc.hpp>
4
5 using namespace std;
6 using namespace cv;
7
8 Mat g_srcImage, g_dstImage;//原始图和效果图
9 int g_nElementShape = MORPH_RECT;//元素结构的形状
10
11 //变量接收的TrackBar位置参数
12 int g_nMaxIterationNum = 10;
13 int g_nOpenCloseNum = 0;
14 int g_nErodeDilateNum = 0;
15 int g_nTopBlackHatNum = 0;
16
17 static void on_OpenClose(int, void*);//回调函数
18 static void on_ErodeDilate(int, void*);//回调函数
19 static void on_TopBlackHat(int, void*);//回调函数
20 static void ShowHelpText();//帮助文字显示
21
22 int main( )
23 {
24
25 ShowHelpText();
26
27 //载入原图
28 g_srcImage = imread("1.jpg");//工程目录下需要有一张名为1.jpg的素材图
29 if( !g_srcImage.data )
30 {
31 cout << "载入错误!" << endl;
32 }
33 //显示原始图
34 namedWindow("【原始图】");
35 imshow("【原始图】", g_srcImage);
36
37 //创建三个窗口
38 namedWindow("【开运算/闭运算】",1);
39 namedWindow("【腐蚀/膨胀】",1);
40 namedWindow("【顶帽/黑帽】",1);
41
42 //参数赋值
43 g_nOpenCloseNum=9;
44 g_nErodeDilateNum=9;
45 g_nTopBlackHatNum=2;
46
47 //分别为三个窗口创建滚动条
48 createTrackbar("迭代值", "【开运算/闭运算】",&g_nOpenCloseNum,g_nMaxIterationNum*2+1,on_OpenClose);
49 createTrackbar("迭代值", "【腐蚀/膨胀】",&g_nErodeDilateNum,g_nMaxIterationNum*2+1,on_ErodeDilate);
50 createTrackbar("迭代值", "【顶帽/黑帽】",&g_nTopBlackHatNum,g_nMaxIterationNum*2+1,on_TopBlackHat);
51
52 //轮询获取按键信息
53 while(1)
54 {
55 int c;
56
57 //执行回调函数
58 on_OpenClose(g_nOpenCloseNum, 0);
59 on_ErodeDilate(g_nErodeDilateNum, 0);
60 on_TopBlackHat(g_nTopBlackHatNum,0);
61
62 //获取按键
63 c = waitKey(0);
64
65 //按下键盘按键Q或者ESC,程序退出
66 if( (char)c == 'q'||(char)c == 27 )
67 break;
68 //按下键盘按键1,使用椭圆(Elliptic)结构元素结构元素MORPH_ELLIPSE
69 if( (char)c == 49 )//键盘按键1的ASII码为49
70 g_nElementShape = MORPH_ELLIPSE;
71 //按下键盘按键2,使用矩形(Rectangle)结构元素MORPH_RECT
72 else if( (char)c == 50 )//键盘按键2的ASII码为50
73 g_nElementShape = MORPH_RECT;
74 //按下键盘按键3,使用十字形(Cross-shaped)结构元素MORPH_CROSS
75 else if( (char)c == 51 )//键盘按键3的ASII码为51
76 g_nElementShape = MORPH_CROSS;
77 //按下键盘按键space,在矩形、椭圆、十字形结构元素中循环
78 else if( (char)c == ' ' )
79 g_nElementShape = (g_nElementShape + 1) % 3;
80 }
81
82 return 0;
83 }
84
85 static void on_OpenClose(int, void*)
86 {
87 //偏移量的定义
88 int offset = g_nOpenCloseNum - g_nMaxIterationNum;//偏移量
89 int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
90 //自定义核
91 Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
92 //进行操作
93 if( offset < 0 )
94 morphologyEx(g_srcImage, g_dstImage, CV_MOP_OPEN, element);
95 else
96 morphologyEx(g_srcImage, g_dstImage, CV_MOP_CLOSE, element);
97 //显示图像
98 imshow("【开运算/闭运算】",g_dstImage);
99 }
100
101
102 static void on_ErodeDilate(int, void*)
103 {
104 //偏移量的定义
105 int offset = g_nErodeDilateNum - g_nMaxIterationNum; //偏移量
106 int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
107 //自定义核
108 Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
109 //进行操作
110 if( offset < 0 )
111 erode(g_srcImage, g_dstImage, element);
112 else
113 dilate(g_srcImage, g_dstImage, element);
114 //显示图像
115 imshow("【腐蚀/膨胀】",g_dstImage);
116 }
117
118
119 static void on_TopBlackHat(int, void*)
120 {
121 //偏移量的定义
122 int offset = g_nTopBlackHatNum - g_nMaxIterationNum;//偏移量
123 int Absolute_offset = offset > 0 ? offset : -offset;//偏移量绝对值
124 //自定义核
125 Mat element = getStructuringElement(g_nElementShape, Size(Absolute_offset*2+1, Absolute_offset*2+1), Point(Absolute_offset, Absolute_offset) );
126 //进行操作
127 if( offset < 0 )
128 morphologyEx(g_srcImage, g_dstImage, MORPH_TOPHAT , element);
129 else
130 morphologyEx(g_srcImage, g_dstImage, MORPH_BLACKHAT, element);
131 //显示图像
132 imshow("【顶帽/黑帽】",g_dstImage);
133 }
134
135 static void ShowHelpText()
136 {
137 //输出一些帮助信息
138 cout << "
请调整滚动条观察图像效果~
";
139 cout << "
按键操作说明:
"
140 " 键盘按键【ESC】或者【Q】- 退出程序
"
141 " 键盘按键【1】- 使用椭圆(Elliptic)结构元素
"
142 " 键盘按键【2】- 使用矩形(Rectangle )结构元素
"
143 " 键盘按键【3】- 使用十字型(Cross-shaped)结构元素
"
144 " 键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环
"
145 "
";
146 }