Дискретное преобразование Фурье (ДПФ) и, соответственно, БПФ (которое вычисляет ДПФ) имеют начало в первом элементе (для изображения, верхний левый пиксель) для оба вход и выход.По этой причине мы часто используем функцию fftshift
на выходе, чтобы переместить начало координат в более знакомое нам место (середина изображения).
Это означает, что нам нужно преобразоватьравномерно взвешенное размытое ядро 3x3, чтобы оно выглядело так перед передачей его функции FFT:
1/9 1/9 0 0 ... 0 1/9
1/9 1/9 0 0 ... 0 1/9
0 0 0 0 ... 0 0
... ... ...
0 0 0 0 ... 0 0
1/9 1/9 0 0 ... 0 1/9
То есть середина ядра находится в верхнем левом углу изображения с пикселями вышеи слева от середины, оборачиваясь вокруг и появляясь в правом и нижнем концах изображения.
Мы можем сделать это, используя функцию ifftshift
, применяемую к ядру после заполнения.При заполнении ядра, мы должны позаботиться о том, чтобы источник (середина ядра) находился в расположении k_im.shape // 2
(целочисленное деление) внутри образа ядра k_im
.Первоначально источник находится на [3,3]//2 == [1,1]
.Обычно изображение, размер которого мы сопоставляем, имеет четный размер, например [256,256]
.Начало координат будет в [256,256]//2 == [128,128]
.Это означает, что нам нужно заполнить различное количество слева и справа (и снизу и сверху).Мы должны быть осторожны при вычислении этого отступа:
sz = img.shape # the sizes we're matching
kernel = np.ones((3,3)) / 9
sz = (sz[0] - kernel.shape[0], sz[1] - kernel.shape[1]) # total amount of padding
kernel = np.pad(kernel, (((sz[0]+1)//2, sz[0]//2), ((sz[1]+1)//2, sz[1]//2)), 'constant')
kernel = fftpack.ifftshift(kernel)
Обратите внимание, что входное изображение, img
, не нужно дополнять (хотя вы можете сделать это, если хотите установить размер, для которогоБПФ дешевле).Также нет необходимости применять fftshift
к результату БПФ перед умножением, а затем изменять это смещение сразу после того, как эти сдвиги являются избыточными.Вам следует использовать fftshift
, только если вы хотите отобразить изображение домена Фурье.Наконец, неверно применять логарифмическое масштабирование к отфильтрованному изображению.
В результате получается код (я использую pyplot для отображения, вообще не использую PIL):
import numpy as np
from scipy import misc
from scipy import fftpack
import matplotlib.pyplot as plt
img = misc.face()[:,:,0]
kernel = np.ones((3,3)) / 9
sz = (img.shape[0] - kernel.shape[0], img.shape[1] - kernel.shape[1]) # total amount of padding
kernel = np.pad(kernel, (((sz[0]+1)//2, sz[0]//2), ((sz[1]+1)//2, sz[1]//2)), 'constant')
kernel = fftpack.ifftshift(kernel)
filtered = np.real(fftpack.ifft2(fftpack.fft2(img) * fftpack.fft2(kernel)))
plt.imshow(filtered, vmin=0, vmax=255)
plt.show()
Обратите внимание, что яЯ беру реальную часть обратного БПФ.Мнимая часть должна содержать только значения, очень близкие к нулю, которые являются результатом ошибок округления в вычислениях.Взятие абсолютного значения, хотя и обычное, неверно.Например, вы можете применить фильтр к изображению, содержащему отрицательные значения, или применить фильтр, который создает отрицательные значения.Принятие здесь абсолютного значения создаст артефакты.Если выходные данные обратного БПФ содержат мнимые значения, значительно отличающиеся от нуля, то возникает ошибка в способе заполнения ядра фильтрации.
Также обратите внимание, что ядро здесь крошечное, и, следовательно, эффект размытиятоже крошечный.Чтобы лучше увидеть эффект размытия, сделайте ядро большего размера, например np.ones((7,7)) / 49
.