デジタルフィルタ設計入門|FIR と IIR の違いと実装の足がかり
シリーズ この記事は「信号処理まとめ」の一部です。 シリーズ目次を見る
(2026年5月時点)デジタルフィルタは、ノイズ除去や周波数成分の取り出しなどに使われる信号処理の基盤です。タイトルどおり入門の要点と実装の足がかりに絞り、FIR / IIR の違い、SciPy を使った設計と適用の最低限の例、オフラインとストリーム処理の違いを整理します(大学教科書レベルの網羅は目的にしていません)。
デジタルフィルタとは
デジタルフィルタは、離散化された信号(デジタル信号)に対して演算を施し、特定の周波数成分を通過または除去する処理です。アナログフィルタ(RC回路やオペアンプ)と異なり、ソフトウェアで実装するため、再現性が高く、パラメータの変更も容易です。
フィルタ種別(周波数特性による分類)
- ローパスフィルタ(LPF):カットオフ周波数以下の低周波成分を通過させ、高周波ノイズを除去。センサデータの平滑化に多用
- ハイパスフィルタ(HPF):低周波成分(DCオフセット等)を除去し、高周波の変動成分を抽出
- バンドパスフィルタ(BPF):特定の周波数帯域のみを通過。音声の特定帯域抽出や通信のチャネル選択に使用
- バンドストップフィルタ(BSF/ノッチ):特定の周波数帯域を除去。50Hz/60Hzの電源ノイズ除去が代表例
FIRフィルタとIIRフィルタの比較
デジタルフィルタは構造によりFIR(Finite Impulse Response)とIIR(Infinite Impulse Response)に大別されます。
FIRフィルタ
入力信号の重み付き加算のみで出力を計算します。フィードバック構造がないため、インパルス応答が有限長で収束します。
# FIRフィルタの差分方程式
y[n] = b[0]*x[n] + b[1]*x[n-1] + b[2]*x[n-2] + ... + b[M]*x[n-M]
# 現在と過去の入力だけで出力を計算(フィードバックなし) - 利点:直接形の FIR は実装が単純な限り常に安定。線形位相(偶対称係数など)にできるため、群遅延が一定になりやすく波形のタイミング歪曲を抑えやすい場面がある
- 欠点:急峻な特性ほど係数数(タップ数)が増え、演算コストが増えやすい
IIRフィルタ
入力だけでなく、過去の出力もフィードバックして計算します。アナログフィルタに対応する特性を少ない係数で再現できます。
# IIRフィルタの差分方程式
y[n] = b[0]*x[n] + b[1]*x[n-1] + ... + b[M]*x[n-M]
- a[1]*y[n-1] - a[2]*y[n-2] - ... - a[N]*y[n-N]
# 過去の出力(y[n-k])がフィードバックされる - 利点:少ない次数で急峻なフィルタ特性を実現可能。計算コストが低い
- 欠点:係数によっては不安定になりうる。位相特性も非線形になりやすく、波形の見え方(位相ゆがみ)にも注意が必要なことがある
用途別の選択ガイド(あくまで目安)
- 音声など位相特性を気にしたい → FIR(線形位相 FIR など)を検討しやすい
- 遅延と演算を抑えたいセンサ系 → 次数の低い IIR が候補になりやすい(安定性確認は別途)
- 通信など堅牢性優先 → 要件により FIR 実装や固定点数設計まで含めて詰める
Python でフィルタを設計する方法は?
SciPyの signal モジュールを使えば、数行のコードでフィルタの設計から適用、周波数応答の可視化まで行えます。 参考:SciPy signal モジュール
FIRフィルタの設計例(窓関数法)
import numpy as np
import scipy.signal as signal
import matplotlib.pyplot as plt
# パラメータ
fs = 1000 # サンプリング周波数 [Hz]
cutoff = 100 # カットオフ周波数 [Hz]
numtaps = 101 # フィルタ係数数(タップ数)
# FIRローパスフィルタの設計
fir_coeff = signal.firwin(numtaps, cutoff, fs=fs, window='hamming')
# 周波数応答の計算・描画
w, h = signal.freqz(fir_coeff, worN=8000, fs=fs)
plt.plot(w, 20 * np.log10(np.abs(h)))
plt.xlabel('Frequency [Hz]')
plt.ylabel('Gain [dB]')
plt.title('FIR Lowpass Filter (Hamming window)')
plt.grid(True)
plt.show() IIRフィルタの設計例(バターワース)
# 4次バターワース・ローパスフィルタ
order = 4
cutoff = 100 # Hz
b, a = signal.butter(order, cutoff, btype='low', fs=fs)
# 周波数応答
w, h = signal.freqz(b, a, worN=8000, fs=fs)
plt.plot(w, 20 * np.log10(np.abs(h)))
plt.title('IIR Butterworth Lowpass (4th order)')
plt.grid(True)
plt.show() 設計したフィルタを信号に適用する方法は?
設計したフィルタ係数を実際の信号に適用する方法は2通りあります。
# テスト信号: 50Hz + 200Hz + ノイズ
t = np.linspace(0, 1, fs, endpoint=False)
sig = np.sin(2*np.pi*50*t) + 0.5*np.sin(2*np.pi*200*t) + 0.2*np.random.randn(len(t))
# 方法1: lfilter(因果的・リアルタイム処理向き)
y_causal = signal.lfilter(fir_coeff, 1.0, sig)
# 方法2: filtfilt(非因果的・オフライン解析向き、位相遅延ゼロ)
y_zerophase = signal.filtfilt(fir_coeff, 1.0, sig) リアルタイム処理へ応用する方法は?
ストリーミングデータにフィルタを適用するときは、内部状態を持ちながらチャンクごとに lfilter する方法が典型です。下の RealtimeFilter は素朴な例で、zi の初期化は用途に合わせて調整してください(ここでは説明用に単純化しています)。
class RealtimeFilter:
def __init__(self, b, a):
self.b = b
self.a = a
self.zi = signal.lfilter_zi(b, a) * 0 # 初期状態
def process(self, chunk):
"""チャンクごとにフィルタを適用"""
y, self.zi = signal.lfilter(self.b, self.a, chunk, zi=self.zi)
return y
# 使用例
b, a = signal.butter(4, 100, btype='low', fs=1000)
rt_filter = RealtimeFilter(b, a)
for chunk in stream_chunks:
filtered = rt_filter.process(chunk)
# filtered を出力デバイスや後段処理へ フィルタ設計時の注意点は?
- ナイキスト周波数:カットオフはサンプリング周波数の半分(ナイキスト)未満に置く
- 次数と遅延:次数を上げるほど特性は立ちやすいが、演算量と遅延も増えやすい
- 量子化誤差:組み込みで固定小数点に落とすと係数量化で特性が変わる
- IIR の安定性:極が単位円内か(例:signal.tf2zpk で確認)
読了後のセルフチェック(3問)
- この用途なら FIR が向きそう / IIR で十分そう と、一文で言えるか。
- ナイキスト周波数とカットオフの関係を、自分のサンプリング周波数の例で説明できるか。
- 同じ係数でも lfilter と filtfilt で位相の見え方がどう変わるかを、用途(オンライン / オフライン)とセットで言えるか。
まとめ:FIR と IIR の選び方の違いは?
- FIR はフィードバックなしで扱いやすく、係数対称などで位相特性を設計しやすいことが多い
- IIR は少ない係数で急峻な特性を狙いやすいが、安定性や位相に注意が必要
- lfilter と filtfilt は前者が因果的・オンライン向き、後者がオフライン向きのイメージ