2019-01-08 14:54:01 hacker_long 阅读数 4116

文章首发于微信公众号《有三AI》

【技术综述】一文道尽传统图像降噪方法

图像预处理算法的好坏直接关系到后续图像处理的效果,如图像分割、目标识别、边缘提取等,为了获取高质量的数字图像,很多时候都需要对图像进行降噪处理,尽可能的保持原始信息完整性(即主要特征)的同时,又能够去除信号中无用的信息。

并且,降噪还引出了一个非常热门的研究方向,即美颜磨皮,这对于中国用户来说,是非常重要的计算机视觉领域,今天就来认真讲讲传统的图像降噪算法。

 

01 图像降噪算法分类

虽然各种图像降噪算法犹如雨后春笋般不断新增,然而很多方法都存在一个通用的缺点,就是在降噪的同时往往会丢失图像的细节或边缘信息。

一般的图像处理,微小的细节对图像降噪的后续处理程序影响不太明显,但是当处理对象为医学图像时,这样的小失误是不被允许的,因为在医疗诊断或治疗中,每一个微小的失误都会影响医师的治疗方法甚至威胁到患者的生命。这就要求更多的研究者来投入时间和精力研究新的降噪技术,以达到降噪并同时仍能保留足够细节信息的目的。

目前常用的图像去噪算法大体上可非为两类,即空域像素特征去噪算法和变换域去噪算法。前者是直接地在图像空间中进行的处理,后者是间接地在图像变换域中进行处理。

1.1 空域像素特征去噪算法 

首先说明一点就是在信号处理教科书中,虽然介绍过很多经典的图像去噪方法,但主要都是针对随机噪声的,对于sensor缺陷导致的一些脉冲噪声(impulse noise)这里我们不考虑。

那么什么是随机噪声呢?相比于图像的真实信号来说随机噪声就是一种或高或低呈现出不确定变化的一种信号,如下图所示虚线代表真实信号,红蓝线表示的就是随机噪声信号,所有的随机噪声信号求和后结果为0。

由于这个零和特点,目前几乎所有的空域降噪算法都是基于这个理论为出发点来进行降噪处理的。

基于空域像素特征的方法,是通过分析在一定大小的窗口内,中心像素与其他相邻像素之间在灰度空间的直接联系,来获取新的中心像素值的方法,因此往往都会存在一个典型的输入参数,即滤波半径r。此滤波半径可能被用于在该局部窗口内计算像素的相似性,也可能是一些高斯或拉普拉斯算子的计算窗口。在邻域滤波方法里面,最具有代表性的滤波方法有以下几种:

(1) 算术均值滤波与高斯滤波

算术均值滤波用像素邻域的平均灰度来代替像素值,适用于脉冲噪声,因为脉冲噪声的灰度级一般与周围像素的灰度级不相关,而且亮度高出其他像素许多。

  • 均值滤波结果A'(i,j)随着L(滤波半径)取值的增大而变得越来越模糊,图像对比度越来越小。经过均值处理之后,噪声部分被弱化到周围像素点上,所得到的结果是噪声幅度减小,但是噪声点的颗粒面积同时变大,所以污染面积反而增大。为了解决这个问题,可以通过设定阈值,比较噪声和邻域像素灰度,只有当差值超过一定阈值时,才被认为是噪声。不过阈值的设置需要考虑图像的总体特性和噪声特性,进行统计分析。自适应均值滤波算法通过方向差分来寻找噪声像素,从而赋予噪声像素与非噪声像素不同的权重,并自适应地寻找最优窗口大小,优于一般的均值滤波方法。

  • 高斯滤波矩阵的权值,随着与中心像素点的距离增加,而呈现高斯衰减的变换特性。这样的好处在于,离算子中心很远的像素点的作用很小,从而能在一定程度上保持图像的边缘特征。通过调节高斯平滑参数,可以在图像特征过分模糊和欠平滑之间取得折中。与均值滤波一样,高斯平滑滤波的尺度因子越大,结果越平滑,但由于其权重考虑了与中心像素的距离,因此是更优的对邻域像素进行加权的滤波算法。

(2) 统计中值滤波

中值滤波首先确定一个滤波窗口及位置(通常含有奇数个像素),然后将窗口内的像素值按灰度大小进行排序,最后取其中位数代替原窗口中心的像素值(如下图)。

但当噪声像素个数大于窗口像素总数的一半时,由于灰度排序的中间值仍为噪声像素灰度值,因为滤波效果很差。此时如果增加窗口尺寸,会使得原边缘像素被其他区域像素代替的几率增加,图像更容易变模糊,并且运算量也大大增加。

无论是中值滤波还是加权滤波,两者受窗口的尺寸大小影响非常大。一种对中值滤波的改进是自适应中值滤波,它首先判断窗口内部的中心像素是否是一个脉冲,如果不是,则输出标准中值滤波的结果;如果是,则通过继续增大窗口滤波尺寸来寻找非脉冲的中值,因此该方法相比较原始的统计中值滤波器,在保持清晰度和细节方面更优。

(3) 双边滤波

这是一种非线性的保边滤波方法,是结合图像的空间邻近度和像素值相似度的一种折中处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。双边滤波器之所以可以达到保边去噪的效果,是因为滤波器是由两个函数构成。一个函数是由几何空间距离决定滤波器系数。另一个由像素差值决定滤波器系数。双边滤波器中,输出像素的值g(i,j)依赖于邻域像素的值的加权组合:

上图中权重系数w(i,j)取决于空域核和值域核的乘积。其中空域滤波器对空间上邻近的点进行加权平均,加权系数随着距离的增加而减少。值域滤波器则是对像素值相近的点进行加权平均,加权系数随着值差的增大而减少。

(4) 引导滤波(guided filter)

高斯滤波等线性滤波算法所用的核函数相对于待处理的图像是独立无关的,这里的独立无关也就意味着,对任意图像都是采用相同的操作。

引导滤波就是在滤波过程中加入引导图像中的信息,这里的引导图可以是单独的图像也可以是输入图像,当引导图为输入图像时,引导滤波就成为了一个可以保持边缘的去噪滤波操作。我们来看一下具体算法原理:

第一步:假设该引导滤波函数的输出与输入在一个二维窗口内满足线性关系,如下:

 

