Python图像处理【13】使用PIL执行图像降噪

0. 前言

在本节中,我们将介绍一些空域滤波器,以及如何使用 Pillow 库函数实现这些滤波器。我们将介绍诸如平均 (mean) 和加权平均 (weighted mean) 之类的线性滤波器,在后续的学习中我们会介绍诸如 maxmin 滤波器之类的非线性滤波器。通过在图像上滑动应用卷积核窗口,每个输出像素是输入图像中对应输入像素的邻域像素的(线性或非线性)函数。

1. 均值滤波器

1.1 均值滤波器原理

移动均值或方形滤波器是所有滤波器中最简单的,它用以像素为中心的正方形中的像素值的平均值替换像素中心值。我们可以使用以下公式定义通用线性滤波器,方形滤波器是线性滤波器的一种特殊情况:

( 2 m + 1 ) × ( 2 m + 1 ) (2m+1)\times(2m+1) (2m+1)×(2m+1) 线性滤波器:
g i j = ∑ k = − m m ∑ l = − m m w k l ⋅ f i + k , j + l g_{ij}=\sum ^m _{k=-m} \sum ^m _{l=-m} wkl \cdot f_{i+k,j+l} gij=k=mml=mmwklfi+k,j+l
m = 1 m=1 m=1 时表示 3 × 3 3\times 3 3×3 滤波器,在方形滤波器中 w k l = 1 ( 2 m + 1 ) 2 w_{kl}=\frac 1 {(2m+1)^2} wkl=(2m+1)21。将以上滤波器展开,可得:
g i j = w − 1 , − 1 f i − 1 , j − 1 + w − 1 , 0 f i − 1 , j + w − 1 , 1 f i − 1 , j + 1 + w 0 , − 1 f i , j − 1 + w 0 , 0 f i , j + w 0 , 1 f i , j + 1 + w 1 , − 1 f i + 1 , j − 1 + w 1 , 0 f i + 1 , j + w 1 , 1 f i + 1 , j + 1 g_{ij}=w_{-1,-1}f_{i-1,j-1}+w_{-1,0}f_{i-1,j}+w_{-1,1}f_{i-1,j+1}+w_{0,-1}f_{i,j-1}+w_{0,0}f_{i,j}+w_{0,1}f_{i,j+1}+w_{1,-1}f_{i+1,j-1}+w_{1,0}f_{i+1,j}+w_{1,1}f_{i+1,j+1} gij=w1,1fi1,j1+w1,0fi1,j+w1,1fi1,j+1+w0,1fi,j1+w0,0fi,j+w0,1fi,j+1+w1,1fi+1,j1+w1,0fi+1,j+w1,1fi+1,j+1

在本节中,我们将使用 PILfilter() 函数将方形模糊滤波器应用于带有噪声的输入 RGB 图像,并观察不同噪声水平的平滑度。

1.2 使用均值滤波器去除椒盐噪声

(1) 首先代码导入所需库:

import numpy as np
import matplotlib.pylab as plt
from PIL import Image, ImageFilter
from copy import deepcopy

(2) 定义函数 plot_image() 使用 matlplotlib.pyplot 模块的 imshow() 函数显示图像如下:

def plot_image(image, title=None, sz=10):
    plt.imshow(image)
    plt.title(title, size=sz)
    plt.axis('off')

(3) 定义函数 add_noise() 在图像中添加椒盐(脉冲)噪声。除了输入图像外,该函数还可以接受三个参数,其中 prop_noise 参数用于定义输入像素的噪声比例,参数 saltpepper 用于控制图像应用盐噪声(白像素)还是椒噪声(黑像素),令函数更加通用,默认情况下,同时使用两种类型的噪声像素:

def add_noise(im, prop_noise, salt=True, pepper=True):
    im = deepcopy(im)
    n = int(im.width * im.height * prop_noise)
    x, y = np.random.randint(0, im.width, n), np.random.randint(0, im.height, n)
    for (x,y) in zip(x,y):
        im.putpixel((x, y),         # generate salt-and-pepper noise
        ((0,0,0) if np.random.rand() < 0.5 else (255,255,255)) if salt and pepper \
        else (255,255,255) if salt \
        else (0, 0, 0)) # if pepper
    return im

(4) 读取输入图像。对于不同的噪声级别/比例,在输入图像中添加脉冲噪声,然后使用 image.filter() 方法应用模糊滤波器以平滑带有噪声的图像,绘制每种情况下获得的输出图像。

