高斯滤波在图像处理中的应用

高斯滤波详解 python实现高斯滤波 一. 高斯滤波 高斯滤波是一种线性平滑滤波器,对于服从正态分布的噪声有很好的抑制作用。在实际场景中,我们通常会假定图像包含的噪声为高斯白噪声,所以在许多实际应用的预处理部分,都会采用高斯滤波抑制噪声。 高斯滤波和均值滤波一样,都是利用一个掩膜和图像进行卷积求解。不同之处在于:均值滤波器的模板系数都是相同的,为1。而高斯滤波器的模板系数,随着距离模板中心距离的增大... 阅读详情

卷积:

相信很多时候,当我们在看到“卷积”时,总是处于一脸懵逼的状态,不但因为它的本义概念比较难理解,还因为它在不同的应用中发挥出的变幻莫测的作用也时常让人迷糊。但这些应用其实本质上都是同一种东西,理解了卷积的来源,就可以举一反三。其实我个人对于卷积的理解,很长时间都处于似懂非懂的状态,就像傅里叶变换的一些tricky points,只求在应用中不出差错,不求甚解。但是如果想要真正做好learning的东西,我认为在真正的学习理论基础前,必须将概念的本质搞清楚。因此,这篇文章便是为工科,理科以及其他领域对此感兴趣的同学而整理,内容简易通俗,便于理解。

什么是卷积

卷积,很多时候都是我们在各种工程领域,信号领域所看到的常用名词,比如系统

 

 

通俗易懂的说,就是

输出 = 输入 * 系统

虽然它看起来只是个复杂的数学公式,但是却有着重要的物理意义,因为自然界这样的系统无处不在,计算一个系统的输出最好的方法就是运用卷积。更一般的,我们还有很多其他领域的应用:

统计学中,加权的滑动平均是一种卷积。

概率论中,两个统计独立变量X与Y的和的概率密度函数是X与Y的概率密度函数的卷积。

声学中,回声可以用源声与一个反映各种反射效应的函数的卷积表示。

电子工程与信号处理中,任一个线性系统的输出都可以通过将输入信号与系统函数(系统的冲激响应)做卷积获得。

物理学中,任何一个线性系统(符合叠加原理)都存在卷积。

计算机科学中,卷积神经网络(CNN)是深度学习算法中的一种,近年来被广泛用到模式识别、图像处理等领域中。

 

这6个领域中,卷积起到了至关重要的作用。在面对一些复杂情况时,作为一种强有力的处理方法,卷积给出了简单却有效的输出。对于机器学习领域,尤其是深度学习,最著名的CNN卷积神经网络(Convolutional Neural Network, CNN),在图像领域取得了非常好的实际效果,始一出现便横扫各类算法。

 

 

那么,到底什么是卷积呢?

 

首先给大家一个果壳上关于卷积的著名暴力讲解:

比如说你的老板命令你干活,你却到楼下打台球去了,后来被老板发现,他非常气愤,扇了你一巴掌(注意,这就是输入信号,脉冲),于是你的脸上会渐渐地(贱贱地)鼓起来一个包,你的脸就是一个系统,而鼓起来的包就是你的脸对巴掌的响应,好,这样就和信号系统建立起来意义对应的联系。

下面还需要一些假设来保证论证的严谨:假定你的脸是线性时不变系统,也就是说,无论什么时候老板打你一巴掌,打在你脸的同一位置(这似乎要求你的脸足够光滑,如果你说你长了很多青春痘,甚至整个脸皮处处连续处处不可导,那难度太大了,我就无话可说了哈哈),你的脸上总是会在相同的时间间隔内鼓起来一个相同高度的包来,并且假定以鼓起来的包的大小作为系统输出。好了,那么,下面可以进入核心内容——卷积了! 

如果你每天都到地下去打台球,那么老板每天都要扇你一巴掌,不过当老板打你一巴掌后,你5分钟就消肿了,所以时间长了,你甚至就适应这种生活了……如果有一天,老板忍无可忍,以0.5秒的间隔开始不间断的扇你的过程,这样问题就来了,第一次扇你鼓起来的包还没消肿,第二个巴掌就来了,你脸上的包就可能鼓起来两倍高,老板不断扇你,脉冲不断作用在你脸上,效果不断叠加了,这样这些效果就可以求和了,结果就是你脸上的包的高度随时间变化的一个函数了(注意理解);

如果老板再狠一点,频率越来越高,以至于你都辨别不清时间间隔了,那么,求和就变成积分了。可以这样理解,在这个过程中的某一固定的时刻,你的脸上的包的鼓起程度和什么有关呢?和之前每次打你都有关!但是各次的贡献是不一样的,越早打的巴掌,贡献越小,所以这就是说,某一时刻的输出是之前很多次输入乘以各自的衰减系数之后的叠加而形成某一点的输出,然后再把不同时刻的输出点放在一起,形成一个函数,这就是卷积,卷积之后的函数就是你脸上的包的大小随时间变化的函数。

本来你的包几分钟就可以消肿,可是如果连续打,几个小时也消不了肿了,这难道不是一种平滑过程么?反映到剑桥大学的公式上,f(a)就是第a个巴掌,g(x-a)就是第a个巴掌在x时刻的作用程度,乘起来再叠加就ok了


通过这个通俗化的例子我们从基本概念上了解了卷积,那么更严格的定义是怎样的呢?

