time:2015年10月04日 星期日 00时00分27秒

opencv笔记4:模板运算和常见滤波操作


这一篇主要是学习模板运算,了解各种模板运算的运算过程和分类,理论方面主要参考《图像工程——图像处理》(章毓晋)一书第3章,空域增强:模板操作。同时也有个疑问:此书第四章,频域图像增强,讲了低通滤波和高通滤波,然而这些东西和模板运算中的平滑、锐化操作有什么区别?。。。

以下是正文:

模板运算

首先我们把所有图像看作矩阵。

模板一般是nxn(n通常是3、5、7、9等很小的奇数)的矩阵。模板运算基本思路:将原图像中某个像素的值,作为它本身灰度值和其相邻像素灰度值的函数。模板中有一个锚点(anchor point),通常是矩阵中心点,和原图像中待计算点对应;整个模板对应的区域,就是原图像中像素点的相邻区域。模板也称为核(kernel)。

前面的解释翻译成公式就是:

  1. g(x,y)=function(f(x,y), template)

常见的function操作有卷积和排序两种。卷积可以立即为一个map-reduce过程:元素对应相乘(mapper),乘积累加(reducer)。显然,卷积是一个线性操作。

排序操作也不难理解:模板的锚点和待计算点绑定后,邻域内所有点进行排序操作,将排序结果中符合策略规定的作为结果。一般的排序算法是O(n log n)的,不知道是不是因此有人认为模板排序运算不是线性的。其实通常处理的图像像像素值都是unsigned char类型的,是[0,255]之间的非负整数,显然用桶排序是可以O(n)复杂度内完成排序的,依我看也是一种线性运算。

如果模板排序前,需要对应元素和模板元素相乘,然后将乘积排序,那么这时候乘积可能是浮点数,排序就基本上是O(n log n)了,这确实是非线性操作了。不过目前我没有见到类似的操作,也觉得没有什么实际的用处。

滤波

模板运算的效果,可能让图像变好,也可能让图像变坏。我们当然需要好的那种模板运算了:)利用像素本身及其邻域像素的灰度关系进行增强的方法,被称为滤波,滤波使用到的模板就是滤波器。(注意:滤波器是一个模板矩阵,也就是核kernel,而具体的卷积操作还是排序操作,不是滤波器)

滤波和卷积的区别

卷积是滤波的一种实现方式。卷积是一种具体的运算,虽然它其实也是有点一种抽象的表述;而滤波则是比卷积要抽象的描述。

高频分量和低频分量

先看看频率的本意:(狭义概念)频率是单位时间内完成周期性变化的次数。推广开来,(广义概念)频率就是指一定时间内的变化次数。

频率在信号处理领域大量使用。信号处理中的函数自变量是时间;数字图像处理被看作类似信号处理,只不过这里的函数自变量不再是时间,而是换成了图像矩阵的像素灰度值。

原来在信号处理中,从前一秒到后一秒,信号周期性变化的次数,就是频率;相应地,在数字图像处理中,从一个像素点到相邻的一个像素点,灰度值变化的多少,就是频率。

所谓高频分量,就是频率值高,就是像素之间灰度变化大,这通常对应着图像区域边缘等;而低频分量,就是频率值低,就是像素灰度之间灰度变化小,这通常是图像中稳定的区域,是在一个object的内部,同属于一个superpixel...

总之,这样的理解下,高频分量对应图像边缘等像素变化大的像素点;低频分量对应着图像中稳定的区域。

平滑滤波和锐化滤波

平滑滤波能去除高频分量,而锐化滤波能去除低频分量。这么说还是抽象,具体讲是:平滑滤波去处噪声,锐化滤波强化边缘、细节与周围的对比度。

平滑滤波主要包括:线性平滑滤波(方框滤波、均值滤波、高斯滤波等)、非线性平滑滤波(中值滤波、序统计滤波)。opencv中对应boxblur、blur、gaussianblur函数。

锐化滤波主要包括:线性锐化滤波(拉普拉斯算子、高频提升滤波)、非线性锐化滤波(基于梯度的锐化滤波、最大-最小锐化变换等)

