第3章 Opencv图像处理进阶-【第一部分 图像处理B】(imgproc组件、feature2D组件)

来源:互联网 发布:四通一达哪个最快知乎 编辑:程序博客网 时间:2024/05/21 14:01

1.3形态学图像处理:膨胀与腐蚀

1.3.1理论与概念讲解

<1>形态学概述
形态学(morphology)一词通常表示生物学的一个分支,该分支主要研究动植物的形态和结构。而我们图像处理中指的形态学,往往表示的是数学形态学。下面一起来了解数学形态学的概念。
数学形态学(Mathematical morphology) 是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论。其基本的运算包括:二值腐蚀和膨胀、二值开闭运算、骨架抽取、极限腐蚀、击中击不中变换、形态学梯度、Top-hat变换、颗粒分析、流域变换、灰值腐蚀和膨胀、灰值开闭运算、灰值形态学梯度等。
简单来讲,形态学操作就是基于形状的一系列图像处理操作。opencv为进行图像的形态学变换提供了快捷、方便的函数。最基本的形态学操作有二种,他们是:膨胀与腐蚀(Dilation与Erosion)。
膨胀与腐蚀能实现多种多样的功能,主要如下:
 消除噪声;
 分割(isolate)出独立的图像元素,在图像中连接(join)相邻的元素;
 寻找图像中的明显的极大值区域或极小值区域;
 求出图像的梯度。

我们在这里给出下文会用到的,用于对比膨胀与腐蚀运算的“i”字样毛笔字原图:

这里写图片描述

图1

【注】图片来自OpenCV_Tutorials网站。
在进行腐蚀和膨胀的讲解之前,首先需要注意,腐蚀和膨胀是对白色部分(高亮部分)而言的,不是黑色部分。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。
<2>膨胀
其实,膨胀就是求局部最大值的操作。按数学方面来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,我们称之为A)与核(我们称之为B)进行卷积。
核可以是任何的形状和大小,它拥有一个单独定义出来的参考点,我们称其为锚点(anchorpoint)。多数情况下,核是一个小的中间带有参考点和实心正方形或者圆盘,其实,我们可以把核视为模板或者掩码。
而膨胀就是求局部最大值的操作,核B与图形卷积,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素。这样就会使图像中的高亮区域逐渐增长。如下图所示,这就是膨胀操作的初衷。
这里写图片描述

图2

膨胀的数学表达式:

这里写图片描述

膨胀效果图(毛笔字):
这里写图片描述

图3 左图像:原始图像反向,右图像: 原始图像反向的膨胀图像

<3>腐蚀
再来看一下腐蚀,大家应该知道,膨胀和腐蚀是一对好基友,是相反的一对操作,所以腐蚀就是求局部最小值的操作。
我们一般都会把腐蚀和膨胀对应起来理解和学习。下文就可以看到,两者的函数原型也是基本上一样的。
原理图:
这里写图片描述

图4

腐蚀的数学表达式:
这里写图片描述

腐蚀效果图(毛笔字):
这里写图片描述

图5左图像:原始图像反向,右图像: 原始图像反向造成的侵蚀

1.3.2 OpenCV源码分析

直接上源码吧,在…\sources\modules\imgproc\src\ morph.cpp路径中 的第1353行开始就为erode(腐蚀)函数的源码,1361行为dilate(膨胀)函数的源码。

