OpenCV—Python 导向滤波

一、导向滤波原理

导向滤波是使用导向图像作为滤波内容图像,在导向图像上实现局部线性函数表达,实现各种不同的线性变换,输出变形之后的导向滤波图像。根据需要,导向图像可以跟输入图像不同或者一致。
《OpenCV—Python 导向滤波》

公式及推导

假设 I I I 是导向图像、 p p p 是输入图像、 q q q 是导向滤波输出图像;导向滤波是作为局部线性模型描述 导向图像 I I I输出图像 q q q 之间的关系。

对于任意像素 I \rm I I 来说, ω k \omega_k ωk窗口下的线性变换可以表述如下:
q i = a k I i + b k q_i = a_k I_i + b_k qi=akIi+bk 其中 ( a k , b k ) (a_k,b_k) (ak,bk)是窗口 ω k \omega_k ωk 范围内的参数常量。
为了寻找线性相关性,窗口 ω k \omega_k ωk 定义的损失函数为:
E ( a k , b k ) = ∑ i ∈ ω k ( ( a k I i + b k − p i ) 2 + ϵ a k 2 ) E(a_k,b_k) = \sum_{i \in \omega_k} ((a_k I_i + b_k-p_i)^2+\epsilon a_k^2) E(ak,bk)=iωk((akIi+bkpi)2+ϵak2)
其中:
ω k \omega_k ωk是对 a k a_k ak 值过大时侯的正则化补偿。
ε a k 2 \varepsilon a_k^2 εak2 是抑制 a k a_k ak 值过大的。
ε \varepsilon ε 是调整图的模糊程度与边缘检测精度的参数。
如果导向图 I \rm I I 没有边缘信息,输出均值模糊结果;
如果导向图 I \rm I I 包含边缘信息,边缘信息则迁移到输出图像中实现边缘保留滤波;
上述的损失函数可以被看成一个线性回归问题,其中两个参数的求解如下:

  • a k = 1 ω k ∑ i ∈ ω k I i P i − μ k p ˉ k σ k 2 + ϵ a_k = \frac{\frac{1}{\omega_k}\sum_{i \in \omega_k} I_iP_i-\mu_k \bar{p}_k}{\sigma_k^2 + \epsilon} ak=σk2+ϵωk1iωkIiPiμkpˉk

  • b k = p ˉ k − a k μ k b_k = \bar{p}_k – a_k\mu_k bk=pˉkakμk

  • μ k \mu_k μk σ k 2 \sigma_k^2 σk2 是导向图在 ω k \omega_k ωk 窗口大小均值与方差

  • ∣ ω ∣ |\omega| ω表示窗口内像素总数

  • p ˉ k = 1 ∣ ω ∣ ∑ i ∈ ω k p i \bar{p}_k = \frac{1}{|\omega|}\sum_{i \in \omega_k}p_i pˉk=ω1iωkpi ω k \omega_k ωk 窗口内输入图像像素均值

使用线性相关参数 ( a k , b k ) (a_k,b_k) (ak,bk),滤波输出图像就可以通过 q i = a k I i + b k q_i = a_k I_i + b_k qi=akIi+bk 线性模型得到。
针对不同的窗口大小我们就会得到不同的 q i q_i qi 值,所以通过它的均值作为最终的输出结果
q i = 1 ω k ∑ i ∈ ω k ( a k I i + b k ) = a ˉ i I i + b ˉ i q_i = \frac{1}{\omega_k}\sum_{i \in \omega_k}(a_k I_i + b_k) = \bar{a}_iI_i+\bar{b}_i qi=ωk1iωk(akIi+bk)=aˉiIi+bˉi

最终导向滤波公式为:
a ˉ , b ˉ i \bar{a}_,\bar{b}_i aˉbˉi 是所有像素点 i i i 上重叠窗口相关因子的均值。

导向滤波算法实现的一般步骤为:

  1. 读取导向图像 I \rm I I 与 输入图像 P \rm P P
  2. 积分图计算 I \rm I I 的均值与方差、输入图像 P \rm P P的均值、 I \rm I I P \rm P P的乘积 I P \rm IP IP
  3. 计算线性相关因子 a \rm a a b \rm b b
    a = ( I P − I m e a n P m e a n ) / ( I V a r + ϵ ) a=(IP-I_{mean}P_{mean})/(I_{Var} +\epsilon ) a=(IPImeanPmean)/(IVar+ϵ)
    b = P m e a n − a I m e a n b=P_{mean}-\textrm{a}I_{mean} b=PmeanaImean
  4. 计算a与b的均值
  5. 使用均值得到导向滤波结果 Q = a m e a n ∗ I + b m e a n Q = a_{mean}*I+b_{mean} Q=ameanI+bmean

《OpenCV—Python 导向滤波》

导向滤波最常用四个功能是:
  • 边缘保留滤波
  • 图像去噪声
  • 图像边缘羽化
  • 图像增强(对比度)
import numpy as np
import cv2

def guideFilter(I, p, winSize, eps):

    mean_I = cv2.blur(I, winSize)      # I的均值平滑
    mean_p = cv2.blur(p, winSize)      # p的均值平滑

    mean_II = cv2.blur(I * I, winSize) # I*I的均值平滑
    mean_Ip = cv2.blur(I * p, winSize) # I*p的均值平滑

    var_I = mean_II - mean_I * mean_I  # 方差
    cov_Ip = mean_Ip - mean_I * mean_p # 协方差

    a = cov_Ip / (var_I + eps)         # 相关因子a
    b = mean_p - a * mean_I            # 相关因子b

    mean_a = cv2.blur(a, winSize)      # 对a进行均值平滑
    mean_b = cv2.blur(b, winSize)      # 对b进行均值平滑

    q = mean_a * I + mean_b
    return q


