• opencv-图像变换:傅里叶变换


    图像变换

    傅里叶变换
    目标
      本小节我们将要学习:
      • 使用 OpenCV 对图像进行傅里叶变换
      • 使用 Numpy 中 FFT(快速傅里叶变换)函数
      • 傅里叶变换的一些用处
      • 我们将要学习的函数有:cv2.dft(),cv2.idft() 等

    原理
      傅里叶变换经常被用来分析不同滤波器的频率特性。我们可以使用 2D 离散傅里叶变换 (DFT) 分析图像的频域特性。实现 DFT 的一个快速算法被称为快速傅里叶变换(FFT)。关于傅里叶变换的细节知识可以在任意一本图像处理或信号处理的书中找到。请查看本小节中更多资源部分。
      对于一个正弦信号:x (t) = A sin (2πf t), 它的频率为 f,如果把这个信号转到它的频域表示,我们会在频率 f 中看到一个峰值。如果我们的信号是由采样产生的离散信号好组成,我们会得到类似的频谱图,只不过前面是连续的,现在是离散。你可以把图像想象成沿着两个方向采集的信号。所以对图像同时进行 X 方向和 Y 方向的傅里叶变换,我们就会得到这幅图像的频域表示(频谱图)。
      更直观一点,对于一个正弦信号,如果它的幅度变化非常快,我们可以说他是高频信号,如果变化非常慢,我们称之为低频信号。你可以把这种想法应用到图像中,图像那里的幅度变化非常大呢?边界点或者噪声。所以我们说边界和噪声是图像中的高频分量(注意这里的高频是指变化非常快,而非出现的次数多)。如果没有如此大的幅度变化我们称之为低频分量。
      现在我们看看怎样进行傅里叶变换。

    Numpy 中的傅里叶变换
      首先我们看看如何使用 Numpy 进行傅里叶变换。Numpy 中的 FFT 包可以帮助我们实现快速傅里叶变换。函数 np.fft.fft2() 可以对信号进行频率转换,输出结果是一个复杂的数组。本函数的第一个参数是输入图像,要求是灰度格式。第二个参数是可选的, 决定输出数组的大小。输出数组的大小和输入图像大小一样。如果输出结果比输入图像大,输入图像就需要在进行 FFT 前补0。如果输出结果比输入图像小的话,输入图像就会被切割。
      现在我们得到了结果,频率为 0 的部分(直流分量)在输出图像的左上角。如果想让它(直流分量)在输出图像的中心,我们还需要将结果沿两个方向平移 N2 。函数 np.fft.fftshift() 可以帮助我们实现这一步。(这样更容易分析)。进行完频率变换之后,我们就可以构建振幅谱了。

    # -*- coding: utf-8 -*-
    """
    Created on Sat Jan 18 15:30:10 2014
    @author: duan
    """
    import cv2
    import numpy as np
    from matplotlib import pyplot as plt
    img = cv2.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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    结果如下:
    在这里插入图片描述
      我们可以看到输出结果的中心部分更白(亮),这说明低频分量更多。
      现在我们可以进行频域变换了,我们就可以在频域对图像进行一些操作了,例如高通滤波和重建图像(DFT 的逆变换)。比如我们可以使用一个60x60 的矩形窗口对图像进行掩模操作从而去除低频分量。然后再使用函数np.fft.ifftshift() 进行逆平移操作,所以现在直流分量又回到左上角了,左后使用函数 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结果如下:
    在这里插入图片描述
      上图的结果显示高通滤波其实是一种边界检测操作。这就是我们在前面图像梯度那一章看到的。同时我们还发现图像中的大部分数据集中在频谱图的低频区域。我们现在已经知道如何使用 Numpy 进行 DFT 和 IDFT 了,接着我们来看看如何使用 OpenCV 进行这些操作。
      如果你观察仔细的话,尤其是最后一章 JET 颜色的图像,你会看到一些不自然的东西(如我用红色箭头标出的区域)。看上图那里有些条带装的结构,这被成为振铃效应。这是由于我们使用矩形窗口做掩模造成的。这个掩模被转换成正弦形状时就会出现这个问题。所以一般我们不适用矩形窗口滤波。最好的选择是高斯窗口。
      OpenCV 中的傅里叶变换
      OpenCV 中相应的函数是 cv2.dft() 和 cv2.idft()。和前面输出的结果一样,但是是双通道的。第一个通道是结果的实数部分,第二个通道是结果的虚数部分。输入图像要首先转换成np.float32 格式。我们来看看如何操作。

    # -*- coding: utf-8 -*-
    """
    Created on Sat Jan 18 15:33:18 2014
    @author: duan
    """
    import numpy as np
    import cv2
    from matplotlib import pyplot as plt
    img = cv2.imread('messi5.jpg',0)
    dft = cv2.dft(np.float32(img),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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注意:你可以使用函数 cv2.cartToPolar(),它会同时返回幅度和相位。

    现在我们来做逆 DFT。在前面的部分我们实现了一个 HPF(高通滤波),现在我们来做 LPF(低通滤波)将高频部分去除。其实就是对图像进行模糊操作。首先我们需要构建一个掩模,与低频区域对应的地方设置为 1, 与高频区域对应的地方设置为 0。

    rows, cols = img.shape
    crow,ccol = rows/2 , cols/2
    # 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    结果如下:
    在这里插入图片描述

    注意:OpenCV 中的函数 cv2.dft() 和 cv2.idft() 要比 Numpy 快。但是Numpy函数更加用户友好。关于性能的描述,请看下面的章节。

    DFT 的性能优化
      当数组的大小为某些值时 DFT 的性能会更好。当数组的大小是 2 的指数时 DFT 效率最高。当数组的大小是 2,3,5 的倍数时效率也会很高。所以如果你想提高代码的运行效率时,你可以修改输入图像的大小(补 0)。对于OpenCV 你必须自己手动补 0。但是 Numpy,你只需要指定 FFT 运算的大小,它会自动补 0。
      那我们怎样确定最佳大小呢?OpenCV 提供了一个函数:cv2.getOptimalDFTSize()。它可以同时被 cv2.dft() 和 np.fft.fft2() 使用。让我们一起使用 IPython的魔法命令%timeit 来测试一下吧。

    In [16]: img = cv2.imread('messi5.jpg',0)
    In [17]: rows,cols = img.shape
    In [18]: print rows,cols
    342 548
    In [19]: nrows = cv2.getOptimalDFTSize(rows)
    In [20]: ncols = cv2.getOptimalDFTSize(cols)
    In [21]: print nrows, ncols
    360 57
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    看到了吧,数组的大小从(342,548)变成了(360,576)。现在我们为它补 0,然后看看性能有没有提升。你可以创建一个大的 0 数组,然后把我们的数据拷贝过去,或者使用函数 cv2.copyMakeBoder()。

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

    或者:

    right = ncols - cols
    bottom = nrows - rows
    #just to avoid line breakup in PDF file
    bordertype = cv2.BORDER_CONSTANT
    nimg = cv2.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)

    现在我们看看 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= cv2.dft(np.float32(img),flags=cv2.DFT_COMPLEX_OUTPUT)
    100 loops, best of 3: 13.5 ms per loop
    In [27]: %timeit dft2= cv2.dft(np.float32(nimg),flags=cv2.DFT_COMPLEX_OUTPUT)
    100 loops, best of 3: 3.11 ms per loop

    也提高了 4 倍,同时我们也会发现 OpenCV 的速度是 Numpy 的 3 倍。你也可以测试一下逆 FFT 的表现。
    为什么拉普拉斯算子是高通滤波器?
      我在论坛中遇到了一个类似的问题。为什么拉普拉斯算子是高通滤波器?为什么 Sobel 是 HPF?等等。对于第一个问题的答案我们以傅里叶变换的形式给出。我们一起来对不同的算子进行傅里叶变换并分析它们:

    # -*- coding: utf-8 -*-
    """
    Created on Sat Jan 18 15:37:28 2014
    @author: duan
    """
    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 guassian filter
    x = cv2.getGaussianKernel(5,10)
    #x.T 为矩阵转置
    gaussian = x*x.T
    # different edge detecting filters
    # 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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    结果:
    在这里插入图片描述
      从图像中我们就可以看出每一个算子允许通过那些信号。从这些信息中我们就可以知道那些是 HPF 那是 LPF。

  • 相关阅读:
    Java基础之循环语句
    Qt的QObject类
    JVM 执行引擎部分 (编译器、解释器)
    Go连接Redis:快速入门
    JavaFx 生成二维码工具类封装
    运维的进阶:用它解决90%以上问题
    springcloud-eureka--集群搭建
    Ginvel是Golang的一款web MVC框架,基础特性基于Gin、gRPC、Go1.17+、Redis5+
    Python描述 LeetCode 81. 搜索旋转排序数组 II
    笔记应用选择和知识管理
  • 原文地址:https://blog.csdn.net/yyyyyya_/article/details/125551712