數字信號處理5:FIR濾波器設計

之前的一系列博客中,詳細分解了從卷積到FFT的相關知識,不過那些屬於理論,是爲了讓我們清楚認識到信號處理的本質。本篇博客將會詳細講解數字信號處理最廣泛的應用——濾波器。

注意,本章所採用的dB標準爲

dB=20logVVbase dB = 20log\frac{V}{V_{base}}

其中,VbaseV_{base}表示某種作爲標準的幅度值,它或者是電平,或者是信號振幅。在這裏,我們通常將它認爲是原信號的“大小”,這個大小不光可以是信號在時域的幅度,也可以是在頻域的頻率分量的大小。

1. 濾波器初識

濾波器,如同字面意思是過濾信號的某些頻率分量。入門時書上最常說的幾種濾波器,比如低通、帶通、高通、帶阻等濾波器。如下是一個低通濾波器。

在這裏插入圖片描述
它將信號中的中高頻分量降低了60dB。按照dB的定義,也就是中高頻信號的幅度降低爲原來的0.001倍。

實際上,更爲廣泛的濾波器,並不只有“濾”的作用,它同樣還可以對某些頻段進行增強,如下濾波器:

在這裏插入圖片描述

它將信號中的中高頻部分提高了60dB。按照dB的定義,也就是中高頻信號的幅度提高到原來的1000倍。

2. 最直觀的濾波方式:頻域濾波

回想一下《數字信號處理2:傅里葉變換》博客中講的傅里葉變換的直觀理解那部分。傅里葉變換之後的結果是一組虛數,這組虛數的相位反應的就是原信號中各頻率正弦波的相位,而這些虛數的絕對值某種程度上反應了對應頻率的信號在原始信號中的大小。所以,我們只要對頻域上的這些虛數的絕對值進行調節,再作傅里葉逆變換,即可以調整原信號中的某些頻率分量的大小,也就成爲了濾波的一種手段。步驟通常如下:

  1. 對信號加窗
  2. 對加窗之後的結果作傅里葉變換。
  3. 根據你的目的對傅里葉變換結果的某些頻率上的分量調節絕對值。
  4. 將調節完畢後的結果作傅里葉逆變換。

這便是頻域濾波。

3. 傅里葉變換中的加窗

在上一部分中提到了對信號進行加窗,爲什麼要進行加窗操作?加的又是什麼窗?

要理解加窗的意義,有兩種方式。

首先,回想一下博客《數字信號處理2:傅里葉變換》中對傅里葉變換的推導過程,對於傅里葉變換所做的所有推導,都是基於無限長週期信號進行的。我們假設用來做傅里葉變換的信號是從週期信號中截取的一整個週期。所以,如果這段信號前後進行拼接成我們假想的週期信號後,中間沒有突變點(包括斜率突變、斷點等情況),那麼直接進行傅里葉變換是沒有問題的。而如果前後進行拼接後,中間出現了突變點,那麼從直覺就可以知道,這個突變點中包含了相當多的頻率分量。但單從我們截取的這段信號來說,是不應該有這些多餘的頻率分量的。

下面舉個例子:

構造一個頻率爲20的週期信號。首先,我們讓這段信號有整數個週期。使用matlab。

T = 200;

x = [0:1:T-1];
sinWave = sin(2 * pi / T * 20 * x);
plot(x, sinWave);
title("頻率爲20的正弦波");
b = fft(sinWave);
plot(x, abs(b));
title("整數個週期正弦波的頻譜")

代碼中生成的sin波是完整的20個週期,採樣點200個,週期是10。繪製出來的圖像如下:

在這裏插入圖片描述

在這裏插入圖片描述
可以看到,頻譜上僅在週期爲20的位置有值。

如果不給整數個週期。比如只給19.5個週期。顯然,這樣的信號前後拼接成周期信號後,必然是會有突變點的。

