ari23の研究ノート

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

ディジタル信号処理|IIRフィルタ設計(scipy.signal.iirdesign)

Scipyにあるscipy.signal.iirdesignの使い方と、IIRフィルタの安定性評価の方法を整理します🐜

開発環境

サンプルプログラムを実行する開発環境は次の通りです。Python3であれば動くと思います。

項目 内容
OS Windows 10
Python 3.6.8
Numpy 1.18.5
Scipy 1.4.1
Matplotlib 3.2.1

サンプルプログラム

サンプルプログラム(sample_iirdesign.py)は以下の通りです。自由にコピペして頂いて構いません。ぜひ実際に動かしてみてください。

なお、フィルタの仕様をプロットする最新のスクリプトはGitHubで公開しています。

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


def plotiirba(b, a, fs, title='', worN=512, shape=100):
    # ------- F特計算
    # --- ナイキスト周波数計算
    fn = fs / 2.0
    rad_to_freq = 1 / np.pi * fn

    # 周波数特性(振幅特性)
    w, h = signal.freqz(b, a, worN)
    x_freq_rspns = w * rad_to_freq
    y_freq_rspns = 20 * np.log10(abs(h) / 1.0 + 1.0e-06)

    # 群遅延
    w, gd = signal.group_delay((b, a), worN)
    x_gd = w * rad_to_freq
    y_gd = gd

    # 単位円、ゼロ点、極
    theta = np.linspace(0, 2*np.pi, 360)
    x_unit_circle = np.cos(theta)
    y_unit_circle = np.sin(theta)
    zeros, poles, k = signal.tf2zpk(b, a)

    # インパルス応答
    x_impluse = signal.unit_impulse(shape)
    y_impluse = signal.lfilter(b, a, x_impluse)

    # ------- グラフ表示
    fig = plt.figure(figsize=(8, 10), tight_layout=True)
    fig.suptitle(title)

    # --- 周波数特性(振幅特性)
    ax = plt.subplot2grid((3, 2), (0, 0), colspan=2)
    ax.plot(x_freq_rspns, y_freq_rspns)
    ax.set_ylabel('Amplitude [dB]')
    ax.set_xlabel('Frequency [Hz]')
    ax.grid()

    # --- 群遅延
    ax = plt.subplot2grid((3, 2), (1, 0), colspan=2)
    ax.plot(x_gd, y_gd)
    ax.set_ylabel('Group delay [samples]')
    ax.set_xlabel('Frequency [Hz]')
    ax.grid()

    # --- Poles-zeros plot
    ax = plt.subplot2grid((3, 2), (2, 0))
    # 単位円
    ax.plot(x_unit_circle, y_unit_circle, c='k', ls=':')
    # ゼロ点
    for z in zeros:
        x, y = np.real(z), np.imag(z)
        if np.sqrt(x**2+y**2) > 1:
            ax.plot(x, y, marker='o', c='r', fillstyle='none')
        else:
            ax.plot(x, y, marker='o', c='k', fillstyle='none')
    # 極
    for p in poles:
        x, y = np.real(p), np.imag(p)
        if np.sqrt(x**2+y**2) > 1:
            ax.plot(x, y, marker='x', c='r')
        else:
            ax.plot(x, y, marker='x', c='k')
    ax.axis('equal')  # 正方形にする
    ax.set_ylabel('Imaginary part')
    ax.set_xlabel('Real part')
    ax.grid()

    # --- インパルス応答
    ax = plt.subplot2grid((3, 2), (2, 1))
    ax.plot(y_impluse, label='System')
    ax.plot(x_impluse, label='Unit impulse')
    ax.set_ylabel('Magnitude')
    ax.set_xlabel('Samples')
    ax.legend()
    ax.grid()

    return fig


class TestIirDesign():
    def __init__(self):
        # --- 波形
        self.fs = 250  # サンプリング周波数
        self.sec = 3  # 時間

        # 抽出したい波形
        self.A = 1  # 振幅
        self.f = 5  # 周波数

        # ノイズ|sin波
        self.A_n = 0.1
        self.f_n = 50

        # --- IIRフィルタ
        self.wp = 10  # 通過域遮断周波数[Hz]
        self.ws = 50  # 阻止域遮断周波数[Hz]
        self.gpass = 0.5  # 通過域最大損失量[dB]
        self.gstop = 30  # 阻止域最小減衰量[dB]
        self.analog = False  # ディジタルフィルタなのでFalse
        self.ftype = 'butter'  # バタワース
        self.output = 'ba'  # baまたはtf型

    def run(self):
        # --- 波形
        # 抽出したい波形
        t = np.arange(0, self.sec, 1/self.fs)
        wave = self.A * np.sin(2.0 * np.pi * self.f * t)

        # ノイズ|sin波
        noise = self.A_n * np.sin(2.0 * np.pi * self.f_n * t)

        # 合成波
        wave2 = wave + noise

        # --- IIRフィルタ
        b, a = signal.iirdesign(self.wp, self.ws, self.gpass, self.gstop,
                                self.analog, self.ftype, self.output, self.fs)
        plotiirba(b, a, self.fs)

        # --- フィルタリング
        wave3 = signal.lfilter(b, a, wave2)

        fig = plt.figure(figsize=(10, 3), tight_layout=True)
        ax = fig.add_subplot()
        ax.plot(wave2, label='raw')
        ax.plot(wave3, label='filt')
        ax.legend()
        ax.grid()

        plt.show()


