バンドパスフィルタとは
バンドパスフィルタ(帯域通過フィルタ)は、特定の周波数帯域(通過帯域)の信号を通過させ、それ以外の周波数(阻止帯域)を減衰させるフィルタです。
ラジオの選局、音声処理の特定周波数強調、生体信号(心拍・脳波)のノイズ除去など、幅広い分野で活用されています。
主要パラメータ
| パラメータ | 説明 |
|---|---|
| \(f_l\) | 下側遮断周波数(-3dB点) |
| \(f_h\) | 上側遮断周波数(-3dB点) |
| \(f_0 = \sqrt{f_l f_h}\) | 中心周波数(幾何平均) |
| \(BW = f_h - f_l\) | 帯域幅(Bandwidth) |
| \(Q = f_0 / BW\) | Q値(鋭さの指標) |
Q値が大きいほど通過帯域が狭く(鋭い選択性)、小さいほど広帯域になります。
周波数応答の導出
ローパス→バンドパス変換
バンドパスフィルタは、ローパスフィルタ(LPF)のプロトタイプから周波数変換によって設計できます。
アナログ領域でのローパス→バンドパス変換は、複素変数 \(s\) を以下のように置き換えます。
\[ s \rightarrow \frac{s^2 + \omega_l \omega_h}{s(\omega_h - \omega_l)} \tag{1}\]ここで \(\omega_l = 2\pi f_l\)、\(\omega_h = 2\pi f_h\) です。
1次のバターワースLPFの伝達関数 \(H_{LP}(s) = \frac{\omega_c}{s + \omega_c}\) に変換 \((1)\) を適用すると、2次のバンドパスフィルタが得られます。
\[ H\_{BP}(s) = \frac{BW \cdot s}{s^2 + BW \cdot s + \omega_0^2} \tag{2}\]ここで \(BW = \omega_h - \omega_l\)、\(\omega_0 = \sqrt{\omega_l \omega_h}\) です。
離散時間バンドパスフィルタ(双一次変換)
式 \((2)\) を双一次変換(Bilinear Transform)でデジタル領域に変換します。
\[s = \frac{2}{T} \cdot \frac{1 - z^{-1}}{1 + z^{-1}} \tag{3}\]これを式 \((2)\) に代入すると、デジタルバンドパスフィルタの伝達関数 \(H(z)\) が得られます。
\[ H(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{1 + a_1 z^{-1} + a_2 z^{-2}} \tag{4}\]実用上は scipy.signal がこの計算を自動的に行ってくれます。
Pythonによる実装
scipy.signalを用いたIIRバンドパスフィルタ
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# --- パラメータ設定 ---
fs = 1000.0 # サンプリング周波数 [Hz]
f_low = 50.0 # 下側遮断周波数 [Hz]
f_high = 150.0 # 上側遮断周波数 [Hz]
order = 4 # フィルタ次数
# --- バターワース バンドパスフィルタの設計 ---
nyq = fs / 2.0 # ナイキスト周波数
low = f_low / nyq
high = f_high / nyq
b, a = signal.butter(order, [low, high], btype='bandpass')
# --- 周波数応答の計算 ---
w, h = signal.freqz(b, a, worN=8000, fs=fs)
# --- プロット ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# ゲイン特性 (dB)
ax1.semilogx(w, 20 * np.log10(np.abs(h) + 1e-10))
ax1.axvline(f_low, color='r', linestyle='--', label=f'$f_l$ = {f_low} Hz')
ax1.axvline(f_high, color='g', linestyle='--', label=f'$f_h$ = {f_high} Hz')
ax1.axhline(-3, color='gray', linestyle=':', label='-3 dB')
ax1.set_xlabel('Frequency [Hz]')
ax1.set_ylabel('Gain [dB]')
ax1.set_title(f'Butterworth Bandpass Filter (order={order})')
ax1.legend()
ax1.grid(True, which='both', alpha=0.3)
ax1.set_xlim([1, nyq])
ax1.set_ylim([-80, 5])
# 位相特性
ax2.semilogx(w, np.angle(h, deg=True))
ax2.axvline(f_low, color='r', linestyle='--')
ax2.axvline(f_high, color='g', linestyle='--')
ax2.set_xlabel('Frequency [Hz]')
ax2.set_ylabel('Phase [degrees]')
ax2.set_title('Phase Response')
ax2.grid(True, which='both', alpha=0.3)
ax2.set_xlim([1, nyq])
plt.tight_layout()
plt.savefig('bandpass_response.png', dpi=150, bbox_inches='tight')
plt.show()
FIRバンドパスフィルタの設計
IIRフィルタは位相が非線形ですが、FIRフィルタは線形位相を実現できます。音声処理など位相の保存が重要な場面で有用です。
from scipy.signal import firwin, freqz
# --- FIR バンドパスフィルタ ---
numtaps = 101 # フィルタ係数の数(奇数推奨)
fir_bpf = firwin(
numtaps,
[f_low, f_high],
pass_zero=False, # バンドパス指定
fs=fs,
window='hamming'
)
w_fir, h_fir = freqz(fir_bpf, worN=8000, fs=fs)
# IIRとFIRの比較プロット
plt.figure(figsize=(10, 5))
plt.semilogx(w, 20 * np.log10(np.abs(h) + 1e-10), label='IIR Butterworth (order=4)', linewidth=2)
plt.semilogx(w_fir, 20 * np.log10(np.abs(h_fir) + 1e-10), label=f'FIR Hamming (taps={numtaps})', linewidth=2, linestyle='--')
plt.axvline(f_low, color='r', linestyle=':', alpha=0.7, label=f'$f_l$ = {f_low} Hz')
plt.axvline(f_high, color='g', linestyle=':', alpha=0.7, label=f'$f_h$ = {f_high} Hz')
plt.axhline(-3, color='gray', linestyle=':', alpha=0.7)
plt.xlabel('Frequency [Hz]')
plt.ylabel('Gain [dB]')
plt.title('Bandpass Filter Comparison: IIR vs FIR')
plt.legend()
plt.grid(True, which='both', alpha=0.3)
plt.xlim([1, nyq])
plt.ylim([-80, 5])
plt.tight_layout()
plt.show()
信号への適用例
実際のノイズ混じり信号にバンドパスフィルタを適用します。
# --- テスト信号の生成 ---
t = np.linspace(0, 1.0, int(fs), endpoint=False) # 1秒分
# 目的信号: 100 Hz のサイン波
signal_pure = np.sin(2 * np.pi * 100 * t)
# ノイズ: 10 Hz の低周波 + 300 Hz の高周波 + ホワイトノイズ
noise = (
0.5 * np.sin(2 * np.pi * 10 * t) +
0.5 * np.sin(2 * np.pi * 300 * t) +
0.3 * np.random.randn(len(t))
)
x_noisy = signal_pure + noise
# --- フィルタ適用 ---
# lfilter: 因果的フィルタ(リアルタイム処理向け、位相遅れあり)
y_lfilter = signal.lfilter(b, a, x_noisy)
# filtfilt: ゼロ位相フィルタ(オフライン処理向け、位相遅れなし)
y_filtfilt = signal.filtfilt(b, a, x_noisy)
# --- 比較プロット ---
fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
axes[0].plot(t[:200], x_noisy[:200], alpha=0.8, label='Noisy signal')
axes[0].set_ylabel('Amplitude')
axes[0].set_title('Input Signal (noisy)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].plot(t[:200], y_lfilter[:200], color='orange', label='lfilter (causal)')
axes[1].plot(t[:200], signal_pure[:200], color='gray', linestyle='--', alpha=0.6, label='True signal')
axes[1].set_ylabel('Amplitude')
axes[1].set_title('After lfilter (with phase delay)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
axes[2].plot(t[:200], y_filtfilt[:200], color='green', label='filtfilt (zero-phase)')
axes[2].plot(t[:200], signal_pure[:200], color='gray', linestyle='--', alpha=0.6, label='True signal')
axes[2].set_ylabel('Amplitude')
axes[2].set_xlabel('Time [s]')
axes[2].set_title('After filtfilt (zero-phase)')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('bandpass_filtering_result.png', dpi=150, bbox_inches='tight')
plt.show()
IIR vs FIR 設計の比較
| 特性 | IIR(バターワース等) | FIR |
|---|---|---|
| 位相特性 | 非線形(位相歪みあり) | 線形位相が実現可能 |
| フィルタ係数数 | 少ない(\(2N+1\)程度) | 多い(数十〜数百) |
| 計算コスト | 低い | 高い(タップ数依存) |
| 安定性 | 設計によっては不安定 | 常に安定 |
| 遮断特性 | 低次でも急峻 | タップ数増加で改善 |
| 遅延 | 非定数 | 定数遅延(\((N-1)/2\)サンプル) |
| 適した用途 | リアルタイム処理 | 音声・画像処理、位相重視 |
設計指針:
- リアルタイムで計算コストを抑えたい → IIR(バターワース/チェビシェフ)
- 位相の線形性が重要(音声・医療信号) → FIR(Hamming/Kaiser窓)
- オフライン処理で位相遅れを完全除去 →
filtfilt(IIR/FIR共通)
次数と帯域幅の影響
# 次数の違いによる周波数応答の比較
fig, ax = plt.subplots(figsize=(10, 6))
for ord_n in [2, 4, 6, 8]:
b_n, a_n = signal.butter(ord_n, [low, high], btype='bandpass')
w_n, h_n = signal.freqz(b_n, a_n, worN=8000, fs=fs)
ax.semilogx(w_n, 20 * np.log10(np.abs(h_n) + 1e-10), label=f'order={ord_n}')
ax.axhline(-3, color='gray', linestyle=':', label='-3 dB')
ax.axvline(f_low, color='r', linestyle='--', alpha=0.5)
ax.axvline(f_high, color='g', linestyle='--', alpha=0.5)
ax.set_xlabel('Frequency [Hz]')
ax.set_ylabel('Gain [dB]')
ax.set_title('Effect of Filter Order on Bandpass Response')
ax.legend()
ax.grid(True, which='both', alpha=0.3)
ax.set_xlim([1, nyq])
ax.set_ylim([-80, 5])
plt.tight_layout()
plt.show()
次数を上げると遮断特性が急峻になりますが、IIRフィルタでは次数が高くなるほど位相遅れと数値的不安定性のリスクが増すため、次数8以下が実用的な目安です。
関連記事
- ローパスフィルタの設計と比較:移動平均・バターワース・チェビシェフ - バンドパスの基礎となるLPF設計を解説しています。
- バターワースフィルタの設計とPython実装 - バンドパス設計に利用するバターワースLPFプロトタイプを詳しく解説しています。
- FIRフィルタとIIRフィルタの比較 - FIR/IIRの設計思想の違いを理論から解説しています。
- ノッチフィルタの設計とPython実装 - 特定周波数のみを除去するノッチ(バンドストップ)フィルタを解説しています。
- 指数移動平均(EMA)フィルタの周波数特性 - 1次IIRフィルタとして見たEMAの周波数特性をZ変換から導出しています。
- 高速フーリエ変換(FFT)の仕組みとPython実装 - バンドパスフィルタの効果を周波数領域で確認するために必要なFFTの基礎を解説しています。
- 窓関数とパワースペクトル密度(PSD)の理論とPython実装 - FIRフィルタ設計に使われる窓関数の数学的背景を解説しています。
- 適応フィルタ(LMS/RLS)の理論とPython実装 - 固定帯域ではなく環境に応じて特性を自動調整する適応フィルタを解説しています。
参考文献
- Proakis, J. G., & Manolakis, D. G. (2006). Digital Signal Processing: Principles, Algorithms, and Applications. Pearson.
- scipy.signal.butter — SciPy documentation
- scipy.signal.filtfilt — SciPy documentation
関連ツール
- DevToolBox - 開発者向け無料ツール集 - JSON整形、正規表現テスターなど85種類以上の開発者向けツール
- CalcBox - 暮らしの計算ツール - 統計計算、周波数変換など61種類以上の計算ツール