x = [0:1:195];
sinWave = sin(2 * pi / T * 20 * x);
plot(x, sinWave);
title("19.5個週期的正弦波");
b = fft(sinWave);
%b = circshift(b', fix(length(sinWave) / 2))';
plot(x, abs(b));
title("非整數個週期正弦波的頻譜")

在這裏插入圖片描述
在這裏插入圖片描述

可以看到,這時在頻率20周圍出現了其他的頻率分量,這就是頻率泄漏。

實際運用中,一段信號通常有很多頻率的正弦波組成,我們不能保證總是能截取到整數個週期,因此,通過加窗的方式,我們使信號兩端加一個漸緩的過程,使得拼接成周期信號後,沒有突變點。

舉一個例子:漢寧窗。它的形狀如下:

在這裏插入圖片描述

對於上面提到的非整數個週期的信號,我們對其進行加窗操作:

x = [0:1:195];
window = hanning(length(x))';
plot(window);
title("漢寧窗");
sinWave = sin(2 * pi / T * 20 * x) .* window;
plot(x, sinWave);
title("加窗後的19.5個週期的正弦波");
b = fft(sinWave);
%b = circshift(b', fix(length(sinWave) / 2))';
plot(x, abs(b));
title("加窗後的非整數個週期正弦波的頻譜")

之後信號就變成這樣

在這裏插入圖片描述
其頻譜

在這裏插入圖片描述
可以看到,加窗之後,頻率泄漏的情況改善了很多。

從另一方面理解。之前的博客《數字信號處理4:採樣定理》中證明了一個重要的結論:時域卷積等於頻域相乘,時域相乘等於頻域卷積。

因此,將信號做傅里葉變換,其實隱含了一個過程,就是原信號的頻譜與窗函數的頻譜進行卷積。而如果不想改變原信號頻譜,最理想的情況就是讓它與一個衝激信號進行卷積,這樣原信號的頻譜其實只是發生了位移。這樣一來,我們就希望能有一個窗函數,它的頻譜是一個衝激函數,或者儘量接近衝激函數。

對於一段信號,不加窗等價於加矩形窗。我們不妨看一下矩形窗的頻譜:

a=zeros(1, 400);
a(100:299) = ones(1, 200);
b = fft(a);
b = circshift(b', 200)';
plot(a);
title("矩形窗")

plot([-199:1:200], abs(b));
title("矩形窗頻譜")

在這裏插入圖片描述
在這裏插入圖片描述

顯然,矩形窗有很多旁瓣,與這樣的信號在頻域上做卷積,會改變原信號的頻譜。

而看一下漢寧窗:

a=zeros(1, 400);
a(100:299) = hanning(200)';

plot(a);
title("漢寧窗")
b = fft(a);
b = circshift(b', 200)';

plot([-199:1:200],abs(b));
title("漢寧窗頻譜")

在這裏插入圖片描述

在這裏插入圖片描述
可見漢寧窗的頻譜相當接近一個衝激函數。

至此,窗函數的意義已經明瞭:消除傅里葉變換過程中的頻率泄漏。

4. FIR濾波器設計

濾波器是一個線性時不變系統,我們使用一個差分方程來表示該系統的信號傳輸特性:

y[n]=k=1Naky[nk]+k=0M1bkx[nk] y[n] = -\sum_{k=1}^{N}a_ky[n-k] + \sum_{k=0}^{M-1}b_kx[n-k]

如果ak0a_k \neq 0,那麼當前輸出不僅與當前輸入有關,還有過去的M1M-1個輸入,以及過去的NN個輸出有關。這樣的濾波器成爲無限衝激響應濾波器(IIR Filter)。如何理解無限這個詞,當前輸出與過去的輸出有關,那麼可以預見,第一個輸入對後面所有的輸出都會有影響。

如果ak=0a_k = 0,那麼當前輸出僅與當前輸入和過去的M1M-1個輸入有關。這樣的濾波器被成爲有限衝激響應濾波器(FIR Filter)。

這兩種濾波器各有優缺點,IIR的優點在於計算快,能以較少的階數達到性能要求。但設計比較複雜,而且難以設計出具有準確頻率響應的濾波器,另外,IIR濾波器的相位不可能是線性的。

而FIR濾波器則反了過來,設計簡單,能快速設計出具有精確線性相位以及需要的頻率響應的濾波器。但是需要較高的階數才能達到濾波要求。

h[n]h[n]是濾波器,H[k]H[k]h[n]h[n]的傅里葉變換。

我們從頻域濾波開始,既然從頻域調節就可以達到濾波器的目的,那麼幹脆將頻域上調節的係數看做H[k]H[k],然後用傅里葉逆變換求出h[n]h[n],頻域相乘等於時域卷積,使用h[n]h[n]與信號x[n]x[n]進行卷積即可。

首先要明確的一個點,爲什麼濾波器要是線性相位?按照傅里葉變換公式

X[k]=X[k]ejakw0 X[k] = |X[k]|e^{-ja_kw_0}

然後有一濾波器h[n]h[n],其頻譜是

H[k]=H[k]ejbkw0 H[k] = |H[k]|e^{-jb_kw_0}

X[k]X[k]H[k]H[k]的每一項相乘。
Y[k]=H[k]X[k]=H[k]X[k]ej(ak+bk)w0 Y[k] = H[k]X[k] = |H[k]||X[k]|e^{-j(a_k + b_k)w_0}

我們已經知道傅里葉變換是可逆的。所以對於X[k]X[k]來說

x[n]=1Nk=<N>X[k]ejakw0ejkw0n=1Nk=<N>X[k]ej(knak)w0 x[n] = \frac{1}{N}\sum_{k=<N>}|X[k]|e^{-ja_kw_0}e^{jkw_0n} = \frac{1}{N}\sum_{k=<N>}|X[k]|e^{j(kn - a_k)w_0}
得出的結果必然是實數。
那麼對於Y[k]Y[k]

y[n]=1Nk=<N>X[k]H[k]ej(knakbk)w0 y[n] = \frac{1}{N}\sum_{k=<N>}|X[k]H[k]|e^{j(kn - a_k - b_k)w_0}

假設H[k]=1|H[k]|=1,相當於沒有對X[k]X[k]進行調節,此時只看bkb_k
bk=0b_k = 0時,就退化成x[n]x[n]的傅里葉逆變換,這個時候x[n]x[n]自然是沒有問題,但是我們知道對於非全0的h[n]h[n]來說,H[k]H[k]不可能是零相位的。
如果bkb_k是個常數,也就是H[k]H[k]的相位是線性的,很顯然,y[n]y[n]只是相當於將x[n]x[n]延遲了bkb_k
而如果相位是非線性的,則無法保證y[n]y[n]是實數。

接下來的任務就是確定bkb_k的值。

注意,從這裏開始,就與大多數教科書講的不一樣了


H[k]=Ha[k]ejbkw0 H[k] = H_a[k]e^{-jb_kw_0}

其中,Ha[k]H_a[k]是其H[k]H[k]的幅度響應,因此Ha[k]=H[k]|H_a[k]| = |H[k]|

由傅里葉變換公式可知,對於實序列的傅里葉變換,有
H[k]=H[k]=H[N1k] H[k] = H^*[-k] = H^*[N-1-k]

因此,H[k]=H[k]|H[k]| = |H[-k]|,意味着Ha[k]H_a[k]既可以是偶函數也可以是奇函數。

很多教材將Ha[k]H_a[k]是奇函數還是偶函數區分開來,並且由於濾波器長度N可以爲奇數或偶數,因此衍生了四種情況,記起來頗爲麻煩。

實際上,我們只要考慮Ha[k]H_a[k]是偶函數的情況,因爲它已經可以構造任何形式的FIR濾波器。而奇函數的形式卻無法構造調節低頻的濾波器。

第一步:構造幅度響應

這一步是我們根據濾波器要求來構造的。舉例來說,信號採樣率是16kHz,我們要將4kHz以上的信號降低60dB。

這個時候,根據濾波器長度來區分不同的情況。

如果濾波器長度爲20,那麼在0點處對應的是0Hz,每兩個點頻率差爲16000/20=80016000/20=800Hz。那麼第4點對應3200Hz,第5點對應4000Hz,第10點對應的是8000Hz,剛好是採樣率爲16k能表示的最大信號頻率。

要將4kHz以上的信號降低60dB,則第5點到第10點,其幅度響應都爲0.001(通過dB換算)。而第0點到第4點,幅度響應都爲1。

現在確認了11個點,那剩下的9個點呢?別忘了此時幅度響應是偶函數,而且傅里葉變換後的頻譜現在是週期爲20的周期函數。因此第11點與第9點相同,一直往後,第19點與第1點相同。

因此得到的幅度響應爲
|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19||-

如果濾波器長度爲21,那麼每兩個點頻率差爲16000/2176216000/21\approx762Hz。按照同樣的方式,可以得到下表:

在這裏插入圖片描述

注意其中3810Hz處將幅度響應設爲0.01,因爲過於陡峭的變化會導致吉布斯現象出現抖動。其實以上兩個響應我沒有經過檢驗, 可能也會存在吉布斯現象。

第二步:構造相位響應
之前已經證明過FIR濾波器必須是線性相位的。但是我們不知道線性係數是多少。這裏我們先假設它是1。

使用上面的偶數長度濾波器的幅度響應。不過由於濾波器長度太短,爲了防止吉布斯現象,我們降低6dB即可,這樣,對應的幅度響應約爲0.5。

H_amp = [1, 1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1];

N = length(H_amp);

w0 = 2 * pi / length(H_amp);

theta = (w0 * 1) .* [0 : 1 : N-1];

H_phase = exp(1i .* theta);

H = H_amp .* H_phase;

h = ifft(H);

plot(real(h));
title("h的實部");

plot(imag(h));
title("h的虛部");

顯示如下:
在這裏插入圖片描述
虛部是一些很小的數,這可能是由於計算誤差引起的,可以忽略掉。

但是實部是不太對的,因爲畢竟之後我們要加窗的,這樣一加窗,這個濾波器肯定就變了,我們至少要保證起主要作用的那些值比較大的點在加窗之後要保留下來。因此,我們不妨將其循環移位N/2N/2。再來看效果。

修改代碼如下,對h進行循環移位,之後加漢寧窗,並且使用freqz函數查看濾波器的頻域響應。

H_amp = [1, 1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1];

N = length(H_amp);

w0 = 2 * pi / length(H_amp);

theta = (w0 * 1) .* [0 : 1 : N-1];

H_phase = exp(1i .* theta);

H = H_amp .* H_phase;

h = ifft(H);

h = circshift(h',fix(N/2))';

plot(real(h));
title("h的實部");

plot(imag(h));
title("h的虛部");

在這裏插入圖片描述
其頻域響應爲:
在這裏插入圖片描述
可以看到,非常完美的線性相位,並且濾波器指標也達到我們的要求。

其實對於循環移位這個操作,相當於把h[n]延遲了N/2。由傅里葉變換的位移定理:

n=<N>x[na]ejkw0n=n=<N>x[na]ejkw0(na)ejkw0a=X[k]ejkw0a \sum_{n=<N>}x[n-a]e^{-jkw_0n} = \sum_{n=<N>}x[n-a]e^{-jkw_0(n-a)} e^{-jkw_0a} = X[k]e^{-jkw_0a}
因此,對於FIR濾波器來說,線性相位的係數是N/2。若N爲奇數,則爲(N-1)/2。線性相位係數必須是正數。
之前我們實際上已經給h[n]延遲了1,因爲相位的係數是1。這次我們直接將1改爲N/2,並且去掉後面的循環移位操作。

H_amp = [1, 1, 1, 1, 1, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1];

N = length(H_amp);

w0 = 2 * pi / length(H_amp);

theta = (w0 * fix(N/2)) .* [0 : 1 : N-1];

H_phase = exp(1i .* theta);

H = H_amp .* H_phase;

h = ifft(H);

%h = circshift(h',fix(N/2))';


plot(real(h));
title("h的實部");

plot(imag(h));
title("h的虛部");

window = hanning(N);

h = h .* window';

freqz(real(h));
title("h的頻率響應");

最終的頻率響應是

在這裏插入圖片描述

5. 總結

好了,至此FIR濾波器的構造方式已經講完了,實際上關於濾波器指標還有很多細節的東西需要學習,比如濾波器長度、截止頻率等。

總結一下構造FIR濾波器的步驟:

  1. 根據要求構造幅度響應。
  2. 構造相位響應,必須是線性相位,線性係數爲:N爲偶數時是N/2;N爲奇數時是(N-1)/2。
  3. 幅度響應和相位響應合起來成爲濾波器頻譜H[k]H[k]
  4. H[k]H[k]進行傅里葉逆變換並取其實部得到濾波器h[n]h[n]
  5. h[n]h[n]加窗。

具體的matlab代碼可以查看我的github:FIR-filter。博客中使用的示例代碼都可以從中找到。包含了一個我已經寫好的fir_filter.m用來直接生成FIR濾波器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章