傅立葉轉換(四)


在〈傅立葉轉換(三)〉前,談到的是一維的訊號,雖然最後硬是舉了個 x 方向有灰階度變化,而 y 方向沒有的例子,然而影像實際上就是二維的,有沒有二維的傅立葉轉換轉換呢?

確實是有〈二維傅立葉轉換〉,也就是若有訊號是二維形式,可使用 f(p1, p2) 來表示,可以轉換為 F(ξ1, ξ2),ξ1、ξ2 是二維的頻率。

二維訊號的例子之一,是想像有個水中的震動源,在不斷振動下產生的漣漪,那麼 p1、p2 可以是指什麼呢?因為時間只有一個方向,難以想像時域 f(t1, t2) 描述這訊號的方式,然而,若是在某個時間點,漣漪的位置與水平面的高低關係,就容易想像了。

也就是說,在某個時間點,可以使用 f(x, y) 來表示漣漪在位置 (x, y) 的水平面高低關係,也就是從空間域(Spatial domain)而不是時域的觀點,〈二維傅立葉轉換〉中談到的二維傅立葉級數,則是將漣漪以許多的小漣漪疊加來表示,二維傅立葉轉換後的結果,就是這些小漣漪的頻域表示。

水中有個單一振動源時,會產生一圈圈的漣漪,若是有多個振動源,就會構成波光粼粼,某些程度上,就會像是以二維 Pelin 雜訊建立的灰階圖:

傅立葉轉換(四)

也就是說,二維傅立葉轉換是個從空間域到頻域的轉換,就水波而言,空間域 f(x, y) 表示在位置 (x, y) 的水平面高低關係,二維傅立葉轉換後的結果,可以看到它是由哪些頻率組成。

將之應用在圖片上,空間域 f(x, y) 表示在位置 (x, y) 的灰階值,二維傅立葉轉換後的結果,可以看到它是由哪些灰階度週期變化組成,在〈傅立葉轉換(三)〉已經看過一維灰階度週期變化的樣子,二維的話,可以想像許多不同的灰階圖,每個灰階圖視覺上像個小漣漪,最後這些小漣漪疊加成一張灰階圖。

當你將灰階圖進行二維傅立葉轉換,得到的頻域表示就是這些小漣漪的頻率,也就是所謂的圖像頻率。

NumPy 提供了二維傅立葉轉換的實作函式 fft2,〈傅立葉轉換(二)〉中談過的一維傅立葉轉換,轉換後是含複數的一維陣列,fft2 轉換後是含複數的二維陣列。

如果取振幅絕對值並標準化 0 ~ 255,作為圖片顯示,會是個以圖片左上角開始,越外圍表示越高頻的圖像,通常我們會將之位移至中心以便觀察,這可以透過 fftshift 函式,NumPy 也提供了二維傅立葉逆轉換 ifft2

以上面的 Perlin 雜訊圖片為例:

import cv2
import numpy as np

img = cv2.imread('perlin2d.jpg', cv2.IMREAD_GRAYSCALE)

f = np.fft.fft2(img)
shifted = np.fft.fftshift(f) # 將頻率 (0, 0) 位移至中心

amp = np.abs(shifted)
cv2.imshow('FFT 2D', amp / np.max(amp) * 255)    # 頻域表示

inversed = np.fft.ifft2(f).real.astype('uint8')  # 逆轉換
cv2.imshow('INVERSE FFT 2D', inversed)           

cv2.waitKey(0)
cv2.destroyAllWindows()

這會顯示以下的圖案,因為已經將頻率 (0, 0) 位移至中心,從這張圖中心很亮來看,這張 Perlin 雜訊圖片有很大的低頻成分:

傅立葉轉換(四)

OpenCV 本身也有提供傅立葉轉換的相關實作,cv2.dft 接受影像資料,必須是 float32 型態,flags 參數指定 cv2.DFT_COMPLEX_OUTPUT 表示轉換為複數輸出,不過輸出會分為實數與虛數兩個部份,若 f 是輸出結果,可以透過 f[:,:,0] 取得實數部份,f[:,:,1] 取得虛數部份。

在取得實數與虛數部份後,雖然可以自行實作標準化,然而可以透過 cv2.magnitude 指定實數與虛數部份取得振幅大小,再透過 cv2.normalize 標準化為 0 ~ 255。

傅立葉逆轉換的話,可以透過 cv2.dft 指定 flagscv2.DFT_INVERSE,或者直接使用 cv2.idft

以下是透過 OpenCV 的來進行傅立葉轉換與逆轉換,執行結果與上面的圖像是相同的:

import cv2
import numpy as np

img = cv2.imread('perlin2d.jpg', cv2.IMREAD_GRAYSCALE)

f = cv2.dft(np.float32(img))

shifted = np.fft.fftshift(f) # 將頻率 (0, 0) 位移至中心
freq = cv2.normalize(
           cv2.magnitude(shifted[:,:,0], shifted[:,:,1]), 
           None, 
           0, 255, 
           cv2.NORM_MINMAX)
cv2.imshow('FFT 2D', freq)    # 頻域表示

inversed = cv2.dft(f, flags = cv2.DFT_INVERSE)  # 逆轉換,也可以使用 cv2.idft(f)
inversed_img = cv2.normalize(
                   cv2.magnitude(inversed[:,:,0], inversed[:,:,1]), 
                   None, 
                   0, 255, 
                   cv2.NORM_MINMAX
               ).astype('uint8')
cv2.imshow('INVERSE FFT 2D', inversed_img)

cv2.waitKey(0)
cv2.destroyAllWindows()

知道如何進行圖像的傅立葉轉換與逆轉換後,來複習一下雜訊,之前的文件談過,椒鹽雜訊是高頻雜訊,如果在圖像上產生椒鹽雜訊,會如何呢?

import cv2
import numpy as np

# 椒鹽雜訊
def salt_pepper_noise(image, fraction, salt_vs_pepper):
    img = np.copy(image)
    size = img.size
    num_salt = np.ceil(fraction * size * salt_vs_pepper).astype('int')
    num_pepper = np.ceil(fraction * size * (1 - salt_vs_pepper)).astype('int')
    row, column = img.shape

    x = np.random.randint(0, column - 1, num_pepper)
    y = np.random.randint(0, row - 1, num_pepper)
    img[y, x] = 0

    x = np.random.randint(0, column - 1, num_salt)
    y = np.random.randint(0, row - 1, num_salt)
    img[y, x] = 255
    return img

def fftshow(title, img):
    f = np.fft.fft2(img)
    shifted = np.fft.fftshift(f)
    amp = np.abs(shifted)
    fimg = amp / np.max(amp) * 255
    cv2.imshow(title, fimg)

img = cv2.imread('caterpillar.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('caterpillar', img)
fftshow('caterpillar FFT', img)

noisy = salt_pepper_noise(img, 0.1 , 0.5)
cv2.imshow('noisy', noisy)

fftshow('Noisy FFT', noisy)

cv2.waitKey(0)
cv2.destroyAllWindows()

產生的圖像如下:

傅立葉轉換(四)

可以看到原圖像的頻域與產生雜訊後的頻域比較結果,從中心開始,越外圈頻率越高,原圖像在外圍本來沒什麼訊號,在加上椒鹽雜訊後,顯然地,就增加了大量的高頻訊號。

在〈傅立葉轉換(二)〉中看過,透過一維的傅立葉轉換與逆轉換,可以實現濾波的功能,在圖像上也可以這麼做,這就在下篇文件再來談…