//-----------------------------------【erode()函数中文注释版源代码】----------------------------   //    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码   //    OpenCV源代码版本:2.4.9//    源码路径:…\sources\modules\imgproc\src\ morph.cpp   //    源文件中如下代码的起始行数:1353行   //--------------------------------------------------------------------------------------------------------    void cv::erode( InputArray src, OutputArraydst, InputArray kernel,                  Point anchor, int iterations,                  int borderType, constScalar& borderValue )  {  //调用morphOp函数,并设定标识符为MORPH_ERODE     morphOp( MORPH_ERODE, src, dst, kernel, anchor, iterations, borderType,borderValue );  }  //-----------------------------------【dilate()函数中文注释版源代码】----------------------------   //    说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码   //    OpenCV源代码版本:2.4.9//    源码路径:…\sources\modules\imgproc\src\ morph.cpp   //    源文件中如下代码的起始行数:1361行   //--------------------------------------------------------------------------------------------------------   void cv::dilate( InputArray src,OutputArray dst, InputArray kernel,                   Point anchor, int iterations,                   int borderType, constScalar& borderValue )  {  //调用morphOp函数,并设定标识符为MORPH_DILATE     morphOp( MORPH_DILATE, src, dst, kernel, anchor, iterations, borderType,borderValue );  }  

可以发现erode和dilate这两个函数内部就是调用了一下morphOp,只是他们调用morphOp时,第一个参数标识符不同,一个为MORPH_ERODE(腐蚀),一个为MORPH_DILATE(膨胀)。
morphOp函数的源码在… \sources\modules\imgproc\src\morph.cpp中的第1286行,有兴趣的朋友们可以研究研究,这里就不费时费力花篇幅展开分析了。
【注】笔者分析的是2.4.9源码,对于3.0版本以上的源码大同小异,请读者自行对比学习。

1.3.3 API函数讲解

<1>形态学膨胀——dilate函数
erode函数,使用像素邻域内的局部极大运算符来膨胀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

C++: void dilate( InputArray src,                    OutputArray dst,                    InputArray kernel,                    Point anchor=Point(-1,-1),                    int iterations=1,                    int borderType=BORDER_CONSTANT,                    const Scalar& borderValue=morphologyDefaultBorderValue()   );  

【参数】
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的kernel,膨胀操作的核。若为NULL时,表示的是使用参考点位于中心3x3的核。
我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。
其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:
 矩形: MORPH_RECT
 交叉形: MORPH_CROSS
 椭圆形: MORPH_ELLIPSE

而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。
getStructuringElement函数相关的调用示例代码如下:

int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸  //获取自定义核  Mat element = getStructuringElement(MORPH_RECT,      Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),      Point( g_nStructElementSize, g_nStructElementSize ));  

调用这样之后,我们便可以在接下来调用erode或dilate函数时,第三个参数填保存了getStructuringElement返回值的Mat类型变量。对应于我们上面的示例,就是填element变量。
第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。
调用范例:

//载入原图   Mat image = imread("1.jpg");  //获取自定义核  Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  Mat out;  //进行膨胀操作  dilate(image, out, element);  

用上面核心代码架起来的完整程序代码。
参见附件【demo1】
运行截图。

这里写图片描述

图6膨胀

<2>形态学腐蚀——erode函数
erode函数,使用像素邻域内的局部极小运算符来腐蚀一张图片,从src输入,由dst输出。支持就地(in-place)操作。

C++: void erode(InputArray src,                  OutputArray dst,                  InputArray kernel,                  Point anchor=Point(-1,-1),                  int iterations=1,                  int borderType=BORDER_CONSTANT,                  const Scalar& borderValue=morphologyDefaultBorderValue()   );  

【参数】
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像通道的数量可以是任意的,但图像深度应为CV_8U,CV_16U,CV_16S,CV_32F或 CV_64F其中之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。
第三个参数,InputArray类型的kernel,腐蚀操作的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement配合这个参数的使用。getStructuringElement函数会返回指定形状和尺寸的结构元素(内核矩阵)。(具体看上文中浅出部分dilate函数的第三个参数讲解部分)
第四个参数,Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于单位(element)的中心,我们一般不用管它。
第五个参数,int类型的iterations,迭代使用erode()函数的次数,默认值为1。
第六个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_DEFAULT。
第七个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。
同样的,使用erode函数,一般我们只需要填前面的三个参数,后面的四个参数都有默认值。而且往往结合getStructuringElement一起使用。
调用范例:

