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


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

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

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

模板运算

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

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

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个非线性平滑滤波:中值滤波和双边滤波

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

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

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(){
    //【1】定义原图像
    //【2】调用模糊函数
    //【3】显示结果

    //【1】定义原图像
    Mat srcImage = imread("/home/chris/workspace/clion/blur_img1.png");
    Mat meanBlurImage, medianBlurImage;

    //【2】调用模糊函数
    blur(srcImage, meanBlurImage, Size(3,3));
    medianBlur(srcImage, medianBlurImage, 3);

    //【3】显示结果
    imshow("原图", srcImage);
    imshow("均值滤波", meanBlurImage);
    imshow("中值滤波", medianBlurImage);
    while(waitKey(1)!='q'){}
    destroyAllWindows();

    return 0;
}

双边滤波
双边滤波是一种简单的、非迭代的保边平滑过滤器:能够去除图像噪声,同时很好地保持边界。缺点是比其他过滤器慢。
查看opencv官方文档,目前(opencv3.0)中,双边滤波器函数的实现依然有问题,“This filter does not work inplace.”
看起来有点沮丧,不管了,先了解下原理。

双边滤波同时考虑了空间域和值域的差别:空间域给人一种“出身”的感觉,模板框定了你周围的像素点,这些点不管它们灰度值是多少,你总要按相应权重对待它们(按模板中对应元素值来处理),比如均值滤波是“一视同仁”,高斯滤波是“像冲击波一样从自身衰减”。空间域滤波器的效果是,能去除噪声。
而从值域的角度看,给人一种“看后天努力程度”的感觉:对于模板框定的周围像素点,考虑它们的灰度值,而不去官它们当中的“老幼尊卑”。这方面的代表是alpha-截尾均值滤波器。值域滤波器的效果是,能保留边界效果。

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

锐化滤波

主要包括:线性锐化滤波(拉普拉斯算子、高频提升滤波)、非线性锐化滤波(基于梯度的锐化滤波、最大-最小锐化变换等)
线性锐化滤波可以借助模板卷积实现。对应积分运算的模板卷积可以平滑图像,反过来对应微分运算的模板卷积可以锐化图像。锐化模板系数的取值,应该在中心为正而周围远离中心处为负。

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

非线性锐化滤波

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

梯度锐化

梯度锐化依然是一种模板算法卷积算法,经过一系列推导,并整理出对应的运算模板,就是我们最终需要的。
图像一般是二维矩阵,因此梯度锐化法一般在x、y方向分别计算出梯度幅值Gx、Gy,然后再合并。
用差分来近似微分.比如水平垂直差分:

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

或者交叉差分:

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

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

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

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

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

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

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

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

Sx=[1  0]
   [0 -1]

Sy=[0 -1]
   [1  0]

Gx=Sx * A   #卷积操作
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 0 1]
Sx=[-1 0 1]
   [-1 0 1]

   [ 1  1  1]
Sy=[ 0  0  0]
   [-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 0 1]
Sx=[-2 0 2]
   [-1 0 1]

   [ 1  2  1]
Sy=[ 0  0  0]
   [-1 -2 -1]

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

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

在Sobel算子中,容易计算:

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

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

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

   [-3  0  3]
Sx=[-10 0 10]
   [-3  0  3]

   [-3 -10 -3]
Sy=[ 0  0   0]
   [-3 -10 -3]

线性锐化滤波

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

拉普拉斯算子

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

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

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

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

对应的模板是:

 0 -1  0
-1  4 -1
 0 -1  0

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

-1 -1 -1
-1  8 -1
-1 -1 -1

高频提升滤波

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

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实现详解(一)

时间: 2024-11-10 12:07:50

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

opencv笔记6:角点检测

time:2015年10月09日 星期五 23时11分58秒 # opencv笔记6:角点检测 update:从角点检测,学习图像的特征,这是后续图像跟踪.图像匹配的基础. 角点检测是什么鬼?前面一篇学习笔记是各种模板操作,是图像增强技术. 那么我节写来应该继续找下有没有别的图像增强技术. 但是,我对增强还不是特别理解.图像增强:划定ROI区域,然后想方设法将感兴趣的特征有选择的突出.注意,这可是不去考虑图像质量下降的原因的.图像恢复:针对图像降质的原因,设法去补偿降质因素,从而使改善后的图像尽

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

