web-dev-qa-db-fra.com

FFT sur l'image avec Python

J'ai un problème avec l'implémentation FFT en Python. J'ai des résultats complètement étranges. Ok donc, je veux ouvrir l'image, obtenir la valeur de chaque pixel en RVB, puis je dois utiliser fft dessus et la reconvertir en image.

Mes pas:

1) J'ouvre une image avec la bibliothèque PIL en Python comme ceci

from PIL import Image
im = Image.open("test.png")

2) J'obtiens des pixels

pixels = list(im.getdata())

3) Je sépare chaque pixel des valeurs r, g, b

for x in range(width):
    for y in range(height):
        r,g,b = pixels[x*width+y]
        red[x][y] = r
        green[x][y] = g
        blue[x][y] = b

4). Supposons que j'ai un pixel (111.111.111). Et utilisez fft sur toutes les valeurs rouges comme celle-ci

red = np.fft.fft(red)

Puis:

print (red[0][0], green[0][0], blue[0][0])

Ma sortie est:

(53866+0j) 111 111

C'est complètement faux je pense. Mon image est 64x64 et la FFT de gimp est complètement différente. En fait, ma FFT ne me donne que des tableaux avec des valeurs énormes, c'est pourquoi mon image de sortie est noire.

Avez-vous une idée d'où est le problème?

[ÉDITER]

J'ai changé comme suggéré pour

red= np.fft.fft2(red)

Et après ça je le redimensionne

scale = 1/(width*height)
red= abs(red* scale)

Et pourtant, je n'obtiens qu'une image noire.

[EDIT2]

Ok, prenons donc une image. test.png

Supposons que je ne souhaite pas l'ouvrir et l'enregistrer en tant qu'image en niveaux de gris. Donc je fais comme ça.

def getGray(pixel):
    r,g,b = pixel
    return (r+g+b)/3  

im = Image.open("test.png")
im.load()

pixels = list(im.getdata())
width, height = im.size
for x in range(width):
    for y in range(height):
        greyscale[x][y] = getGray(pixels[x*width+y])  

data = []
for x in range(width):
     for y in range(height):
         pix = greyscale[x][y]
         data.append(pix)

img = Image.new("L", (width,height), "white")
img.putdata(data)
img.save('out.png')

Après cela, je reçois cette image greyscale , ce qui est correct. Alors maintenant, je veux faire un fft sur mon image avant de l'enregistrer dans une nouvelle, donc je fais comme ça

scale = 1/(width*height)
greyscale = np.fft.fft2(greyscale)
greyscale = abs(greyscale * scale)

après l'avoir chargé. Après l'avoir enregistré dans un fichier, j'ai bad FFT . Essayons donc maintenant d'ouvrir test.png avec gimp et d'utiliser le plugin de filtre FFT. Je reçois cette image, qui est correcte good FFT

Comment puis-je le gérer?

8
Tatarinho

Grande question. Je n'en ai jamais entendu parler mais le plugin Gimp Fourier semble vraiment bien:

Un plug-in simple pour effectuer une transformation de Fourier sur votre image. L'avantage majeur de ce plugin est de pouvoir travailler avec l'image transformée dans GIMP. Vous pouvez ainsi dessiner ou appliquer des filtres dans un espace de Fourier, et obtenir l'image modifiée avec une FFT inverse.

Cette idée - de faire une manipulation de style Gimp sur des données du domaine fréquentiel et de les transformer en image - est très cool! Malgré des années de travail avec les FFT, je n'ai jamais pensé à faire ça. Au lieu de jouer avec les plugins Gimp et les exécutables C et la laideur, faisons cela en Python!