//载入原图   Mat image = imread("1.jpg");  //获取自定义核  Mat element = getStructuringElement(MORPH_RECT, Size(15, 15));  Mat out;  //进行腐蚀操作   erode(image,out, element);  

用上面核心代码架起来的完整程序代码。
参见附件【demo2】
运行结果:

这里写图片描述

图7腐蚀操作

1.3.4膨胀与腐蚀综合实例

这个示例程序中的效果图窗口有两个滚动条,顾名思义,第一个滚动条“腐蚀/膨胀”用于在腐蚀/膨胀之间进行切换;第二个滚动条”内核尺寸”用于调节形态学操作时的内核尺寸,以得到效果不同的图像。
参见附件【demo3】
放出一些效果图吧。

这里写图片描述

图8

这里写图片描述

图9

参考链接:

英文:https://docs.opencv.org/master/db/df6/tutorial_erosion_dilatation.html
中文:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.html#morphology-1

1.4形态学图像处理:开运算、闭运算、形态学梯度、顶帽、黑帽合辑

1.4.1理论与概念讲解

首先呢,要知道形态学的高级形态,往往都是建立在腐蚀和膨胀这两个基本操作之上的。而关于腐蚀和膨胀,概念和细节以及相关代码可以看前文。对膨胀和腐蚀心中有数了,接下来的高级形态学操作,应该就不难理解。另外,为了下面对比和演示以及理解的方便,笔者自己制作了一张毛笔字图,这里先上原图:

这里写图片描述

图10

<1>开运算(Opening Operation)
开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。其数学表达式如下:
dst=open(src,element)=dilate(erode(src,element))

开运算可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。

这里写图片描述

图11左图像:原始图像反向,右图像: 原始图像反向的开运算

<2>闭运算(Closing Operation)
先膨胀后腐蚀的过程称为闭运算(Closing Operation),其数学表达式如下:
dst=close(src,element)=erode(dilate(src,element))

闭运算能够排除小型黑洞(黑色区域)。效果图如下所示:

这里写图片描述

图12左图像:原始图像反向,右图像: 原始图像反向的闭运算

<3>形态学梯度(MorphologicalGradient)
形态学梯度(Morphological Gradient)为膨胀图与腐蚀图之差,数学表达式如下:
dst=morphgrad(src,element)=dilate(src,element)erode(src,element)

对二值图像进行这一操作可以将团块(blob)的边缘突出出来。我们可以用形态学梯度来保留物体的边缘轮廓,如下所示:

这里写图片描述

图13

<4>顶帽(Top Hat)
顶帽运算(Top Hat)又常常被译为”礼帽“运算。为原图像与上文刚刚介绍的“开运算“的结果图之差,数学表达式如下:
dst=tophat(src,element)=srcopen(src,element)

因为开运算带来的结果是放大了裂缝或者局部低亮度的区域,因此,从原图中减去开运算后的图,得到的效果图突出了比原图轮廓周围的区域更明亮的区域,且这一操作和选择的核的大小相关。
顶帽运算往往用来分离比邻近点亮一些的斑块。当一幅图像具有大幅的背景的时候,而微小物品比较有规律的情况下,可以使用顶帽运算进行背景提取。

这里写图片描述

图14

<5> 黑帽(Black Hat)
黑帽(Black Hat)运算为”闭运算“的结果图与原图像之差。数学表达式为:
dst=blackhat(src,element)=close(src,element)src

黑帽运算后的效果图突出了比原图轮廓周围的区域更暗的区域,且这一操作和选择的核的大小相关。所以,黑帽运算用来分离比邻近点暗一些的斑块。非常完美的轮廓效果图:

这里写图片描述

图15

1.4.2 OpenCV源码分析

