基于小波变换执行图像去噪
- 0. 前言
- 1. 小波变换基础
- 2. 小波变换去噪原理
- 3. 使用 pywt 执行小波变换图像去噪
- 4. 使用 scikit-image 执行小波变换图像去噪
- 4.1 循环旋转技术
- 4.2 改进图像去噪质量
- 小结
- 系列链接
0. 前言
小波 (wavelets
) 变换是表示和分析多分辨率图像的通用方法,该方法在图像处理的许多不同领域中都有应用,例如图像压缩、去噪等。在本节中,我们将学习如何使用 pywt
和 scikit-image
实现的小波变换执行图像去噪。
1. 小波变换基础
我们首先介绍小波变换的基础知识。以下代码使用离散小波变换 (Discrete Wavelet Transformation
, DWT
) 转换输入灰度图像(具有不同级别),并提取近似图像和水平/垂直/对角线细节。
我们使用原始图像经过高通滤波生成三幅图像,每个图像都描述了原始图像中亮度(细节)的局部变化。然后,将其执行低通滤波和缩放,从而产生近似图像;该图像经过高通滤波生产三个较小尺寸的细节图新,并使用低通滤波生成最终近似图像:
import numpy as np
import pywt
from pywt._doc_utils import wavedec2_keys, draw_2d_wp_basis
from skimage.filters import threshold_otsu
from skimage import img_as_float
import matplotlib.pylab as plt
x = pywt.data.ascent().astype(np.float32)
shape = x.shape
plt.rcParams.update({'font.size': 8})
max_lev = 3 # how many levels of decomposition to draw
label_levels = 3 # how many levels to explicitly label on the plots
fig, axes = plt.subplots(4, 2, figsize=[15, 35])
plt.subplots_adjust(0, 0, 1, 0.95, 0.05, 0.05)
for level in range(0, max_lev + 1):
if level == 0:
# show the original image before decomposition
axes[0, 0].set_axis_off()
axes[0, 1].imshow(x, cmap=plt.cm.gray)
axes[0, 1].set_title('Image')
axes[0, 1].set_axis_off()
continue
# plot subband boundaries of a standard DWT basis
draw_2d_wp_basis(shape, wavedec2_keys(level), ax=axes[level, 0],
label_levels=label_levels)
axes[level, 0].set_title('{} level\ndecomposition'.format(level))
# compute the 2D DWT
c = pywt.wavedec2(x, 'db2', mode='periodization', level=level)
# normalize each coefficient array independently for better visibility
c[0] /= np.abs(c[0]).max()
for detail_level in range(level):
c[detail_level + 1] = [d/np.abs(d).max() > threshold_otsu(d/np.abs(d).max()) for d in c[detail_level + 1]]
# show the normalized coefficients
arr, slices = pywt.coeffs_to_array(c)
axes[level, 1].imshow(arr, cmap=plt.cm.gray)
axes[level, 1].set_title('Coefficients\n({} level)'.format(level))
axes[level, 1].set_axis_off()
plt.tight_layout()
plt.show()
2. 小波变换去噪原理
小波变换通常用于图像去噪,基于小波变换的图像去噪步骤如下:
- 选择一个小波类型(例如,双正交小波或
N
级分解小波),利用小波对图像执行离散小波变换 - 在图像分解后,确定每个级别的阈值 (
Birgé-Massart
策略是选择阈值的常见方法),使用此过程,可以为N
个级别设置单独的阈值 - 最后一步是使用逆离散小波变换从修改后的级别重建图像
需要注意的是,选择使用不同小波、级别和阈值策略可能会导致不同类型的滤波。
3. 使用 pywt 执行小波变换图像去噪
在本节中,我们将在输入 RGB
图像中添加高斯噪声,并使用小波的软阈值消除噪声。
(1) 首先,导入所需库,读取输入 RGB
图像,并用
σ
=
0.25
σ=0.25
σ=0.25 添加高斯噪声,得到带有噪声的图像:
import numpy as np
import pywt
from skimage import img_as_float
import matplotlib.pylab as plt
from skimage.io import imread
image = img_as_float(imread('3.png'))
noise_sigma = 0.25 #16.0
image += np.random.normal(0, noise_sigma, size=image.shape)
(2) 我们可以使用 pywt
中的函数 Wavelet()
应用多级 2D DWT
,该函数会应用小波变换并且级别 level=7
:
wavelet = pywt.Wavelet('haar')
levels = int(np.floor(np.log2(image.shape[0])))
print(levels)
wavelet_coeffs = pywt.wavedec2(image, wavelet, level=levels)
# 7
(3) 定义函数 denoise()
执行图像去噪操作,该函数接受给定类型的小波对象和(估计的)噪声标准差作为参数。然后,函数计算出不同级别的 DWT
系数,并使用估计的噪声应用软阈值来计算新的系数;最后,使用新系数重建图像,并得到的返回图像:
def denoise(image, wavelet, noise_sigma):
levels = int(np.floor(np.log2(image.shape[0])))
wc = pywt.wavedec2(image, wavelet, level=levels)
arr, coeff_slices = pywt.coeffs_to_array(wc)
arr = pywt.threshold(arr, noise_sigma, mode='soft')
nwc = pywt.array_to_coeffs(arr, coeff_slices, output_format='wavedec2')
return pywt.waverec2(nwc, wavelet)
(4) 使用不同类型的离散小波,并将它们应用于带有噪声的图像,并使用 denoise()
函数从有噪输入 RGB
图像的每个颜色通道中去除噪声:
print(pywt.wavelist(kind='discrete'))
wlts = ['bior1.5', 'coif5', 'db6', 'dmey', 'haar', 'rbio2.8', 'sym15'] # pywt.wavelist(kind='discrete')
Denoised={}
for wlt in wlts:
out = image.copy()
for i in range(3):
out[...,i] = denoise(image[...,i], wavelet=wlt, noise_sigma=3/2*noise_sigma)
Denoised[wlt] = np.clip(out, 0, 1)
print(len(Denoised))
(5) 最后,绘制用不同小波类型去噪的所有输出图像,并使用 PSNR
比较图像质量:
plt.figure(figsize=(15,8))
plt.subplots_adjust(0,0,1,0.9,0.05,0.07)
plt.subplot(241), plt.imshow(np.clip(image,0,1)), plt.axis('off'), plt.title('original image', size=8)
i = 2
for wlt in Denoised:
plt.subplot(2,4,i), plt.imshow(Denoised[wlt]), plt.axis('off'), plt.title(wlt, size=8)
i += 1
plt.suptitle('Image Denoising with Wavelets', size=12)
plt.show()
4. 使用 scikit-image 执行小波变换图像去噪
4.1 循环旋转技术
离散的小波变换不具备平移不变性,为了实现平移不变性可以使用非抽样小波变换 (undecimated wavelet transform
,也称平稳小波,stationary wavelet
),但需要增加冗余的成本,即比输入图像像素更多的小波系数。为了使用离散小波变换实现图像去噪并近似平移不变性,我们也可以使用另一种称为循环旋转 (cycle-spinning
) 的技术,这需要对多个空间移位结果求取均值:
- 将信号(循环)移动量
n
- 应用去噪
- 应用反向平移
4.2 改进图像去噪质量
在本节中,我们将看到,对于 2D
图像去噪,循环旋转技术可以令图像质量得到大幅提高,通过对每个轴上仅 n=0
和 n=1
的偏移求取均值,就可以获取大部分图像增益:
(1) 首先,导入所有必需的 Python
模块和函数:
from skimage.restoration import (denoise_wavelet, estimate_sigma)
from skimage import data, img_as_float
from skimage.util import random_noise
from skimage.metrics import peak_signal_noise_ratio
import numpy as np
from skimage import img_as_float
import matplotlib.pylab as plt
from skimage.io import imread
(2) 读取输入图像,并在图像中添加随机噪声:
original = img_as_float(imread('3.png'))[...,:3]
sigma = 0.12
noisy = random_noise(original, var=sigma**2)
(3) 估计跨颜色通道的平均噪声标准差:
sigma_est = estimate_sigma(noisy, multichannel=True, average_sigmas=True)
print(f"Estimated Gaussian noise standard deviation = {sigma_est}")
# Estimated Gaussian noise standard deviation = 0.10885399509583953
如以上代码执行结果所示,由于对值进行了裁剪,得到的平均噪声标准差估计值比指定的 sigma
小一点。
(4) 使用 skimage.restoration
模块的 denoise_wavelet()
函数在图像上执行小波去噪。分别使用两种不同的阈值方法,即 Bayesshrink
和 VisuShrink
:
im_bayes = denoise_wavelet(noisy, multichannel=True, convert2ycbcr=True,
method='BayesShrink', mode='soft',
rescale_sigma=True)
im_visushrink = denoise_wavelet(noisy, multichannel=True, convert2ycbcr=True,
method='VisuShrink', mode='soft',
sigma=sigma_est, rescale_sigma=True)
Visushrink
旨在消除具有高概率的噪声,但这会导致图像在视觉上过于光滑。使用不同阈值重复以上过程,观察得到的不同结果:
im_visushrink2 = denoise_wavelet(noisy, multichannel=True, convert2ycbcr=True,
method='VisuShrink', mode='soft',
sigma=sigma_est/2, rescale_sigma=True)
im_visushrink4 = denoise_wavelet(noisy, multichannel=True, convert2ycbcr=True,
method='VisuShrink', mode='soft',
sigma=sigma_est/4, rescale_sigma=True)
(5) 计算 PSNR
作为图像质量的指标:
psnr_noisy = peak_signal_noise_ratio(original, noisy)
psnr_bayes = peak_signal_noise_ratio(original, im_bayes)
psnr_visushrink = peak_signal_noise_ratio(original, im_visushrink)
psnr_visushrink2 = peak_signal_noise_ratio(original, im_visushrink2)
psnr_visushrink4 = peak_signal_noise_ratio(original, im_visushrink4)
(6) 绘制输入图像、输出图像以及相应的PSNR值:
plt.figure(figsize=(20,20))
plt.subplots_adjust(0,0,1,1,0.05,0.05)
plt.subplot(231), plt.imshow(original), plt.axis('off'), plt.title('Original', size=10)
plt.subplot(232), plt.imshow(noisy), plt.axis('off'), plt.title('Noisy\nPSNR={:0.4g}'.format(psnr_noisy), size=10)
plt.subplot(233), plt.imshow(im_bayes/im_bayes.max()), plt.axis('off'), plt.title('Wavelet denoising\n(BayesShrink)\nPSNR={:0.4f}'.format(psnr_bayes), size=10)
plt.subplot(234), plt.imshow(im_visushrink/im_visushrink.max()), plt.axis('off')
plt.title('Wavelet denoising\n' + r'(VisuShrink, $\sigma=\sigma_{est}$)' + '\nPSNR={:0.4g}'.format(psnr_visushrink), size=10)
plt.subplot(235), plt.imshow(im_visushrink2/im_visushrink2.max()), plt.axis('off')
plt.title('Wavelet denoising\n' + r'(VisuShrink, $\sigma=\sigma_{est}/2$)' + '\nPSNR={:0.4g}'.format(psnr_visushrink2), size=10)
plt.subplot(236), plt.imshow(im_visushrink4/im_visushrink4.max()), plt.axis('off')
plt.title('Wavelet denoising\n' + r'(VisuShrink, $\sigma=\sigma_{est}/4$)' + '\nPSNR={:0.4g}'.format(psnr_visushrink4), size=10)
plt.show()
运行以上代码,可以得到以下结果图像:
小结
离散小波变换是对基本小波的尺度和平移进行离散化的一种谱分析工具,能够同时考察局部时域过程的频域特征以及局部频域过程的时域特征。对于图像来说,离散小波变换能够将图像变换为一系列的小波系数并将这些系数进行高效的压缩和储存,并且可以更好地还原和表现图像。本节中,我们学习了小波变换的基本原理,以及如何利用 pywt
和 scikit-image
库实现小波变换图像去噪。
系列链接
Python图像处理【1】图像与视频处理基础
Python图像处理【2】探索Python图像处理库
Python图像处理【3】Python图像处理库应用
Python图像处理【4】图像线性变换
Python图像处理【5】图像扭曲/逆扭曲
Python图像处理【6】通过哈希查找重复和类似的图像
Python图像处理【7】采样、卷积与离散傅里叶变换
Python图像处理【8】使用低通滤波器模糊图像
Python图像处理【9】使用高通滤波器执行边缘检测
Python图像处理【10】基于离散余弦变换的图像压缩
Python图像处理【11】利用反卷积执行图像去模糊