在〈傅立葉轉換(三)〉前,談到的是一維的訊號,雖然最後硬是舉了個 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
指定 flags
為 cv2.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()
產生的圖像如下:
可以看到原圖像的頻域與產生雜訊後的頻域比較結果,從中心開始,越外圈頻率越高,原圖像在外圍本來沒什麼訊號,在加上椒鹽雜訊後,顯然地,就增加了大量的高頻訊號。
在〈傅立葉轉換(二)〉中看過,透過一維的傅立葉轉換與逆轉換,可以實現濾波的功能,在圖像上也可以這麼做,這就在下篇文件再來談…