if __name__ == '__main__':
    eps = 0.01
    winSize = (5,5)
    image = cv2.imread(r'./5921.png', cv2.IMREAD_ANYCOLOR)
    image = cv2.resize(image, None,fx=0.7, fy=0.7, interpolation=cv2.INTER_CUBIC)
    I = image/255.0        #将图像归一化
    p =I
    guideFilter_img = guideFilter(I, p, winSize, eps)

    # 保存导向滤波结果
    guideFilter_img  = guideFilter_img  * 255
    guideFilter_img [guideFilter_img  > 255] = 255
    guideFilter_img  = np.round(guideFilter_img )
    guideFilter_img  = guideFilter_img.astype(np.uint8)
    cv2.imshow("image",image)
    cv2.imshow("winSize_5", guideFilter_img )
    cv2.waitKey(0)
    cv2.destroyAllWindows()

I = p I=p I=p 时,导向滤波就变成了边缘保持的滤波操作,此时原来求出的 a a a b b b 的表达式就变成了:
a k = σ k 2 σ k 2 + ϵ a_k=\frac{σ_k^2}{σ_k^2+ϵ} ak=σk2+ϵσk2

b k = ( 1 − a k ) μ k b_k=(1−a_k)μ_k bk=(1ak)μk
考虑两种情况:

  • 情况1:高方差区域,即表示图像 I I I 在窗口 w k w_k wk 中变化比较大,此时我们有 σ k 2 > > ϵ σ_k^2>>ϵ σk2>>ϵ,于是有 a k ≈ 1 a_k≈1 ak1 b k ≈ 0 b_k≈0 bk0
  • 情况2:平滑区域(方差不大),即图像 I I I 在窗口 w k w_k wk 中基本保持固定,此时有 σ k 2 &lt; &lt; ϵ σ_k^2&lt;&lt;ϵ σk2<<ϵ,于是有 a k ≈ 0 a_k≈0 ak0 b k ≈ μ k b_k≈μ_k bkμk

也就是说在方差比较大的区域,保持值不变,在平滑区域,使用临近像素平均(也就退化为普通均值滤波)。

《OpenCV—Python 导向滤波》

快速导向滤波

通过下采样减少像素点,计算 m e a n a \rm mean_a meana & m e a n b \rm mean_b meanb 后进行上采样恢复到原有的尺寸大小。
假设缩放比例为s,那么缩小后像素点的个数为 N / s 2 , N/s^2, N/s2那么时间复杂度变为 O ( N / s 2 ) O(N/s^2) O(N/s2)
《OpenCV—Python 导向滤波》

import cv2
import numpy as np

def guideFilter(I, p, winSize, eps, s):
    # 输入图像的高、宽
    h, w = I.shape[:2]

    # 缩小图像
    size = (int(round(w * s)), int(round(h * s)))
    small_I = cv2.resize(I, size, interpolation=cv2.INTER_CUBIC)
    small_p = cv2.resize(I, size, interpolation=cv2.INTER_CUBIC)

    # 缩小滑动窗口
    X = winSize[0]
    small_winSize = (int(round(X * s)), int(round(X * s)))

    # I的均值平滑 p的均值平滑
    mean_small_I = cv2.blur(small_I, small_winSize)
    mean_small_p = cv2.blur(small_p, small_winSize)

    # I*I和I*p的均值平滑
    mean_small_II = cv2.blur(small_I * small_I, small_winSize)
    mean_small_Ip = cv2.blur(small_I * small_p, small_winSize)

    # 方差、协方差
    var_small_I = mean_small_II - mean_small_I * mean_small_I
    cov_small_Ip = mean_small_Ip - mean_small_I * mean_small_p

    small_a = cov_small_Ip / (var_small_I + eps)
    small_b = mean_small_p - small_a * mean_small_I

    # 对a、b进行均值平滑
    mean_small_a = cv2.blur(small_a, small_winSize)
    mean_small_b = cv2.blur(small_b, small_winSize)

    # 放大
    size1 = (w, h)
    mean_a = cv2.resize(mean_small_a, size1, interpolation=cv2.INTER_LINEAR)
    mean_b = cv2.resize(mean_small_b, size1, interpolation=cv2.INTER_LINEAR)

    q = mean_a * I + mean_b

    return q
    
if __name__ == '__main__':
    eps = 0.01
    winSize = (16,16)       #类似卷积核(数字越大,磨皮效果越好)
    image = cv2.imread(r'./5921.png', cv2.IMREAD_ANYCOLOR)
    image = cv2.resize(image,None,fx=0.8,fy=0.8,interpolation=cv2.INTER_CUBIC)
    I = image/255.0       #将图像归一化
    p =I
    s = 3 #步长
    guideFilter_img = guideFilter(I, p, winSize, eps,s)

    # 保存导向滤波结果
    guideFilter_img = guideFilter_img  * 255         #(0,1)->(0,255)
    guideFilter_img[guideFilter_img  > 255] = 255    #防止像素溢出
    guideFilter_img = np.round(guideFilter_img )
    guideFilter_img = guideFilter_img.astype(np.uint8)
    cv2.imshow("image",image)
    cv2.imshow("winSize_16", guideFilter_img )
    cv2.waitKey(0)
    cv2.destroyAllWindows()

输出效果图如上:winSize_16

为了写这篇博客,查了许久的资料,可气的是,那些都是只写了一半的代码,如今代码运行通了,与诸位共勉。

鸣谢
https://blog.csdn.net/qq_40755643/article/details/83831071

特别鸣谢
http://blog.sina.com.cn/s/blog_734f70550102wof3.html
https://blog.csdn.net/baimafujinji/article/details/74750283

    原文作者:SongpingWang
    原文地址: https://blog.csdn.net/wsp_1138886114/article/details/84228939
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