Python 对图像进行 FFT

FFT on image with Python

我在 Python 中执行 FFT 时遇到问题。我有完全奇怪的结果。 好的,我想打开图像,获取 RGB 中每个像素的值,然后我需要对其使用 fft,然后再次转换为图像。

我的步数:

1) 我在 Python 中使用 PIL 库打开图像,就像这样

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

2) 我正在获取像素

pixels = list(im.getdata())

3) 我将每个像素分离为 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).假设我有一个像素 (111,111,111)。并像这样对所有红色值使用 fft

red = np.fft.fft(red)

然后:

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

我的输出是:

(53866+0j) 111 111

我认为这是完全错误的。我的图像是 64x64,而 gimp 的 FFT 完全不同。实际上,我的 FFT 只给我具有巨大值的数组,这就是为什么我的输出图像是黑色的。

你知道问题出在哪里吗?

[编辑]

我已经按照建议改成了

red= np.fft.fft2(red)

然后我缩放它

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

而且,我仍然只得到黑色图像。

[EDIT2]

好的,让我们拍一张吧。

假设我不想打开它并保存为灰度图像。所以我就是这样做的。

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

在此之后,我得到这张图片 ,没问题。所以现在,我想在我将它保存到新图像之前对我的图像进行 fft,所以我这样做

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

加载后。将其保存到文件后,我有 . So lets try now open test.png with gimp and use FFT filter plugin. I'm getting this image, which is correct

我该如何处理?

这里有几个问题。

1) 手动转灰度不好。使用 Image.open("test.png").convert('L')

2) 很可能类型有问题。在不确定类型是否兼容的情况下,不应将 np.ndarrayfft2 传递给 PIL 图像。 abs(np.fft.fft2(something)) 会 return 你一个 np.float32 类型的数组或类似这样的东西,而 PIL 图像将接收类似 np.uint8.

类型数组的东西

3) 评论中建议的缩放看起来不对。您实际上需要您的值适合 0..255 范围。

这是我解决这 3 点的代码:

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

我必须承认我没有设法获得与 GIMP FFT 插件相同的结果。据我所知,它做了一些 post 处理。我的结果都是非常低的对比度混乱,GIMP 似乎通过调整对比度和缩小非信息通道来克服这个问题(在你的情况下,除了红色之外的所有通道都是空的)。参考图片:

好问题。我从没听说过,但是 Gimp Fourier 插件看起来真的很简洁:

A simple plug-in to do fourier transform on you image. The major advantage of this plugin is to be able to work with the transformed image inside GIMP. You can so draw or apply filters in fourier space, and get the modified image with an inverse FFT.

这个想法——对频域数据进行 Gimp 风格的操作并转换回图像——非常酷!尽管多年来一直使用 FFT,但我从未想过要这样做。让我们在 Python!

中这样做,而不是搞乱 Gimp 插件和 C 可执行文件和丑陋

警告。 我尝试了多种方法来做到这一点,试图从原始输入图像中获得接近输出 Gimp 傅里叶图像(带有波纹图案的灰色)的东西,但我根本做不到。 Gimp 图像似乎围绕图像中间有点对称,但它既没有垂直翻转也没有水平翻转,也不是转置对称的。我希望该插件使用真正的 2D FFT 将 H×W 图像转换为频域中的 H×W 实值数据数组,在这种情况下不会有对称性(它只是 -复杂的 FFT,对于像图像这样的实值输入是共轭对称的)。所以我放弃了尝试对 Gimp 插件的功能进行逆向工程,转而研究如何从头开始。

代码。非常简单:读取图像,在前两个维度应用scipy.fftpack.rfft得到“频率图像”,重新缩放到0-255 , 并保存。

请注意这与其他答案有何不同! 无灰度—2D 实对实 FFT 在所有三个通道上独立发生。 不需要abs:频域图像可以合法地具有负值,如果将它们设为正值,则无法恢复原始图像。 (还有一个不错的功能:不影响图像大小。数组的大小在 FFT 前后保持不变,无论 width/height 是偶数还是奇数。)

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

(旁白:FFT 极客笔记。 查看 rfft 的文档了解详情,但我使用了 Scipy 的 FFTPACK 模块,因为它的 rfft 将单个像素的实部和虚部交织为两个相邻的实数值,保证任何大小的 2D 图像(偶数与奇数,宽度与高度)的输出将被保留。这与 Numpy 的形成对比numpy.fft.rfft2,因为它 returns 大小为 width/2+1 的复杂数据 height/2+1,迫使您处理一个额外的 row/column 并处理去交织复杂到-真实的自己。谁需要为这个应用程序那么麻烦。)

结果。 给定名为 test.png:

的输入

此代码段产生以下输出(全局 min/max 已重新缩放并量化为 0-255):

并放大:

在此频率图像中,DC(0 Hz 频率)分量位于左上角,频率随着向右和向下移动而升高。

现在,让我们看看当您以几种方式处理这张图片时会发生什么。让我们使用 cat photo.

而不是这个测试图像

我在 Gimp 中制作了一些蒙版图像,然后将其加载到 Python 中并将频率图像相乘以查看蒙版对图像的影响。

代码如下:

# 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')

左侧是 低通滤波器 蒙版,右侧是结果——点击查看全分辨率图像:

在蒙版中,黑色 = 0.0,白色 = 1.0。所以最低频率保留在这里(白色),而高频率被阻止(黑色)。这通过衰减高频使图像模糊。低通滤波器无处不在,包括抽取(“下采样”)图像时(尽管它们的形状比我在 Gimp 中绘制要仔细得多)。

这是一个 带通滤波器 ,其中保留了最低频率(看到左上角的那一点白色?)和高频,但保留了中频 -频率被阻止。很奇怪!

这里是一个高通滤波器,上面蒙版中留白的左上角被涂黑了:

这就是边缘检测的工作原理。

后记。某人,使用这种技术制作一个网络应用程序,让您绘制蒙版并将它们实时应用于图像!!!