本文的主角是OpenCV中的morphologyEx函数,它利用基本的膨胀和腐蚀技术,来执行更加高级的形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。这一节我们来一起看一下morphologyEx函数的源代码。

//-----------------------------------【erode()函数中文注释版源代码】----------------------------    //   说明:以下代码为来自于计算机开源视觉库OpenCV的官方源代码    //   OpenCV源代码版本:2.4.9//   源码路径:… \sources\modules\imgproc\src\morph.cpp    //   源文件中如下代码的起始行数:1369行    //--------------------------------------------------------------------------------------------------------     void cv::morphologyEx( InputArray _src,OutputArray _dst, int op,                         InputArray kernel, Pointanchor, int iterations,                         int borderType, constScalar& borderValue )  {  //拷贝Mat数据到临时变量     Mat src = _src.getMat(), temp;     _dst.create(src.size(), src.type());     Mat dst = _dst.getMat();  //一个大switch,根据不同的标识符取不同的操作     switch( op )      {     case MORPH_ERODE:         erode( src, dst, kernel, anchor, iterations, borderType, borderValue );         break;     case MORPH_DILATE:         dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );         break;     case MORPH_OPEN:         erode( src, dst, kernel, anchor, iterations, borderType, borderValue );         dilate( dst, dst, kernel, anchor, iterations, borderType, borderValue );         break;     case CV_MOP_CLOSE:         dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );         erode( dst, dst, kernel, anchor, iterations, borderType, borderValue );         break;     case CV_MOP_GRADIENT:         erode( src, temp, kernel, anchor, iterations, borderType, borderValue );         dilate( src, dst, kernel, anchor, iterations, borderType, borderValue );         dst -= temp;         break;     case CV_MOP_TOPHAT:         if( src.data != dst.data )             temp = dst;         erode( src, temp, kernel, anchor, iterations, borderType, borderValue );          dilate( temp, temp, kernel, anchor,iterations, borderType, borderValue );         dst = src - temp;         break;     case CV_MOP_BLACKHAT:         if( src.data != dst.data )             temp = dst;         dilate( src, temp, kernel, anchor, iterations, borderType, borderValue);         erode( temp, temp, kernel, anchor, iterations, borderType, borderValue);         dst = temp - src;         break;     default:         CV_Error( CV_StsBadArg, "unknown morphological operation" );      }  }  

看上面的源码可以发现,其实morphologyEx函数其实就是内部一个大switch而已。根据不同的标识符取不同的操作。比如开运算MORPH_OPEN,按我们上文中讲解的数学表达式,就是先腐蚀后膨胀,即依次调用erode和dilate函数,为非常简明干净的代码。
【注】笔者分析的是2.4.9源码,对于3.0版本以上的源码大同小异,请读者自行对比学习。

1.4.3 API函数讲解

<1> morphologyEx函数详解
上面我们已经讲到,morphologyEx函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换,如开闭运算,形态学梯度,“顶帽”、“黑帽”等等。这一节我们来了解它的参数意义和使用方法。