注意 实际上对图像进行二维傅立叶变换得到频谱图,就是图像梯度的分布图,当然频谱图上的各点与图像上各点并不存在一一对应的关系,即使在不移频的情况下也是没有。傅立叶频谱图上我们看到的明暗不一的亮点,实际上图像上某一点与邻域点差异的强弱,即梯度的大小,也即该点的频率的大小(可以这么理解,图像中的低频部分指低梯度的点,高频部分相反)。一般来讲,梯度大则该点的亮度强,否则该点亮度弱。这样通过观察傅立叶变换后的频谱图,也叫功率图,我们首先就可以看出,图像的能量分布,如果频谱图中暗的点数更多,那么实际图像是比较柔和的(因为各点与邻域差异都不大,梯度相对较小),反之,如果频谱图中亮的点数多,那么实际图像一定是尖锐的,边界分明且边界两边像素差异较大的。

平滑滤波

线性平滑滤波

opencv现在有3个线性平滑滤波器:方框滤波、均值滤波、高斯滤波

先说均值滤波。均值滤波就是用指定大小的、元素全为1的模糊核,对原图进行卷积操作(其实,就是原图像中当前位置对应的核大小的区域,各个元素相加),然后除以核的元素个数。也可以理解为,其核为:元素全为1、系数为元素个数的矩阵。

然后是方框滤波。是均值滤波的推广:元素全为1、系数为alpha的矩阵。alpha等于核的个数时,就是均值滤波;否则一般取1(哦,为什么要搞这么大?难道不会超出255麻?)

再看高斯滤波。前面两个滤波的核(也叫模板),元素值都只有一种。如果模板的元素不只一种,就是加权线性滤波了。

高斯滤波是加权线性滤波的一种,准确说是:模板元素的分布符合二次高斯分布。高斯分布其实就是正态分布。因为一般认为噪声的分布都符合高斯分布,那么去噪也用符合高斯分布的模板,效果会比较好。

通过为GaussianBlur函数传入sigmaX,sigmaY,size等参数,函数能生成相应的符合高斯分布的模板。然后对原图像和模板进行卷积操作,就得到滤波后的图像。

非线性平滑滤波

opencv现在有2个非线性平滑滤波:中值滤波和双边滤波

中值滤波:模板限定区域内,取像素灰度的中值(我理解为中位数),作为计算结果。中值滤波的效果是,让与周围像素灰度值的差比较大的像素改取与周围像素值接近的值,消除了信号序列(这里是模板框定范围内像素点灰度值)的孤立点。因为不是简单的取均值,产生的模糊更少些,通常能比均值滤波更好地保持图像的细节。

测试发现,在有白色噪声的图像上,中值滤波和均值滤波的对比效果很明显:

  1. #include <iostream>
  2. #include <opencv2/opencv.hpp>
  3. using namespace std;
  4. using namespace cv;
  5. int main(){
  6. //【1】定义原图像
  7. //【2】调用模糊函数
  8. //【3】显示结果
  9. //【1】定义原图像
  10. Mat srcImage = imread("/home/chris/workspace/clion/blur_img1.png");
  11. Mat meanBlurImage, medianBlurImage;
  12. //【2】调用模糊函数
  13. blur(srcImage, meanBlurImage, Size(3,3));
  14. medianBlur(srcImage, medianBlurImage, 3);
  15. //【3】显示结果
  16. imshow("原图", srcImage);
  17. imshow("均值滤波", meanBlurImage);
  18. imshow("中值滤波", medianBlurImage);
  19. while(waitKey(1)!='q'){}
  20. destroyAllWindows();
  21. return 0;
  22. }

双边滤波

双边滤波是一种简单的、非迭代的保边平滑过滤器:能够去除图像噪声,同时很好地保持边界。缺点是比其他过滤器慢。

查看opencv官方文档,目前(opencv3.0)中,双边滤波器函数的实现依然有问题,“This filter does not work inplace.”

看起来有点沮丧,不管了,先了解下原理。

双边滤波同时考虑了空间域和值域的差别:空间域给人一种“出身”的感觉,模板框定了你周围的像素点,这些点不管它们灰度值是多少,你总要按相应权重对待它们(按模板中对应元素值来处理),比如均值滤波是“一视同仁”,高斯滤波是“像冲击波一样从自身衰减”。空间域滤波器的效果是,能去除噪声。

而从值域的角度看,给人一种“看后天努力程度”的感觉:对于模板框定的周围像素点,考虑它们的灰度值,而不去官它们当中的“老幼尊卑”。这方面的代表是alpha-截尾均值滤波器。值域滤波器的效果是,能保留边界效果。

双边滤波综合考虑了空间域和值域,其计算公式中的权重系统,是定义域核与值域核的乘积。

锐化滤波

