上一章中学习了一维离散傅里叶变换的概念和实现。但在具体的图像中实现还是有点不足,首先我们需要输入图像的尺寸都是2的整数次幂,这个条件太过苛刻了,如果使用Resize又会损失信息。目前还是以熟悉为主,所以选择用numpy自带的方法来进行频域的转换。
那么很简单的,首要就是试一下把一张图片从空间域转换到频域上,然后在频域上分解成幅度谱和相位谱,然后再反过来利用幅度谱和相位谱合成一张图片。实现的代码如下,主要是使用numpy自带的函数,numpy.abs计算复数的绝对值,numpy.angle计算相位值。
from PIL import Image
import numpy as np
girl = "C:/Users/60214/Desktop/python_work/DigitalExecution/gray_girl.jpg"
newgirl = "C:/Users/60214/Desktop/python_work/DigitalExecution/newgirl.jpg"
im = Image.open(girl)
im_arr = np.array(im)
fft = np.fft.fft2(im_arr)
phase = np.angle(fft)
amplitude = np.abs(fft)
new_fft = np.zeros(amplitude.shape, dtype = "complex64")
for i in range(new_fft.shape[0]):
for j in range(new_fft.shape[1]):
new_fft[i, j] = amplitude[i, j] * np.exp(complex(0, phase[i, j]))
new_fft = np.fft.ifft2(new_fft)
new_fft = np.abs(new_fft)
new_arr = np.zeros(new_fft.shape, dtype = "uint8")
for i in range(new_fft.shape[0]):
for j in range(new_fft.shape[1]):
new_arr[i, j] = new_fft[i, j]
new_im = Image.fromarray(new_arr)
new_im.save(newgirl)
然后可以做一个有趣的实验,即把两张图片转换后的幅度谱和相位谱交换,看生成的新图片是什么样子的,可以帮助我们更好的理解幅度谱和相位谱到底代表了图片在时域上的什么内容。
from PIL import Image
import numpy as np
man = "C:/Users/60214/Desktop/python_work/DigitalExecution/gray_man.jpg"
woman = "C:/Users/60214/Desktop/python_work/DigitalExecution/gray_woman.jpg"
a = Image.open(man)
a = a.resize((800, 800))
a = np.array(a)
a = np.fft.fft2(a)
a_phase = np.angle(a)
a_amplitude = np.abs(a)
b = Image.open(woman)
b = b.resize((800, 800))
b = np.array(b)
b = np.fft.fft2(b)
b_phase = np.angle(b)
b_amplitude = np.abs(b)
#build picture for a_phase and b_amplitude
aa = np.zeros((800,800), dtype = "complex64")
for i in range(800):
for j in range(800):
aa[i, j] = b_amplitude[i, j] * np.exp(complex(0, a_phase[i, j]))
aa = np.fft.ifft2(aa)
aa = np.abs(aa)
aaa = np.zeros((800, 800), dtype = "uint8")
for i in range(800):
for j in range(800):
aaa[i,j] = aa[i, j]
aaa_im = Image.fromarray(aaa)
aaa_im.save("C:/Users/60214/Desktop/python_work/DigitalExecution/mix1.jpg")
#build picture for b_phase and a_amplitude
bb = np.zeros((800,800), dtype = "complex64")
for i in range(800):
for j in range(800):
bb[i, j] = a_amplitude[i, j] * np.exp(complex(0, b_phase[i, j]))
bb = np.fft.ifft2(bb)
bb = np.abs(bb)
bbb = np.zeros((800, 800), dtype = "uint8")
for i in range(800):
for j in range(800):
bbb[i,j] = bb[i, j]
bbb_im = Image.fromarray(bbb)
bbb_im.save("C:/Users/60214/Desktop/python_work/DigitalExecution/mix2.jpg")
从代码中,我们知道图片mix1,使用男头的相位谱和女头的幅度谱合成的,而图片中看得出图片的内容是男士头像,也符合之前说的相位谱决定图像结构的论断。至于幅度图,更多的决定了图片的明暗和灰度变化趋势,从图中仔细看确实看得出mix1的明暗和女头图片中的明暗是对应的。
接下来看一下频率滤波的基本步骤:
1.计算原始图像f(x,y)的DFT,得到F(u,v)
2.将频谱F(u,v)的零频点移动到频谱图的中心位置
3.计算滤波器函数H(u,v)和F(u,v)的乘积G(u,v)
4.将频谱G(u,v)的零频点移动到频谱图的左上角位置上
5.计算第四步结果的逆傅里叶变化得到g(x,y)
6.取实部作为最终结果
对于为什么要把频谱F(u,v)的零频点移动到频谱图的中心位置,我的理解是,首先从周期性出发,我们知道四个顶点的数值必然都是F(0,0),这是指导我们对角互换的一个思路。其次,平移之后低频点都集中在图像中心,而越接近边缘则频率越高,方便我们设置滤波函数。这个可以通过numpy.fft.fftshift和numpy.fft.ifftshift来实现。
在频谱中,低频主要对应了图像在平滑区域的总体灰度级分布,而高频对应图像的细节部分例如边缘噪声。因此图像平滑可以通过衰减图像频谱中的高频部分来实现,这就建立起了空间域图像平滑和频域低通滤波之间的对应关系。最直接的方法就是设置一个截止频率,高于此频率的频谱成分置为0,低于的保存,这就是理想的低通滤波器,如果图像的宽为M,高为N,其H(u,v)的表现形式为
实现的代码为
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
girl = "C:/Users/60214/Desktop/python_work/DigitalExecution/gray_girl.jpg"
im = Image.open(man)
imarr = np.array(im)
height, width = imarr.shape
fft = np.fft.fft2(imarr)
fftshift = np.fft.fftshift(fft)
cutstop = 20
for i in range(height):
for j in range(width):
if (i - (height - 1)/2)**2 + (j - (width - 1)/2)**2 >= cutstop**2:
fftshift[i, j] = 0
fftshift = np.fft.ifftshift(fftshift)
ifft = np.fft.ifft2(fftshift)
new = np.zeros((height, width), dtype = "uint8")
for i in range(height):
for j in range(width):
new[i, j] = ifft[i, j].real
new_im = Image.fromarray(new)
new_im.save("C:/Users/60214/Desktop/python_work/DigitalExecution/girl1.jpg")
我们来对比图片前后的变化,可以看到图片确实被平滑了许多,平滑的程度我们可以通过改变截断的范围来控制,截断的频率越低,图片就越平滑。就效果而言,有点类似于空间域滤波中的平滑滤波,没有针对性。另外还观察到在图片中有一些黑色块,这个现象我看的教材中并没有出现。发现这些黑色块主要集中在图片中颜色过渡较快的区域,例如图像的边缘处头发、下巴、衣服和头发相连接处等位置,这些位置都是高频区域,受到高频成分削减的影响从而转换回空间域中时数值很接近于0即黑色。但是别人的博客中处理结果也没有出现相似的现象,所以应该还是我哪里写的问题不对。结合之前的经验,这种突然的信息消失很可能与我们设置的numpy.array的数据类型有关,所以我额外多添加了一个数组来盛放逆傅里叶变换后的处理毕的图像像素,其中都是复数,在取得实部的时候如果有一部分实部为负数那么就会被我们的uint8所截断,所以用float64的先储存后再缩放到0-255内再保存到uint8的数组中,来生成图片。新的代码如下所示,虽然有点累赘,但确实没有问题了。
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
girl = "C:/Users/60214/Desktop/python_work/DigitalExecution/gray_girl.jpg"
im = Image.open(girl)
imarr = np.array(im)
height, width = imarr.shape
fft = np.fft.fft2(imarr)
fftshift = np.fft.fftshift(fft)
cutstop = 50
for i in range(height):
for j in range(width):
if (i - (height)/2)**2 + (j - (width)/2)**2 >= cutstop**2:
fftshift[i, j] = 0
fftshift = np.fft.ifftshift(fftshift)
ifft = np.fft.ifft2(fftshift)
new = np.zeros((height, width))
for i in range(height):
for j in range(width):
new[i, j] = ifft[i, j].real
max = np.max(new)
min = np.min(new)
res = np.zeros((height, width), dtype = "uint8")
for i in range(height):
for j in range(width):
res[i, j] = 255*(new[i, j] - min)/(max - min)
res_im = Image.fromarray(res)
res_im.save("C:/Users/60214/Desktop/python_work/DigitalExecution/girl1.jpg")
处理后的图像如下,有明显的平滑效果。
给出高斯低通滤波器的函数式
试一下用opencv的库来打开和保存图片,而不是用PIL。实现的代码如下:
import cv2
import numpy as np
def GaussianFrequencyFilter(src, dst, sigma = 1):
im = cv2.imread(src, 0)
imarr = np.array(im)
height, width = imarr.shape
fft = np.fft.fft2(imarr)
fft = np.fft.fftshift(fft)
for i in range(height):
for j in range(height):
fft[i, j] *= np.exp(-((i - (height - 1)/2)**2 + (j - (width - 1)/2)**2)/2/sigma**2)
fft = np.fft.ifftshift(fft)
ifft = np.fft.ifft2(fft)
ifft = np.real(ifft)
max = np.max(ifft)
min = np.min(ifft)
res = np.zeros((height, width), dtype = "uint8")
for i in range(height):
for j in range(width):
res[i, j] = 255 * (ifft[i, j] - min)/(max - min)
cv2.imwrite(dst, res)
gray_girl = "C:/Users/60214/Desktop/python_work/DigitalExecution/gray_girl.jpg"
gaussian_girl = "C:/Users/60214/Desktop/python_work/DigitalExecution/gaussian_girl.jpg"
GaussianFrequencyFilter(gray_girl, gaussian_girl, 20)
得到的图片的效果比起理想低通滤波要更好一些,对于图像的细节保留的更好一点,也显得更加平滑,不会像前者那样有波纹。