ari23の研究ノート

メーカ勤務エンジニアの技術ブログです

ディジタル信号処理|フィルタの周波数応答をPythonで実装

前回に引き続き、今回は周波数応答のサンプルプログラムを公開します🐜

例としてローパスフィルタを扱います。

ローパスフィルタとハイパスフィルタ

以下にざっくり説明です。

フィルタ 特徴
ローパスフィルタ ・低い周波数帯域を通し、高い周波数帯域を遮断する
・ハイカットフィルタともいわれる
・FIRだと移動平均フィルタがよく使われる
ハイパスフィルタ ・高い周波数帯域を通し、低い周波数帯域を遮断する
・ローカットフィルタともいわれる
・生データからローパス後のデータを減算することでも実現可能

意外と大事なのは、最後に書いてある

ハイパスフィルタ = (フィルタリング前のデータ) - (ローパスをかけたデータ)

です。

考えてみれば当たり前ですが、見落としがちな内容だと思います。ハイパスフィルタをさくっと作りたいときは、引き算するのが手っ取り早いですよ。

以降は、ローパスフィルタの代表である移動平均フィルタを題材に、実際にPythonで書いたサンプルプログラムを見ながら整理していきます。

開発環境

後述するサンプルプログラムを実行する開発環境は、次の通りです。

項目 内容
OS Windows 10
Python 3.7.6(Anaconda 4.8.1)
Numpy 1.17.4
Scipy 1.3.2
matplotlib 3.1.1

サンプルプログラム

Pythonを使ったFFTのサンプルスクリプト(sample_fvtool.py)は以下の通りです。自由にコピペして、実際に動かしてみてください。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import time
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt

class TestFFT():
    def __init__(self):
        self.msg = 'Test script for fvtool'
        # 設定値
        self.fs = 250   # サンプリング周波数

    def process(self):
        print(self.msg)

        lowpass = make_MA_filter(5)

        fvtool(lowpass, self.fs)

        plt.show()

def fvtool(b, fs, a=1, worN=8192):
    # ナイキスト周波数計算
    fn = fs / 2

    # 周波数応答計算
    w, h = signal.freqz(b, a, worN)
    x_freq_rspns = w / np.pi * fn
    y_freq_rspns = db(abs(h))  # 複素数→デシベル変換

    # 群遅延計算
    w, gd = signal.group_delay([b, a], worN)
    x_gd = w / np.pi * fn
    y_gd = gd

    # グラフ表示
    plt.figure(figsize=(8, 6))
    # 周波数応答プロット
    plt.subplot(2, 1, 1)
    plt.plot(x_freq_rspns, y_freq_rspns)
    plt.ylim(-70, 2)  # MATLAB fvtool仕様
    plt.ylabel('Amplitude [dB]')
    plt.grid()
    # 群遅延プロット
    plt.subplot(2, 1, 2)
    plt.plot(x_gd, y_gd)
    plt.ylim(0, len(b))  # MATLAB fvtool仕様
    plt.xlabel('Frequency [Hz]')
    plt.ylabel('Group delay [samples]')
    plt.grid()

def db(x, dBref=1):  # デシベル変換
    y = 20 * np.log10(x / dBref)
    return y

def make_MA_filter(N):
    b = np.ones(N) / N
    a = [1 for i in range(N) ]
    return b/a


if __name__ == '__main__':
    proc = TestFFT()
    proc.process()

実行結果は以下の通りです。

サンプリング周波数250Hzでタップ数5のときの移動平均フィルタ
サンプリング周波数250Hzでタップ数5のときの移動平均フィルタ

解説|フィルタの要求仕様

下記のような移動平均フィルタを設計します。

項目 内容
サンプリング周波数[Hz] 250
除去したい周波数[Hz] 50

電気・電子回路をいじっていると、よく信号に電源ノイズが重畳しますよね1。今回はそれを除去します。

解説|周波数応答

基本的には下記ブログを参考に実装していけばよいので、軽くコメントする程度にします。

ディジタル信号処理|周波数応答とフィルタ設計 - ari23の研究ノート

sample_fvtool.pyのmake_MA_filter関数

引数に応じた移動平均フィルタの係数をリストで戻します。遅延の都合上、奇数であることが望ましいです。

sample_fvtool.pyのfvtool関数

前回と同様に上から順に解説していきます。

def fvtool(b, fs, a=1, worN=8192):
    # ナイキスト周波数計算
    fn = fs / 2

    # 周波数応答計算
    w, h = signal.freqz(b, a, worN)
    x_freq_rspns = w / np.pi * fn
    y_freq_rspns = db(abs(h))  # 複素数→デシベル変換

ナイキスト周波数はサンプリング周波数の半分で、周波数応答ではこのナイキスト周波数までが評価できます。詳細については、他の文献を参考にしてください2

scipy.signal.freqzの第3引数のworNはデフォルト値でも問題ありませんが、ここではMATLABのfvtoolと同じ結果が得られるようにしています。

x軸は正規化角周波数だとわかりにくいので、単位変換します。

また、デシベル変換はFFTで用いたものと同じ関数です。

    # 群遅延計算
    w, gd = signal.group_delay([b, a], worN)
    x_gd = w / np.pi * fn
    y_gd = gd

こちらもx軸が正規化角周波数だとわかりにくいので、単位変換します。

最後に、周波数応答と群遅延の結果をグラフプロットします。

sample_fvtool.pyのグラフプロットの結果

以下に再掲し、上から順に解説します。

サンプリング周波数250Hzでタップ数5のときの移動平均フィルタ
サンプリング周波数250Hzでタップ数5のときの移動平均フィルタ

まず、1つ目は設計した周波数応答を示しています。50Hzと100Hzに谷があり、目的の50Hzを遮断できることが確認できます。

2つ目は設計したフィルタの群遅延を示しています。設計したフィルタは、FIRフィルタであり次数(タップ数)が5つなので、(5 - 1) /2 = 2サンプル遅れるのは正しいとわかります。

これで、周波数応答のサンプルプログラムを作ることができました。

おわりに

以前の記事を参考に、サンプルプログラムを作成しました。

設計したローパスフィルタはノッチフィルタに似ていますが、もちろん移動平均の係数の数を増やせば、より強めのフィルタになります。
ぜひ、試してみてくださいね(たくさんの山ができるような周波数応答になるはずです)。

また、公開したスクリプトは、FIRフィルタしか想定していないので、IIRフィルタを設計する際は適宜修正してください。

参考になれば幸いです(^^)


  1. 東日本だと50Hzで、西日本だと60Hzですね。意外とハード屋さんっぽいでしょw

  2. 端折ってすみません。飽きてきた。