1. 引言
在信號分析中,傅里葉變換可稱得上是神器。但在實際應用中,人們發現它還是存在一些不可忽視的缺陷。
爲了便於敘述考察以下兩種情形:
Case 1
考察這樣一個函數:
fs = 1000;
t = 0:1/fs:1 - 1/fs;
x = [10 * cos(2 * pi * 10 * t), 20 * cos(2 * pi * 20 * t),...
30 * cos(2 * pi * 30 * t), 40 * cos(2 * pi * 40 * t)];
繪製這個函數的時域圖像和經過傅立葉變換後的頻譜圖像,長這個樣子:
現在把信號反轉過來:
x = [10 * cos(2 * pi * 10 * t), 20 * cos(2 * pi * 20 * t),...
30 * cos(2 * pi * 30 * t), 40 * cos(2 * pi * 40 * t)];
x = x(end:-1:1);
再次繪製時域和頻域的圖像,它長這樣:
不難發現,儘管這兩個信號的時域分佈完全相反,但是它們的頻譜圖是完全一致的。顯然,FFT無法捕捉到信號在時域分佈上的不同。
Case 2
考察一個普普通通的信號:
fs = 1000;
t = 0:1/fs:1-1/fs;
x = 2 * cos(2 * 10 * t) + 4 * sin(2 * 30 * t);
同樣繪製它的時域以及頻域圖像:
現在給信號加入一個高頻突變:
sharp = zeros(1, length(x));
% 給信號中間加一個突變
sharp(501:510) = 5 * cos(2 * pi * 100 * linspace(0, 1, 10));
x = x + sharp;
然後繪圖:
對比兩個信號的時域圖,我們能很明顯發現在第二個信號中央的部分出現了一個突變擾動。然而在頻域圖中,這樣的變化並沒有很好的被捕捉到。注意到紅框中部分,顯然傅里葉變換把突變解釋爲了一系列低成分的高頻信號的疊加,並沒有很好的反應突變擾動給信號帶來的變化。
爲什麼我們需要時頻分析
通過以上的兩個例子,我們不難發現傅立葉變換的缺陷。
第一個例子告訴我們,傅里葉變換隻能獲取一段信號總體上包含哪些頻率的成分,但是對各成分出現的時刻並無所知。因此時域相差很大的兩個信號,可能頻譜圖一樣。
第二個例子告訴我們,對於信號中的突變,傅里葉變換很難及時捕捉。而在有些場合,這樣的突變往往是十分重要的。
當然如果非要硬槓,也不是完全沒辦法——這就需要需分析相位譜了,但在實際應用中,有誰會不嫌麻煩地去看相位譜呢?
總而言之,傅里葉變換非常擅長分析那些頻率特徵均一穩定的平穩信號。但是對於非平穩信號,傅立葉變換只能告訴我們信號當中有哪些頻率成分——而這對我們來講顯然是不夠的。我們還想知道各個成分出現的時間。知道信號頻率隨時間變化的情況,各個時刻的瞬時頻率及其幅值——這也就是時頻分析(引用自知乎)。
所謂時頻分析,就是既要考慮到頻率特徵,又要考慮到時間序列變化。常用的有兩種方法:短時傅里葉變化,以及小波變換。本文我們只介紹短時傅里葉變換
2. 短時傅里葉變換原理
短時傅里葉變換的思路非常直觀:既然對整個序列做FFT會丟失時間信息,那我一段一段地做FFT不就行了嘛!這也正是短時傅里葉變換名稱的來源,Short Time Fourier Transorm,這裏的 Short Time 就是指對一小段序列做 FFT。
那麼怎麼一段一段處理呢?直接截取信號的一段來做 FFT 嗎?一般我們通過加窗的方法來截取信號的片段。定義一個窗函數 ,比如這樣。
將窗函數位移到某一中心點 ,再將窗函數和原始信號相乘就可以得到截取後的信號 y(t)。
前面提到的直接截取的方法其實就是對信號加一個矩形窗,不過一般我們很少選用矩形窗,因爲矩形窗簡單粗暴的截斷方法會產生的頻譜泄露以及吉布斯現象,不利於頻譜分析。更多關於窗函數的內容,可以看這裏:加窗法。
對原始信號 做 STFT 的步驟如下。
首先將將窗口移動到信號的開端位置,此時窗函數的中心位置在 處,對信號加窗處理
然後進行傅里葉變換
由此得到第一個分段序列的頻譜分佈 。在現實應用中,由於信號是離散的點序列,所以我們得到的是頻譜序列 。
爲了便於表示,我們在這裏定義函數 ,它表示,在窗函數中心爲 時,對原函數進行變換後的頻譜結果 ,即:
對應到離散場景中, 就是一個二維矩陣,每一列代表了在不同位置對信號加窗,對得到的分段進行傅里葉變換後的結果序列。
完成了對第一個分段的FFT操作後,移動窗函數到 。把窗體移動的距離稱爲 Hop Size。移動距離一般小於窗口的寬度,從而保證前後兩個窗口之間存在一定重疊部分,我們管這個重疊叫 Overlap。
重複以上操作,不斷滑動窗口、FFT,最終得到從 上所有分段的頻譜結果:
最終我們得到的 ,就是 STFT 變換後的結果。
3. STFT實現
以下代碼基於 Matlab 2019b。
3.1 算法實現
STFT 的實現如下,算法返回的三個參數:
- f: m 維向量,表示傅里葉變換後每個點對應的頻率值,單位爲 Hz
- t: n 維向量,表示 n 個窗口中心時間 ,單位爲秒
- STFT: 一個二維矩陣 [m, n],每個列向量代表了在對應 上 FFT 變換的結果
function [STFT, f, t] = mystft(x, win, hop, nfft, fs)
% 計算短時傅里葉變換
% Input:
% x - 一維信號
% win - 窗函數
% hop - hop size,移動長度
% nfft - FFT points
% fs - 採樣率
%
% Output:
% STFT - STFT-矩陣 [T, F]
% f - 頻率向量
% t - 時間向量
% 把 x 變爲列向量
x = x(:);
xlen = length(x);
wlen = length(win);
% 窗口數目 L
L = 1+fix((xlen-wlen)/hop);
STFT = zeros(nfft, L);
% STFT
for l = 0:L-1
% 加窗
xw = x(1+l*hop : wlen+l*hop).*win;
% FFT計算
X = fft(xw, nfft);
X = fftshift(X);
STFT(:, 1+l) = X(1:nfft);
end
% 取每個窗口中點的時間點
t = (wlen/2:hop:wlen/2+(L-1)*hop)/fs;
%f = (0:nfft-1)*fs/nfft;
% 頻率 (fftshift之後的)
f = (-nfft/2:nfft/2-1) * (fs/nfft);
end
3.2 使用範例
我們這裏使用 Case 1 的範例來看看 STFT 效果如何。
爲了方便可視化,這裏給出了對 STFT 變換後的可視化函數。
function PlotSTFT(T,F,S)
% Plots STFT
plotOpts = struct();
plotOpts.isFsnormalized = false;
plotOpts.cblbl = getString(message('signal:dspdata:dspdata:MagnitudedB'));
plotOpts.title = 'Short-time Fourier Transform';
plotOpts.threshold = max(20*log10(abs(S(:))+eps))-60;
signalwavelet.internal.convenienceplot.plotTFR(T,F,20*log10(abs(S)+eps),plotOpts);
end
對 Case 1 中的兩種情況進行分析,代碼如下
close all; clear; clc;
fs = 1000;
t = 0:1/fs:1 - 1/fs;
% 窗口大小,推薦取 2 的冪次
wlen = 256;
% hop size 即移動步長,一般要取一個小於 wlen 的數,推薦取 2 的冪次
hop = wlen/4;
% FFT 點數,理論上應該不小於wlen,推薦取 2 的冪次
nfft = 256;
x = [10 * cos(2 * pi * 10 * t), 20 * cos(2 * pi * 20 * t),...
30 * cos(2 * pi * 30 * t), 40 * cos(2 * pi * 40 * t)];
figure;
subplot(2, 2, 1);
plot(x);
% 隨便選的一個窗函數
win = blackman(wlen, 'periodic');
[S, f, t] = mystft(x, win, hop, nfft, fs);
subplot(2, 2, 2);
PlotSTFT(t,f,S);
x = x(end:-1:1);
subplot(2, 2, 3);
plot(x);
win = blackman(wlen, 'periodic');
[S, f, t] = mystft(x, win, hop, nfft, fs);
subplot(2, 2, 4);
PlotSTFT(t,f,S);
可以看到,在 FFT 中無法區分的頻譜圖像在 STFT 中區分就非常明顯,可以看出按照不同的時間分段,頻譜分佈的變化。
爲了更好地理解,將右上角的圖做一次三維旋轉:
可以非常清晰地看出頻率分佈隨時間的變換。注意到分界線處存在異常的高頻成分(就是 STFT 圖像中那三條豎線),這是因爲時域信號突變導致的高頻成分。
3.3 Matlab 中的實現
在老的版本中,Matlab 中 STFT 的函數名爲 spectrogram
,而在 2019 版本中,引入了新的函數 stft
,用法和我上面的實現的程序基本一致。
close all; clear; clc;
fs = 1000;
t = 0:1/fs:1 - 1/fs;
% 窗口大小,推薦取 2 的冪次
wlen = 256;
% hop size 即移動步長,一般要取一個小於 wlen 的數,推薦取 2 的冪次
hop = wlen/4;
% FFT 點數,理論上應該不小於wlen,推薦取 2 的冪次
nfft = 256;
x = [10 * cos(2 * pi * 10 * t), 20 * cos(2 * pi * 20 * t),...
30 * cos(2 * pi * 30 * t), 40 * cos(2 * pi * 40 * t)];
figure;
subplot(1, 3, 1);
win = blackman(wlen, 'periodic');
[S, f, t] = mystft(x, win, hop, nfft, fs);
PlotSTFT(t,f,S);
title('My STFT');
subplot(1, 3, 2);
[S1, f1, t1] = spectrogram(x, win, wlen - hop, nfft, fs);
PlotSTFT(t1, f1, S1);
title('spectrogram');
subplot(1, 3, 3);
[S2, f2, t2] = stft(x, fs, 'Window', win, 'OverlapLength',wlen - hop,'FFTLength',nfft);
PlotSTFT(t2, f2, S2);
title('stft');
需要注意的是,我實現的時候用的參數是 hop size,而matlab提供的函數需要的參數是 overlap 這個別搞混了。結果如下。
要注意的是,spectrogram 輸出的是單邊譜,而 stft 輸出的是雙邊譜,其他區別倒不大。但是 spectrogram 還可以輸出功率譜,而 stft 就不行了。
4. STFT 的缺點
如果你仔細分析上面的內容,你會發現短時傅立葉變換也有不容忽視的缺陷。
最明顯的一個問題:窗口的寬度該設多少爲好呢?爲了闡明這個問題的影響,我們做這麼一個實驗:調整不同 wlen
的值,來看看影響。
len = [32, 64, 128, 256];
for i = 1:4
wlen = len(i);
hop = wlen/4;
nfft = wlen;
win = blackman(wlen, 'periodic');
[S, f, t] = mystft(x, win, hop, nfft, fs);
subplot(2, 2, i);
PlotSTFT(t,f,S);
[m, n] = size(S);
t = sprintf('Wlen = %d, S: [%d, %d]', wlen, m, n);
title(t);
end
結果如下:
注意 S
的尺寸隨 wlen
的變換,不難發現一個事實:
- 窗太窄,窗內的信號太短,會導致頻率分析不夠精準,頻率分辨率差,具體表現是黃色的橫線越來越寬、越來越模糊
- 窗太寬,時域上又不夠精細,時間分辨率低,具體表現是淡藍色的豎線越來越寬、越來越模糊(還記得嗎,豎線表示交界處的突變造成的高頻干擾成分)
從定量的角度來看,STFT的時間分辨率取決於滑移寬度 ,而頻率分辨率則取決於 。顯然,一方的增加必然意味着另一方的減小。這就是所謂的時頻測不準原理(跟海森堡測不準是一個性質),具體關係爲:
另外,固定的窗口大小過於死板。對低頻信號而言,有可能連一個週期都不能覆蓋;對高頻信號而言,可能覆蓋過多週期,不能反映信號變化。
也就是說,這又是一個 Trade-Off 問題,而一個問題一旦進入 Trade-Off 模式,就開始變得玄學起來了。
爲了打破這種玄學困境,就需要一個更加強大的武器——小波變換。
至於小波變換,那就是另一個故事了。