如前所述,模糊滤波器通过将每个像素设置为方形核窗口中像素的平均值来模糊图像,从而在每个方向上延伸半径像素。支持任意大小的浮点类型半径,默认使用 3x3` 方形模糊滤波器。

orig = Image.open('1.png')
i = 1
plt.figure(figsize=(12,35))

for prop_noise in np.linspace(0.05,0.3,6):
    # choose random locations inside image
    im = add_noise(orig, prop_noise)
    plt.subplot(6,2,i), plot_image(im, 'Original Image with ' + str(int(100*prop_noise)) + '% added noise')
    im1 = im.filter(ImageFilter.BLUR)
    plt.subplot(6,2,i+1), plot_image(im1, 'Blurred Image')
    i += 2

plt.show()

均值滤波器
从以上图像可以看出,噪声比例越少,降噪和平滑效果越好(模糊程度越低)。

2. 高斯滤波器

2.1 高斯滤波器原理

尽管移动平均滤波器 (moving average filter) 的计算非常方便快捷,但它具有以下两个缺点,因此可能导致滤波图像中产生伪影:

  • 它并不是各向同性的(即圆形对称的),沿着对角线比沿着行和列更平滑
  • 权重间的变化非常突兀,而不是逐渐衰减至零,这会使平滑图像中的产生不连续性

为了克服这些缺点,可以计算近似于圆形而不是正方形邻域的平均值。为了实现此目的,我们通常可以使用高斯滤波器实现,高斯过滤器是唯一可分解的滤波器,可以将高斯滤波器分解表示为两个 1D 高斯滤波器的乘积,并且至少是近似圆形对称。从核中心到核边缘的权重会逐渐过渡衰减为零。以下表达式展示了高斯滤波器核,并给出了两个高斯核示例,核尺寸大小分别为 3x35x5

高斯滤波器核权重:
w k l = 1 2 π σ 2 e x p { − ( k 2 + l 2 ) 2 σ 2 } w_{kl}=\frac 1 {2\pi \sigma ^2}exp\{\frac {-(k^2+l^2)} {2\sigma ^2}\} wkl=2πσ21exp{ 2σ2(k2+l2)}

3 × 3 3\times3 3×3 高斯滤波器如下:
1 16 [ 1 2 1 2 4 2 1 2 1 ] \frac 1 {16} \left[ \begin{array}{ccc} 1 & 2 & 1\\ 2 & 4 & 2\\ 1 & 2 & 1\\ \end{array} \right] 161 121242121

5 × 5 5\times5 5×5 高斯滤波器如下:
1 256 [ 1 4 6 4 1 4 16 24 16 4 6 24 36 24 6 4 16 24 16 4 1 4 6 4 1 ] \frac 1 {256} \left[ \begin{array}{ccc} 1 & 4 & 6 & 4 & 1\\ 4 & 16 & 24 & 16 & 4\\ 6 & 24 & 36 & 24 & 6\\ 4 & 16 & 24 & 16 & 4\\ 1 & 4 & 6 & 4 & 1\\ \end{array} \right] 2561 1464141624164624362464162416414641

2.2 使用高斯模糊滤波器去除椒盐噪声

(1) 首先读取输入 RGB 彩色图像,并添加固定比例 (20%) 的脉冲噪声:

import numpy as np
import matplotlib.pylab as plt
from PIL import Image, ImageFilter
from copy import deepcopy

def plot_image(image, title=None, sz=10):
    plt.imshow(image)
    plt.title(title, size=sz)
    plt.axis('off')

def add_noise(im, prop_noise, salt=True, pepper=True):
    im = deepcopy(im)
    n = int(im.width * im.height * prop_noise)
    x, y = np.random.randint(0, im.width, n), np.random.randint(0, im.height, n)
    for (x,y) in zip(x,y):
        im.putpixel((x, y),         # generate salt-and-pepper noise
        ((0,0,0) if np.random.rand() < 0.5 else (255,255,255)) if salt and pepper \
        else (255,255,255) if salt \
        else (0, 0, 0)) # if pepper
    return im

im = Image.open('1.png')
im = add_noise(im, prop_noise = 0.2)

(2) 使用 PIL 中的 image.filter() 方法,使用 ImageFilter.GaussianBlur 实例化具有不同的半径参数值(从 1 开始到 3 )的高斯滤波器,并绘制输出图像:

plt.figure(figsize=(20,15))
i = 1
for radius in np.linspace(1, 3, 12):
    im1 = im.filter(ImageFilter.GaussianBlur(radius))
    plt.subplot(3,4,i)
    plot_image(im1, 'radius = ' + str(round(radius,2)))
    i += 1
plt.suptitle('PIL Gaussian Blur with different Radius', size=13)
plt.show()

高斯模糊

上图显示了代码的执行结果。可以看出,在使用高斯滤波器执行去噪任务时,半径越大,图像就会变得更加平滑,可以消除更多的噪声,但同时会使图像逐渐模糊,丢失细节。

3. 中值滤波器

3.1 中值滤波器原理

邻域平均可以抑制孤立的异常值噪声,但相应的副作用是它也会模糊图像中较为突然变化,例如线条特征、锐利的边缘和其他图像细节,这些图像细节均与高频相对应。非线性中值滤波器在保持图像中有用细节方面通常比线性均值滤波器更加有效,并且更适合去除图像中的椒盐噪声。
与平均滤波器一样,中值滤波器依次考虑图像中的每个像素,并查看其窗口邻域中的像素值来决定该像素能否表示周围邻域。它不是简单地用相邻像素值的平均值替换像素,而是用这些值的中位数代替。首先将周围邻域中的所有像素值按数字顺序排序,然后用排序后的序列中间值替换像素值。

3.2 使用中值滤波器去除椒盐噪声

首先,导入所需 Python 库,读取输入 RGB 彩色图像,并在输入图像中添加 1% 随机脉冲噪声:

import numpy as np
import matplotlib.pylab as plt
from PIL import Image, ImageFilter
from copy import deepcopy

def plot_image(image, title=None, sz=10):
    plt.imshow(image)
    plt.title(title, size=sz)
    plt.axis('off')

def add_noise(im, prop_noise, salt=True, pepper=True):
    im = deepcopy(im)
    n = int(im.width * im.height * prop_noise)
    x, y = np.random.randint(0, im.width, n), np.random.randint(0, im.height, n)
    for (x,y) in zip(x,y):
        im.putpixel((x, y),         # generate salt-and-pepper noise
        ((0,0,0) if np.random.rand() < 0.5 else (255,255,255)) if salt and pepper \
        else (255,255,255) if salt \
        else (0, 0, 0)) # if pepper
    return im

im = Image.open('1.png')
im = add_noise(im, prop_noise = 0.1)

使用 PILimage.filter() 函数指定参数 ImageFilter.MedianFilter 创建多个不同核窗口大小的中值滤波器,绘制使用不同尺寸大小的滤波器得到的输出图像。

如前所述,中值滤波器为从给定大小的滑动窗口中选取中值像素值:

plt.figure(figsize=(20,10))
plt.subplot(1,4,1)
plot_image(im, 'Input noisy image')
i = 2
for sz in [3,7,11]:
    im1 = im.filter(ImageFilter.MedianFilter(size=sz))
    plt.subplot(1,4,i), plot_image(im1, 'Output (Filter size=' + str(sz) + ')', 10)
    i += 1
plt.tight_layout()
plt.show()

中值滤波器
从以上输出结果可以看出,与平均滤波器相比,中值滤波器产生的模糊性较小,但是对于较大尺寸的中值滤波器核,它会在输出图像中导致较多纹理损失。

小结

噪声是干扰图像正常分析和处理的一个重要因素,一幅图像在实际应用中可能存在各种各样的噪声,噪声可能在拍摄中产生,也可能在传输过程中产生。在本节中,我们学习了几种常见的线性滤波器包括均值模糊和高斯模糊,以及非线性滤波器中值滤波器,并将这些滤波器用于图像降噪,从而提高图像质量,便于后续进行处理与分析。

系列链接

Python图像处理【1】图像与视频处理基础
Python图像处理【2】探索Python图像处理库
Python图像处理【3】Python图像处理库应用
Python图像处理【4】图像线性变换
Python图像处理【5】图像扭曲/逆扭曲
Python图像处理【6】通过哈希查找重复和类似的图像
Python图像处理【7】采样、卷积与离散傅里叶变换
Python图像处理【8】使用低通滤波器模糊图像
Python图像处理【9】使用高通滤波器执行边缘检测
Python图像处理【10】基于离散余弦变换的图像压缩
Python图像处理【11】利用反卷积执行图像去模糊
Python图像处理【12】基于小波变换执行图像去噪

猜你喜欢

转载自blog.csdn.net/qq_30167691/article/details/128450352