主要包括:线性锐化滤波(拉普拉斯算子、高频提升滤波)、非线性锐化滤波(基于梯度的锐化滤波、最大-最小锐化变换等)

线性锐化滤波可以借助模板卷积实现。对应积分运算的模板卷积可以平滑图像,反过来对应微分运算的模板卷积可以锐化图像。锐化模板系数的取值,应该在中心为正而周围远离中心处为负。

当然,图像锐化还可以用高通滤波法来做,不过不属于模板操作的范围,现在还不懂,以后再说。

非线性锐化滤波

这次先来看看非线性锐化滤波。所谓线性还是非线性,是从最后的结果来看,计算步骤是不是线性的:虽然拉普拉斯算子是二阶差分得到的,但是结果上开它等同于做线性模板卷积运算;而Sobel算子等一阶差分方法,因为要分别考虑x、y两个方向然后再合并,整个步骤没法简化,所以是非线性的。从推导的角度看,要先看一阶差分操作,也就是非线性的几个算子。

梯度锐化

梯度锐化依然是一种模板算法卷积算法,经过一系列推导,并整理出对应的运算模板,就是我们最终需要的。

图像一般是二维矩阵,因此梯度锐化法一般在x、y方向分别计算出梯度幅值Gx、Gy,然后再合并。

用差分来近似微分.比如水平垂直差分:

  1. f'(x) = f(i,j)-f(i+1,j)
  2. f'(y) = f(i,j)-f(i,j+1)

或者交叉差分:

  1. f'(x) = f(i,j)-f(i+1, j+1)
  2. f'(y) = f(i+1,j ) - f(i, j+1)

x、y两个方向上差分的结果,可以通过距离公式和在一起,用来表示最后的计算结果,比如采用水平垂直差分+曼哈顿距离公式,有:

  1. g(i,j)=|f(i,j)-f(i+1,j)|+|f(i,j)-f(i,j+1)|