从数学上讲,卷积只不过是一种运算,对于很多没有学过信号处理,自动控制的同学来说各种专业的名词可以不做了解。我们接着继续:

 

本质上卷积是将二元函数 U(x,y) = f(x)g(y) 卷成一元函数 V(t) ,俗称降维打击。

 

 

怎么卷?

考虑到函数 f 和 g 应该地位平等,或者说变量 x 和 y 应该地位平等,一种可取的办法就是沿直线 x+y = t 卷起来:

V(t) = \int_{x+y=t} U(x,y) \,\mathrm{d}x

卷了有什么用?

可以用来做多位数乘法,比如:

\begin{align}42 \times137 &= (2\times10^0+4\times10^1)(7\times10^0+3\times10^1+1\times10^2) \\&= (2\times7)\times10^0 + (2\times3+4\times7)\times10^1+(2\times1+4\times3)\times10^2 + (4\times1)\times10^3 \\&= 14 + 340+1400+4000 \\&= 5754\end{align}

注意第二个等号右边每个括号里的系数构成的序列 (14,34,14,4),实际上就是序列 (2,4) 和 (7,3,1) 的卷积

在乘数不大时这么干显得有点蛋疼,不过要计算很长很长的两个数乘积的话,这种处理方法就能派上用场了,因为你可以用快速傅立叶变换 FFT 来得到卷积,比示例里的硬乘要快。

 

这里有一个不太严格的理解:
(\sum_{n=1}^{\infty}{a_nx^n})(\sum_{n=1}^{\infty}{b_nx^n})=\sum_{n=1}^{\infty}(\sum_{k=1}^{n}a_kb_{n-k})x^n
x^n是“基”,a_n是在这个基上的展开系数。两个多项式乘积的在基上展开的系数就是两个多项式各自在基上展开系数的卷积。
x^n对应着频率不同的\exp(ikt),系数对应着其傅里叶变换。自然就是乘积的傅里叶变换等于傅里叶变换的卷积了。

 

卷积的内核(涉及推导过程,可以跳过):

首先我们有这样一个概念:内积、积分、投影这三者其实从某个角度上讲是一个意思。

定义一组向量\alpha=(\alpha_{1},\alpha_{2},...,\alpha_{n}),另一组向量\beta=(\beta_{1},\beta_{2},...,\beta_{n}),那么内积可以表达为:

\alpha \beta = \alpha_{1} \beta_{1}+\alpha_{2} \beta_{2}+...+\alpha_{n} \beta_{n}=\sum_{i=1}^{n}{\alpha_{i} \beta_{i}}

这即是内积,也是累加(积分)。投影的概念则可以理解为向量a在基向量b上的一组投影,坐标为(\alpha_{1},\alpha_{2},...,\alpha_{n}),这和一个点在3D欧几里得空间的三轴投影坐标是一个道理。

 

这样,先来看看Fourier变换在做什么:

F(\omega)=\int_{-\infty }^{+\infty} f(t)e^{-j\omega t}dt

再引入一个完美的式子,欧拉公式:

e^{ix}=cosx+isinx
 

从Fourier的定义式可以看出是对f(t)和e^{-j\omega t}相乘后在无穷域上对其进行积分,那么其实就是将f(t)投影在e^{-j\omega t}上,如果不理解e^{-j\omega t}就变换为两个正交的三角函数(欧拉公式就在这里起作用)。所以这就明朗了:Fourier把f(t)投影到了两个为正交关系的正弦和余弦空间中。也可以从周期信号的Fourier级数分解表达式更容易看出这个投影关系。

 

看完Fourier再看来控制论领域的Laplace变换在做什么:

F(s)=\int_{0}^{\infty}f(t)e^{-\sigma t}e^{-j\omega t}dt=\int_{0}^{\infty}f(t)e^{-st}dt
 

首先,控制领域里面经常用到阶跃信号,不幸的是它不满足狄利克雷第三条件,因此它对Fourier变换免疫,所以聪明的Laplace用了一个衰减因子e^{-\sigma t}将其进行衰减后再做Fourier变换。到了负无穷的区域这衰减因子可就成了递增因子,所以Laplace变换仅限于大于0的区域,对于小于0的区域用系统初始状态表达就好了。从这点角度上讲,Laplace变换相当于对f(t)e^{-\sigma t}做了一个单边Fourier变换。

 

然后,分析方法同上,可以看到Laplace把f(t)投影到了e^{-st}空间,这就是s平面。它比Fourier更厉害的地方是不仅可以看到虚轴上\omega的成分,还可以在实轴上看到Fourier看不到的衰减因子\sigma成分,这是Fourier做不到的。所以Laplace在Fourier的基础上把信号拓展到了衰减因子实轴上,这个衰减因子\sigma和系统的阻尼\zeta,自然震荡角频率\omega_{n}密切相关,直接影响了系统的调节时间t_{s}。学过自控原理的同学应该知道在频域章节,我们得到系统的频域响应曲线都是通过传递函数来直接转化的,公式就是s=jw。这也就是说Fourier活在一维虚轴空间,Laplace活在二维平面空间,想要得到一维空间上关于w的表达形式,只需要在s平面上做降维处理即可。

 

回过头来再来看看卷积投影:

f(t)*g(t)=\int_{-\infty }^{+\infty}f(\tau)g(t-\tau)d\tau

