opencv24:傅里叶变换

目标

在本节中,将学习

  • 使用OpenCV查找图像的傅立叶变换
  • 利用Numpy中可用的FFT函数
  • 傅立叶变换的某些应用程序
  • 函数:cv2.dft()cv2.idft()

理论

傅立叶变换用于分析各种滤波器的频率特性。对于图像,使用2D离散傅里叶变换(DFT)查找频域。一种称为**快速傅立叶变换(FFT)**的快速算法用于DFT的计算。关于这些的详细信息可以在任何图像处理或信号处理教科书中找到。

对于正弦信号 x ( t ) = A sin ⁡ ( 2 π f t ) x(t)=A\sin(2\pi ft) x(t)=Asin(2πft), 可以说f是信号的频率,如果采用其频域,则可以看到f的尖峰。如果对信号进行采样以形成离散信号,将获得相同的频域,但是在[ − π , π ][ 0 , 2 π ]范围内(对于N点DFT为[ 0 , N ])是周期性的。可以将图像视为在两个方向上采样的信号。因此,在X和Y方向都进行傅立叶变换,可以得到图像的频率表示

更直观地说,对于正弦信号如果幅度在短时间内变化非常快,则可以说它是高频信号。如果变化缓慢,则为低频信号。可以将相同的想法扩展到图像。图像中的振幅在哪里急剧变化?在边缘点或噪声。因此,可以说边缘和噪声是图像中的高频内容。如果幅度没有太大变化,则它是低频分量。

现在,将学习如何进行傅立叶变换。

Numpy中的傅里叶变换

首先,将看到如何使用Numpy进行傅立叶变换。Numpy具有FFT软件包来执行此操作。np.fft.fft2()提供了频率转换,它将是一个复杂的数组。

np.fft.fft2()

  • 第一个参数是输入图像,即灰度图像。
  • 第二个参数是可选的,决定输出数组的大小。
    如果它大于输入图像的大小,则在计算FFT之前用零填充输入图像。如果小于输入图像,将裁切输入图像。如果未传递任何参数,则输出数组的大小将与输入的大小相同。

现在,一旦获得结果,零频率分量(DC分量)将位于左上角。如果要使其居中,则需要在两个方向上将结果都移动 N 2 \frac{N}{2} 2N 。也可以通过函数np.fft.fftshift()完成。找到频率变换后,就可以找到幅度谱。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('messi.png', 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()

结果看起来像下面这样:

opencv24:傅里叶变换_第1张图片
可以看到,在中心看到更多白色区域,这表明低频内容更多

因此,发现了频率变换,可以在频域中进行一些操作例如高通滤波和重建图像,即找到逆DFT。为此,只需用尺寸为60x60的矩形窗口遮罩即可消除低频。然后,使用np.fft.ifftshift()应用反向移位,以使DC分量再次出现在左上角。然后使用np.ifft2()函数找到逆FFT。同样,结果将是一个复数。可以采用其绝对值。

# ifft
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('messi.png', 0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)

rows, cols = img.shape
crow, ccol = rows//2, cols//2
fshift[crow-30: crow+31, ccol-30:ccol+31] = 0
f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)
img_back = np.real(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, cmap='gray')
plt.title('Result in JET')
plt.xticks([])
plt.yticks([])

plt.show()

结果看起来像下面这样:

opencv24:傅里叶变换_第2张图片

结果表明高通滤波是边缘检测操作。这就是在“图像渐变”一章中看到的。这也表明大多数图像数据都存在于频谱的低频区域
如果仔细观察结果,尤其是最后一张JET颜色的图像,会看到一些伪像(用红色箭头标记的一个实例)。它在那里显示出一些波纹状结构,称为振铃效应(ringings effects)。这是由用于遮罩的矩形窗口引起的此掩码转换为正弦形状,从而导致此问题。因此,矩形窗口不用于过滤。更好的选择是高斯窗口。

OpenCV中的傅里叶变换

OpenCV为此提供了cv2.dft()cv2.idft()函数。它返回与前一个相同的结果,但是有两个通道。

  • 第一个通道是结果的实部
  • 第二个通道是结果的虚部。

输入图像首先应转换为np.float32