其中,q是输出像素的值,即p去除噪声或者纹理之后的图像,ni表示噪声,I是输入图像的值,i和k是像素索引,a和b是当窗口中心位于k时该线性函数的系数。(当引导图为输入图像时,引导滤波就成为一个保持边缘的滤波操作,即 I= p,对上示两边取梯度可得q'=aI',即当输入图I有梯度时,输出q也有类似的梯度,这也就可以解释为什么引导滤波有边缘保持特性了。

第二步求出线性函数的系数,也就是线性回归,即希望拟合函数的输出值q与真实值p之间的差距最小,转化为下面但最优化问题,也就是让下式最小:

 

在这里,μk和σk^2表示I在局部窗口wk中的均值和方差。 |ω|是窗口内的所有像素数,pk表示p在窗口wk中的均值,ϵ就是规整化参数,当I=p时,上面第二个公式即可简化为:

  • 如果ϵ=0,显然a=1, b=0是E(a,b)为最小值的解,从上式可以看出,这时的滤波器没有任何作用,将输入原封不动的输出。

  • 如果ϵ>0,在像素强度变化小的区域(方差不大),即图像I在窗口wk中基本保持固定,此时有σ2k<<ϵ,于是有ak≈0和bk≈μk,即做了一个加权均值滤波,而在高方差区域,即表示图像I在窗口wk中变化比较大,此时我们有σ2k>>ϵ,于是有ak≈1和bk≈0,对图像的滤波效果很弱,有助于保持边缘。

  • 在窗口大小不变的情况下,随着ϵ的增大,滤波效果越明显。

第三步:在计算每个窗口的线性系数时,我们可以发现一个像素会被多个窗口包含,也就是说,每个像素都由多个线性函数所描述。因此,如之前所说,要具体求某一点的输出值qi时,只需将所有包含该点的线性函数值平均即可,如下:

其中,输出值q又与两个均值有关,分别为a和b在窗口w中的均值,我们将上一步得到两个图像ak和bk都进行盒式滤波,得到两个新图:ai'和bi'。然后用ai'乘以引导图像Ii,再加上bi',即得最终滤波之后的输出图像q。

(5) NLM(Non-Local means)算法

前面基于邻域像素的滤波方法,基本上只考虑了有限窗口范围内的像素灰度值信息,没有考虑该窗口范围内像素的统计信息如方差,也没有考虑整个图像的像素分布特性,和噪声的先验知识。

针对其局限性,NLM算法被提出,该算法使用自然图像中普遍存在的冗余信息来去噪声。与常用的双线性滤波、中值滤波等利用图像局部信息来滤波不同的是,它利用了整幅图像来进行去噪,以图像块为单位在图像中寻找相似区域,再对这些区域求平均,能够比较好地去掉图像中存在的高斯噪声。这里我直接拿图来说可能会更能说明问题:

如上图所示,其中p为去噪的点,从图中看出q1和q2的邻域与p相似,所以权重和比较大,而q3因为与q邻域相差比较大所以赋予的权重值就很小。NLM就是将一幅图像中所有点的权重都表示出来,那就得到下面这些权重图:

上面权值图像中,左边是原图,中心的白色色块代表了像素  块邻域,右边是计算出来的权重图,权重范围从0(黑色)到1(白色)。

这个块邻域在整幅图像中移动,计算图像中其他区域跟这个块的相似度,相似度越高,得到的权重越大。最后将这些相似的像素值根据归一化之后的权重加权求和,得到的就是去噪之后的图像了。

由于原始NLM方法需要用图像中所有的像素来估计每一个像素的值,因此计算量非常大,研究者不断对该方法进行了几点改进。(a) 采用一定的搜索窗口代替所有的像素,使用相似度阈值,对于相似度低于某一阈值的像素,不加入到权重的计算(即不考虑其相对影响,这些都可以降低计算复杂度。(b)使用块之间的显著特征,如纹理特征等代替灰度值的欧氏距离来计算相似度,在计算上更加有优势,应用上也更加灵活。

除了上面所说的方法,还有如加权最小二乘法(WLS),变分法(TV)等滤波算法,并且上面的这些算法都产生出了非常多的变种,篇幅有限不再一一详述,可以参考文献【1】。

1.2 变换域去噪算法 

空域去噪都是从空间的角度去思考如何去噪,也就是所谓的spatial noise reduction,这条路子能想的方法也都做得差不多了,于是有人就换个角度想问题,就有了变换域做去噪的方法。通过数学变换,在变换域上把信号和噪声分离,然后把噪声过滤掉,剩下的就是信号。如下图没有噪声的信号就比较顺滑没有杂质。

而下图中含有噪声的信号就会显得参差不齐,毛刺较多。而如果我们可以将噪声变换一个域后设定一个阈值将高于阈值的部分去掉,再反变换后剩下的就是干净的信号了。

因此图像变换域去噪算法的基本思想其实就是首先进行某种变换,将图像从空间域转换到变换域,然后从频率上把噪声分为高中低频噪声,用这种变换域的方法就可以把不同频率的噪声分离,之后进行反变换将图像从变换域转换到原始空间域,最终达到去除图像噪声的目的。

图像从空间域转换到变换域的方法很多,其中最具代表性的有傅里叶变换、离散余弦变换、小波变换以及多尺度几何分析方法等。

其中基于小波萎缩法是目前研究最为广泛的方法,小波萎缩法又分成如下两类:第1类是阈值萎缩,由于阈值萎缩主要基于如下事实,即比较大的小波系数一般都是以实际信号为主,而比较小的系数则很大程度是噪声。因此可通过设定合适的阈值,首先将小于阈值的系数置零,而保留大于阈值的小波系数;然后经过阈值函数映射得到估计系数;最后对估计系数进行逆变换,就可以实现去噪和重建;而另外一种萎缩方法则不同,它是通过判断系数被噪声污染的程度,并为这种程度引入各种度量方法(例如概率和隶属度等),进而确定萎缩的比例,所以这种萎缩方法又被称为比例萎缩。

1.3 BM3D去噪算法 

空域中NLM算法和变换域中小波萎缩法效果都很好,一个很自然的想法就是是否可以将两者相结合呢?是的,BM3D就是融合了spatial denoise和tranform denoise,从而可以得到最高的峰值信噪比。它先吸取了NLM中的计算相似块的方法,然后又融合了小波变换域去噪的方法。我们来看一下具体算法流程如下图:

BM3D算法总共有两大步骤,分为基础估计(Step1)和最终估计(Step2)。在这两大步中,分别又有三小步:相似块分组,协同滤波和聚合。

Stpe1:基础估计

(1) 相似块分组:首先在噪声图像中选择一些大小的参照块(考虑到算法复杂度,不用每个像素点都选参照块,通常隔3个像素为一个步长进行选取,复杂度降到1/9),在参照块的周围适当大小区域内进行搜索,寻找若干个差异度最小的块,并把这些块整合成一个3维的矩阵。

(2) 协同滤波:形成若干个三维的矩阵之后,首先将每个三维矩阵中的二维的块(即噪声图中的某个块)进行二维变换,可采用小波变换或DCT变换等。二维变换结束后,在矩阵的第三个维度进行一维变换,变换完成后对三维矩阵进行硬阈值处理,将小于阈值的系数置0,然后通过在第三维的一维反变换和二维反变换得到处理后的图像块。

(3) 聚合:此时,每个二维块都是对去噪图像的估计。这一步分别将这些块融合到原来的位置,每个像素的灰度值通过每个对应位置的块的值加权平均,权重取决于置0的个数和噪声强度。

Step2:最终估计

具体的步骤从流程图可看出和Step1基本一样,不同的有两处:

一处是聚合过程将会得到两个三维数组:噪声图形成的三维矩阵和基础估计结果的三维矩阵。

另一处是协同滤波中用维纳滤波(Wiener Filtering)代替了硬阈值处理。

 

02 滤波器抑制噪声比较

对图像进行滤波去噪的算法其实就是一个加权平均的运算过程,滤波后图像中的每个像素点都是由其原图像中该点邻域内多个像素点值得加权平均,不同的滤波器最根本的差异就是权值不同。另外根据噪声的不同,滤波效果也各有不同。

  • 均值滤波处理会噪声部分被弱化到周围像素点上,所得到的结果是噪声幅度减小,但是噪声点的颗粒面积同时变大,所以污染面积反而增大。

  • 高斯滤波器用像素邻域的加权均值来代替该点的像素值,而每一邻域像素点权值是随该点与中心点的距离单调增减的.这一性质是很重要的,因为边缘是一种图像局部特征,如果平滑运算对离算子中心很远的像素点仍然有很大作用,则平滑运算会使图像失真,因此缺点是权重完全取决于图像像素之间欧氏距离,与图像的内容没有关系。

  • 中值滤波适用于椒盐噪声和脉冲噪声。因为对于受脉冲噪声和椒盐噪声污染的图像,相应位置的图像灰度发生了跳变,是不连续的,而此处的中值滤波正是一种非线性滤波方法,对这些类型的随机噪声,它比相同尺寸的线性平滑滤波器引起的模糊更少,能较好的保持边缘,但会使图像中的小目标丢失,因此对点、线和尖顶多的图像不宜采用中值滤波。

  • 双边滤波器的好处是可以做边缘保存,一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显。双边滤波顾名思义比高斯滤波多了一个高斯方差,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多的影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波因此,双边滤波器即平滑滤波了图像,又保持的图像边缘。虽然去噪效果很明显,但很多细节被去除,只有整体形状被保留,不过美颜相机磨皮恰恰就需要这种算法(如下图美女磨皮后的效果)。

  • 引导滤波不像高斯滤波等线性滤波算法所用的核函数相对于待处理的图像是独立无关的,而是在滤波过程中加入了引导图像中(去噪时用的就是图像本身)的信息,所以引导滤波本质上就是通过一张引导图I,对初始图像p(输入图像)进行滤波处理,使得最后的输出图像大体上与初始图像P相似,但是纹理部分与引导图I相似。在滤波效果上,引导滤波和双边滤波差不多,在一些细节上,引导滤波较好。

    引导滤波最大的优势在于能够保持线性复杂度,每个像素虽然由多个窗口包含,求某一点像素值的具体输出值时,只需将包含该点所有的线性函数值平均即可,而双边滤波不是线性复杂度在于他考虑了每个点的几何差距与强度差距两个因素,当处理图像较大时,运算量很明显会增大很多。

  • 非局部算法获得的信噪比比双边滤波略高,有时候还不如双边滤波。但是,非局部滤波是一种基于快的匹配度来计算滤波权值的,所以能获得比较好的视觉效果。然而,它的计算复杂度实在是太高了。最原始非局部均值算法是在整个图片中进行块搜索,根据块的匹配度来计算权值。实际执行过程,都会把搜索区域限定在一个局部的搜索窗口中。

  • BM3D算法是目前传统算法中效果最好的去噪算法,相比于NLM噪声更少,图像细节恢复更多,但算法复杂度实在太高,除非解决计算性能问题,不然至少工业界是无法容忍几分钟的处理时间进行降噪处理。

03 总结

图像去噪难点在于区别高频信号(如纹理、边缘)和噪声,去噪常用思想是利用图像的相似性。空域去噪是认为相近的点相似,通过平滑可以降低随机性的噪声,效果较好的去噪方法大多是多种方法结合,既能很好地保持边缘信息,又能去除图像中的噪声,比如将中值滤波和小波滤波结合起来进行滤波。

基本上传统的去噪算法都是从噪音图像中找出规律后再进行相对应的去噪处理。那么如果从有噪音的图片本身无法找到规律,我们是否也可以借助其他类似但又没有噪音的图片,来总结图片具有的固有属性呢?

深度学习方法是数据驱动的方法,在仿真图像去噪上,数据(也就是干净图像)是非常充足的,所以当前深度学习方法在高斯白噪声假设条件下的滤波问题中已经达到甚至超过BM3D算法。

深度学习的发展水平如何,就等待我们下一期文章吧。

[1] 龙鹏. MRI医学图像增强与分割新方法[D]. 中国科学院大学, 2015.

感谢各位看官的耐心阅读,不足之处希望多多指教。后续内容将会不定期奉上,欢迎大家关注有三公众号 有三AI

 

 

2019-12-23 15:48:36 weixin_39504171 阅读数 38

图像处理18:传统图像降噪算法综述

        图像预处理算法的好坏直接关系到后续图像处理的效果,如图像分割、目标识别、边缘提取等,为了获取高质量的数字图像,很多时候都需要对图像进行降噪处理,尽可能的保持原始信息完整性(即主要特征)的同时,又能够去除信号中无用的信息。

        并且,降噪还引出了一个非常热门的研究方向,即美颜磨皮,这对于中国用户来说,是非常重要的计算机视觉领域,今天就来认真讲讲传统的图像降噪算法。

1.图像降噪算法分类:

        虽然各种图像降噪算法犹如雨后春笋般不断新增,然而很多方法都存在一个通用的缺点,就是在降噪的同时往往会丢失图像的细节或边缘信息。

        一般的图像处理,微小的细节对图像降噪的后续处理程序影响不太明显,但是当处理对象为医学图像时,这样的小失误是不被允许的,因为在医疗诊断或治疗中,每一个微小的失误都会影响医师的治疗方法甚至威胁到患者的生命。这就要求更多的研究者来投入时间和精力研究新的降噪技术,以达到降噪并同时仍能保留足够细节信息的目的。

        目前常用的图像去噪算法大体上可非为两类,即空域像素特征去噪算法和变换域去噪算法。前者是直接地在图像空间中进行的处理,后者是间接地在图像变换域中进行处理。

        1.1 空域像素特征去噪算法 :

             首先说明一点就是在信号处理教科书中,虽然介绍过很多经典的图像去噪方法,但主要都是针对随机噪声的,对于sensor缺陷导致的一些脉冲噪声(impulse noise)这里我们不考虑。

             那么什么是随机噪声呢?相比于图像的真实信号来说随机噪声就是一种或高或低呈现出不确定变化的一种信号,如下图所示虚线代表真实信号,红蓝线表示的就是随机噪声信号,所有的随机噪声信号求和后结果为0。

             由于这个零和特点,目前几乎所有的空域降噪算法都是基于这个理论为出发点来进行降噪处理的。

             基于空域像素特征的方法,是通过分析在一定大小的窗口内,中心像素与其他相邻像素之间在灰度空间的直接联系,来获取新的中心像素值的方法,因此往往都会存在一个典型的输入参数,即滤波半径r。此滤波半径可能被用于在该局部窗口内计算像素的相似性,也可能是一些高斯或拉普拉斯算子的计算窗口。在邻域滤波方法里面,最具有代表性的滤波方法有以下几种:

                (1) 算术均值滤波与高斯滤波:

                     算术均值滤波用像素邻域的平均灰度来代替像素值,适用于脉冲噪声,因为脉冲噪声的灰度级一般与周围像素的灰度级不相关,而且亮度高出其他像素许多。

                     均值滤波结果A'(i,j)随着L(滤波半径)取值的增大而变得越来越模糊,图像对比度越来越小。经过均值处理之后,噪声部分被弱化到周围像素点上,所得到的结果是噪声幅度减小,但是噪声点的颗粒面积同时变大,所以污染面积反而增大。为了解决这个问题,可以通过设定阈值,比较噪声和邻域像素灰度,只有当差值超过一定阈值时,才被认为是噪声。不过阈值的设置需要考虑图像的总体特性和噪声特性,进行统计分析。自适应均值滤波算法通过方向差分来寻找噪声像素,从而赋予噪声像素与非噪声像素不同的权重,并自适应地寻找最优窗口大小,优于一般的均值滤波方法。

                     高斯滤波矩阵的权值,随着与中心像素点的距离增加,而呈现高斯衰减的变换特性。这样的好处在于,离算子中心很远的像素点的作用很小,从而能在一定程度上保持图像的边缘特征。通过调节高斯平滑参数,可以在图像特征过分模糊和欠平滑之间取得折中。与均值滤波一样,高斯平滑滤波的尺度因子越大,结果越平滑,但由于其权重考虑了与中心像素的距离,因此是更优的对邻域像素进行加权的滤波算法。

                (2) 统计中值滤波:

                     中值滤波首先确定一个滤波窗口及位置(通常含有奇数个像素),然后将窗口内的像素值按灰度大小进行排序,最后取其中位数代替原窗口中心的像素值(如下图)。

                     但当噪声像素个数大于窗口像素总数的一半时,由于灰度排序的中间值仍为噪声像素灰度值,因为滤波效果很差。此时如果增加窗口尺寸,会使得原边缘像素被其他区域像素代替的几率增加,图像更容易变模糊,并且运算量也大大增加。

                     无论是中值滤波还是加权滤波,两者受窗口的尺寸大小影响非常大。一种对中值滤波的改进是自适应中值滤波,它首先判断窗口内部的中心像素是否是一个脉冲,如果不是,则输出标准中值滤波的结果;如果是,则通过继续增大窗口滤波尺寸来寻找非脉冲的中值,因此该方法相比较原始的统计中值滤波器,在保持清晰度和细节方面更优。

                (3) 双边滤波:

                     这是一种非线性的保边滤波方法,是结合图像的空间邻近度和像素值相似度的一种折中处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。双边滤波器之所以可以达到保边去噪的效果,是因为滤波器是由两个函数构成。一个函数是由几何空间距离决定滤波器系数。另一个由像素差值决定滤波器系数。双边滤波器中,输出像素的值g(i,j)依赖于邻域像素的值的加权组合:

                     上图中权重系数w(i,j)取决于空域核和值域核的乘积。其中空域滤波器对空间上邻近的点进行加权平均,加权系数随着距离的增加而减少。值域滤波器则是对像素值相近的点进行加权平均,加权系数随着值差的增大而减少。

                (4) 引导滤波(guided filter):

                     高斯滤波等线性滤波算法所用的核函数相对于待处理的图像是独立无关的,这里的独立无关也就意味着,对任意图像都是采用相同的操作。

                     引导滤波就是在滤波过程中加入引导图像中的信息,这里的引导图可以是单独的图像也可以是输入图像,当引导图为输入图像时,引导滤波就成为了一个可以保持边缘的去噪滤波操作。我们来看一下具体算法原理:

                     第一步:假设该引导滤波函数的输出与输入在一个二维窗口内满足线性关系,如下:

                     其中,q是输出像素的值,即p去除噪声或者纹理之后的图像,ni表示噪声,I是输入图像的值,i和k是像素索引,a和b是当窗口中心位于k时该线性函数的系数。(当引导图为输入图像时,引导滤波就成为一个保持边缘的滤波操作,即 I= p,对上示两边取梯度可得q'=aI',即当输入图I有梯度时,输出q也有类似的梯度,这也就可以解释为什么引导滤波有边缘保持特性了。

                     第二步:求出线性函数的系数,也就是线性回归,即希望拟合函数的输出值q与真实值p之间的差距最小,转化为下面但最优化问题,也就是让下式最小:

                     在这里,μk和σk^2表示I在局部窗口wk中的均值和方差。 |ω|是窗口内的所有像素数,pk表示p在窗口wk中的均值,ϵ就是规整化参数,当I=p时,上面第二个公式即可简化为:

                     如果ϵ=0,显然a=1, b=0是E(a,b)为最小值的解,从上式可以看出,这时的滤波器没有任何作用,将输入原封不动的输出。

                     如果ϵ>0,在像素强度变化小的区域(方差不大),即图像I在窗口wk中基本保持固定,此时有σ2k<<ϵ,于是有ak≈0和bk≈μk,即做了一个加权均值滤波,而在高方差区域,即表示图像I在窗口wk中变化比较大,此时我们有σ2k>>ϵ,于是有ak≈1和bk≈0,对图像的滤波效果很弱,有助于保持边缘。

                     在窗口大小不变的情况下,随着ϵ的增大,滤波效果越明显。

                     第三步:在计算每个窗口的线性系数时,我们可以发现一个像素会被多个窗口包含,也就是说,每个像素都由多个线性函数所描述。因此,如之前所说,要具体求某一点的输出值qi时,只需将所有包含该点的线性函数值平均即可,如下:

                     其中,输出值q又与两个均值有关,分别为a和b在窗口w中的均值,我们将上一步得到两个图像ak和bk都进行盒式滤波,得到两个新图:ai'和bi'。然后用ai'乘以引导图像Ii,再加上bi',即得最终滤波之后的输出图像q。

                (5) NLM(Non-Local means)算法:

                      前面基于邻域像素的滤波方法,基本上只考虑了有限窗口范围内的像素灰度值信息,没有考虑该窗口范围内像素的统计信息如方差,也没有考虑整个图像的像素分布特性,和噪声的先验知识。

                      针对其局限性,NLM算法被提出,该算法使用自然图像中普遍存在的冗余信息来去噪声。与常用的双线性滤波、中值滤波等利用图像局部信息来滤波不同的是,它利用了整幅图像来进行去噪,以图像块为单位在图像中寻找相似区域,再对这些区域求平均,能够比较好地去掉图像中存在的高斯噪声。这里我直接拿图来说可能会更能说明问题:

                      如上图所示,其中p为去噪的点,从图中看出q1和q2的邻域与p相似,所以权重和比较大,而q3因为与q邻域相差比较大所以赋予的权重值就很小。NLM就是将一幅图像中所有点的权重都表示出来,那就得到下面这些权重图:

                      上面权值图像中,左边是原图,中心的白色色块代表了像素  块邻域,右边是计算出来的权重图,权重范围从0(黑色)到1(白色)。

                      这个块邻域在整幅图像中移动,计算图像中其他区域跟这个块的相似度,相似度越高,得到的权重越大。最后将这些相似的像素值根据归一化之后的权重加权求和,得到的就是去噪之后的图像了。

                      由于原始NLM方法需要用图像中所有的像素来估计每一个像素的值,因此计算量非常大,研究者不断对该方法进行了几点改进。

                        (a) 采用一定的搜索窗口代替所有的像素,使用相似度阈值,对于相似度低于某一阈值的像素,不加入到权重的计算(即不考虑其相对影响,这些都可以降低计算复杂度。

                        (b)使用块之间的显著特征,如纹理特征等代替灰度值的欧氏距离来计算相似度,在计算上更加有优势,应用上也更加灵活。

                      除了上面所说的方法,还有如加权最小二乘法(WLS),变分法(TV)等滤波算法,并且上面的这些算法都产生出了非常多的变种,篇幅有限不再一一详述,可以参考文献【1】。

        1.2 变换域去噪算法 :

             空域去噪都是从空间的角度去思考如何去噪,也就是所谓的spatial noise reduction,这条路子能想的方法也都做得差不多了,于是有人就换个角度想问题,就有了变换域做去噪的方法。通过数学变换,在变换域上把信号和噪声分离,然后把噪声过滤掉,剩下的就是信号。如下图没有噪声的信号就比较顺滑没有杂质。

             而下图中含有噪声的信号就会显得参差不齐,毛刺较多。而如果我们可以将噪声变换一个域后设定一个阈值将高于阈值的部分去掉,再反变换后剩下的就是干净的信号了。

             因此图像变换域去噪算法的基本思想其实就是首先进行某种变换,将图像从空间域转换到变换域,然后从频率上把噪声分为高中低频噪声,用这种变换域的方法就可以把不同频率的噪声分离,之后进行反变换将图像从变换域转换到原始空间域,最终达到去除图像噪声的目的。

             图像从空间域转换到变换域的方法很多,其中最具代表性的有傅里叶变换、离散余弦变换、小波变换以及多尺度几何分析方法等。

             其中基于小波萎缩法是目前研究最为广泛的方法,小波萎缩法又分成如下两类:第1类是阈值萎缩,由于阈值萎缩主要基于如下事实,即比较大的小波系数一般都是以实际信号为主,而比较小的系数则很大程度是噪声。因此可通过设定合适的阈值,首先将小于阈值的系数置零,而保留大于阈值的小波系数;然后经过阈值函数映射得到估计系数;最后对估计系数进行逆变换,就可以实现去噪和重建;而另外一种萎缩方法则不同,它是通过判断系数被噪声污染的程度,并为这种程度引入各种度量方法(例如概率和隶属度等),进而确定萎缩的比例,所以这种萎缩方法又被称为比例萎缩。

        1.3 BM3D去噪算法 :

                空域中NLM算法和变换域中小波萎缩法效果都很好,一个很自然的想法就是是否可以将两者相结合呢?是的,BM3D就是融合了spatial denoise和tranform denoise,从而可以得到最高的峰值信噪比。它先吸取了NLM中的计算相似块的方法,然后又融合了小波变换域去噪的方法。我们来看一下具体算法流程如下图:

                BM3D算法总共有两大步骤,分为基础估计(Step1)和最终估计(Step2)。在这两大步中,分别又有三小步:相似块分组,协同滤波和聚合。

                Step1:基础估计:

                        (1) 相似块分组:首先在噪声图像中选择一些大小的参照块(考虑到算法复杂度,不用每个像素点都选参照块,通常隔3个像素为一个步长进行选取,复杂度降到1/9),在参照块的周围适当大小区域内进行搜索,寻找若干个差异度最小的块,并把这些块整合成一个3维的矩阵。

                        (2) 协同滤波:形成若干个三维的矩阵之后,首先将每个三维矩阵中的二维的块(即噪声图中的某个块)进行二维变换,可采用小波变换或DCT变换等。二维变换结束后,在矩阵的第三个维度进行一维变换,变换完成后对三维矩阵进行硬阈值处理,将小于阈值的系数置0,然后通过在第三维的一维反变换和二维反变换得到处理后的图像块。

                        (3) 聚合:此时,每个二维块都是对去噪图像的估计。这一步分别将这些块融合到原来的位置,每个像素的灰度值通过每个对应位置的块的值加权平均,权重取决于置0的个数和噪声强度。

                Step2:最终估计:

                        具体的步骤从流程图可看出和Step1基本一样,不同的有两处:

                        一处是聚合过程将会得到两个三维数组:噪声图形成的三维矩阵和基础估计结果的三维矩阵。

                        另一处是协同滤波中用维纳滤波(Wiener Filtering)代替了硬阈值处理。

 

2.滤波器抑制噪声比较:

        对图像进行滤波去噪的算法其实就是一个加权平均的运算过程,滤波后图像中的每个像素点都是由其原图像中该点邻域内多个像素点值得加权平均,不同的滤波器最根本的差异就是权值不同。另外根据噪声的不同,滤波效果也各有不同。

        均值滤波处理会噪声部分被弱化到周围像素点上,所得到的结果是噪声幅度减小,但是噪声点的颗粒面积同时变大,所以污染面积反而增大。

        高斯滤波器用像素邻域的加权均值来代替该点的像素值,而每一邻域像素点权值是随该点与中心点的距离单调增减的.这一性质是很重要的,因为边缘是一种图像局部特征,如果平滑运算对离算子中心很远的像素点仍然有很大作用,则平滑运算会使图像失真,因此缺点是权重完全取决于图像像素之间欧氏距离,与图像的内容没有关系。

        中值滤波适用于椒盐噪声和脉冲噪声。因为对于受脉冲噪声和椒盐噪声污染的图像,相应位置的图像灰度发生了跳变,是不连续的,而此处的中值滤波正是一种非线性滤波方法,对这些类型的随机噪声,它比相同尺寸的线性平滑滤波器引起的模糊更少,能较好的保持边缘,但会使图像中的小目标丢失,因此对点、线和尖顶多的图像不宜采用中值滤波。

        双边滤波器的好处是可以做边缘保存,一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显。双边滤波顾名思义比高斯滤波多了一个高斯方差,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多的影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波因此,双边滤波器即平滑滤波了图像,又保持的图像边缘。虽然去噪效果很明显,但很多细节被去除,只有整体形状被保留,不过美颜相机磨皮恰恰就需要这种算法(如下图美女磨皮后的效果)。

        引导滤波不像高斯滤波等线性滤波算法所用的核函数相对于待处理的图像是独立无关的,而是在滤波过程中加入了引导图像中(去噪时用的就是图像本身)的信息,所以引导滤波本质上就是通过一张引导图I,对初始图像p(输入图像)进行滤波处理,使得最后的输出图像大体上与初始图像P相似,但是纹理部分与引导图I相似。在滤波效果上,引导滤波和双边滤波差不多,在一些细节上,引导滤波较好。

        引导滤波最大的优势在于能够保持线性复杂度,每个像素虽然由多个窗口包含,求某一点像素值的具体输出值时,只需将包含该点所有的线性函数值平均即可,而双边滤波不是线性复杂度在于他考虑了每个点的几何差距与强度差距两个因素,当处理图像较大时,运算量很明显会增大很多。

        非局部算法获得的信噪比比双边滤波略高,有时候还不如双边滤波。但是,非局部滤波是一种基于快的匹配度来计算滤波权值的,所以能获得比较好的视觉效果。然而,它的计算复杂度实在是太高了。最原始非局部均值算法是在整个图片中进行块搜索,根据块的匹配度来计算权值。实际执行过程,都会把搜索区域限定在一个局部的搜索窗口中。

        BM3D算法是目前传统算法中效果最好的去噪算法,相比于NLM噪声更少,图像细节恢复更多,但算法复杂度实在太高,除非解决计算性能问题,不然至少工业界是无法容忍几分钟的处理时间进行降噪处理。

3.总结:

        图像去噪难点在于区别高频信号(如纹理、边缘)和噪声,去噪常用思想是利用图像的相似性。空域去噪是认为相近的点相似,通过平滑可以降低随机性的噪声,效果较好的去噪方法大多是多种方法结合,既能很好地保持边缘信息,又能去除图像中的噪声,比如将中值滤波和小波滤波结合起来进行滤波。

        基本上传统的去噪算法都是从噪音图像中找出规律后再进行相对应的去噪处理。那么如果从有噪音的图片本身无法找到规律,我们是否也可以借助其他类似但又没有噪音的图片,来总结图片具有的固有属性呢?

        深度学习方法是数据驱动的方法,在仿真图像去噪上,数据(也就是干净图像)是非常充足的,所以当前深度学习方法在高斯白噪声假设条件下的滤波问题中已经达到甚至超过BM3D算法。

        深度学习的发展水平如何,就等待我们下一期文章吧。

[1] 龙鹏. MRI医学图像增强与分割新方法[D]. 中国科学院大学, 2015.


————————————————
版权声明:本文为CSDN博主「言有三」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hacker_long/article/details/85239638

2016-11-07 10:56:25 shenziheng1 阅读数 3861

1.前言:

图像去噪是信号处理的一个经典问题,传统的去噪方法多采用平均或线性法法进行,最常用到的就是维纳滤波,但是他的降噪效果并不是很明显。小波分析法开辟了非线性降噪的先河,小波能够降噪得益于小波变换的以下特点:低熵性(小波系数稀松分布,使图像变换后的熵降低)、多分辨率特性(极好的刻画了信号的非平稳性)、去相关性(噪声在变换后有白化趋势,小波域更有利于去噪)

目前,主流的小波去噪方法主要集中在三个方面:基于小波变换模极大值降噪、基于相邻尺度小波系数相关性去燥、基于小波变换域阈值去噪(硬阈值与软阈值、全局阈值与局部自适应阈值)。

2.小波图像去燥实现的步骤:

1.二维信号的小波分解。选择一个小波和小波分解的层次N,然后计算信号s到第N层的分解。

2.对高频系数进行阈值量化,对于从1~N的每一层,选择一个阈值,并对这一层的高频系数进行软阈值量化处理。

3.二维小波重构

3.小波系数阈值降噪

<span style="font-size:18px;">clear all;
load facets;
subplot(221);image(X);
colormap(map);
xlabel('(a)原始图像');
axis square
%产生含噪声图像
init=2055615866;randn('seed',init)
x=X+50*randn(size(X));
subplot(222);image(x);
colormap(map);
xlabel('(b)含噪声图像');
axis square
%下面进行图像的去噪处理
%用小波画数coif3对x进行2层小波分解
[c,s]=wavedec2(x,2,'coif3');
%提取小波分解中第一层的低频图像,即实现了低通滤波去噪
%设置尺度向量n
n=[1,2];
%设置阈值向量p
p=[10.12,23.28];
%对三个方向高频系数进行阈值处理
nc=wthcoef2('h',c,s,n,p,'s');
nc=wthcoef2('v',c,s,n,p,'s');
nc=wthcoef2('d',c,s,n,p,'s');
%对新的小波分解结构[nc,s]进行重构
x1=waverec2(nc,s,'coif3');
subplot(223);image(x1);
colormap(map);
xlabel('(c)第一次去噪后的图像');
axis square;
xx=wthcoef2('v',nc,s,n,p,'s');
x2=waverec2(xx,s,'coif2');%图像的二维小波重构
subplot(2,2,4);image(x2);
colormap(map);
xlabel('(d)第二次消噪后图解');
axis square;
</span>
降噪结果:


4.全局软阈值降噪

<span style="font-size:18px;"><span style="font-size:18px;">clear all;
load detfingr;
subplot(131);image(X);
colormap(map);
xlabel('(a)原始图像');
axis square;
init=255615866;
randn('state',init);  %添加随机值
x=X+20*randn(size(X));
subplot(132);image(x);
colormap(map);
xlabel('(b)含噪图像');
axis square;
[thr,sorh,kep]=ddencmp('den','wv',x);   %使用全局阈值选项进行图像消噪处理
xd=wdencmp('gbl',x,'sym5',2,thr,sorh,kep);
subplot(133);image(xd)
colormap(map);
xlabel('(c)消噪图像');
axis square;</span><span style="font-size:24px;">
</span></span>
降噪结果:


2016-08-02 18:16:05 balconychy 阅读数 961
图像降噪的本质是:从图像中去掉无关的信号。
假设噪音性质:平均值为零;则可以对像素点周围像素取平均值。

均值滤波:直接对周围像素取平均值;
注意到,不但对噪音信号进行平均,对要提取的图像信号也进行了平均,这导致了边缘的模糊。

高斯滤波
为了降低模糊的效果,采用高斯滤波;高斯滤波改变加权的权值,是得原始图像更突出。

中值滤波
高斯滤波对付幅度比较小的噪声比较有效,但对于峰值比较大的单点噪音效果不好。中值滤波取邻近像素的中值,可以想象对于平滑过渡的图像,中值滤波几乎在不影响原来平滑的画面的情况下可以很好的去掉极大和极小的噪点。但中值滤波对于尖角形状不友好,对其有圆滑的效果。

双边滤波
高斯滤波考虑了,距离的因素,距离越远,对当前像素的贡献越小。双边滤波在考虑空间距离的影响 的同时,在加权系数上同时考虑灰度或颜色距离的影响,灰度越接近,权重越高。因此,对梯度也就是边缘有很好的保留。

非局部均匀滤波
前面的考虑的都只是局部加权,通过寻找图像中相似的块来进行综合确定加权系数。

BM3D
BM3D也是一种非局部加权滤波,通过区块匹配(block mathing)的方式寻址类似区块,然后堆叠在一起形成3D,在对3D堆叠数据进行维纳斯滤波,最后对原始图像进行估计。实际的算法要复杂的多。


参考资料:
双边滤波器的原理及实现

BM3D
http://www.cs.tut.fi/~foi/GCF-BM3D/BM3D_TIP_2007.pdf



2014-11-17 21:11:19 u014687890 阅读数 797


Reference:

 

如何用C++读取bmp图像:

http://bbs.csdn.net/topics/340208546

bmp格式图像结构:

http://www.cnblogs.com/kingmoon/archive/2011/04/18/2020097.html


一、问题描述

给一幅二值图,在其中加入一定比例(这里默认是10%)的噪声,然后使用相应的降噪搜索算法,对噪声图像进行降噪处理,获得噪声图像和降噪后的图像,并对比原始图像和降噪后的图像,评估降噪的效果。

 

 

二、算法与模型描述

在进行搜索的时候,用到了如下评估模型:

其中,xi表示在搜索得到的当前状态,即当前处理的二值图,yi表示噪声图像,由于图像为二值图,我们设每个像素点的取值为 {-1,  1},这里的三个参数都是正数。

则图中第一项指,当前图像像素点值的综合与参数的积,至于这项的物理意义我不是很理解;第二项指,当前图像每个像素点与周围点(例如周围的八个点)的乘积和与参数的积,如果这一项越小说明,当前的像素点与周围的差异越小,也就越不可能是噪点;第三项指,当前图像与噪声图像对应点的乘积和与参数的积,因为噪声图像的噪点一般较少,所以我们搜索得到的图像要和噪声图像尽可能拟合,如果这一项的值越小,说明拟合效果越好。

        

         关于实现的算法:

         这里实现了两种降噪算法:

1.      遍历噪声图像的所有点,对于每个像素点计算周围八个点中,其值与当前像素点值不等的个数count,如果count > 4说明当前像素点为噪点,则改变像素值。

这个算法虽然简单,但是高效而且降噪效果很好,但是容易丢失图像细节。

 

2.      类似于“爬山法“的局部搜索算法,对于当前图像,随机选取一个像素点,根据前面提到的评估模型进行试算,判断改变这个像素点的值,会不会使得评估模型的值变小,如果是则改变它,否则进行下一次选取。对于随机选取策略,首先在全局范围内选取一个像素点进行试算,接下来的200次都在这个像素点周围随机选取另一个点进行试算,每进行200次就在全局范围内再选取一个点重复上面的操作。

这样也有点类似,在宽度优先搜索的基础上进行一定深度的深度优先搜索。

 

三、降噪效果

 

这里我定义了一个降噪结果的评估函数,具体就是比较一下降噪后的图像与原图像像素点匹配的比例,这样就可以初步判断降噪效果。

 

下面是测试样例:

简单遍历算法:

src0.bmp

src1.bmp

src2.bmp

src3.bmp

以上是使用第一种简单遍历算法降噪的效果,从左至右三幅图分别指,原图、降噪后的图、噪声图。

 

搜索算法:

src0.bmp

src1.bmp

src2.bmp

src3.bmp

以上是使用搜索算法降噪的效果,从左至右三幅图分别指,原图、降噪后的图、噪声图。

 

 

从上面的结果我们可以看到,虽然两种降噪算法的在与原图的匹配率上相差无几,但是视觉效果上还是简单遍历算法的降噪效果比较好!


四、实现代码:

程序只能处理bmp格式的24位真彩色图,其他格式图像的处理可以自己再进行设计,且只能在windows下运行。

bmp.h

#ifndef BMP_H
#define BMP_H 

#include <windows.h>

class BMP
{
public:
    BMP();
    ~BMP(); 
    bool readBmp(char* path);
    bool saveBmp(char* path);
    void addNoise();
    void noSearchRemove();
    void searchRemove();
    double grade(char* path);
    
private:
    //读入图像数据的指针
    unsigned char *pBmpBuf;
    //图像的宽
    int bmpWidth;
    //图像的高
    int bmpHeight;
    //颜色表指针
    RGBQUAD *pColorTable;
    int biBitCount;
    int lineByte;
};
#endif
bmp.cpp

#include "bmp.h"
#include <cmath>
#include <iomanip>
#include <fstream.h>
#include <iostream>
#include <stdlib.h>
#include <cstdio>
#include <ctime>
using namespace std;

BMP::BMP()
{
    pBmpBuf = NULL;
    bmpWidth = bmpHeight = lineByte = 0;
    pColorTable = NULL;
    biBitCount = 0;
}

BMP::~BMP()
{
    delete []pBmpBuf;
    delete []pColorTable;
}
// 读取 bmp格式图像 参数是图像路径 
bool BMP::readBmp(char* path)
{
    FILE *fp = fopen(path, "rb");
    if (!fp) return 0;
    fseek(fp, sizeof(BITMAPFILEHEADER), 0);
    BITMAPINFOHEADER header;  
    fread(&header, sizeof(BITMAPINFOHEADER), 1,fp);
    bmpWidth = header.biWidth;
    bmpHeight = header.biHeight;
    biBitCount = header.biBitCount;
    // 因为lineByte在wins中必须是4的倍数 所以这里为了满足这个要求做了一下处理  lineByte指一行像素的字节数 
    lineByte = (bmpWidth * biBitCount / 8 + 3) / 4 * 4; 
    //申请颜色表所需要的空间,读颜色表进内存
    if (biBitCount == 1)
    {
        delete []pColorTable;
        pColorTable = new RGBQUAD[1];
        fread(pColorTable, sizeof(RGBQUAD), 1, fp);
    }   
    else if (biBitCount == 4)
    {
        delete []pColorTable;
        pColorTable = new RGBQUAD[16];
        fread(pColorTable, sizeof(RGBQUAD), 16, fp); 
    }    
    else if(biBitCount == 8)
    {
        delete []pColorTable;
        pColorTable = new RGBQUAD[256];
        fread(pColorTable, sizeof(RGBQUAD), 256, fp);
    }
    //申请位图数据所需要的空间,读位图数据进内存
    delete []pBmpBuf;
    pBmpBuf = new unsigned char[lineByte * bmpHeight];
    fread(pBmpBuf, 1, lineByte * bmpHeight, fp);
    
    fclose(fp);
    return 1;
}
// 保存得到的bmp图像 默认保存路径是当前 目录下的 autoSave.bmp
bool BMP::saveBmp(char* path = "autoSave.bmp")
{
    if(!pBmpBuf)
        return 0;

    int colorTablesize=0;
    if(biBitCount == 1)
        colorTablesize = 4;
    else if (biBitCount == 4)
        colorTablesize = 64;
    else if (biBitCount == 8)
        colorTablesize = 1024;

    FILE *fp = fopen(path, "wb");
    if(!fp) return 0;

    BITMAPFILEHEADER fileHead;
    // bmp类型
    fileHead.bfType = 0x4D42;
    // bfSize是图像文件4个组成部分之和
    fileHead.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + colorTablesize + lineByte * bmpHeight;
    fileHead.bfReserved1 = 0;
    fileHead.bfReserved2 = 0;
    //bfOffBits是图像文件前3个部分所需空间之和
    fileHead.bfOffBits = 54 + colorTablesize;
    fwrite(&fileHead, sizeof(BITMAPFILEHEADER), 1, fp);
    
    //申请位图信息头结构变量,填写信息头信息
    BITMAPINFOHEADER header;
    header.biBitCount = biBitCount;
    header.biClrImportant = 0;
    header.biClrUsed = 0;
    header.biCompression = 0;
    header.biHeight = bmpHeight;
    header.biPlanes = 1;
    header.biSize = 40;
    header.biSizeImage = lineByte*bmpHeight;
    header.biWidth = bmpWidth;
    header.biXPelsPerMeter=0;
    header.biYPelsPerMeter=0;

    //写位图信息头进内存
    fwrite(&header, sizeof(BITMAPINFOHEADER), 1, fp);

    //如果灰度图像,有颜色表,写入文件 
    if (biBitCount == 1)
        fwrite(pColorTable, sizeof(RGBQUAD), 1, fp);
    else if (biBitCount == 4)
        fwrite(pColorTable, sizeof(RGBQUAD), 16, fp);
    else if (biBitCount == 8)
        fwrite(pColorTable, sizeof(RGBQUAD), 256, fp);

    fwrite(pBmpBuf, bmpHeight*lineByte, 1, fp);

    fclose(fp);
    return 1;
}
// 给图像添加 10%的噪声 
void BMP::addNoise()
{
    srand(time(NULL));
    int times = bmpHeight * bmpWidth / 10, x, y;
    for (int i = 0; i < times; ++ i)
    {
        x = rand() % bmpHeight;
        y = rand() % bmpWidth;
        if (*(pBmpBuf + x*lineByte + y*3) == 0)
        {
            for (int j = 0; j < 3; ++ j)
                *(pBmpBuf + x*lineByte + y*3 + j) = 255;
        }
        else
        {
            for (int j = 0; j < 3; ++ j)
                *(pBmpBuf + x*lineByte + y*3 + j) = 0;            
        }
    }
    return; 
}
// 简单遍历 
void BMP::noSearchRemove()
{
    int row[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, col[8] = {0, 1, 1, 1, 0, -1, -1, -1};
    int  n, tmpi, tmpj;
    for (int i = 0; i < bmpHeight; ++ i)
    {
        for (int j = 0; j < bmpWidth; ++ j)
        {
            n = 0;
            for (int k = 0; k < 8; ++ k)
            {
                tmpi = row[k] + i;
                tmpj = col[k] + j;
                if (tmpi >= 0 && tmpi < bmpHeight && tmpj >= 0 && tmpj < lineByte)  
                {
                    if (*(pBmpBuf + i*lineByte + j*3) == *(pBmpBuf + tmpi*lineByte + tmpj*3))
                    {
                        n ++;
                    }
                }
            }
            if (n < 4)
            {
                if (*(pBmpBuf + i*lineByte + j*3) == 0) 
                {
                    for (int l = 0; l < 3; ++ l)
                        *(pBmpBuf + i*lineByte + j*3 + l) = 255;
                }
                else 
                {
                    for (int l = 0; l < 3; ++ l)
                        *(pBmpBuf + i*lineByte + j*3 + l) = 0;                    
                }
            }
        }
    }
}
// 类爬山法搜索 
void BMP::searchRemove()
{
    int row[8] = {-1, -1, 0, 1, 1, 1, 0, -1}, col[8] = {0, 1, 1, 1, 0, -1, -1, -1}, add;
    // t为搜索次数 count为局部搜索次数 
    int t = bmpHeight * bmpWidth * 8, tmpi, tmpj, i = 0, j = 0, count = 200;
    // 评估模型的三个参数 
    double h = 0.5, B = 1.06, n = 2.15, tmp;
    unsigned char* cur = new unsigned char[lineByte * bmpHeight];
    for (int i = 0; i < lineByte * bmpHeight; ++ i)
        *(cur + i) = *(pBmpBuf + i);
    srand(time(NULL));
    while (t --) 
    {  
        // 随机选取像素点     
        if (count > 0)
        {
            add = rand() % 8;
            i += row[add];
            j += col[add];
            count --;
        }
        else 
        {
            count = 200;
            i = rand() % bmpHeight;
            j = rand() % bmpWidth;
        }
        tmp = 0;
        // 开始试算 
        if (i >= 0 && i < bmpHeight && j >= 0 && j < bmpWidth)
        {
            if (*(cur + i*lineByte + j*3) == *(pBmpBuf + i*lineByte + j*3))
                tmp -= 2*n;
            else 
                tmp += 2*n;
                
            if (*(cur + i*lineByte + j*3) == 0)
                tmp += 2*h;
            else
                tmp -= 2*h;
            for (int k = 0; k < 8; ++ k)
            {
                tmpi = i + row[k];
                tmpj = j + col[k];
                if (tmpi >= 0 && tmpi < bmpHeight && tmpj >= 0 && tmpj < bmpWidth)
                {
                    if (*(cur + i*lineByte + j*3) != *(cur + tmpi*lineByte + tmpj*3))
                        tmp += 4*B;
                    else 
                        tmp -= 4*B;
                }
            }
            // 试算结束 判断是否修改像素点 
            if (tmp > 0) 
            {
                if (*(cur + i*lineByte + j*3) == 0)
                {
                    for (int l = 0; l < 3; ++ l)
                        *(cur + i*lineByte + j*3 + l) = 255;
                }
                else
                {
                    for (int l = 0; l < 3; ++ l)
                        *(cur + i*lineByte + j*3 + l) = 0;                    
                }
            }
        }
    }
    // 保存降噪后的图 
    for (int i = 0; i < lineByte * bmpHeight; ++ i)
        *(pBmpBuf + i) = *(cur + i);  
    delete []cur; 
}
// 评分函数 
double BMP::grade(char* path)
{
    long long count = 0;
    unsigned char* cur = new unsigned char[lineByte * bmpHeight];
    for (int i = 0; i < lineByte * bmpHeight; ++ i)
        *(cur + i) = *(pBmpBuf + i);
    readBmp(path);
    for (int i = 0; i < bmpHeight; ++ i)
    {
        for (int j = 0; j < bmpWidth; ++ j)
        {
            if (*(cur + i*lineByte + j*3) == *(pBmpBuf + i*lineByte + j*3))
                count ++;
        }
    }
    delete []cur;
    return (double)count / (double)(bmpHeight * bmpWidth);
}
main.cpp

#include "bmp.cpp"

int main()
{
    BMP a;
    char s[100];      
    bool flag;   
    cout << "加噪后的图像自动保存在当前路径的 noise.bmp\n降噪后的图像自动保存在当前路径的 reduced.bmp\n";
    cout << "按 ctrl + c 退出程序\n";
    cout << "请选择降噪算法 1 为搜索算法 0 为简单遍历: ";
    cin >> flag;
    cout << endl << endl; 
    while (1)
    {
        cout << "请输入原图像路径: ";
        scanf("%s", s);
        a.readBmp(s);
        
        a.addNoise();
        a.saveBmp("noised.bmp");
        if (flag)
            for (int i = 0; i < 1; ++ i)
                a.searchRemove();
        else
            for (int i = 0; i < 2; ++ i)
                a.noSearchRemove();
                            
        a.saveBmp("reduced.bmp");
        
        double t = a.grade(s);
        cout << fixed << setprecision(4) << "降噪后的图与原图的匹配率为: " << t*100 << "%" << endl;
        cout << endl; 
    }
    
    system("pause");
    return 0;
}
没有更多推荐了,返回首页