傅立葉轉換(二)


在〈傅立葉轉換(一)〉的最後,知道了訊號可以分別從時域(Time domain)及從頻域(Frequency domain)來觀察,根據維基百科〈傅立葉轉換〉,傅立葉提出了一個轉換公式,對於一個可積分的函式 f(x),可以將之轉換為另一函式 F(ξ),若 ξ 表示頻率,F(ξ) 是:

傅立葉轉換(二)

傅立葉轉換簡單來說,是將一個函式轉換為另一個函式,若 x 代表時間,就表示可以將一個時域的函式,轉換為頻域的函式。

NumPy 提供了傅立葉轉換的實作,嚴格來說,是〈快速傅立葉變換〉,是基於電腦運算本身為離散性、改進了計算複雜度而來,NumPy 的傅立葉轉換實作是由 numpy.fft 提供。

來看看基本的應用的場合,假設你取樣了一段訊號 samples

import numpy as np
import matplotlib.pyplot as plt

t = 2              # 取樣時間
sample_rate = 800  # 取樣率,每秒取幾個樣本

...有個 singal 實作,用來對訊號取樣

samples = signal(t, sample_rate) 
x = np.linspace(0, t, int(t * sample_rate), endpoint = False)

plt.plot(x, samples)
plt.show()

傅立葉轉換(二)

若想知道這個訊號,是由哪些頻率的訊號組成,可以透過 numpy.fft.ftt 函式,求得傅立葉轉換後的結果:

sp = np.fft.fft(samples)

取得的結果會是一組複數,每個複數代表著某個頻率下,弦波的相位與振幅:

[ 1.20044946e-13+0.00000000e+00j -1.79877390e-12-5.78775631e-14j
  7.56818470e-15-2.44052282e-12j ...  1.59967379e-12+6.18560896e-14j
  7.56818470e-15+2.44052282e-12j -1.79877390e-12+5.78775631e-14j]

對於一個複數,若畫在複數平面時,複數的實部與虛部構成了一個向量:

傅立葉轉換(二)

旋轉該向量,以旋轉期間得到的垂直分量為 y(也就是虛數部份),時間為 x,畫出來的會是弦波,以旋轉期間得到的水平分量為 y(也就是實數部份),時間為 x,畫出來的也會是弦波,也就是說,這些複數包含了弦波的資訊。

NumPy 提供了 fftfreq,可以指定取樣資料的尺寸與取樣率的倒數,計算出取樣頻率:

freq = np.fft.fftfreq(samples.size, d = 1 / sample_rate)

如果要將頻域的結果畫出來,可以取複數的振幅,在頻域部份,通常不會關心振幅實際的值,可以取標準化後的結果就可以了:

amp = np.abs(sp)
amp_normal = amp / np.max(amp)

將以上組合起來:

import numpy as np
import matplotlib.pyplot as plt

t = 2              # 取樣時間
sample_rate = 800  # 取樣率,每秒取幾個樣本

def signal(t, sample_rate):
    f = 10
    x = np.linspace(0, t, int(t * sample_rate), endpoint = False)
    return (np.sin(f * 2 * np.pi * x) +    
            np.sin(3 * f * 2 * np.pi * x) / 2 + 
            np.sin(4 * f * 2 * np.pi * x) / 5 +
            np.sin(8 * f * 2 * np.pi * x) / 3)

samples = signal(t, sample_rate)
sp = np.fft.fft(samples) 

freq = np.fft.fftfreq(samples.size, d = 1 / sample_rate)
amp = np.abs(sp)

ax = plt.gca()
ax.stem(freq, amp / np.max(amp))
ax.set_xlim([np.min(freq), np.max(freq)])
plt.show()

就可以畫出頻域的圖形了:

傅立葉轉換(二)

嗯?怎麼會有負頻率?想想看,對於一個 sin(f * 2π * t),就好比一個點繞著圓轉,若 f 為正,表示逆時針轉動畫出來的弦波,若 f 為負,表示順時針轉動畫出來的弦波。

方才談到,傅立葉轉換後的複數,包含了弦波的資訊,如果你將正頻率與負頻率相對應的複數旋轉,虛數分量相加會為 0,畫出來就是條水平線,而實數合成的部份,畫出來會是條弦波(可參考〈如何正确理解信号处理中的负频率?〉中的動畫),也就是訊號總是在實數軸的分量組成,而在對訊號取樣時,得到的值確實也都是實數。

若只想知道訊號是由哪些頻率組成,只要看正頻率的部份就可以了,例如,調整一下上圖的區間,就可以清楚地看到訊號是由四個頻率組成,對照一下方才 signal 的實作,這四個頻率正好就是 signal 中的四個頻率:

傅立葉轉換(二)

根據維基百科〈傅立葉轉換〉中的說明,也可以將 F(ξ) 逆轉換為 f(x),NumPy 提供了 ifft 函式,可以從事這項轉換。例如:

samples2 = np.fft.ifft(sp).real # 只需要實部的部份

samples2 畫出來,得到的圖案與 samples 畫出來的訊號是相同的,既然如此,若對傅立葉轉換後的頻域資料動些手腳,再逆轉換回時域,例如,若將頻率 10 與 -10 以外的部份設為 0,就表示這些頻率的訊號振幅都是 0 了,也就是消去了訊號,這就有了濾波的功能:

import numpy as np
import matplotlib.pyplot as plt

t = 2              # 取樣時間
sample_rate = 800  # 取樣率,每秒取幾個樣本

def signal(t, sample_rate):
    f = 10
    x = np.linspace(0, t, int(t * sample_rate), endpoint = False)
    return (np.sin(f * 2 * np.pi * x) +    
            np.sin(3 * f * 2 * np.pi * x) / 2 + 
            np.sin(4 * f * 2 * np.pi * x) / 5 +
            np.sin(8 * f * 2 * np.pi * x) / 3)

samples = signal(t, sample_rate)
sp = np.fft.fft(samples) 
freq = np.fft.fftfreq(samples.size, d = 1 / sample_rate)

sp2 = sp.copy()
# 頻率 10 與 -10 以外的部份設為 0
sp2[np.intersect1d(np.where(freq != 10), np.where(freq != -10))] = 0
# 逆轉換
samples2 = np.fft.ifft(sp2).real

x = np.linspace(0, t, int(t * sample_rate), endpoint = False)
plt.plot(x, samples)
plt.plot(x, samples2)
plt.show()

顯示的結果如下,藍色是原訊號,橘色濾波後的訊號,也就是從原訊號中擷取出頻率 10 的訊號部份:

傅立葉轉換(二)

在下一篇文件中,會將焦點暫且轉換至 Perlin 雜訊,目的是認識訊號與影像處理之間的關係…