这个投影有点奇怪,它在投影之前先把g(\tau)做了一个反对称,然后再投影。对应到前面推导的系统卷积表达式:

r(t)*f(t)=\int_{-\infty }^{+\infty}f(\tau)r(t-\tau)d\tau
 

相当于在投影之前,先把输入信号r(t)在时间轴上翻转了180°,然后与系统f(t)进行投影。投影的概念我们可以很好理解,无论是向量内积运算相当于线投影,或者空间的一个多面体在三维空间平面上的投影面,这种投影运算就相当于一种重合面积。如果从这个角度去看输入、系统和输出三者之间的关系,那么就可以从图形角度去理解为什么一个一阶系统在阶跃响应输出下是一条单调上升的曲线了。这里用一张wikipedia里关于卷积的一张图形化解释,想要了解更多的同学可以自行跳转:Convolution

(特此感谢知乎学霸王尼莫的帮助)

   

 

卷积的应用

图像处理:用一个模板和一幅图像进行卷积,对于图像上的一个点,让模板的原点和该点重合,然后模板上的点和图像上对应的点相乘,然后各点的积相加,就得到该点的卷积值。对图像上的每个点都这样处理。由于多数模板都对称,所以模板不旋转。
卷积是一种积分运算,用来求两个曲线重叠区域面积。可以看作加权求和,可以用来消除噪声、特征增强。 把一个点的像素值用它周围的点的像素值的加权平均代替。

卷积是一种线性运算,图像处理中常见的mask运算都是卷积,广泛应用于图像滤波。 
卷积关系最重要的一种情况,就是在信号与线性系统或数字信号处理中的卷积定理。利用该定理,可以将时间域或空间域中的卷积运算等价为频率域的相乘运算,从而利用FFT等快速算法,实现有效的计算,节省运算代价。


 

下面是来自sselssbh博客的一个例子,非常形象的解释了卷积在图像领域的作用

 

有这么一副图像,可以看到,图像上有很多噪点: 
这里写图片描述

高频信号,就好像平地耸立的山峰: 
这里写图片描述

看起来很显眼。

平滑这座山峰的办法之一就是,把山峰刨掉一些土,填到山峰周围去。用数学的话来说,就是把山峰周围的高度平均一下。

平滑后得到: 
这里写图片描述

4.2 计算

卷积可以帮助实现这个平滑算法。

有噪点的原图,可以把它转为一个矩阵: 
这里写图片描述

然后用下面这个平均矩阵(说明下,原图的处理实际上用的是正态分布矩阵,这里为了简单,就用了算术平均矩阵)来平滑图像:

 

记得刚才说过的算法,把高频信号与周围的数值平均一下就可以平滑山峰。

比如我要平滑 点,就在矩阵中,取出点附近的点组成矩阵 f ,和 g 进行卷积计算后,再填回去 
这里写图片描述

要注意一点,为了运用卷积, g 虽然和 f 同维度,但下标有点不一样: 
这里写图片描述

这里写图片描述

写成卷积公式就是:

 

要求,一样可以套用上面的卷积公式。

这样相当于实现了 g 这个矩阵在原来图像上的划动(准确来说,下面这幅图把 g 矩阵旋转了 ):

再比如做馒头

楼下早点铺子生意太好了,供不应求,就买了一台机器,不断的生产馒头。 

假设馒头的生产速度是 f(t) ,那么一天后生产出来的馒头总量为: ∫240f(t)dt 

馒头生产出来之后,就会慢慢腐败,假设腐败函数为 g(t) ,比如,10个馒头,24小时会腐败: 10∗g(t) 

想想就知道,第一个小时生产出来的馒头,一天后会经历24小时的腐败,第二个小时生产出来的馒头,一天后会经历23小时的腐败。 如此,我们可以知道,一天后,馒头总共腐败了: ∫240f(t)g(24−t)dt 

文章也发表在我的个人博客中:点击打开链接 ,更多与机器学习,数学相关知乎,欢迎访问~

参考文章:

1. 优快云:最容易理解的对卷积(convolution)的解释http://blog.youkuaiyun.com/bitcarmanlee/article/details/54729807

 

2. 知乎:如何通俗易懂的解释卷积?https://www.zhihu.com/question/22298352

3. 卷积为什么叫卷子?https://www.zhihu.com/question/54677157

4. 果壳:关于卷积的一个血腥讲解 https://www.guokr.com/post/342476/

5. 小木虫:https://zh.wikipedia.org/wiki/%E5%8D%B7%E7%A7%AF%E5%AE%9A%E7%90%86 

原文转自:https://blog.youkuaiyun.com/qq_39521554/article/details/79083864

高斯滤波:

本文主要介绍了高斯滤波器的原理及其实现过程

高斯滤波器是一种线性滤波器,能够有效的抑制噪声,平滑图像。其作用原理和均值滤波器类似,都是取滤波器窗口内的像素的均值作为输出。其窗口模板的系数和均值滤波器不同,均值滤波器的模板系数都是相同的为1;而高斯滤波器的模板系数,则随着距离模板中心的增大而系数减小。所以,高斯滤波器相比于均值滤波器对图像个模糊程度较小。

什么是高斯滤波器

既然名称为高斯滤波器,那么其和高斯分布(正态分布)是有一定的关系的。一个二维的高斯函数如下:

 

