官方文档 – https://docs.opencv.org/3.4.0/de/dbc/tutorial_py_fourier_transform.html
傅里叶变换用于分析各种滤波器的频率特性。对于图像,二维离散傅里叶变换(2D Discrete Fourier Transform/DFT)用于寻找频域。快速傅里叶变换(Fast Fourier Transform/FFT)的快速算法用于计算DFT。
对于一个正弦信号,x(t)=Asin(2πft)
,我们可以说 f 是信号的频率,如果它的频率域被接受,我们可以看到 f 的峰值。如果信号被采样来形成一个离散信号,我们得到相同的频率域,但是在[−π,π] or [0,2π]范围内是周期性的(或者N-point的DFT,[0,N])。你可以把图像看作一个信号,从两个方向进行采样。所以在X和Y方向上进行傅里叶变换就会得到图像的频率表示。
更直观的是,对于正弦信号,如果振幅在短时间内变化得非常快,你可以说它是一个高频信号。如果它变化缓慢,它是一个低频信号。你可以把同样的想法扩展到图片上。图像的振幅在哪里变化?在边缘点,或噪音。所以我们可以说,边和噪声是图像中的高频内容。如果振幅没有很大的变化,那就是低频分量。
首先,我们将看到如何使用Numpy找到傅里叶变换。Numpy有一个FFT包来完成这个工作。np.fft.fft2()
为我们提供了一个复杂数组的频率转换。它的第一个参数是输入图像,它是灰度图像。第二个参数是可选的,它决定了输出数组的大小。如果它大于输入图像的大小,则输入图像在计算FFT之前填充了0。如果它小于输入图像,输入图像将被裁剪。如果没有参数传递,输出数组的大小将与输入相同。
一旦得到了结果,零频率组件(DC组件)将位于左上角。如果你想把它带到中心,你需要在两个方向上通过传入 N/2 来改变结果。这是由函数np.fft.fftshift()
完成的。(它更容易分析)。一旦你找到频率变换,你就能找到大小谱。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
img = cv.imread('messi5.jpg', 0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
你可以在中心看到更多的白色区域,表示低频率的内容更多。
所以你找到了频率变换。现在你可以在频域做一些运算,比如高通滤波和重建图像,也就是找到逆DFT。为此,你只需用一个矩形窗口大小的60x60来移除低频部分。然后使用np.fft.ifftshift()
应用反向移动,使DC组件再次出现在左上角。然后使用np.ifft2()
函数找到反FFT。再一次结果,将会是一个复数。你可以取它的绝对值。
rows, cols = img.shape
crow, ccol = rows/2, cols/2
fshift[crow-30:crow+30, ccol-30:ccol+30] = 0
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.abs(img_back)
plt.subplot(131),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132),plt.imshow(img_back, cmap = 'gray')
plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
plt.subplot(133),plt.imshow(img_back)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])
plt.show()
结果表明,高通滤波是一种边缘检测操作。这是我们在图像梯度章节中看到的。这也表明,大多数图像数据都存在于低频区域。
如果您仔细观察结果,特别是JET颜色的最后一个图像,您可以看到一些工件(我用红色箭头标记的一个实例)。它显示了一些波纹状的结构,叫做振铃效应。它是由我们用来屏蔽的矩形窗口造成的。这个掩模被转换成sinc形状,导致了这个问题。因此,矩形窗口不用于过滤。更好的选择是高斯窗口。
OpenCV提供了cv.dft()
和cv.idft()
函数。它返回与前面相同的结果,但是有两个通道。第一个通道将会有结果的实部,第二个通道将会有一个虚部。输入图像首先应该转换为np.float32
。我们将会看到如何去做。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('messi5.jpg', 0)
dft = cv.dft(np.float32(img), flags=cv.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20 * np.log(cv.magnitude(dft_shift[:,:,0], dft_shift[:,:,1]))
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
你也可以使用cv.cartToPolar()
,它可以在一次拍摄中同时返回大小和相位。
现在我们要做的是逆DFT。在前面,我们创建了一个HPF,这次我们将看到如何移除图像中的高频内容,即我们将LPF应用到图像中。它实际上模糊了图像。为此,我们先创建一个具有高值(1)低频率的掩码,即我们通过低频内容,而在高频区域则是0。
rows, cols = img.shape
crow, ccol = rows/2, cols/2
# 首先创建一个掩码,中心方为1,其余为0
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
# 应用掩码,反DFT
fshift = dft_shift * mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv.idft(f_ishift)
img_back = cv.magnitude(img_back[:,:,0], img_back[:,:,1])
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()
对于某些数组的大小来说,DFT的计算性能更好。当数组大小为2时,它是最快的。这些数组的大小是2、3和5的乘积,它们的处理效率也相当高。因此,如果您担心代码的性能,您可以在找到DFT之前将数组的大小修改为任何最佳大小(通过填充零)。对于OpenCV,你必须手动填充0。但是对于Numpy,您指定了FFT计算的新大小,它将自动为您填充0。
那么如何找到这个最优的大小呢?OpenCV为这个提供了一个函数,cv.getOptimalDFTSize()
。它适用于cvdft()
和np.fft.fft2()
。
In [16]: img = cv.imread('messi5.jpg',0)
In [17]: rows,cols = img.shape
In [18]: print("{} {}".format(rows,cols))
342 548
In [19]: nrows = cv.getOptimalDFTSize(rows)
In [20]: ncols = cv.getOptimalDFTSize(cols)
In [21]: print("{} {}".format(nrows,ncols))
360 576
大小(342,548)被修改为(360,576)。现在让我们用0(对于OpenCV)来填充它,并找到它们的DFT计算性能。您可以通过创建一个新的大型零数组并将数据复制到它,或者使用cv.copyMakeBorder()
来实现它。
nimg = np.zeros((nrows, ncols))
nimg[:rows, :cols] = img
OR
right = ncols - cols
bottom = nrows - rows
bordertype = cv.BORDER_CONSTANT # 只是为了避免PDF文件中的行拆分
nimg = cv.copyMakeBorder(img, 0, bottom, 0, right, bordertype, value=0)
现在我们计算DFT NumPy功能性能比较:
In [22]: %timeit fft1 = np.fft.fft2(img)
10 loops, best of 3: 40.9 ms per loop
In [23]: %timeit fft2 = np.fft.fft2(img,[nrows,ncols])
100 loops, best of 3: 10.4 ms per loop
它显示了一个4倍的加速。现在我们来试试OpenCV函数。
In [24]: %timeit dft1= cv.dft(np.float32(img),flags=cv.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 13.5 ms per loop
In [27]: %timeit dft2= cv.dft(np.float32(nimg),flags=cv.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 3.11 ms per loop
它也显示了一个4倍的加速。您还可以看到,OpenCV函数的速度比Numpy函数快了大约3倍
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 无标度参数的简单平均滤波器
mean_filter = np.ones((3,3))
# 创建一个高斯滤波器
x = cv.getGaussianKernel(5,10)
gaussian = x*x.T
# 不同的边缘检测滤波器
# scharr in x-direction
scharr = np.array([[-3, 0, 3],
[-10,0,10],
[-3, 0, 3]])
# sobel in x direction
sobel_x= np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]])
# sobel in y direction
sobel_y= np.array([[-1,-2,-1],
[0, 0, 0],
[1, 2, 1]])
# laplacian
laplacian=np.array([[0, 1, 0],
[1,-4, 1],
[0, 1, 0]])
filters = [mean_filter, gaussian, laplacian, sobel_x, sobel_y, scharr]
filter_name = ['mean_filter', 'gaussian','laplacian', 'sobel_x', \
'sobel_y', 'scharr_x']
fft_filters = [np.fft.fft2(x) for x in filters]
fft_shift = [np.fft.fftshift(y) for y in fft_filters]
mag_spectrum = [np.log(np.abs(z)+1) for z in fft_shift]
for i in xrange(6):
plt.subplot(2,3,i+1),plt.imshow(mag_spectrum[i],cmap = 'gray')
plt.title(filter_name[i]), plt.xticks([]), plt.yticks([])
plt.show()