C++: void morphologyEx( InputArray src,                          OutputArray   dst,                          int op,                          InputArray kernel,                          Pointanchor=Point(-1,-1),                          int iterations=1,                          int borderType=BORDER_CONSTANT,                          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我们上篇文章中讲过了,这里为了大家参阅方便,再写一遍:
其中,getStructuringElement函数的第一个参数表示内核的形状,我们可以选择如下三种形状之一:
 矩形: MORPH_RECT
 交叉形: MORPH_CROSS
 椭圆形: MORPH_ELLIPSE
而getStructuringElement函数的第二和第三个参数分别是内核的尺寸以及锚点的位置。
我们一般在调用erode以及dilate函数之前,先定义一个Mat类型的变量来获得getStructuringElement函数的返回值。对于锚点的位置,有默认值Point(-1,-1),表示锚点位于中心。且需要注意,十字形的element形状唯一依赖于锚点的位置。而在其他情况下,锚点只是影响了形态学运算结果的偏移。
getStructuringElement函数相关的调用示例代码如下:

int g_nStructElementSize = 3; //结构元素(内核矩阵)的尺寸  //获取自定义核  Mat element =getStructuringElement(MORPH_RECT,         Size(2*g_nStructElementSize+1,2*g_nStructElementSize+1),         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)操作。且对于多通道图像,每一个通道都是单独进行操作。 OK,讲解完毕,下面就是使用的范例。
为了方便大家需要的时候随时取用。下面我们依次列举出开运算,闭运算,形态学梯度,顶帽,黑帽,腐蚀,膨胀的效果实现简化版完整代码。其实说白了,这些代码基本上内容一致,其实就是改一下morphologyEx里面的第三个标识符参数而已。核都是选的MORPH_RECT,矩形元素结构。另外,通过看源码我们发现,最基本的腐蚀和膨胀操作也可以用morphologyEx函数来实现,他们由morphologyEx函数源码中switch的前两个case来实现(虽然在case体内就是简单地各自调用了一下erode和dilation函数,但还是有写出来的必要)。所以在这里,我们也用morphologyEx再重新来实现一遍他们。
按着顺序来列出吧,就直接列详细注释好的代码和运行结果了。
<2>开运算示例程序
OpenCV中调用morphologyEx函数进行开运算操作的示例程序如下:
参考附件【demo4】,运行效果图。

这里写图片描述

图16

<3>闭运算示例程序
OpenCV中调用morphologyEx函数进行闭运算操作的示例程序如下。
参见附件【demo5】,运行效果图。
这里写图片描述

图17

<4>形态学梯度示例程序
OpenCV中调用morphologyEx函数进行形态学梯度操作的示例程序如下。
参见附件【demo6】,运行效果图。
这里写图片描述

图18

<5>顶帽运算(Top Hat)示例程序
OpenCV中调用morphologyEx函数进行顶帽运算操作的示例程序如下。
参见附件【demo7】,运行效果图。
这里写图片描述

图19

<6>黑帽运算(BlackHat)示例程序
OpenCV中调用morphologyEx函数进行黑帽运算操作的示例程序如下:
参见附件【demo8】,运行效果图。
这里写图片描述

图20

<7>腐蚀(morphologyEx调用版)示例程序
OpenCV中调用morphologyEx函数进行腐蚀操作的示例程序如下。
参见附件【demo9】,运行效果图。
这里写图片描述

图21

<8>膨胀(morphologyEx调用版)示例程序
OpenCV中调用morphologyEx函数进行膨胀操作的示例程序如下。
参见附件【demo10】,运行效果图。
这里写图片描述

图22

1.4.4形态学滤波综合实例

这个示例程序中,一共有四个显示图像的窗口。原始图一个,开/闭运算为一个,腐蚀/膨胀为一个,顶帽/黑帽运算为一个。分别使用滚动条,来控制得到的形态学效果。且迭代值为10的时候,为中间。另外,还可以通过键盘按键1,2,3以及空格,来调节成不同的元素结构(矩形、椭圆、十字形)。
参看附件【demo11】

这里写图片描述

图23腐蚀/膨胀效果图

这里写图片描述

图24开/闭运算效果图

这里写图片描述

图25顶帽/黑帽运算效果图

有没有感觉很酷呢,继续跟着博主继续学习吧,你会发现不一样的惊奇哟!

参考链接:

英文:https://docs.opencv.org/master/d3/dbe/tutorial_opening_closing_hats.html
中文:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/opening_closing_hats/opening_closing_hats.html#morphology-2

本章附件:

请点击代码链接

【注意】博主在附件中的代码只有Linux版本的,如何使用Windows使用该代码请参看博主的另一篇博文
Opencv环境搭建(Visual Studio+Windows)- 请点击
有任何问题请联系博主。

阅读全文
0 0
原创粉丝点击