h(x,y)=e−x2+y22σ2h(x,y)=e−x2+y22σ2


其中(x,y)(x,y)为点坐标,在图像处理中可认为是整数;σσ是标准差。要想得到一个高斯滤波器的模板,可以对高斯函数进行离散化,得到的高斯函数值作为模板的系数。例如:要产生一个3×33×3的高斯滤波器模板,以模板的中心位置为坐标原点进行取样。模板在各个位置的坐标,如下所示(x轴水平向右,y轴竖直向下)

这样,将各个位置的坐标带入到高斯函数中,得到的值就是模板的系数。
对于窗口模板的大小为 (2k+1)×(2k+1)(2k+1)×(2k+1),模板中各个元素值的计算公式如下:

 

Hi,j=12πσ2e−(i−k−1)2+(j−k−1)22σ2Hi,j=12πσ2e−(i−k−1)2+(j−k−1)22σ2


这样计算出来的模板有两种形式:小数和整数。

  • 小数形式的模板,就是直接计算得到的值,没有经过任何的处理;
  • 整数形式的,则需要进行归一化处理,将模板左上角的值归一化为1,下面会具体介绍。使用整数的模板时,需要在模板的前面加一个系数,系数为1∑(i,j)∈wwi,j1∑(i,j)∈wwi,j,也就是模板系数和的倒数。

高斯模板的生成

知道模板生成的原理,实现起来也就不困难了

void generateGaussianTemplate(double window[][11], int ksize, double sigma)
{
    static const double pi = 3.1415926;
    int center = ksize / 2; // 模板的中心位置,也就是坐标的原点
    double x2, y2;
    for (int i = 0; i < ksize; i++)
    {
        x2 = pow(i - center, 2);
        for (int j = 0; j < ksize; j++)
        {
            y2 = pow(j - center, 2);
            double g = exp(-(x2 + y2) / (2 * sigma * sigma));
            g /= 2 * pi * sigma;
            window[i][j] = g;
        }
    }
    double k = 1 / window[0][0]; // 将左上角的系数归一化为1
    for (int i = 0; i < ksize; i++)
    {
        for (int j = 0; j < ksize; j++)
        {
            window[i][j] *= k;
        }
    }
}

需要一个二维数组,存放生成的系数(这里假设模板的最大尺寸不会超过11);第二个参数是模板的大小(不要超过11);第三个参数就比较重要了,是高斯分布的标准差。
生成的过程,首先根据模板的大小,找到模板的中心位置ksize/2。 然后就是遍历,根据高斯分布的函数,计算模板中每个系数的值。
需要注意的是,最后归一化的过程,使用模板左上角的系数的倒数作为归一化的系数(左上角的系数值被归一化为1),模板中的每个系数都乘以该值(左上角系数的倒数),然后将得到的值取整,就得到了整数型的高斯滤波器模板。
下面截图生成的是,大小为3×3,σ=0.83×3,σ=0.8的模板

对上述解结果取整后得到如下模板:

 

116⎡⎣⎢121242121⎤⎦⎥116[121242121]


这个模板就比较熟悉了,其就是根据σ=0.8σ=0.8的高斯函数生成的模板。

至于小数形式的生成也比较简单,去掉归一化的过程,并且在求解过程后,模板的每个系数要除以所有系数的和。具体代码如下:

void generateGaussianTemplate(double window[][11], int ksize, double sigma)
{
    static const double pi = 3.1415926;
    int center = ksize / 2; // 模板的中心位置,也就是坐标的原点
    double x2, y2;
    double sum = 0;
    for (int i = 0; i < ksize; i++)
    {
        x2 = pow(i - center, 2);
        for (int j = 0; j < ksize; j++)
        {
            y2 = pow(j - center, 2);
            double g = exp(-(x2 + y2) / (2 * sigma * sigma));
            g /= 2 * pi * sigma;
            sum += g;
            window[i][j] = g;
        }
    }
    //double k = 1 / window[0][0]; // 将左上角的系数归一化为1
    for (int i = 0; i < ksize; i++)
    {
        for (int j = 0; j < ksize; j++)
        {
            window[i][j] /= sum;
        }
    }
}

3×3,σ=0.83×3,σ=0.8的小数型模板。

σσ值的意义及选取

通过上述的实现过程,不难发现,高斯滤波器模板的生成最重要的参数就是高斯分布的标准差σσ。标准差代表着数据的离散程度,如果σσ较小,那么生成的模板的中心系数较大,而周围的系数较小,这样对图像的平滑效果就不是很明显;反之,σσ较大,则生成的模板的各个系数相差就不是很大,比较类似均值模板,对图像的平滑效果比较明显。

来看下一维高斯分布的概率分布密度图:

横轴表示可能得取值x,竖轴表示概率分布密度F(x),那么不难理解这样一个曲线与x轴围成的图形面积为1。σσ(标准差)决定了这个图形的宽度,可以得出这样的结论:σσ越大,则图形越宽,尖峰越小,图形较为平缓;σσ越小,则图形越窄,越集中,中间部分也就越尖,图形变化比较剧烈。这其实很好理解,如果sigma也就是标准差越大,则表示该密度分布一定比较分散,由于面积为1,于是尖峰部分减小,宽度越宽(分布越分散);同理,当σσ越小时,说明密度分布较为集中,于是尖峰越尖,宽度越窄!
于是可以得到如下结论:
σσ越大,分布越分散,各部分比重差别不大,于是生成的模板各元素值差别不大,类似于平均模板;
σσ越小,分布越集中,中间部分所占比重远远高于其他部分,反映到高斯模板上就是中心元素值远远大于其他元素值,于是自然而然就相当于中间值得点运算。

