MATLABで行うFFTその2 ~リーク誤差と窓関数~

# **はじめに**
今回は **リーク誤差** と対策方法である **窓** の紹介をしていきます。


# **前回のあらすじ**


[**前回の記事**](https://qiita.com/Hernia_Baby/items/9ad96a7a046671911711) では FFTで片側スペクトルの正規化 まで行いました。




しかし **狙った振幅よりも低い値** になってしまっていました。




今回はその原因と対策を書いていきます。


  
# **前回の復習**


前回の復習から行いましょう。



```matlab:Code
clear,clc,close all;
dt = 0.01;
L  = 512;
t = 0:dt:dt*(L-1);
Fs = 1/dt;
```

  


合成波について最大振幅は **10Hz で 0.5, 30Hz で 2** としています。



```matlab:Code
y = .5*sin(2*pi*10*t) + 2*sin(2*pi*30*t);
plot(t,y)
xlim([0 t(end)])
xlabel '時間[sec]'
ylabel '信号'
```


![figure_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/781521/1396c994-f39c-b310-b81c-9c801b9d16e5.png)

  


ここでFFTして正規化まで行います。



```matlab:Code
% FFT
cy = fft(y);

% 周波数(サンプリング周波数の半分だけ)
f = Fs*(0:(L/2))/L;
f = f(1:end-1);

% 正規化
P = abs(cy(1:ceil(length(cy)/2)))./(length(y)/2);

% 図示
stem(f,P)
hold on

% 10Hz付近, 30Hz付近の行を検索
idx = knnsearch(f',[10;30]);
scatter(f(idx),P(idx),'o')
name = P(idx)';
text(f(idx),P(idx)+0.1,cellstr(num2str(name)));
ylim([0 1.7])
xlabel '周波数[Hz]'
ylabel 'P1(f)'
hold off
```


![figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/781521/6c220300-86db-297d-0b68-c0d1a4d0fe9e.png)


  


数字でピークをみてみましょう



```matlab:Code
Peak = [f(idx)', P(idx)'];
fprintf('ピーク周波数 %-.2f Hz \t 振幅 %-.2f\n\n',Peak(1,:),Peak(2,:))
```


```text:Output
ピーク周波数 9.96 Hz 	 振幅 0.47

ピーク周波数 30.08 Hz  振幅 1.51
```

  


んー、ずれてますねえ(ニッコリ)




10Hzはともかく、30Hz付近は過小評価されています。


  
# リーク誤差


このずれは[リーク誤差](https://www.onosokki.co.jp/HP-WK/eMM_back/03_04_24add.htm)によって生じています。




離散フーリエ変換は範囲を有限として、**外側は範囲内の繰り返し**として仮定しています。



```matlab:Code
subplot(1,2,1)
plot(t,y,'--')
hold on
scatter(t(1),y(1),'red','filled')
xlim([0 t(20)])
xlabel '時間[sec]'
ylabel '信号'
title 'はじめ'
subplot(1,2,2)
plot(t,y,'--')
hold on
scatter(t(end),y(end),'red','filled')
xlim([t(end-20) t(end)])
xlabel '時間[sec]'
ylabel '信号'
title 'おわり'
```

![figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/781521/229a3bbd-c8c4-e31f-7cac-e7414c4add95.png)

一致してませんね…これがリーク誤差の原因です。




つまり最初と最後がぴったし同じで繰り返さないとダメなんですね。


  
  


**そんなん実践で毎回使えるやついる～？**




![image_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/781521/899b8514-b898-b45f-7653-457b5591010a.png)



  
# 対策：窓関数


じゃあどうするのか？偉い人は考えました。


  


**最初と最後を0に収束させればよくね？！**


  


そこで出てくるのが[窓関数](https://ja.wikipedia.org/wiki/%E7%AA%93%E9%96%A2%E6%95%B0)ってやつです。




ここではとりあえず迷ったら使えばいいと噂の[ハニング窓](https://jp.mathworks.com/help/signal/ref/hann.html)を使います。



```matlab:Code
figure
hw = hann(length(y))';
plot(t,y,':','Color',[.7 .7 .7],'LineWidth',0.1); hold on
plot(t,hw.*y,'r','LineWidth',0.8);
xlim([0 t(end)])
xlabel '時間[sec]'
ylabel '信号'
legend({'通常','窓付き'})
hold off
```


![figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/781521/650c513c-cad6-5476-397d-3e27e7d852b2.png)



こんな感じです


  


窓付きをFFTしますか。




ただここでも**正規化**が必要になります。


  
# 窓関数を使用した場合の正規化


窓関数を使った場合の正規化は以下のようになります。




$g\left(n\right)$：窓関数




$\textrm{nfft}$：ブロックサイズ




$\left|S\right|$：片側スペクトル



```math
c\;=\;\frac{\Sigma \left(g\left(n\right)\right)}{\textrm{nfft}}
```


```math
S_{\textrm{norm}} =\frac{\left|S\right|}{c\cdot \left(\textrm{nfft}/2\right)}
```
  


コードで書いてみましょう。



```matlab:Code
% FFT
cy2 = fft(hw.*y);

% 正規化
c = sum(hw)/length(hw);
P = abs(cy2(1:ceil(length(cy2)/2)))./(length(y)/2)./c;

% 図示
stem(f,P)
hold on

% 10Hz付近, 30Hz付近の行を検索
idx = knnsearch(f',[10;30]);
scatter(f(idx),P(idx),'o')
name = P(idx)';
text(f(idx),P(idx)+0.1,cellstr(num2str(name)));
ylim([0 2.5])
xlabel '周波数[Hz]'
ylabel 'P1(f)'
hold off
```


![figure_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/781521/194e1502-8e4d-8233-ef90-38de30c99906.png)



```matlab:Code
Peak = [f(idx)', P(idx)'];
fprintf('ピーク周波数 %-.2f Hz \t 振幅 %-.2f\n\n',Peak(1,:),Peak(2,:))
```


```text:Output
ピーク周波数 9.96 Hz 	 振幅 0.49

ピーク周波数 30.08 Hz  振幅 1.80
```

  


完ぺきではないですが、リーク誤差はある程度改善されました。

# おわりに
次回はオーバーラップでもやろうかな。