Avertissement. J'ai expérimenté un certain nombre de façons de le faire, en essayant de rapprocher quelque chose de l'image de Gimp Fourier de sortie (grise avec motif moiré) l'image d'entrée d'origine, mais je ne pouvais tout simplement pas. L'image Gimp semble être quelque peu symétrique au milieu de l'image, mais elle n'est ni inversée verticalement ni horizontalement, ni transposée symétrique. Je m'attendrais à ce que le plugin utilise une vraie FFT 2D pour transformer une image H × W en un tableau H × W de données à valeur réelle dans le domaine fréquentiel, auquel cas il n'y aurait pas de symétrie (c'est juste le to- FFT complexe qui est conjugué symétrique pour les entrées à valeur réelle comme les images). J'ai donc abandonné toute tentative de rétro-ingénierie de ce que fait le plugin Gimp et j'ai regardé comment je pouvais le faire à partir de zéro.

Le code. Très simple: lire une image, appliquer scipy.fftpack.rfft dans les deux premières dimensions pour obtenir l'image de fréquence, redimensionner à 0–255 et enregistrer.

Notez comment cela est différent des autres réponses! Pas de gris - la FFT 2D réel-réel se produit indépendamment sur les trois canaux. Non abs nécessaire: l'image du domaine fréquentiel peut légitimement avoir des valeurs négatives, et si vous les rendez positives, vous ne pourrez pas récupérer votre image d'origine. (Aussi une fonctionnalité intéressante: aucun compromis sur la taille de l'image. La taille du tableau reste la même avant et après la FFT, que la largeur/hauteur soit paire ou impaire.)

from PIL import Image
import numpy as np
import scipy.fftpack as fp

## Functions to go from image to frequency-image and back
im2freq = lambda data: fp.rfft(fp.rfft(data, axis=0),
                               axis=1)
freq2im = lambda f: fp.irfft(fp.irfft(f, axis=1),
                             axis=0)

## Read in data file and transform
data = np.array(Image.open('test.png'))

freq = im2freq(data)
back = freq2im(freq)
# Make sure the forward and backward transforms work!
assert(np.allclose(data, back))

## Helper functions to rescale a frequency-image to [0, 255] and save
remmax = lambda x: x/x.max()
remmin = lambda x: x - np.amin(x, axis=(0,1), keepdims=True)
touint8 = lambda x: (remmax(remmin(x))*(256-1e-4)).astype(int)

def arr2im(data, fname):
    out = Image.new('RGB', data.shape[1::-1])
    out.putdata(map(Tuple, data.reshape(-1, 3)))
    out.save(fname)

arr2im(touint8(freq), 'freq.png')

( Mis à part: note de geek amateur de FFT. Consultez la documentation de rfft pour plus de détails, mais j'ai utilisé le module FFTPACK de Scipy parce que son rfft entrelace les composants réels et imaginaires d'un seul pixel comme deux valeurs réelles adjacentes, garantissant que la sortie pour n'importe quelle image 2D (paire vs impaire, largeur vs hauteur) sera préservée. Ceci contraste avec Numpy - numpy.fft.rfft2 qui, car il renvoie des données complexes de taille width/2+1 par height/2+1, vous oblige à gérer une ligne/colonne supplémentaire et à traiter vous-même le désentrelacement complexe-réel. Qui a besoin de ces tracas pour cette application.)

Résultats. Étant donné l'entrée nommée test.png:

test input

cet extrait produit la sortie suivante (les valeurs min/max globales ont été redimensionnées et quantifiées à 0-255):

test output, frequency domain

Et mis à l'échelle:

frequency, upscaled

Dans cette image de fréquence, le composant DC (fréquence 0 Hz) est en haut à gauche, et les fréquences montent plus haut à mesure que vous allez vers la droite et vers le bas.

Voyons maintenant ce qui se passe lorsque vous manipulez cette image de plusieurs manières. Au lieu de cette image de test, utilisons un photo de chat .

original cat

J'ai fait quelques images de masque dans Gimp que je charge ensuite dans Python et multiplie l'image de fréquence avec pour voir quel effet le masque a sur l'image.

Voici le code:

# Make frequency-image of cat photo
freq = im2freq(np.array(Image.open('cat.jpg')))

# Load three frequency-domain masks (DSP "filters")
bpfMask = np.array(Image.open('cat-mask-bpfcorner.png')).astype(float) / 255
hpfMask = np.array(Image.open('cat-mask-hpfcorner.png')).astype(float) / 255
lpfMask = np.array(Image.open('cat-mask-corner.png')).astype(float) / 255

# Apply each filter and save the output
arr2im(touint8(freq2im(freq * bpfMask)), 'cat-bpf.png')
arr2im(touint8(freq2im(freq * hpfMask)), 'cat-hpf.png')
arr2im(touint8(freq2im(freq * lpfMask)), 'cat-lpf.png')

Voici un masque de filtre passe-bas à gauche, et à droite, le résultat - cliquez pour voir l'image en pleine résolution:

low-passed cat

Dans le masque, noir = 0,0, blanc = 1,0. Ainsi, les fréquences les plus basses sont conservées ici (blanc), tandis que les hautes sont bloquées (noir). Cela brouille l'image en atténuant les hautes fréquences. Des filtres passe-bas sont utilisés partout, y compris lors de la décimation ("sous-échantillonnage") d'une image (bien qu'ils soient façonnés beaucoup plus soigneusement que moi dans Gimp ????).

Voici un filtre passe-bande , où les fréquences les plus basses (voir ce blanc dans le coin supérieur gauche?) Et les hautes fréquences sont conservées, mais les fréquences intermédiaires sont bloquées. Assez bizarre!

band-passed cat

Voici un filtre passe-haut , où le coin supérieur gauche qui était resté blanc dans le masque ci-dessus est noirci:

high-passed filter

Voici comment fonctionne la détection des contours.

Postscript. Quelqu'un, faites une webapp en utilisant cette technique qui vous permet de dessiner des masques et de les appliquer à une image en temps réel !!!

10
Ahmed Fasih

Il y a plusieurs problèmes ici.

1) La conversion manuelle en niveaux de gris n'est pas bonne. Utilisez Image.open("test.png").convert('L')

2) Il y a très probablement un problème avec les types. Vous ne devez pas passer np.ndarray De fft2 À une image PIL sans être sûr que leurs types sont compatibles. abs(np.fft.fft2(something)) vous renverra un tableau de type np.float32 ou quelque chose comme ça, alors que l'image PIL va recevoir quelque chose comme un tableau de type np.uint8.

3) La mise à l'échelle suggérée dans les commentaires semble incorrecte. Vous avez réellement besoin que vos valeurs s'inscrivent dans la plage 0..255.

Voici mon code qui aborde ces 3 points:

import numpy as np
from PIL import Image

def fft(channel):
    fft = np.fft.fft2(channel)
    fft *= 255.0 / fft.max()  # proper scaling into 0..255 range
    return np.absolute(fft)

input_image = Image.open("test.png")
channels = input_image.split()  # splits an image into R, G, B channels
result_array = np.zeros_like(input_image)  # make sure data types, 
# sizes and numbers of channels of input and output numpy arrays are the save

if len(channels) > 1:  # grayscale images have only one channel
    for i, channel in enumerate(channels):
        result_array[..., i] = fft(channel)
else:
    result_array[...] = fft(channels[0])

result_image = Image.fromarray(result_array)
result_image.save('out.png')

Je dois admettre que je n'ai pas réussi à obtenir des résultats identiques au plugin GIMP FFT. Pour autant que je le vois, cela fait du post-traitement. Mes résultats sont tous un peu désordonnés à très faible contraste, et GIMP semble surmonter cela en ajustant le contraste et en réduisant les canaux non informatifs (dans votre cas, tous les canaux sauf Red sont juste vides). Référez-vous à l'image:

enter image description here

2
Vovanrock2002