基于OpenCV的实现

在生成高斯模板好,其简单的实现和其他的空间滤波器没有区别,具体代码如下:

void GaussianFilter(const Mat &src, Mat &dst, int ksize, double sigma)
{
    CV_Assert(src.channels() || src.channels() == 3); // 只处理单通道或者三通道图像
    const static double pi = 3.1415926;
    // 根据窗口大小和sigma生成高斯滤波器模板
    // 申请一个二维数组,存放生成的高斯模板矩阵
    double **templateMatrix = new double*[ksize];
    for (int i = 0; i < ksize; i++)
        templateMatrix[i] = new double[ksize];
    int origin = ksize / 2; // 以模板的中心为原点
    double x2, y2;
    double sum = 0;
    for (int i = 0; i < ksize; i++)
    {
        x2 = pow(i - origin, 2);
        for (int j = 0; j < ksize; j++)
        {
            y2 = pow(j - origin, 2);
            // 高斯函数前的常数可以不用计算,会在归一化的过程中给消去
            double g = exp(-(x2 + y2) / (2 * sigma * sigma));
            sum += g;
            templateMatrix[i][j] = g;
        }
    }
    for (int i = 0; i < ksize; i++)
    {
        for (int j = 0; j < ksize; j++)
        {
            templateMatrix[i][j] /= sum;
            cout << templateMatrix[i][j] << " ";
        }
        cout << endl;
    }
    // 将模板应用到图像中
    int border = ksize / 2;
    copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
    int channels = dst.channels();
    int rows = dst.rows - border;
    int cols = dst.cols - border;
    for (int i = border; i < rows; i++)
    {
        for (int j = border; j < cols; j++)
        {
            double sum[3] = { 0 };
            for (int a = -border; a <= border; a++)
            {
                for (int b = -border; b <= border; b++)
                {
                    if (channels == 1)
                    {
                        sum[0] += templateMatrix[border + a][border + b] * dst.at<uchar>(i + a, j + b);
                    }
                    else if (channels == 3)
                    {
                        Vec3b rgb = dst.at<Vec3b>(i + a, j + b);
                        auto k = templateMatrix[border + a][border + b];
                        sum[0] += k * rgb[0];
                        sum[1] += k * rgb[1];
                        sum[2] += k * rgb[2];
                    }
                }
            }
            for (int k = 0; k < channels; k++)
            {
                if (sum[k] < 0)
                    sum[k] = 0;
                else if (sum[k] > 255)
                    sum[k] = 255;
            }
            if (channels == 1)
                dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
            else if (channels == 3)
            {
                Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
                dst.at<Vec3b>(i, j) = rgb;
            }
        }
    }
    // 释放模板数组
    for (int i = 0; i < ksize; i++)
        delete[] templateMatrix[i];
    delete[] templateMatrix;
}

只处理单通道或者三通道图像,模板生成后,其滤波(卷积过程)就比较简单了。不过,这样的高斯滤波过程,其循环运算次数为m×n×ksize2m×n×ksize2,其中m,n为图像的尺寸;ksize为高斯滤波器的尺寸。这样其时间复杂度为O(ksize2)O(ksize2),随滤波器的模板的尺寸呈平方增长,当高斯滤波器的尺寸较大时,其运算效率是极低的。为了,提高滤波的运算速度,可以将二维的高斯滤波过程分解开来。

分离实现高斯滤波

由于高斯函数的可分离性,尺寸较大的高斯滤波器可以分成两步进行:首先将图像在水平(竖直)方向与一维高斯函数进行卷积;然后将卷积后的结果在竖直(水平)方向使用相同的一维高斯函数得到的模板进行卷积运算。具体实现代码如下:

// 分离的计算
void separateGaussianFilter(const Mat &src, Mat &dst, int ksize, double sigma)
{
    CV_Assert(src.channels()==1 || src.channels() == 3); // 只处理单通道或者三通道图像
    // 生成一维的高斯滤波模板
    double *matrix = new double[ksize];
    double sum = 0;
    int origin = ksize / 2;
    for (int i = 0; i < ksize; i++)
    {
        // 高斯函数前的常数可以不用计算,会在归一化的过程中给消去
        double g = exp(-(i - origin) * (i - origin) / (2 * sigma * sigma));
        sum += g;
        matrix[i] = g;
    }
    // 归一化
    for (int i = 0; i < ksize; i++)
        matrix[i] /= sum;
    // 将模板应用到图像中
    int border = ksize / 2;
    copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
    int channels = dst.channels();
    int rows = dst.rows - border;
    int cols = dst.cols - border;
    // 水平方向
    for (int i = border; i < rows; i++)
    {
        for (int j = border; j < cols; j++)
        {
            double sum[3] = { 0 };
            for (int k = -border; k <= border; k++)
            {
                if (channels == 1)
                {
                    sum[0] += matrix[border + k] * dst.at<uchar>(i, j + k); // 行不变,列变化;先做水平方向的卷积
                }
                else if (channels == 3)
                {
                    Vec3b rgb = dst.at<Vec3b>(i, j + k);
                    sum[0] += matrix[border + k] * rgb[0];
                    sum[1] += matrix[border + k] * rgb[1];
                    sum[2] += matrix[border + k] * rgb[2];
                }
            }
            for (int k = 0; k < channels; k++)
            {
                if (sum[k] < 0)
                    sum[k] = 0;
                else if (sum[k] > 255)
                    sum[k] = 255;
            }
            if (channels == 1)
                dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
            else if (channels == 3)
            {
                Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
                dst.at<Vec3b>(i, j) = rgb;
            }
        }
    }
    // 竖直方向
    for (int i = border; i < rows; i++)
    {
        for (int j = border; j < cols; j++)
        {
            double sum[3] = { 0 };
            for (int k = -border; k <= border; k++)
            {
                if (channels == 1)
                {
                    sum[0] += matrix[border + k] * dst.at<uchar>(i + k, j); // 列不变,行变化;竖直方向的卷积
                }
                else if (channels == 3)
                {
                    Vec3b rgb = dst.at<Vec3b>(i + k, j);
                    sum[0] += matrix[border + k] * rgb[0];
                    sum[1] += matrix[border + k] * rgb[1];
                    sum[2] += matrix[border + k] * rgb[2];
                }
            }
            for (int k = 0; k < channels; k++)
            {
                if (sum[k] < 0)
                    sum[k] = 0;
                else if (sum[k] > 255)
                    sum[k] = 255;
            }
            if (channels == 1)
                dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
            else if (channels == 3)
            {
                Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
                dst.at<Vec3b>(i, j) = rgb;
            }
        }
    }
    delete[] matrix;
}

代码没有重构较长,不过其实现原理是比较简单的。首先得到一维高斯函数的模板,在卷积(滤波)的过程中,保持行不变,列变化,在水平方向上做卷积运算;接着在上述得到的结果上,保持列不边,行变化,在竖直方向上做卷积运算。 这样分解开来,算法的时间复杂度为O(ksize)O(ksize),运算量和滤波器的模板尺寸呈线性增长。

在OpenCV也有对高斯滤波器的封装GaussianBlur,其声明如下:

CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                                double sigmaX, double sigmaY = 0,
                                int borderType = BORDER_DEFAULT );

二维高斯函数的标准差在x和y方向上应该分别有一个标准差,在上面的代码中一直设其在x和y方向的标准是相等的,在OpenCV中的高斯滤波器中,可以在x和y方向上设置不同的标准差。
下图是自己实现的高斯滤波器和OpenCV中的GaussianBlur的结果对比

上图是5×5,σ=0.85×5,σ=0.8的高斯滤波器,可以看出两个实现得到的结果没有很大的区别。

总结

高斯滤波器是一种线性平滑滤波器,其滤波器的模板是对二维高斯函数离散得到。由于高斯模板的中心值最大,四周逐渐减小,其滤波后的结果相对于均值滤波器来说更好。
高斯滤波器最重要的参数就是高斯分布的标准差σσ,标准差和高斯滤波器的平滑能力有很大的能力,σσ越大,高斯滤波器的频带就较宽,对图像的平滑程度就越好。通过调节σσ参数,可以平衡对图像的噪声的抑制和对图像的模糊。

原文转自:https://www.cnblogs.com/wangguchangqing/p/6407717.html

[matlab] 自适应高斯滤波器在二维图像上的应用 为什么使用自适应高斯滤波器: 通常情况下,在使用高斯滤波器对二维图像进行平滑处理时,高斯函数的方差sigma是一个固定的值。在图像的细节区域(比如头发丝),使用较大的sigma会造成过度平滑,使之失去细节信息,在平坦的区域,使用较大的sigma却是一个比较好的选择。 自适应高斯滤波器就是根据被平滑的图像的局部特征,选择不同的sigma,使得在平滑后的结果图像中,既可以保持细节信息,又可以去掉噪 阅读详情

相关推荐

从平滑到锐化:高斯滤波器在频域图像处理中的核心应用

本文深入探讨了高斯滤波器在频域图像处理中的核心应用,从平滑到锐化的双重特性。通过高斯低通滤波器(GLPF)和高斯高通滤波器(GHPF)的对比分析,揭示了截止频率D0的关键作用,并提供了实战代码和参数优化建议,适用于医学影像、自动驾驶和工业检测等多个领域。

weixin_34005090的博客 312

opencv-python——图像增强(高斯滤波,均值滤波,中值滤波,直方图均衡,伽马变换)

这种方法的好处是可以有效地避免滤波结果中的边界伪影,但会在图像边缘引入额外的噪声。通过将伽马值设定在低于1的值,可以提升图像中灰度值较低的像素的亮度值,这可以提高图像的整体对比度,从而增强图像的可视性。伽马变换是一种实用的图像处理方法,它可以用于提高图像质量、调整图像的亮度和色彩平衡等方面,增强图像的可视性和实用性,在图像处理和计算机视觉等领域具有广泛的应用价值。在图像编码中的应用:伽马变换在图像编码中被广泛使用,它可以有效的消除图像数据中的大量冗余信息,并在保持图像质量的情况下缩小图像的数据规模。

qq_51687050的博客 9496

OpenCV高斯滤波器详解及代码实现