而使用交叉差分+欧几里得距离公式,有:

  1. g(i,j)=sqrt( (f(i,j)-f(i+1,j+1))^2 + (f(i+1,j)-f(i,j+1)^2 )

或者使用交叉差分+曼哈顿距离,有:

  1. g(i,j)=|(f(i,j)-f(i+1,j+1)| + |(f(i+1,j)-f(i,j+1)|

上式就是Roberts算子的梯度幅值公式,对应的模板为:

  1. Sx=[1 0]
  2. [0 -1]
  3. Sy=[0 -1]
  4. [1 0]
  5. Gx=Sx * A #卷积操作
  6. Gy=Sy * A #卷积操作

梯度锐化的改进 上述算法计算出来的,是一个考虑了周遭像素的、差分、然后相加的结果。这个结果拿来取代原来的像素值,不一定合适,我们可以对此加以判断,如果它落在某个阈值范围内(比如大于阈值T),才算作有效,否则仍然取原来的f(i,j)灰度值。

梯度锐化的不足 考虑低频区域中的一个点f(i,j),其g(i,j)通常是0;再噪声点f(i,j),显然它对应的g(i,j)不为0,而且还比较大。往往噪声点在梯度锐化算法中,被增强的效果比普通点更大。因此使用此算法前尽量去除噪声。

梯队锐化对应的模板? 显然上面提到的公式,对应的模板是2x2的。这和通常使用的奇阶方阵不一样,是不实用的。无论是x方向还是y方向,2x2的方阵都仅仅是考虑了半邻域,而不是整个邻域。不过这个思路是可取的。Sobel算子就是考虑了整个邻域的一阶算子,当然还可以用拉普拉斯这样的二阶算子。

Prewitt算子

最简单的、考虑了整个邻域的算子,是Prewitt算子,其卷积模板为:

  1. [-1 0 1]
  2. Sx=[-1 0 1]
  3. [-1 0 1]
  4. [ 1 1 1]
  5. Sy=[ 0 0 0]
  6. [-1 -1 -1]

显然,它仅仅在一个方向上考虑了权值,而另一个方向上则没有考虑。Sobel算法则是其的进一步优化。

Soble和Canny

Sobel算子用来计算图像的导数,目的是获得图像边缘。因此,它常常被用于边缘检测。Canny算法是比Sobel更完整的边缘检测算法,包括了预处理(高斯滤波)、后处理(阈值法去除非边缘点)。

为什么用导数检测边缘?考虑一维图像f(x),其边缘是一个点。作为边缘点,其左右两侧像素灰度的变化滤肯定不一样(否则,就是真的平滑图像了,哪还有什么边界)。如果计算f(x)的导数,f(x)一定是导数中的极值。推广到二维图像这也是成立的。只不过求导公式使用差分公式来近似代替了。Sobel和前面的锐化梯度的区别在于,它在x、y方向分别使用一个模板进行卷积运算,得到Gx、Gy两个分量,然后用G=sqrt(Gx^2 + Gy^2)得到梯度幅值。

Sobel算子,一般取模板:

  1. [-1 0 1]
  2. Sx=[-2 0 2]
  3. [-1 0 1]
  4. [ 1 2 1]
  5. Sy=[ 0 0 0]
  6. [-1 -2 -1]

Canny是指Canny边缘检测算法,其步骤包括:

  1. 滤波:读取灰度图像后,使用高斯滤波做平滑处理(去噪)
  2. 增强:用一阶偏导的有限差分来计算梯度的幅值和方向(锐化)。这一步可以使用Roberts、Sobel、Prewitt等算子,或者说,使用类似Sobel滤波器的滤波步骤。
  3. 检测:使用阈值法将非边缘点去除,获得真正的边缘点

    如此看来,Canny边缘检测是模板操作的综合应用了,既有平滑处理,也有锐化处理。

在Sobel算子中,容易计算:

  1. Gx(x,y)=Sx * A #卷积操作
  2. Gy(x,y)=Sy * A #卷积操作
  3. G(x,y)=sqrt(Gx^2+Gy^2) #梯度幅值
  4. theta=arctan(Gy(x,y)/Gx(x,y)) #梯度幅角

其中,梯度幅角在Canny最后一步的检测判断中使用到。

P.S.:opencv中的Sobel函数,如果指定的模板规格为3x3,按照前面的模板矩阵,会产生明显的不精确的结果。通过调用Scharr算子的模板,效果会好些:

  1. [-3 0 3]
  2. Sx=[-10 0 10]
  3. [-3 0 3]
  4. [-3 -10 -3]
  5. Sy=[ 0 0 0]
  6. [-3 -10 -3]

线性锐化滤波

前面使用的是一阶差分,现在使用二阶差分,同样能得到用来锐化滤波的操作。只不过很巧妙的是,拉普拉斯算子的推导过程到最后发现,能够等价于一个权值模板卷积操作,所以是一阶操作。

拉普拉斯算子

拉普拉斯算子是二阶微分算子,也用于线性锐化滤波:

  1. g(i,j)=f''(x)+f''(y)

仍然使用差分的方式,容易得到:

  1. f''(x) = 2f(i,j)-f(i+1,j)-f(i-1,j)
  2. f''(y) = 2f(i,j)-f(i,j+1)-f(i,j-1)
  3. g(i,j)=4f(i,j)-f(i+1,j)-f(i-1,j)-f(i,j+1)-f(i,j-1)

对应的模板是:

  1. 0 -1 0
  2. -1 4 -1
  3. 0 -1 0

这是只考虑4邻域的情况。如果考虑8邻域,对应的模板是:

  1. -1 -1 -1
  2. -1 8 -1
  3. -1 -1 -1

高频提升滤波

用原始图像见去平滑或模糊图像能得到非锐化掩模,将非锐化掩模加到原始图像上能得到锐化图像。更进一步,可以把原始图像放大A倍后再减去平滑图像:

  1. h(x,y) = A*f(x,y)-g(x,y) = (A-1)*f(x,y)+(f(x,y)-g(x,y)) = (A-1)*f(x,y) + mask(x,y)

其实就是稍微复杂点的线性组合了。

自定义滤波

如果想到了什么新的算法,或者纯粹想试一试碰碰运气,可以自定义模板矩阵,扔给opencv的filter2d函数,就可以看到效果了。

ref

RachealZhang:双边滤波器的原理及实现

浅墨:【OpenCV入门教程之九】 非线性滤波专场:中值滤波、双边滤波

MrMystery:图像增强-图像锐化

奋斗斌斌的专栏:Canny边缘检测算法原理及其VC实现详解(一)

opencv笔记4:模板运算和常见滤波操作的更多相关文章

  1. OpenCV 学习笔记(模板匹配)

    OpenCV 学习笔记(模板匹配) 模板匹配是在一幅图像中寻找一个特定目标的方法之一.这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否"相似",当相似度足够 ...

  2. opencv笔记6:角点检测

    time:2015年10月09日 星期五 23时11分58秒 # opencv笔记6:角点检测 update:从角点检测,学习图像的特征,这是后续图像跟踪.图像匹配的基础. 角点检测是什么鬼?前面一篇 ...

  3. opencv笔记5:频域和空域的一点理解

    time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决 ...

  4. opencv笔记3:trackbar简单使用

    time:2015年 10月 03日 星期六 13:54:17 CST # opencv笔记3:trackbar简单使用 当需要测试某变量的一系列取值取值会产生什么结果时,适合用trackbar.看起 ...

  5. OpenCV基本架构[OpenCV 笔记0]

    最近正在系统学习OpenCV,将不定期发布笔记,主要按照毛星云的<OpenCV3编程入门>的顺序学习,会参考官方教程和文档.学习工具是Xcode+CMake,会对书中一部分内容更正,并加入 ...

  6. 第十五节、OpenCV学习(四)图像平滑与滤波

    图像的平滑与滤波 平滑滤波是低频增强的空间域滤波技术,是图像模糊.消除噪声. 一.2D滤波器cv2.filter2D() 对于2D图像可以进行低通或者高通滤波操作,低通滤波(LPF)有利于去噪声,模糊 ...

  7. Elasticsearch7.X 入门学习第八课笔记-----索引模板和动态模板

    原文:Elasticsearch7.X 入门学习第八课笔记-----索引模板和动态模板 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接: ...

  8. opencv——常见的操作

    一 图像阈值处理 准备一张灰度图像 阈值处理通常是设定一个阈值,让图片的所有像素点的值与其比较做出一系列的操作. 在opencv常用的阈值处理函数有五种,分别是THRESH_BINARY.THRESH ...

  9. 【工程应用五】 opencv中linemod模板匹配算法诸多疑惑和自我解读。

    研究这个前前后后也有快两三个月了,因为之前也一直在弄模板匹配方面的东西,所以偶尔还是有不少朋友咨询或者问你有没有研究过linemod这个算法啊,那个效率啥的还不错啊,有段时间一直不以为然,觉得我现在用 ...

随机推荐

  1. 数字对 (长乐一中模拟赛day2T2)

    2.数字对 [题目描述] 小H是个善于思考的学生,现在她又在思考一个有关序列的问题. 她的面前浮现出一个长度为n的序列{ai},她想找出一段区间[L, R](1 <= L <= R < ...

  2. flex+AS3编程规范

    flex+AS3编程规范 Flex+AS3编码规范 http://www.cnblogs.com/jiahuafu/   1.  缩写: 尽量避免使用缩写,使用缩写时尽量和Flex保持一致.但要记住一 ...

  3. Android签名机制:生成keystore、签名、查看签名信息

    转自:http://www.ourunix.org/post/146.html

  4. Spring MVC之cookies跟session 数据绑定

    在我最早接触web开发的中学时代,学习的asp技术对于session的概念其实很清楚 Session("username")="张三"下次要用的时候,直接用se ...

  5. html和css知识总结

    今天把整个html和css的总结了一遍.可能还有疏忽之处,共同学习吧 [行为样式三者分离] 不加行内css样式,不加行内js效果 [标签]1>单标签<!doctype html> 文 ...

  6. int 与Integer的用法与区别

    1.int是基本类型,直接存取数值,Integer是对象,用一个引用指向这个对象. 2.java中的数据类型分为基本数据类型和复杂数据类型,int是前者,Integer是后者(也就是一个类). 3.初 ...

  7. Java compiler level does not match the version of the installed Java project facet.(转)

    Java compiler level does not match解决方法 从别的地方导入一个项目的时候,经常会遇到eclipse/Myeclipse报Description  Resource P ...

  8. Linux 基础入门 第二周9.21~9.27

    一.学习内容 本周主要学习内容主要贴合: 在进行<深入理解计算机系统>这门课的实验中没有遇到什么大问题,学习内容与上周实验<linux基础入门>有相似之处.本实验中的内容比较贴 ...

  9. 创建Maven工程

    一.Maven工程创建 File->New->Other,进入: 点击Next,进入: 勾选上Create a simple project(不使用骨架) 点击Next,进入: 输入项目名 ...

  10. IT应届生如何准备找工作?

    今天和一个弟弟吃饭,他明年年初即将计算机研究生毕业.谈论到怎么找工作,觉得自己会的不多,心里非常发虚.虽然我当年找工作也走了很多弯路,思路并不是很清晰.但是工作了这么多年,对企业需要什么样子的人还是有 ...