18、【opencv入门】形态学图像处理(一):开运算、闭运算、形态学梯度、顶帽、黑帽合辑

、理论与概念讲解

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 << "\n\n\n\t请调整滚动条观察图像效果~\n\n"; 139 cout << "\n\n\t按键操作说明: \n\n" 140 "\t\t键盘按键【ESC】或者【Q】- 退出程序\n" 141 "\t\t键盘按键【1】- 使用椭圆(Elliptic)结构元素\n" 142 "\t\t键盘按键【2】- 使用矩形(Rectangle )结构元素\n" 143 "\t\t键盘按键【3】- 使用十字型(Cross-shaped)结构元素\n" 144 "\t\t键盘按键【空格SPACE】- 在矩形、椭圆、十字形结构元素中循环\n" 145 "\n\n\t\t\t\t\t\t\t\t"; 146 }

猜你喜欢

转载自www.cnblogs.com/Long-w/p/9663316.html