传统图像去噪方法(二)之引导滤波

各向同性滤波

我们知道对于滤波器而言,在均值滤波中W时是1/n,n是w中所有像素个数。在高斯平滑中,W服从二维的高斯分布。
在这里插入图片描述
但是无论是均值滤波还是高斯滤波,他们都有一个共同的弱点,它们都属于各向同性滤波

噪声的特点是以其为中心的各个方向上梯度都较大并且相差不多。图像边缘在相对于区域也会出现梯度的越变,但是边缘只有在其法向方向才会出现较大的梯度,在切向方向梯度较小。

因此才会出现各向同性滤波对待噪声和边缘信息采取一致的态度,从而导致噪声被磨皮的同时,图像中边缘,纹理和细节也被抹去了。

引导滤波(guided filter)原理

引导滤波(导向滤波)是一种图像滤波技术,通过一张引导图,对初始图象(输入图像)进行滤波处理,使得最后的输出图像大体上与初始图象相似,但是,纹理部分与引导图相似。其典型应用有两个:保边图像平滑,抠图

引导滤波不仅能实现双边滤波的边缘平滑,而且在检测到边缘附件有很好的表现。可应用在图像增强,HDR压缩,图像抠图以及图像去霾等场景。

算法原理:
滤波器的数学公式如下:
在这里插入图片描述
其中I是引导图像(guided Image),P是输入的带滤波图像,Q是滤波后的输出图像,W是根据引导图像I来确定加权平均运算中所采取的权值。一般我们选择原彩色图的灰度图作为引导图 I 或者对灰度图进行一些保留边缘的滤波操作再作为引导图。
在这里插入图片描述
μk是窗口内像素点的均值,Ii和Ij指相邻两个像素点的值,σk代表窗口内像素点的方差,ε是一个惩罚值。自适应权重可以根据上式分析得到:Ii和Ij在边界两侧时,(Ii-μk)和(Ij-μk)异号,否则,则同号。而异号时的权重值将远远小于同号时的权重值,这样处于平坦区域的像素则会被加以较大的权重,平滑效果效果更明显,而处于边界两侧的像素则会被加以较小的权重,平滑效果较弱,能够起到保持边界的效果。
惩罚值ε对滤波效果影响也很大,当ε值很小时,滤波如前面所述;当ε值很大时,权重的计算公式将近似为一个均值滤波器,平滑效果会更明显。

导向滤波器示意图如下:
传统图像去噪方法(二)之引导滤波_第1张图片
由于我们无法知道每一个具体的ni是多少,所以我们尝试通过引导图计算出想要的图。也就是说我们通过引导图I和滤波输出q的线性引导关系才能知道哪些是边缘,哪些是区域。其实就是再求梯度运算时保持了一致:▽q=a▽I
所以也可以从线性滤波的公式角度来看(推导可参考暗通道先验之Guided Filter 导向滤波):
传统图像去噪方法(二)之引导滤波_第2张图片
当I=p时公式可以简化为:
传统图像去噪方法(二)之引导滤波_第3张图片

  1. 如果ϵ=0,显然a=1, b=0是E(a,b)为最小值的解,从上式可以看出,这时的滤波器没有任何作用,将输入原封不动的输出。
  2. 如果ϵ>0,在像素强度变化小的区域(方差不大),即图像I在窗口wk中基本保持固定,此时有σ2k<<ϵ,于是有ak≈0和bk≈μk,即做了一个加权均值滤波,而在高方差区域,即表示图像I在窗口wk中变化比较大,此时我们有σ2k>>ϵ,于是有ak≈1和bk≈0,对图像的滤波效果很弱,有助于保持边缘。
  3. 在窗口大小不变的情况下,随着ϵ的增大,滤波效果越明显。

此外,在计算每个窗口的线性系数时,我们可以发现一个像素会被多个窗口包含,也就是说,每个像素都由多个线性函数所描述。因此,如之前所说,要具体求某一点的输出值时,只需将所有包含该点的线性函数值平均即可(局部线性模型)如下
在这里插入图片描述

导向滤波Guided Filter简单实现

cv2.ximgproc.guidedFilter()可以实现这个功能:

cv2.guidedFilter(guide, src, dst, radius, eps[, dDepth]) 
guide: nparray
      guided image (or array of images) with up to 3 channels,
      if it have more then 3 channels then only first 3 channels will be used.
src: nparray
      filtering image with any numbers of channels.
radius: int
      radius of Guided Filter.
eps: float
      regularization term of Guided Filter.
      {eps}^2 is similar to the sigma in the color space into bilateralFilter().
dDepth: int
      optional depth of the output image. 一般取-1

完整代码以及解释:

import argparse  # 导包
import cv2
import matplotlib.pyplot as plt
import skimage
import numpy as np

ap=argparse.ArgumentParser()  # 创建ArgumentParser对象
ap.add_argument("-i","--image",required=True,help="path to the image")  # 通过add_argument()告ArgumentParser如何将命令行中的参数转化成所需要的对象
# 第三个参数什么意思呢?意思你在命令输入python load_display_save.py后面必须加上“-i”或者”–image” 
# 第四个参数是帮助参数,就是解释你这个python load_display_save.py -i后面跟的参数是什么,可以看懂在这后面应该跟图片的路径

args=vars(ap.parse_args())
# 这里为什么要是用vars()呢?是为了能够像字典一样访问 ap.parse_args()的值,即现args[“image”]=ap.parse_args() 