time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决定.因为积分后左边剩下的为一变量是频率,所以我们说傅立叶变换域是频率域. (<数字图像处理>冈萨雷斯,中文第三版P128) 当变量t用于说明图像时,我们一般将变量t的域称为空间域. 按<图像处理>(章毓晋)的理解,首先是认同模板操作的,然后借助卷积定理,将模板操作转化为傅立叶的乘积,也

Symfony2学习笔记之模板用法详解_php实例

本文实例讲述了Symfony2学习笔记之模板用法.分享给大家供大家参考,具体如下: 我们知道,controller负责处理每一个进入Symfony2应用程序的请求.实际上,controller把大部分的繁重工作都委托给了其它地方,以使代码能够被测试和重用.当一个controller需要生成HTML,CSS或者其他内容时,它把这些工作给了一个模板化引擎. 模板: 一个模板仅仅是一个文本文件,它能生成任意的文本格式(HTML,XML,CSV,LaTex...).最著名的模板类型就是PHP模板了,可以

opencv笔记3:trackbar简单使用

time:2015年 10月 03日 星期六 13:54:17 CST # opencv笔记3:trackbar简单使用 当需要测试某变量的一系列取值取值会产生什么结果时,适合用trackbar.看起来就是debug的一种技术手段了. 主要是使用createTrackbar函数.具体讲,是把trackbar放到一个窗口中,并为trackbar设定回调函数,步骤还是有点繁琐的: 定义图像 定义窗口 定义回调函数 创建trackbar 回调函数初始化 善后工作 其中回调函数参数规定为(int, vo

destoon二次开发笔记 数据库 模板制作

入门代码示例 一.初始化系统 包含系统根目录下的common.inc.php即可初始化系统. 例如在站点根目录下创建一个hello.php. 示例代码:  代码如下 复制代码 <?php require 'common.inc.php'; echo 'Hello World'; ?> 二.编写逻辑 系统初始化之后,就可以在php文件里编写逻辑代码,同时也可以调用系统内置的变量.函数和类了. 示例代码:  代码如下 复制代码 <?php require 'common.inc.php';

DSP中浮点转定点运算--定点数模拟浮点数运算及常见的策略_C 语言

4.定点数模拟浮点数运算及常见的策略 相信大家到现在已经大致明白了浮点数转换成定点数运算的概貌.其实,原理讲起来很简单,真正应用到实际的项目中,可能会遇到各种各样的问题.具我的经验,常见的策略有如下几条: 1)除法转换为乘法或移位运算 我们知道,不管硬件平台如果变换,除法运算所需要的时钟周期都远远多于乘法运算和加减移位运算,尤其是在嵌入式应用中,"效率"显得尤为重要.以笔者的经验,其实,项目中的很大一部分除法运算是可以转换成乘法和移位运算,效率还是有很大提升空间的. 2)查表计算 有些

opencv笔记2:图像ROI

time:2015年 10月 03日 星期六 12:03:45 CST # opencv笔记2:图像ROI ROI ROI意思是Region Of Interests,感兴趣区域,是一个图中的一个子区域. OpenCV中定义的ROI是矩形的. ROI的用处包括而不限于:提取出ROI区域做进一步处理(比如人脸识别.车牌识别):将另一张图片贴放到ROI区域. 这里以第二种用处为例,将一个logo图像添加到一张大图上指定的ROI区域. 图像贴放 粗略想想,包括这四个步骤 定义大图和小图 在大图上定义R

opencv笔记1:opencv的基本模块,以及环境搭建

opencv笔记1:opencv的基本模块,以及环境搭建 安装系统 使用fedora22-workstation-x86_64 安装opencv sudo dnf install opencv-devel 安装cmake sudo dnf install cmake 查看opencv的基本模块 cd /usr/include/opencv2/ vim opencv_modules.hpp 内容如下: #define HAVE_OPENCV_CALIB3D #define HAVE_OPENCV_

《算法设计与分析》一一2.1 数学运算背后的算法操作

2.1 数学运算背后的算法操作 虽然我们已经熟知很多数学概念与性质,但是从算法设计与分析的角度来看,还需要进一步将这些数学的概念与算法的运作联系起来.下面就从这一角度来讨论几组算法设计与分析中常用的数学概念与性质.2.1.1 取整x和x 我们熟知取整函数的定义:下取整函数x表示不超过x的最大整数:上取整函数x表示不小于x的最小整数.需要取整函数的本质原因在于算法分析中涉及的一些量往往是某种离散对象的个数,它必然是正整数.例如,算法的代价是关键操作的个数,问题的规模经常表示为输入元素的个数.输入数