Imagen de convolución con kernel en dominio de Fourier

Estoy usando el relleno cero alrededor de mi imagen y el núcleo de convolución, convirtiéndolos al dominio de Fourier e invirtiéndolos de nuevo para obtener la imagen convuelta. El resultado, sin embargo, es incorrecto. Esperaba una imagen borrosa, pero la salida es de cuatro cuartos desplazados. ¿Por qué la salida es incorrecta y cómo puedo corregir el código?

Imagen de entrada:

imagen de entrada

Resultado de la convolución:

imagen de salida

from PIL import Image,ImageDraw,ImageOps,ImageFilter import numpy as np from scipy import fftpack from copy import deepcopy import imageio ## STEP 1 ## im1=Image.open("pika.jpeg") im1=ImageOps.grayscale(im1) im1.show() print("s",im1.size) ## working on this image array im_W=np.array(im1).T print("before",im_W.shape) if(im_W.shape[0]%2==0): im_W=np.pad(im_W, ((1,0),(0,0)), 'constant') if(im_W.shape[1]%2==0): im_W=np.pad(im_W, ((0,0),(1,0)), 'constant') print("after",im_W.shape) Boxblur=np.array([[1/9,1/9,1/9],[1/9,1/9,1/9],[1/9,1/9,1/9]]) dim=Boxblur.shape[0] ##padding before frequency domain multipication pad_size=(Boxblur.shape[0]-1)/2 pad_size=int(pad_size) ##padded the image(starts here) p_im=np.pad(im_W, ((pad_size,pad_size),(pad_size,pad_size)), 'constant') t_b=(p_im.shape[0]-dim)/2 l_r=(p_im.shape[1]-dim)/2 t_b=int(t_b) l_r=int(l_r) ##padded the image(ends here) ## padded the kernel(starts here) k_im=np.pad(Boxblur, ((t_b,t_b),(l_r,l_r)), 'constant') print("hjhj",k_im) print("kernel",k_im.shape) ##fourier transforms image and kernel fft_im = fftpack.fftshift(fftpack.fft2(p_im)) fft_k = fftpack.fftshift(fftpack.fft2(k_im)) con_in_f=fft_im*fft_k ifft2 = abs(fftpack.ifft2(fftpack.ifftshift(con_in_f))) convolved=(np.log(abs(ifft2))* 255 / np.amax(np.log(abs(ifft2)))).astype(np.uint8) final=Image.fromarray(convolved.T) final.show() u=im1.filter(ImageFilter.Kernel((3,3), [1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9], scale=None, offset=0)) u.show() 

La transformada de Fourier discreta (DFT) y, por extensión, la FFT (que calcula la DFT) tienen el origen en el primer elemento (para una imagen, el píxel superior izquierdo) tanto para la entrada como para la salida. Esta es la razón por la que a menudo usamos la función fftshift en la salida, para cambiar el origen a una ubicación más familiar para nosotros (la mitad de la imagen).

Esto significa que necesitamos transformar un núcleo difuminado ponderado uniforme de 3×3 para tener este aspecto antes de pasarlo a la función 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 

Es decir, la mitad del kernel se encuentra en la esquina superior izquierda de la imagen, con los píxeles arriba y a la izquierda del centro envolviendo y apareciendo en los extremos derecho e inferior de la imagen.

Podemos hacer esto usando la función ifftshift , aplicada al kernel después del relleno. Al rellenar el kernel, debemos tener cuidado de que el origen (centro del kernel) esté en la ubicación k_im.shape // 2 (división de enteros), dentro de la imagen del kernel k_im . Inicialmente, el origen está en [3,3]//2 == [1,1] . Por lo general, la imagen cuyo tamaño coincidimos es igual en tamaño, por ejemplo [256,256] . El origen estará en [256,256]//2 == [128,128] . Esto significa que tenemos que rellenar una cantidad diferente a la izquierda y a la derecha (y la parte inferior y superior). Tenemos que tener cuidado al calcular este relleno:

 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) 

Tenga en cuenta que no es necesario rellenar la imagen de entrada, img (aunque puede hacerlo si desea imponer un tamaño para el que la FFT es más barata). Tampoco es necesario aplicar fftshift al resultado de la FFT antes de la multiplicación, y luego revertir este cambio justo después, estos cambios son redundantes. Debe usar fftshift solo si desea mostrar la imagen del dominio de Fourier. Finalmente, aplicar la escala logarítmica a la imagen filtrada es incorrecto.

El código resultante es (estoy usando pyplot para mostrar, no uso PIL en absoluto):

 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() 

Tenga en cuenta que estoy tomando la parte real de la FFT inversa. La parte imaginaria debe contener solo valores muy cercanos a cero, que son el resultado de errores de redondeo en los cálculos. Tomar el valor absoluto, aunque común, es incorrecto. Por ejemplo, es posible que desee aplicar un filtro a una imagen que contenga valores negativos, o aplicar un filtro que produzca valores negativos. Tomar el valor absoluto aquí crearía artefactos. Si la salida de la FFT inversa contiene valores imaginarios significativamente diferentes de cero, entonces hay un error en la forma en que se rellenó el núcleo de filtrado.

También tenga en cuenta que el kernel aquí es pequeño y, por consiguiente, el efecto borroso también es pequeño. Para ver mejor el efecto del desenfoque, haga un kernel más grande, por ejemplo np.ones((7,7)) / 49 .