# opencv
import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('messi.png', 0)
img_32 = np.float32(img)
dft = cv2.dft(img_32, flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

magnitude_spectrum = 20*np.log(cv2.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()

opencv24:傅里叶变换_第3张图片

注意
还可以使用cv2.cartToPolar(),它在单个镜头中同时返回幅值和相位

现在要做DFT的逆变换。在上一节中创建了一个HPF(高通滤波),这次将看到如何删除图像中的高频内容,即将LPF(低通滤波)应用到图像中。它实际上模糊了图像。为此,首先创建一个高值(1)在低频部分,即过滤低频内容,0在高频区。

rows, cols = img.shape
crow, ccol = rows//2, cols//2
print(crow, ccol)

# create a mask first, center square is 1, remaining all zeros
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1

# apply mask and inverse DFT
fshift = dft_shift * mask
f_ishift = np.fft.ifftshift(fshift)

img_back = cv2.idft(f_ishift)
img_back = cv2.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()

看看结果:
opencv24:傅里叶变换_第4张图片

注意
通常,OpenCV函数cv2.dft()cv2.idft()比Numpy函数更快。但是Numpy函数更容易使用

DFT的性能优化

对于某些数组尺寸,DFT的计算性能较好。当数组大小为2的幂时,速度最快。对于大小为2、3和5的乘积的数组,也可以非常有效地进行处理。因此,如果担心代码的性能,可以在找到DFT之前将数组的大小修改为任何最佳大小(通过填充零)。对于OpenCV而言,必须手动填充零。但是对于Numpy,指定FFT计算的新大小,它将自动填充零。

那么如何找到最优的大小呢?OpenCV为此提供了一个函数,cv2.getOptimalDFTSize()。它同时适用于cv2.dft()np.fft.fft2()。使用IPython魔术命令timeit来检查它们的性能。

# performance
img = cv2.imread('messi.png', 0)
rows, cols = img.shape
print("{}, {}".format(rows, cols))
# 259, 419

nrows = cv2.getOptimalDFTSize(rows)
ncols = cv2.getOptimalDFTSize(cols)
print("{}, {}".format(nrows, ncols))
# 270, 432

可以看到,将大小(259, 419)修改为(270,432)。现在用零填充(对于OpenCV),并找到其DFT计算性能。可以通过创建一个新的零数组并将数据复制到其中来完成此操作,或者使用cv2.copyMakeBorder()

nimg = np.zeros((nrows,ncols))
nimg[:rows,:cols] = img

或者:

right = ncols - cols
bottom = nrows - rows
bordertype = cv2.BORDER_CONSTANT #只是为了避免PDF文件中的行中断
nimg = cv2.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)

现在,计算Numpy函数的DFT性能比较:

%timeit fft1 = np.fft.fft2(img)
# 15.4 ms ± 1.22 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit fft2 = np.fft.fft2(img,[nrows,ncols])
# 7.55 ms ± 742 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

通过用0填充改变尺寸,性能有了2倍的加速。

现在将尝试使用OpenCV函数。

%timeit dft1= cv2.dft(np.float32(img),flags=cv2.DFT_COMPLEX_OUTPUT)
# 3.21 ms ± 217 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit dft2= cv2.dft(np.float32(nimg),flags=cv2.DFT_COMPLEX_OUTPUT)
# 989 µs ± 91.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

可以看到,性能有了4倍的加速。还可以看到OpenCV函数比Numpy函数快5倍左右。

为什么拉普拉斯算子是高通滤波器?

为什么拉普拉斯变换是高通滤波器? 为什么Sobel是HPF?。第一个答案是关于傅里叶变换的。只需采取Laplacian的傅立叶变换,以获得更高尺寸的FFT:

# laplacian is high pass filter
import cv2
import numpy as np
from matplotlib import pyplot as plt

# simple averaging filter without scaling parameter
mean_filter = np.ones((3, 3))

# creating a gaussian filter
x = cv2.getGaussianKernel(5, 10)
gaussian = x*x.T

# different edge detecting filters
# scharr in x-direction
scharr_x = 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 directio
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_x]
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 = [20*np.log(np.abs(z)+1) for z in fft_shift]

for i in range(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()

看看结果:

opencv24:傅里叶变换_第5张图片

从图像中,可以看到每种内核阻止的频率区域以及它允许经过的区域。从这些信息中,可以说出为什么每个内核都是HPF或LPF

附加资源

  • https://docs.opencv.org/4.1.2/de/dbc/tutorial_py_fourier_transform.html
  • 傅里叶变换的直观解释
  • 傅里叶变换
  • 图像中的频率域指什么?
  • 频域低通滤波、频域高通滤波

你可能感兴趣的:(#,OpenCV,opencv,python,傅里叶变换)