if __name__ == '__main__':
    proc = TestIirDesign()
    proc.run()

実行結果はこちらです。今回は2枚グラフを出力します。

まず、設計したIIRフィルタです。1段目がIIRフィルタの振幅特性、2段目が群遅延、3段目が安定性を示しています。 1段目と2段目について、過去記事(ディジタル信号処理|フィルタの周波数応答をPythonで実装)を参考にしてください。

3段目の左は、設計したIIRフィルタの零点と極を単位円と一緒にプロットしています。3段目の右は、設計したIIRフィルタのインパルス応答を示しています。詳細は後述します。

設計したバタワース型IIRフィルタ
設計したバタワース型IIRフィルタ

フィルタリングする前(raw)とした後(filt)のグラフです。ノイズが除去されて、きれいな正弦波が抽出できていることが確認できます。

生波形とIIRフィルタを通したあとの波形
生波形とIIRフィルタを通したあとの波形

解説

サンプルプログラムでの、波形データの条件は次の通りです。

項目 内容
サンプリング周波数[Hz] 250
データ取得時間[s] 10
抽出したい波形 振幅1, 周波数5[Hz]
ノイズ 振幅0.1, 周波数50[Hz]

計算方法などは、以前の記事(ディジタル信号処理|周波数解析(FFT)をPythonで実装)を参照してください。

以降は、サンプルプログラムの一部を抜粋して解説します。

IIRフィルタ

ディジタル信号処理 |FIRとIIRの違いにある通り、IIRフィルタはFIRフィルタと比較して、少ない次数で急峻な振幅特性が得られます。しかし、発散する可能性があるため、必ず極配置などで安定性を評価する必要があります。 また、線形位相特性がない欠点もありますが、丁寧に設計すれば非常に強力なフィルタです。

scipy.signal.iirdesign

ScipyにはIIRフィルタを設計できる関数がいくつか用意されています。ここでは、通過域や阻止域など比較的細かく設計できるscipy.signal.iirdesign()を採用します。

scipy.signal.iirdesign()の入力値と共に、振幅特性の例を以下に図示します。

振幅特性の例
振幅特性の例

上記を見ながら、設計するIIRフィルタの仕様を以下とします。

項目 内容
通過域遮断周波数(wp)[Hz] 10
阻止域遮断周波数(ws)[Hz] 5
通過域最大損失量(gpass)[dB] 0.5
阻止域最小減衰量(gstop)[dB] 30

これらのパラメタを入力して、フィルタ設計をします。なお、今回はバタワース型を採用したので、通過域最大損失量はほぼゼロです。

上記のグラフより、おおよそ所望のフィルタを設計できていることがわかります。1

安定性評価

過去記事(ディジタル信号処理|周波数応答とフィルタ設計)で言及しましたが、IIRフィルタが安定である必要十分条件は、伝達関数のすべての極が単位円の内側にあることです。これをBIBO(Bounded-Input Bounded-Output)安定性を有すると言います。

また、インパルス応答をみて評価する方法もあります。今回はこの2つの手法を実装しました。

極配置

以下では、単位円と、零点と極を計算しています。設計したIIRフィルタの係数baを、scipy.signal.tf2zpk()に入力することで、零点と極を計算できます。ソースをみると、np.roots()を使っていることがわかります。

    # 単位円、ゼロ点、極
    theta = np.linspace(0, 2*np.pi, 360)
    x_unit_circle = np.cos(theta)
    y_unit_circle = np.sin(theta)
    zeros, poles, k = signal.tf2zpk(b, a)

以下において、計算した極を✕でプロットしています。単位円の内部にある極は黒、外部にある極を赤でプロットすること(つまり不安定)で、安定性をひと目でわかるようにしています。

    # 極
    for p in poles:
        x, y = np.real(p), np.imag(p)
        if np.sqrt(x**2+y**2) > 1:
            ax.plot(x, y, marker='x', c='r')
        else:
            ax.plot(x, y, marker='x', c='k')

インパルス応答

以下では、入力する単位インパルスと、設計したフィルタのインパルス応答を計算しています。

    # インパルス応答
    x_impluse = signal.unit_impulse(shape)
    y_impluse = signal.lfilter(b, a, x_impluse)

以下において、計算したインパルス応答をプロットしています。今回の例では応答がゼロに収束しているので、安定性があることがわかります。

    ax = plt.subplot2grid((3, 2), (2, 1))
    ax.plot(y_impluse, label='System')
    ax.plot(x_impluse, label='Unit impulse')

おわりに

IIRフィルタの設計と実装についてまとめました。

IIRフィルタを設計するには安定性評価が必須であり、その手間からこれまで避けてきましたが、今回整理したことで今後は問題なく設計できそうです。

上記の例ではバタワース型を採用しましたが、scipy.signal.iirdesignは他のタイプも簡単に設計できるので、ぜひ試してみてください。

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

参考文献


  1. 計算誤差があるので、厳密には異なることに注意しましょう。