OpenCV-Python -- Fourier Transform

X_Imagine發表於2020-12-23

學習目標

  • 使用OpenCV計算傅立葉變換
  • 使用Numpy中的傅立葉變換(FFT)
  • 傅立葉變換的應用
  • 學習函式如下:cv2.dft()cv2.idft()

理論

傅立葉變換用來分析不同濾波器的頻率特性。對於影像而言,2D離散傅立葉變換(DFT)用於尋找頻率域。傅立葉變換的快速演算法,FFT,常用於計算DFT

對於正弦訊號, x ( t ) = A s i n ( 2 π f t ) x(t) = Asin(2\pi ft) x(t)=Asin(2πft),我們稱f為頻率訊號,如果頻率域確定,那麼我們可以看到f的具體形狀(spike)。如果一個訊號是離散取樣的,我們得到相同的頻率域,週期的範圍, [ − π , π ] , o r [ 0 , 2 π ] ( o r f o r N − P o i n t 的 D F T , [ 0 , N ] ) [-\pi ,\pi],or[0, 2\pi](or for N-Point的DFT,[0,N]) [π,π]or[0,2π](orforNPointDFT[0,N])。可以將影像看成從兩個方向取樣的訊號。那麼從X和Y方向取樣可以得到影像的表達。

更為直觀的說,對於正弦訊號,如果短時間內振幅變化很快,那麼可以稱為高頻訊號。如果變化很慢,則稱之為低頻訊號。如果將該思想擴充套件到影像中,那麼影像的什麼區域振幅較大,通常是邊界點或者噪聲。所以,我們可以說,邊界和噪聲是影像中的高頻訊號。反之則為低頻區域。

Numpy中的傅立葉變換

首先我們學習如何利用Numpy計算傅立葉變換。Numpy有相應的FFT包。np.fft.fft2()提供了頻率變換,它是複陣列。第一個輸入引數是灰度影像。第二個引數是可選的,決定了輸出陣列的大小。如果輸出陣列大於輸入影像,那麼輸入影像會被填充零,然後計算FFT。如果小於輸入影像,那麼輸入影像會被裁剪。如果沒有傳入引數,輸出陣列大小與輸入相同。

計算得到結果,零頻域部分(DC component)在左上角。如果需要將其移入中心,那麼需要偏移N/2。可以使用函式計算:np.fft.fftshift(). 得到頻率變換後,也可以得到振幅譜。

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

執行結果如下:
在這裡插入圖片描述
從圖中可以看出,白色區域在中心,表示低頻區域更多。得到頻域變換後,可以在頻率域進行一些操作,比如高通濾波和重建影像,或者計算DFT的逆操作。通過設計一個60x60大小的矩形視窗可以濾除低頻部分。然後應用逆操作,np.fft.ifftshift(),那麼DC區域會變換到左上角。並計算FFT的逆操作,np.ifft2()。得到的結果是複數,取絕對值:

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

執行結果如下:
在這裡插入圖片描述
從結果可以看出,高通濾波器是邊界檢測器。在影像梯度的章節我們也看到類似的效果。同樣可以得到,影像大部分是低頻區域。

OpenCV中傅立葉變換

OpenCV提供的函式:cv2.dft()cv2.idft(). 返回結果與上面的一樣,但是通道為2. 第一個通道是結果的實部,第二個通道是結果的虛部。輸入影像必須轉為np.float32,下面是程式碼示例:

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

執行結果如下:
在這裡插入圖片描述

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

執行結果如下:
在這裡插入圖片描述

優化DFT

某些大小的陣列進行DFT會非常快,比如陣列大小時 2 n 2^n 2n. 如果需要提速,可以對陣列進行零填充,然後進行DFT。對於OpenCV,需要人工填充零,而Numpy中,需要指定FFT的大小引數即可,然後會自動填充零。

所以,最佳的陣列大小是?OpenCV提供了函式:cv2.getOptimalDFTSize() 。該函式可以應用於cv2.dft()cv2.idft(),下面來驗證:
在這裡插入圖片描述

為什麼Laplacian是高通濾波器?

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

執行結果:
在這裡插入圖片描述

相關文章