高斯滤波(Gauss Filter)是线性滤波中的一种。在OpenCV图像滤波处理中,高斯滤波用于平滑图像,或者说是图像模糊处理,因此高斯滤波是低通的。其广泛的应用在图像处理的减噪过程中,尤其是被高斯噪声所污染的图像上。 高斯滤波的基本思想是: 图像上的每一个像素点的值,都由其本身和邻域内其他像素点的值经过加权平均后得到。其具体操作是,用一个核(又称为卷积核、掩模、矩阵)扫描图像中每一个像素点,将

青城山小和尚 4万+

为什么高斯滤波器被广泛用于图像处理?

从事图像处理行业的同学们大概都会发现,高斯滤波是日常工作中使用最为广泛的滤波器之一,而且高斯滤波器能够很好的保持图像边缘,这对我们处理图像来说非常重要,这些都有高斯滤波器的特性有着密切的关系。......

qq_33187050的博客 1371

Halcon图像处理实战:高斯滤波(gauss_filter)在工业检测中的噪声抑制应用

本文深入探讨了Halcon中高斯滤波(gauss_filter)在工业视觉检测中的核心应用。通过分析高斯噪声特性,结合金属零件表面划痕检测的实战案例,详细解析了滤波器尺寸(Size)参数在噪声抑制与细节保留之间的关键平衡,并提供了预处理流程组合与常见避坑指南,帮助工程师有效提升检测稳定性与准确性。

weixin_29081703的博客 232

高斯滤波器在图像处理中的应用

在图像处理中,我们使用离散化的高斯函数来近似连续的高斯函数。通过应用高斯滤波器,我们可以降低图像中的噪声,模糊图像并减少图像中的细节。在本文中,我们将探讨高斯函数在图像处理中的应用,并提供相应的源代码供参考。该函数首先计算了高斯核的边界,然后使用两个嵌套的循环来计算高斯核中每个元素的值。通过使用高斯滤波器,我们可以模糊图像并减少图像中的细节,从而减少图像中的噪声。在示例代码的最后部分,我们读取了一个输入图像,并对其进行高斯平滑处理。函数,它接受图像、核大小和标准差作为输入,并返回平滑后的图像。

FfydCommon_Lisp的博客 399

高斯滤波在图像处理的基本应用

Tips:暂时还在学习matlab图像处理,写此小博客梳理相关知识。才疏学浅,还请读者见谅。 图像处理中的高斯滤波,通常来说是通过两步: 1、建立一个高斯模板T       在建立高斯模板时,通过高斯函数(也称正态分布函数)建立相应大小的模板(也即矩阵)。模板矩阵的特点是中心点的值最大,周围的值根据高斯函数(正态分布函数)         来确定。       个人感觉有时

Tomonkey的专栏 2758

掌握高斯滤波技术:图像处理与优化

高斯核是图像处理中实现高斯滤波的基础,其在高斯滤波中扮演着权重分配的关键角色。通过高斯核对邻域像素进行加权平均,可以实现对图像的平滑处理。OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。它提供了大量的图像处理、计算机视觉、以及机器学习方面的功能,广泛应用于学术研究和工业应用中。OpenCV的库是用C/C++编写的,同时提供了Python、Java等多种语言的接口。

weixin_33750664的博客 477

高斯滤波详解 附python和matlab高斯滤波代码

https://blog.youkuaiyun.com/angelia_yue/article/details/105794344 https://www.jianshu.com/p/4eaf349de9e9 一. 高斯滤波 高斯滤波是一种线性平滑滤波器,对于服从正态分布的噪声有很好的抑制作用。在实际场景中,我们通常会假定图像包含的噪声为高斯白噪声,所以在许多实际应用的预处理部分,都会采用高斯滤波抑制噪声。 高斯滤波和均值滤波一样,都是利用一个掩膜和图像进行卷积求解。不同之处在于...

1万+

SpringBoot实战——仿b站项目(一)

热部署就是当应用程序这种运行的时候升级冉家或者修改某一部分代码、配置文件时,无需手动启动应用,即可修改的部分生效工具 + IDEA配置。

weixin_52880572的博客 3456

wsl2内核版本和模块版本不一致解决方法

** /lib/modules/…build: No such file or directory错误解决办法这篇文章历时1个月整,从4月5日一直到5月5日,这一问题才得以解决。

m0_60634555的博客 5104

【图像增强】基于高斯滤波、一阶、二阶巴特沃斯滤波实现图像增强附Matlab代码

图像增强是指利用图像处理技术改善图像的视觉效果或提高图像的质量,从而更便于人眼观察或机器分析。在图像处理中,图像增强技术扮演着至关重要的角色,它可以有效地改善图像的对比度、清晰度、边缘细节等方面,从而提升图像的视觉效果,并为后续的图像分析、识别等应用提供更可靠的基础。本文将介绍三种常用的图像增强方法,分别为 高斯滤波、一阶巴特沃斯滤波 和 二阶巴特沃斯滤波,帮助读者理解其原理和应用。

Matlab_dashi的博客 944

ESP32 IDF开发 应用篇⑲ 空中升级OTA

在实际产品开发过程中,在线升级可以远程解决产品软件开发引入的问题,更好地满足用户需求。OTA(空中)更新是使用 Wi-Fi 连接而不是串行端口将固件加载到 ESP 模块的过程。(1)、ESP32 的 OTA 升级有三种方式:·Arduino IDE:主要用于软件开发阶段,实现不接线固件烧写·Web Browser:通过 Web 浏览器手动提供应用程序更新模块·HTTP Server:自动使用http服务器 - 针对产品应用在三种升级情况下,必须通过串行端口完成第一个固件上传。

