opencv6.1-imgproc图像处理模块之平滑与形态学操作
这个部分是《opencv-tutorials.pdf》的部分,这部分也是几大部分中例子最多的,其实这个教程的例子都很不错,不过有些看得出来还是c接口的例子,说明例子有些年头了,其实在“opencv/sources/samples”有不同的接口的例子,看完这个教程,下一步就可以看看里面的不同的代码来学习,只是没有说明而已,不过在《opencv-refman.pdf》中会说到某某例子可以参考,也说明这里面的例子有很多都是为了解释这个手册中的一些函数的用法的。做完这些,就可以接着看opencv小组写的书籍了,书得不断的看,不断的学习,其实不是说等用到的时候再去学,这个一方面是到时候根本不知道该怎么做,其次,当如果有对某个函数一面之缘的情况下,第二次熟用的时候就会好很多。个人觉得学习:首先得圂囵吞枣的过一遍,有个大概的大局观,然后再是等用到再去查,再去细学,因为如果从第一遍就头仔细学:一,所有的知识都看成同等重要;二、学了前面忘了后面。当然用到才能记住,才能激发自己的欲求,才能重点关注某个知识点,推荐看看 托尼博赞的有关大脑学习的书籍,学习就得非线性的学,就得有取舍的学。
正文:
一、平滑
这里介绍四种平滑的函数,平滑也叫做模糊,是首先采用一个核(和CNN中一样的概念),然后进行卷积操作得到基于这个小滑框(cnn中叫做局部感受野)的值。其实也是很简单的概念,在之前使用的filter2D()函数中差不多的意思,只不过filter2D()函数需要自己指定核大小及其内部的数值。而这里是直接使用了函数得到不同的权值来进行平滑。
1、初始化工作
2、归一化滤波归一化滤波很简单,就是将选取的核,也就是滑框内的所有的值相加然后在接着除以滑框包含的像素的个数,将结果赋予指定的锚点位置的像素。
它的函数原型为:void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT );
参数列表:输入的矩阵、输出的句矩阵、核大小、锚点、边界类型;
其中的输入矩阵可以是多通道的,不过深度必须为CV_8U、CV_16U、 CV_16S、 CV_32F 、CV_64F;
输出矩阵会得到与输入矩阵一样的类型;锚点为负的时候表示会将这个滑框的中间位置作为锚点也就是输出滑框平滑后的值的位置。
最后的边界类型是用来推断图像外的像素。
notes:这里可以通过for等函数来调节其中的核大小,也就是滑框的大小来观察平滑的结果,这里显示框越大,结果越模糊。
3、高斯滤波
最有用的滤波器 (尽管不是最快的)。 高斯滤波是将输入数组的每一个像素点与 高斯内核 卷积将卷积和当作输出像素值。通过观察1维高斯的图像就能知道,靠近锚点的原始像素值的权值会比远离锚点的像素值的权值大,也就是锚点越远的,贡献的就越小。
它的函数原型为:void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0,int borderType=BORDER_DEFAULT );
参数列表:前两个与blur()的一样、第三个是核大小、x轴方向上高斯核的标准差、y轴方向上高斯核的标准差、边界类型。
其中第三个参数核大小,这里核的宽和高可以不同,但是必须是正的而且是奇数,当都为0的时候,就会采用后面两个参数来进行计算;
当sigmaY等于0的时候,sigmaY就会被设置成等于sigmaX。如果两个参数 sigmaX和sigmaY都等于0,那么就从第三个参数的高度和宽度中进行计算;
不过不论是将来如何的修改这几个参数,为了对这个函数的完全控制,还是推荐将第三、第四、第五三个参数进行具体的指定数值。
4、中值滤波
很简单的滤波,中值滤波将图像的每个像素用邻域 (以当前像素为中心的正方形区域)像素的 中值 代替 。
它的函数原型为:
void medianBlur(InputArray src, OutputArray dst, int ksize);
参数列表:前两个参数一样,第三个是因为中值滤波的滑框是限定成正方形的,只需要一个参数就够了,不过必须为正奇数。
5、双边滤波
具体原理见:http://blog.csdn.net/shouhuxianjian/article/details/42324809。保边去噪,不过相比较其他过滤器来说很慢。
它的函数原型为:void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace,int borderType=BORDER_DEFAULT );
参数列表:原图像、目标图像,核大小,颜色标准差,空间标准差,边界类型;
原图像必须是8位或者浮点类型,1通道或者是3通道;第三个参数表示设计到近邻像素范围的直径,如果是非正的,就从sigmaSpace中计算得到。
第四个参数:颜色空间中的过滤器的sigma。这个值越大,表示这个像素极其周边像素(基于第五个参数)会涉及到更深的颜色值,生成更大范围的半相等颜色;控制上面链接中的范围过滤器。
第五个参数;坐标空间中的过滤器的sigma。更大的值表示只要颜色足够接近(基于第四个参数),更远范围内的像素都能够相互影响,当d大于0,就用d来指定邻居的范围而不使用这个参数,否则d 也就是涉及的范围直径,就是与这个参数成比例的。控制上面链接中的域过滤器。
sigma:简单化的话,设置两个值一样就行,如果它们很小,比如小于10,这个过滤器的效果显现不出来,如果很大比如大于150,那么就有着很强的效果。
过滤器的大小:当d大于5的时候是很慢的,所以可以使用d=5来进行实时,对于d=9的离线应用来说,就需要大量的噪声过滤了。
二、形态学操作
简单来讲,形态学操作就是基于形状的一系列图像处理操作。通过将 结构元素 作用于输入图像来产生输出图像。
最基本的形态学操作有二:腐蚀与膨胀(Erosion 与 Dilation)。 他们的运用广泛:
消除噪声;
分割(isolate)独立的图像元素,以及连接(join)相邻的元素;
寻找图像中的明显的极大值区域或极小值区域。
1、腐蚀
原理很简单,就是和filter2D一样采用滑框的方式进行不断的移动,但是移动的过程中的操作却是很有意思,不论是这里的腐蚀还是下面的膨胀,它们都是类似CNN中的池化操作一样,简单,话不多说,重点就是进行滑框的卷积操作(记得是操作),取这个滑框中的最小值作为滑框中的锚点的值。可想而知在滑框划过的时候,总是挑选那些最小值,而这些最小值都是更暗的像素值,如果背景是白色的,前景是黑色的,那么前景区域会变大;如果相反,那么前景会缩小。
void erode(InputArray src, OutputArray dst, InputArray element, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() );
上面是腐蚀的函数原型,可以看出除了前几个参数,后面的参数都有默认值了,所以大部分情况也就不需要自己考虑了。
参数列表:输入图像、输出图像、结构元素(内核,滑框,等等同义的)、锚点、腐蚀迭代的次数、边界类型、边界值。
输入图像:通道数可以是任意的,但是深度必须是CV_8U、 CV_16U、CV_16S、CV_32F 、CV_64F中的一个。
输出图像:与输入图像有着一样的尺寸
结构元素:如果element = Mat();那么就使用一个3×3的矩阵结构元素。
锚点:结构元素中锚点的位置,默认值为Point(-1-1),表示锚点在结构元素的中间。
后三个就不介绍了,
这就是它的操作函数的数学表达
这个函数支持in-place模式,为了应对多通道的图片,这个函数直接对不同的通道进行单独的处理。
上述例子用来解释函数原型中第三个参数,默认的时候为3×3的矩阵,而需要自己定义的时候可以如上图所示使用函数getStructingElement()来自定义,定义不同的结构元素,有三种不同的内核类型可以指定:然后,我们还需要指定内核大小(上面例子中的erosion_size可以自己赋值,前面的代码没贴),以及 锚点 位置。不指定锚点位置,则默认锚点在内核中心位置。
2、膨胀
前面与腐蚀一样,只是将滑框中的卷积操作中取滑框中的最大值作为滑框中的锚点的值。所以经过膨胀之后,图像中亮度较高的区域就会变大
void dilate(InputArray src, OutputArray dst, InputArray kernel, Point anchor=Point(-1,-1),int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() );
这是膨胀的函数原型,其中的参数和腐蚀的是完全一样的。
其中的数学表达就是与上面一样,只不过将min换成了max。
上述:腐蚀和膨胀都是基于更亮的区域的。
3、更高级的形态学操作
a、开运算
开运算是通过先对图像腐蚀再膨胀实现的:。
在假设背景是相比前景较亮的,这样可以排除一些小团块物体。
如上图,开运算可以让上图中下面的白色被包围的小块消失掉:在腐蚀的过程中白色的块被周围黑色的颜色代替了,然后这时候没有白色存在那个闭合小团内,所以这时候使用膨胀是为了让其他位置的粗细程度回到原来的位置上。
b、闭运算
闭运算是通过先对图像膨胀再腐蚀实现的:。
如上图,闭运算让上图中首先进行膨胀,那么有的线较细的地方就会被白色背景代替掉,成了破碎的样子,这时候接着使用腐蚀只是为了剩下的部分的粗细程度能够恢复到以前。
c、形态梯度
膨胀图与腐蚀图之差:
能够保留物体的边缘轮廓,如下所示:
d、顶帽
原图像与开运算结果图之差:
e、黑帽
闭运算结果图与原图像之差:
上面5个心态学操作,其实在opencv中不是5个函数,而是一个函数中的5个不同的参数可选值。
void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType= BORDER_CONSTANT, const Scalar&borderValue=morphologyDefaultBorderValue() );
上面这个函数就是执行高级形态学操作。参数列表:输入图像、输出图像、结构元素、操作类型、迭代次数、边界类型、边界值。
输入图像:图像可以是任意通道的,但是深度必须是CV_8U, CV_16U, CV_16S, CV_32F 、CV_64F。
输出图像:具有和输入图像一样的尺寸和类型;
结构元素:可以使用函数 getStructuringElement() 来创建
操作类型:下面的5个操作它们可以分别用(2、3、4、5、6)这几个数字来代替。
迭代次数:默认为1次;剩下两个参数也和上面的是一样的。