img=cv2.imread(args["image"])  # cv2.imread()接口读图像,读进来直接是BGR 格式数据格式在 0~255,通道格式为(W,H,C)
#img=img[:,:,::-1]
guide=cv2.cvtColor(img,cv2.COLOR_RGB2GRAY) #用灰度图作为引导

dst1=cv2.ximgproc.guidedFilter(guide=guide,src=img,radius=8,eps=50,dDepth=-1)
dst2=cv2.ximgproc.guidedFilter(guide=guide,src=img,radius=8,eps=200,dDepth=-1)
dst3=cv2.ximgproc.guidedFilter(guide=guide,src=img,radius=8,eps=500,dDepth=-1)

if __name__=='__main__':
    cv2.imshow("Img",img)  #在窗口显示图像
    cv2.imshow("dst1",dst1)
    cv2.imshow("dst2",dst2)
    cv2.imshow("dst3",dst3)
    cv2.waitKey(0)  #显示图像必备
    cv2.destroyALLWindows()  #释放窗口 

传统图像去噪方法(二)之引导滤波_第4张图片

伪代码实现导向滤波

下面给出导向滤波算法的流程:
传统图像去噪方法(二)之引导滤波_第5张图片
解释一下:

  1. 这个伪代码使用的时matlab风格。
  2. 全部的fmean指的时cv2.boxfilter(),并且是归一化之后的,也就是均值滤波。
  3. 最好将图像形式改成np.float64以方便之后的加减乘除运算,最后再转换回np.unit8。
  4. 实际这个算法只支持I和P的通道相同的情况,对于输出一张灰度图作为一张彩色图的guide暂时无法成功,不过cv2.ximgproc.guidedFilter()是肯定可以的。
import argparse
import cv2
import matplotlib.pyplot as plt
import numpy as np

def guidedFilter(I,P,Q,r,eps):
# 转换原图像信息,将输入扩展维64位浮点数
    print(I.dtype)
    I=I.astype(np.float64)
    P=P.astype(np.float64)
    assert(I.shape==P.shape)  #输入的I和P的形状一定相同

# 各种均值计算
    meanI=cv2.boxFilter(I,ksize=(2*r+1,2*r+1),ddepth=-1,normalize=True)
    meanP=cv2.boxFilter(P,ksize=(2*r+1,2*r+1),ddepth=-1,normalize=True)
    corrI=cv2.boxFilter(I*I,ksize=(2*r+1,2*r+1),ddepth=-1,normalize=True)
    corrIP=cv2.boxFilter(I*P,ksize=(2*r+1,2*r+1),ddepth=-1,normalize=True)

#计算相关系数,计算IP的协方差cov和I的方差var
    varI=corrI-meanI*meanI
    covIP=corrIP-meanI*meanP

#计算系数参数a,b
    a=covIP/(varI+eps)
    b=meanP-a*meanI

#计算系数a,b的均值
    meanA=cv2.boxFilter(a,ksize=(2*r+1,2*r+1),ddepth=-1,normalize=True)
    meanB=cv2.boxFilter(b,ksize=(2*r+1,2*r+1),ddepth=-1,normalize=True)

#生成输出矩阵
    q=meanA*I+meanB

#转换图片信息,使之便会unit8矩阵
    q=q.astype(Q.dtype)  # Q.dtype=unit8  我这样写是因为我的np.unit8会报错,所以换了一种方式
    return q

ap=argparse.ArgumentParser()  # 创建ArgumentParser对象
ap.add_argument("-i","--image",required=True,help="path to the image")  # 通过add_argument()告诉ArgumentParser如何将命令行中的参数转化成所需要的对象
# 第三个参数什么意思呢?意思你在命令输入python load_display_save.py后面必须加上“-i”或者”–image” 
# 第四个参数是帮助参数,就是解释你这个python load_display_save.py -i后面跟的参数是什么,可以看懂在这后面应该跟图片的路径

args=vars(ap.parse_args())
# 这里为什么要是用vars()呢?是为了能够像字典一样访问 ap.parse_args()的值,即现在 args[“image”]=ap.parse_args() 

img=cv2.imread(args["image"])  # cv2.imread()接口读图像,读进来直接是BGR 格式数据格式在 0~255,通道格式为(W,H,C)
#img=img[:,:,::-1]
guide=cv2.cvtColor(img,cv2.COLOR_RGB2GRAY) #用灰度图作为引导

dst1=cv2.ximgproc.guidedFilter(guide=guide,src=img,radius=8,eps=50,dDepth=-1)
dst4=guidedFilter(P=img,I=img,Q=img,r=8,eps=50)
dst2=cv2.ximgproc.guidedFilter(guide=guide,src=img,radius=8,eps=200,dDepth=-1)
dst5=guidedFilter(P=img,I=img,Q=img,r=8,eps=200)
dst3=cv2.ximgproc.guidedFilter(guide=guide,src=img,radius=8,eps=500,dDepth=-1)
dst6=guidedFilter(P=img,I=img,Q=img,r=8,eps=500)

if __name__=='__main__':
    cv2.imshow("Salt",img)  #在窗口显示图像
    cv2.imshow("cv.eps50",dst1)
    cv2.imshow("eps50",dst4)
    cv2.imshow("cv.eps200",dst2)
    cv2.imshow("eps200",dst5)
    cv2.imshow("cv.eps500",dst3)
    cv2.imshow("eps500",dst6)
    cv2.waitKey(0)  #显示图像必备
    cv2.destroyALLWindows()  #释放窗口 

参考博客:
OpemCV-Python教程(16)
一文道尽传统去噪方法

你可能感兴趣的:(图像去噪)