lu330274924的博客 3326

高斯滤波器

高斯滤波器

GodNotAMen的博客 888

TPL5010硬件看门狗电路

在做一些采集频率很低的产品的时候,会考虑电池供电,比如表、电气表等,为了控制功耗,往往会在设备不需要工作的时候让它进入低功耗模式。为了设备的稳定性,避免死机后无法恢复,必须配置看门狗,一般的看门狗芯片,喂狗间隔时间是几秒钟。这样在喂狗和低功耗之间就产生了一个矛盾,频繁的喂狗就需要频繁的醒过来,这样功耗就很难做到很低。这颗看门狗芯片,特别适合上面的应用。2小时的间隔意味着主控芯片每2小时醒过来喂狗一次即可,而在2.5V供电的情况下,功耗仅仅35nA,几乎可以忽略不计。

zxm8513优快云博客 3309

Matlab:imageDatastore() 高效读取图像数据集的实战技巧

本文详细介绍了Matlab中imageDatastore()函数的高效使用技巧,帮助用户从繁琐的手动读取图像数据集中解放出来。通过核心语法、关键参数(如IncludeSubfolders、LabelSource、ReadSize)的解析,以及数据集分割、打乱等进阶实战操作,指导读者构建稳定、高效的图像数据读取管道,特别适用于大型图像数据集处理和机器学习项目。

hill8的博客 1101

OpenCV高斯滤波GaussianBlur

图像处理中,常用的滤波算法有均值滤波、中值滤波以及高斯滤波等。 三种滤波器的对比 滤波器种类 基本原理 特点 均值滤波 使用模板内所有像素的平均值代替模板中心像素灰度值 易收到噪声的干扰,不能完全消除噪声,只能相对减弱噪声 中值滤波 计算模板内所有像素中的中值,并用所计算出来的中值体改模板中心像素的灰度值 对噪声不是那么敏感,能够较好的消除椒盐噪声,...

godadream的博客 10万+

OpenCV C++ 图像处理实战 ——《基于轮廓比对的缺陷检测》

本文使用OpenCV C++ 实现基于轮廓比对的缺陷检测。

Zero___Chen的博客 1150

双边滤波与高斯滤波

目录 双边滤波器工作原理

qq_42261630的博客 1万+

以 Joule 驱动的生成式开发:把需求文档直接变成可运行的 SAP 应用

摘要:SAP BTP的AI助手Joule正在改变开发方式,支持从自然语言描述自动生成数据模型、服务、UI及测试代码。在SAP Business Application Studio中,Joule能根据业务需求生成结构化代码,并提供代码补全、重构、解释等功能。开发者需注意避免上传敏感数据,并对AI生成的代码进行人工审核。Joule特别针对ABAP开发进行了优化,提供符合SAP规范的代码建议,帮助迁移遗留系统。此外,它还能快速生成CAP项目原型,但所有AI生成内容都需经过严格测试验证。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 343

IGCT 测试原理概述

IGCT(Integrated Gate-Commutated Thyristor,集成门极换流晶闸管)是一种高功率半导体器件,基于门极可关断晶闸管(GTO)的原理,但通过集成门极驱动单元和优化结构(如硬驱动技术),实现了更低的导通损耗、更快的开关速度和更高的可靠性。它适用于中高压大功率应用,如高压直流输电(HVDC)、中压变频器和模块化多电平变换器(MMC)。IGCT 的结构为四层 p-n-p-n 器件,门极电流控制导通和关断,阳极电流在关断时快速换流至门极,通常在 1 μs 内完成。

专注于前沿技术的探索与实践,热衷于从代码逻辑、架构设计到行业应用等多维度深挖技术本质。期待在技术之路上与你共同成长~ 238

图像处理之高斯滤波

高斯函数广泛应用于统计学领域,用于表述正态分布,在信号处理领域,用于定义高斯滤波器,在图像处理领域,二维高斯核函数常用于高斯模糊Gaussian Blur,在数学领域,主要是用于解决热力方程和扩散方程,以及定义Weiertrass Transform。二维高斯函数在计算机视觉领域用处广泛,利用0均值的二维高斯函数,可以生成高斯卷积核,用于图像处理中的高斯滤波,实现高斯模糊,可以去除噪声。

weixin_42491648的博客 2952

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

c语言一维高斯滤波器,高斯滤波简介,高斯滤波性质及应用

1、高斯滤波简介了解高斯滤波之前,我们首先熟悉一下高斯噪声。高斯噪声是指它的概率密度函数服从高斯分布(即正态分布)的一类噪声。如果一个噪声,它的幅度分布服从高斯分布,而它的功率谱密度又是均匀分布的,则称它为高斯白噪声。高斯白噪声的二阶矩不相关,一阶矩为常数,是指先后信号在时间上的相关性,高斯白噪声包括热噪声和散粒噪声。高斯滤波器是一类根据高斯函数的形状来选择权值的线性平滑滤波器。高斯平滑滤波器对于...

weixin_42541463的博客 4904

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

上一篇: FLANN库介绍及其应用
下一篇: .h文件和.hpp文件的区别描述
LiBin1103
博客等级 码龄9年 38粉丝 68原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值