采样 (Sampling
) 是用于选择/丢弃图像像素的空间操作,通常用于增加/减小图像大小;而卷积是一种局部数学运算,通过将像素及其相邻像素的强度值乘以卷积核(通常是一个尺寸较小的窗口矩阵)实现;使用不同核执行图像卷积会在输出图像中产生不同的效果(例如,模糊、锐化、边缘提取等)。离散傅里叶变换 (Discrete Fourier Transform
, DFT
) 的基本思想是将图像视为二维函数,该函数可以表示为二维正弦和余弦(傅里叶基/系数)的加权和。DFT
可以用于将图像从空域变换到频域,因为卷积等操作在频域中可以更快地执行。在本节中,我们将使用常用 Python
库利用采样、卷积和 DFT
定理解决图像处理问题。
在本节中,我们将介绍离散傅里叶变换 (Discrete Fourier Transform
, DFT
) 相关基础概念,并使用 Numpy
的 fft
模块在图像中应用 DFT
,也可以使用 Scipy
的 fftpack
模块实现 DFT
。
我们首先介绍 2D
傅里叶变换及 2D
逆离散傅里叶变换。(灰度)图像可以被定义为 2D
函数 f ( x , y ) f(x,y) f(x,y),其中 ( x , y ) ∈ { 0 , . . . , M − 1 } × { 0 , . . . , N − 1 } (x,y)∈\{0,...,M-1\}×\{0,...,N-1\} (x,y)∈{0,...,M−1}×{0,...,N−1}。DFT
可以将图像从其空间表示 f ( x , y ) f(x,y) f(x,y) 改变为频域表示 f ( u , v ) f(u,v) f(u,v),其中 ( u , v ) ∈ { 0 , . . . , M − 1 } × { 0 , . . . , N − 1 } (u,v)∈\{0,...,M-1\}×\{0,...,N-1\} (u,v)∈{0,...,M−1}×{0,...,N−1} 表示频率分量/傅里叶基向量:
(1) 首先,导入所有必需的库:
import numpy as np
import numpy.fft as fp
from skimage.io import imread
from skimage.color import rgb2gray
from skimage.metrics import peak_signal_noise_ratio
import matplotlib.pyplot as plt
(2) 定义函数 plot_image()
,使用 matplotlib.pyplot
的 imshow()
函数绘制图像:
def plot_image(im, title):
plt.imshow(im, cmap='gray')
plt.axis('off')
plt.title(title, size=10)
(3) 接下来,定义函数 plot_freq_spectrum()
以绘制图像的频(功率)谱,该函数可以通过可选参数控制是否显示颜色映射图和轴刻度。我们需要使用 numpy.fft.fftshift()
函数将零功率谱系数 (0,0)
移动到功率谱中心,然后可视化功率谱:
numpy.fft.fftshift(x, axes=None)
傅里叶变换会返回一复数数组;复数部分对应于相位,实数部分对应于我们感兴趣的幅度:
def plot_freq_spectrum(F, title, cmap=plt.cm.gray, show_axis=True, colorbar=False):
plt.imshow((20*np.log10(0.1 + fp.fftshift(F))).real.astype(int), cmap=cmap)
if not show_axis:
plt.axis('off')
if colorbar:
plt.colorbar()
plt.title(title, size=10)
(4) 接下来,创建一些简单图像并获取 DFT
的功率谱。首先分别生成一对大小为 100x100
且具有(等间距)水平和垂直条纹的周期图像:
h, w = 100, 100
images = list()
im = np.zeros((h,w))
for x in range(h):
im[x,:] = np.sin(x)
images.append(im)
im = np.zeros((h,w))
for y in range(w):
im[:,y] = np.sin(y)
images.append(im)
(5) 接下来,我们生成对角线周期图像,然后使用半径为 10
的圆形掩码(以图像的中心为圆心),从根据此图像中创建另一图像:
im = np.zeros((h,w))
for x in range(h):
for y in range(w):
im[x,y] = np.sin(x + y)
images.append(im)
im = np.zeros((h,w))
for x in range(h):
for y in range(w):
if (x-h/2)**2 + (y-w/2)**2 < 100:
im[x,y] = np.sin(x + y)
images.append(im)
(7) 最后,生成另一对图像,第一个图像带有一个实心圆,第二个带有以图像中心为中心的实心正方形:
im = np.zeros((h,w))
for x in range(h):
for y in range(w):
if (x-h/2)**2 + (y-w/2)**2 < 25:
im[x,y] = 1
images.append(im)
im = np.zeros((h,w))
im[h//2 -5:h//2 + 5, w//2 -5:w//2 + 5] = 1
images.append(im)
(8) 我们将所有图像都存储在列表中,使用 numpy.fft
的 fft2()
函数使用快速傅里叶变换算法来计算 DFT
,并绘制生成的每个输入图像的输出功率谱:
numpy.fft.fft2(a, s=None, axes=(-2, -1), norm=None)
下图显示了相应图像及其 DFT
:
plt.figure(figsize=(25,10))
i = 1
for im in images:
plt.subplot(2,6,i), plot_image(im, 'image {}'.format(i))
plt.subplot(2,6,i+6), plot_freq_spectrum(fp.fft2(im), 'DFT {}'.format(i), show_axis=False)
i += 1
plt.tight_layout()
plt.show()
理想情况下,对于图像 1
和 2
,水平和垂直周期模式的 DFT
应分别为垂直和水平对齐的点(对应于频率)。但是,如上图所示,由于边缘效应,结果为一条垂直和水平线(线上的亮点对应于相应的频率)。同样,对于图像 3
,周期对角线模式应在垂直对角线方向上产生两个对角线点,但由于边缘效应,得到了其他许多线。如果使用二值圆形掩码处理图像 3
中心的圆,则会降低边缘效应,我们可以看到沿主对角线方向的亮点,对应于频率分量。
我们已经了解不同频率对图像的不同作用,如果我们在功率谱上应用逆傅里叶变换 (Inverse Discrete Fourier Transform ,
IDFT`),可以在数值精度内恢复原始图像,下图展示了如何在图像上应用傅立叶变换:
如果我们从功率谱中删除一部分频率并应用 IDFT
以重建图像时会发生什么?接下来,我们使用函数 numpy.fft.ifft2()
执行逆傅立叶变换,函数调用方式如下:
numpy.fft.ifft2(a, s=None, axes=(-2, -1), norm=None)
(1) 读取输入图像并将其转换为灰度图像,然后计算图像的 DFT
,并移动频率中心:
im = rgb2gray(imread("1.png"))
h, w = im.shape
F = fp.fft2(im)
F_shifted = fp.fftshift(F)
(2) 为了观察消除频率分量对 IDFT
输出图像的影响,我们仅选择几个具有较小(绝对)值的频率,即位于 (0,0)
周围的频率,并过滤其他频率。换句话说,只允许介于 (−k1, − k2)
和 (+k1, +k2)
之间的频率,即允许频率 (Fx, Fy)
,其中 |Fx|, |Fy|≤(k1,k2)
,并且在利用 IDFT
变换回空间域之前过滤该区间之外的其他频率:
xs = list(map(int, np.linspace(1, h//5, 10)))
ys = list(map(int, np.linspace(1, w//5, 10)))
plt.figure(figsize=(20,8))
plt.gray()
for i in range(10):
F_mask = np.zeros((h, w))
F_mask[h//2-xs[i]:h//2+xs[i]+1, w//2-ys[i]:w//2+ys[i]+1] = 1
F1 = F_shifted*F_mask
im_out = fp.ifft2(fp.ifftshift(F1)).real #np.abs()
plt.subplot(2,5,i+1), plt.imshow(im_out), plt.axis('off')
plt.title('{}x{},PSNR={}'.format(2*xs[i]+1, 2*ys[i]+1, round(peak_signal_noise_ratio(im, im_out),2)), size=10)
plt.suptitle('Fourier reconstruction by keeping first few frequency basis vectors', size=15)
plt.show()
从上图可以看出,与较低频率相对应的系数值保留了均值信息,并且随着频率的增加,可以捕获图像中的更多边缘和细节,并且 PSNR
也随之增加。从 143x213
频率开始,我们可以较好的重建图像而不会导致明显的细节丢失。
采样 (Sampling
) 可以通过选择/丢弃图像像素来增加/减少图像的分辨率。在本节中,我们将解决两个问题,包括上采样 (Upsampling
) 与下采样 (Downsampling
)。
在本小节,我们将学习如何使用离散傅里叶变换 (Discrete Fourier Transform
, DFT
) 执行上采样并增加图像分辨率。上采样通常在空域中进行,通过使用最近邻、双线性或双立方插值来推测新增的像素值。但在这里,我们将尝试使用 DFT
实现上采样,并在频域中应用低通滤波器( Low Pass Filter
, LPF
)。
为了充分理解离散傅里叶变换与低通滤波器,我们首先了解如何在空域和频域中使用卷积实现 LPF
。我们已经知道,滤波是指改变像素强度值以发现某些图像特征,例如平滑或锐化等。
LPF
仅允许通过来自图像频域表示的低频(使用 DFT
获得)部分,并过滤所有超出截止值的高频部分。可以在空间域中使用合适的核(例如高斯核)执行卷积实现 LPF
,起始时核窗口位于(灰度)图像左上角,核的尺寸大小不能超过图像尺寸。输出图像中的像素值是通过在输入图像中滑动核窗口进行计算的:
接下来,我们将在频域中实现 LPF
,根据卷积定理,我们只需要进行元素乘法操作,因此算法运算速度很快:
f ( x , y ) ∗ h ( x , y ) ⇔ F ( u , v ) ⋅ H ( u , v ) f(x,y)*h(x,y)⇔F(u,v)\cdot H(u,v) f(x,y)∗h(x,y)⇔F(u,v)⋅H(u,v)
根据卷积定理,我们可以将空间卷积转换为频域逐元素乘法操作。
接下来,我们将使用尺寸为 400x600
的输入图像,并计算得到两倍尺寸的图像 (800x1200
)。
(1) 首先,读取图像,将其转换为灰度图像,并在每个交替的行/列上填充零,从而令图像的尺寸大小扩大两倍:
import numpy as np
import numpy.fft as fp
from skimage.io import imread
from skimage.color import rgb2gray
import matplotlib.pyplot as plt
im = 255*rgb2gray(imread('1.png'))
im1 = np.zeros((2*im.shape[0], 2*im.shape[1]))
print(im.shape, im1.shape)
for i in range(im.shape[0]):
for j in range(im.shape[1]):
im1[2*i,2*j] = im[i,j]
def plot_image(im, title):
plt.imshow(im, cmap='gray')
plt.axis('off')
plt.title(title, size=10)
def plot_freq_spectrum(F, title, cmap=plt.cm.gray, show_axis=True, colorbar=False):
plt.imshow((20*np.log10(0.1 + fp.fftshift(F))).real.astype(int), cmap=cmap)
if not show_axis:
plt.axis('off')
if colorbar:
plt.colorbar()
plt.title(title, size=10)
(2) 我们将使用以下 LPF
核,可以看出,它的中心权重最高,而远离中心的权重较小:
kernel = [[0.25, 0.5, 0.25], [0.5, 1, 0.5], [0.25, 0.5, 0.25]]
(3) 要在频域中实现滤波器,我们需要将图像乘以核,为此,核的形状需要完全等于图像的形状。为了使用 NumPy
库的函数 pad()
,我们首先定义填充函数 pad_with_zeros()
用值零填充核:
def pad_with_zeros(vector, pad_width, iaxis, kwargs):
vector[:pad_width[0]] = 0
vector[-pad_width[1]:] = 0
return vector
kernel = np.pad(kernel, (((im1.shape[0]-3)//2,(im1.shape[0]-3)//2+1), ((im1.shape[1]-3)//2,(im1.shape[1]-3)//2+1)), pad_with_zeros)
(4) 接下来,计算输入图像和扩展核的功率谱。由于核已经居中,因此,在应用 DFT
之前,我们需要应用 fftshift()
的逆函数 ifftshift()
:
freq = fp.fft2(im1)
freq_kernel = fp.fft2(fp.ifftshift(kernel))
(5) 通过频域中的逐元素乘法计算 LPF
:
freq_LPF = freq*freq_kernel # 卷积理论
(6) 最后,使用逆 DFT
获得输出图像,我们需要提取复数输出中的实数部分:
im2 = fp.ifft2(freq_LPF).real
(7) 绘制输入、核和输出图像以及它们的功率谱:
plt.figure(figsize=(15,10))
plt.gray()
cmap = 'nipy_spectral'
plt.subplot(231), plot_image(im, 'Original Input Image')
plt.subplot(232), plot_image(im1, 'Padded Input Image')
plt.subplot(233), plot_freq_spectrum(freq, 'Original Image Spectrum', cmap=cmap)
plt.subplot(234), plot_freq_spectrum(freq_kernel, 'Image Spectrum of the LPF', cmap=cmap)
plt.subplot(235), plot_freq_spectrum(fp.fft2(im2), 'Image Spectrum after LPF', cmap=cmap)
plt.subplot(236), plot_image(im2.astype(np.uint8), 'Output Image')
plt.show()
为了减少图像的尺寸大小,我们需要对图像执行下样本。得到的尺寸较小的新图像中的每个像素对应于原始大图像中的多个像素。直接从原始图像中删除像素有助于减少图像的分辨率,但是它会引入空间混叠效应(例如摩尔纹),并且导致输出图像质量较差。为了防止这种情况,需要使用抗混叠滤波器(在下采样之前)以过滤图像高频分量。
在本节中,我们将学习一种简单的抗锯齿技术,该方法通过在下采样前应用低通滤波器( LPF
)来实现。
(1) 导入所需的库,读取输入图像并将其转换为 float
类型(使所有像素值均在 0
和 1
之间),我们的目标是通过将高度和宽度减少三倍,将图像大小减少九倍:
from skimage.filters import gaussian
from skimage import img_as_float
import numpy as np
from skimage.io import imread
import matplotlib.pyplot as plt
im = img_as_float(imread('1.png'))
print(im.shape)
(2) 在下采样之前,应用高斯滤波器作为预处理步骤,以使图像更加平滑。使用 skimage.filters.gaussian()
函数将高斯模糊应用于图像,等价的,我们也可以使用 scipy.ndimage.gaussian_filter()
函数:
im_blurred = gaussian(im, sigma=1.25, multichannel=True)
(3) 从原始图像中在 X
和 Y
轴方向的每隔三个像素保留一个像素,以获取尺寸较小的图像。观察没有使用高斯滤波器(带有空间混叠)通过下采样获得的输出图像的质量:
n = 3 # 缩放图像倍数
h, w = im.shape[0] // n, im.shape[1] // n
im_small = np.zeros((h, w, 3))
for i in range(h):
for j in range(w):
im_small[i,j] = im[n*i, n*j]
im_small_aa = np.zeros((h, w, 3))
for i in range(h):
for j in range(w):
im_small_aa[i,j] = im_blurred[n*i, n*j]
(4) 绘制原始输入图像:
plt.figure(figsize=(15,15))
plt.imshow(im), plt.title('Original Image', size=10)
plt.show()
(5) 绘制没有使用高斯滤波器进行下采样的图像:
plt.figure(figsize=(15,15))
plt.imshow(im_small), plt.title('Resized Image (without Anti-aliasing)', size=10)
plt.show()
plt.figure(figsize=(15,15))
plt.imshow(im_small_aa), plt.title('Resized Image (with Anti-aliasing)', size=10)
plt.show()
正如上图所示,没有抗锯齿的下采样图像中存在空间混叠现象,但是具有抗锯齿操作的图像中几乎不存在这种现象。由于高斯模糊是 LPF
,因此从原始输入图像中移除了高频部分。因此,可以避免空间混叠现象。
在本节中,我们介绍了频域图像处理概念以及相关问题,包括采样、卷积和离散傅里叶变换。讲解了这些频域图像处理的基本概念,并利用相关技术解决了图像处理问题,重点介绍了如何使用离散傅里叶变换/低通滤波器对图像执行上采样以及使用高斯滤波器实现抗锯齿下采样。
Python图像处理【1】图像与视频处理基础
Python图像处理【2】探索Python图像处理库
Python图像处理【3】Python图像处理库应用
Python图像处理【4】图像线性变换
Python图像处理【5】图像扭曲/逆扭曲
Python图像处理【6】通过哈希查找重复和类似的图像