ハイパスフィルタの設計とPython実装

ハイパスフィルタの設計理論・IIR/FIR比較・Python実装。LPF→HPF周波数変換と双一次変換を導出し、scipy.signal.butter・firwin・filtfilt・lfilterによる設計と次数比較、低周波ドリフト除去の実例まで解説します。

ハイパスフィルタとは

ハイパスフィルタ(高域通過フィルタ)は、設定した遮断周波数より高い周波数成分を通過させ、低い周波数成分を減衰させるフィルタです。

直流成分(DC)の除去、振動センサの低周波ドリフト抑制、画像処理でのエッジ強調など、幅広い分野で活用されています。

主要パラメータ

パラメータ説明
\(f_c\)遮断周波数(-3dB点)
\(\omega_c\)遮断角周波数(\(\omega_c = 2\pi f_c\) )
\(N\)フィルタ次数(大きいほど遷移帯域が急峻)
通過帯域\(f > f_c\) の周波数範囲
阻止帯域\(f < f_c\) の周波数範囲

次数 \(N\) が大きいほど遮断特性が急峻になりますが、位相遅れや数値的不安定性のリスクも増します。

周波数応答の導出

ローパス→ハイパス変換

ハイパスフィルタはローパスフィルタ(LPF)のプロトタイプから周波数変換によって設計できます。

アナログ領域でのローパス→ハイパス変換は、複素変数 \(s\) を以下のように置き換えます。

\[ s \rightarrow \frac{\omega_c^2}{s} \tag{1}\]

\(N\) 次のバターワース LPF の伝達関数

\[H_{LP}(s) = \frac{1}{\prod_{k=1}^{N}(s - s_k)} \tag{2}\]

に変換 \((1)\) を適用すると、同次のバターワースハイパスフィルタが得られます。1次の場合は次式になります。

\[H_{HP}(s) = \frac{s}{s + \omega_c} \tag{3}\]

式 \((3)\) は「微分器 + 1次ローパス」の構造を持ち、\(s \to 0\) (直流)で出力がゼロ、\(s \to \infty\) (高周波)で出力が1に近づくことを示しています。

なぜ周波数軸が反転するのか(変換の導出)

式 \((1)\) が「なぜ」ハイパス特性を生むのかを、周波数軸上の対応関係から導出します。虚軸上 \(s = j\omega\) に変換 \((1)\) を適用すると、

\[ j\omega \;\longrightarrow\; \frac{\omega_c^2}{j\omega} = -j\frac{\omega_c^2}{\omega} = j\left(-\frac{\omega_c^2}{\omega}\right) \]

となります。つまり実周波数 \(\omega\) は、新しい周波数 \(\omega' = -\omega_c^2/\omega\) に対応づけられます。ローパスプロトタイプの伝達関数は実係数(インパルス応答が実数)を持つため、振幅特性は周波数について偶関数 \(|H_{LP}(j\omega)| = |H_{LP}(-j\omega)|\) です。符号を無視して整理すると、

\[ |H_{HP}(j\omega)| = \left| H_{LP}\!\left(j\,\frac{\omega_c^2}{\omega}\right) \right| \]

が成り立ちます。これは「ハイパスフィルタの周波数 \(\omega\) における応答の大きさは、ローパスプロトタイプの周波数 \(\omega_c^2/\omega\) における応答の大きさに等しい」ことを意味します。

  • \(\omega \to 0\) (直流)のとき \(\omega_c^2/\omega \to \infty\) となり、ローパス側は阻止帯域の彼方(完全減衰域)に対応するため \(H_{HP}(0) = 0\) 。
  • \(\omega \to \infty\) (高周波)のとき \(\omega_c^2/\omega \to 0\) となり、ローパス側は通過帯域の中心(ゲイン1)に対応するため \(H_{HP}(j\infty) \to 1\) 。

つまり式 \((1)\) は、周波数軸を \(\omega \leftrightarrow \omega_c^2/\omega\) という反比例の関係で折り返し、ローパスの通過帯域と阻止帯域を入れ替えることでハイパス特性を作り出しています。\(\omega = \omega_c\) はこの折り返しの不動点(\(\omega_c^2/\omega_c = \omega_c\) )にあたり、両プロトタイプの -3dB点が一致する理由もここから分かります。

実際にこの導出を検証します。4次バターワースローパスプロトタイプの極(\(\omega_c = 1\) に正規化)を scipy.signal.buttap で取得し、手計算で \(s \to \omega_c^2/s\) を適用した極と、scipy.signal.lp2hp が返す極を比較します。

import numpy as np
from scipy import signal

N = 4
wc = 2 * np.pi * 100.0  # rad/s, fc = 100 Hz

# 正規化ローパスプロトタイプ(wc=1)の極
z_lp, p_lp, k_lp = signal.buttap(N)

# wc にスケールしたローパス極
p_lp_scaled = p_lp * wc

# 手計算による変換: wc^2/s = p_lp  =>  s = wc^2 / p_lp
p_hp_manual = wc**2 / p_lp_scaled

# scipy.signal.lp2hp による変換(比較対象)
b_lp, a_lp = signal.zpk2tf(z_lp, p_lp, k_lp)
b_hp, a_hp = signal.lp2hp(b_lp, a_lp, wo=wc)
_, p_hp_scipy, _ = signal.tf2zpk(b_hp, a_hp)

diff = np.max(np.abs(np.sort_complex(p_hp_manual) - np.sort_complex(p_hp_scipy)))
print(f"手計算とscipyの極の最大差: {diff:.3e}")

# --- 周波数軸の反転関係 |H_HP(jω)| = |H_LP(j・ωc^2/ω)| も検証 ---
test_f = np.array([10.0, 30.0, 60.0, 100.0, 150.0, 300.0, 1000.0])
w_test = 2 * np.pi * test_f
_, H_hp = signal.freqs(b_hp, a_hp, worN=w_test)

b_lp_scaled, a_lp_scaled = signal.lp2lp(b_lp, a_lp, wo=wc)
w_inv = wc**2 / w_test  # 反転周波数
_, H_lp_at_inv = signal.freqs(b_lp_scaled, a_lp_scaled, worN=w_inv)

for f, hhp, hlp in zip(test_f, np.abs(H_hp), np.abs(H_lp_at_inv)):
    print(f"f={f:7.1f} Hz  |H_HP(f)|={hhp:.6f}  |H_LP(wc^2/f)|={hlp:.6f}")

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

\(f\) [Hz]\(\lvert H_{HP}(f) \rvert\)\(f_{inv} = \omega_c^2/(2\pi f)\) [Hz]\(\lvert H_{LP}(f_{inv}) \rvert\)
10.00.0001001000.000.000100
30.00.008100333.330.008100
60.00.128525166.670.128525
100.00.707107100.000.707107
150.00.98104466.670.981044
300.00.99992433.330.999924
1000.01.00000010.001.000000

すべての周波数で両者が完全に一致し、\(f=100\) Hz(\(=f_c\) )ではどちらも \(1/\sqrt{2} \approx 0.7071\) (-3dB点)となっています。周波数軸反転による導出が正しいことが数値的にも裏付けられました。

離散時間ハイパスフィルタ(双一次変換)

式 \((3)\) を双一次変換(Bilinear Transform)でデジタル領域に変換します。

\[s = \frac{2}{T} \cdot \frac{1 - z^{-1}}{1 + z^{-1}} \tag{4}\]

これを式 \((3)\) に代入すると、1次デジタルハイパスフィルタの伝達関数が得られます。

\[H(z) = \frac{1 - z^{-1}}{1 + \alpha z^{-1}} \cdot \frac{1}{1 + \frac{1}{\alpha}} \tag{5}\]

ここで \(\alpha = 1 - 2f_c / f_s\) (プリワーピング係数)です。実用上は scipy.signal がこの計算を自動的に行います。

ハイパスフィルタ固有の性質と設計上の注意点

ここまでの変換の導出を踏まえ、ハイパスフィルタに固有の性質——直流完全遮断の代償、微分器的振る舞いによるノイズ増幅、そして実務での設計の使い分け——を掘り下げます。

直流成分の完全遮断とその代償:ステップ応答のオーバーシュート・アンダーシュート

式 \((3)\) より \(H_{HP}(0) = 0/\omega_c = 0\) が任意の次数・任意の設計法(バターワース、チェビシェフ等)のハイパスフィルタで成り立ちます。これは伝達関数の定義

\[ H(s) = \int_{-\infty}^{\infty} h(t) \, e^{-st} \, dt \]

において \(s=0\) を代入すると

\[ H(0) = \int_{-\infty}^{\infty} h(t) \, dt \]

となることから、\(H(0)=0\) は「インパルス応答 \(h(t)\) の面積(正味の積分値)がゼロである」ことと同値だと分かります。\(h(t)\) が恒等的にゼロでない限り、面積がゼロになるには \(h(t)\) が正負に符号反転する部分を持たなければなりません。

この事実はステップ応答の形にも直接反映されます。単位ステップ入力 \(u(t)\) に対する応答は \(y(t) = \int_0^t h(\tau) \, d\tau\) であり、\(t \to \infty\) で

\[ y(\infty) = \int_0^{\infty} h(\tau) \, d\tau = H(0) = 0 \]

に収束します。つまりハイパスフィルタにステップ入力を与えると、出力は必ず0に戻る——直流成分は原理的に保持されないということです。これはDCオフセット除去(マイクのバイアス除去、ECGのベースライン補正など)の理論的根拠そのものです。

一方で、この「面積ゼロ」の制約を満たす過程で、次数が上がるほど極が複素平面上に多く配置され、インパルス応答が振動しながら減衰するようになります。その結果、ステップ応答は一度大きく振れた後、逆方向に振れ(アンダーシュート)ながら0に収束する——という代償を伴います。次数ごとにこれを検証しました。

import numpy as np
from scipy import signal

fs = 1000.0
fc = 50.0
wn = fc / (fs / 2)
u = np.ones(4000)  # 単位ステップ(causalフィルタなので先頭からステップ開始)

for order in [1, 2, 4, 8]:
    b, a = signal.butter(order, wn, btype='high')
    y = signal.lfilter(b, a, u)
    overshoot = np.max(y)
    undershoot = np.min(y)
    idx_over, idx_under = np.argmax(y), np.argmin(y)
    print(f"order={order}: overshoot={overshoot:.4f} @ {idx_over/fs*1000:.2f}ms, "
          f"undershoot={undershoot:.4f} @ {idx_under/fs*1000:.2f}ms, "
          f"|undershoot|/overshoot={abs(undershoot)/overshoot:.3f}, tail={y[-1]:.1e}")

実行結果(\(f_c=50\) Hz, \(f_s=1000\) Hz)は次の通りです。

次数 \(N\)オーバーシュートアンダーシュート|アンダーシュート|/オーバーシュート終端値(\(t=4\) s)
10.86330.0000(なし)0.0001.11e-16
20.8006-0.20870.2611.11e-16
40.6620-0.35140.531-2.82e-14
80.4451-0.39890.896-4.48e-12

いずれの次数でも終端値は理論通り0に収束しています(\(H(0)=0\) の数値的確認)。1次フィルタは単調に減衰しアンダーシュートを持ちませんが、次数を上げるほど初期の跳躍(オーバーシュート)は緩和される一方、逆向きの振動(アンダーシュート)が急速に増大し、8次では振れ幅がオーバーシュートの約90%にまで達します。次数を上げて遷移帯域を急峻にするほど、時間領域では「リンギング」という形でコストを支払っていることが分かります。図にすると次の通りです。

Butterworth HPFのステップ応答:次数ごとのオーバーシュート・アンダーシュート

実務上の含意: 加速度センサの重力成分除去や心電図のベースライン補正など、入力に階段状の変化(センサ姿勢の急変、電極接触ノイズ等)が混入し得る場面では、高次フィルタほどこのリンギングが計測波形に偽のオーバーシュート/アンダーシュートとして現れ、誤診断や誤検出の原因になり得ます。次数選定はロールオフの急峻さとリンギングの許容量のトレードオフです。

カットオフ付近の微分器的振る舞いとノイズ増幅

1次ハイパスフィルタの伝達関数 \((3)\) について、\(\omega \ll \omega_c\) の極限を考えます。

\[ H_{HP}(j\omega) = \frac{j\omega}{j\omega + \omega_c} \approx \frac{j\omega}{\omega_c} \quad (\omega \ll \omega_c) \]

これは理想微分器 \(j\omega\) を \(1/\omega_c\) でスケールしたものと一致し、振幅は周波数に比例(\(\lvert H_{HP}(j\omega)\rvert \approx \omega/\omega_c\) )、位相は常に \(+90^\circ\) 進みます。この近似の精度を数値的に確認しました(\(f_c=100\) Hz)。

import numpy as np

wc = 2 * np.pi * 100.0
test_f = np.array([1.0, 5.0, 10.0, 20.0])
w = 2 * np.pi * test_f
H_exact = (1j * w) / (1j * w + wc)
H_approx = (1j * w) / wc
for f, he, ha in zip(test_f, H_exact, H_approx):
    err = abs(abs(he) - abs(ha)) / abs(he) * 100
    print(f"f={f:5.1f}Hz  |H_exact|={abs(he):.5f}  |H_approx|={abs(ha):.5f}  誤差={err:.3f}%")

実行結果、\(f=1\) Hzで誤差0.005%、\(f=5\) Hzで0.125%、\(f=10\) Hzで0.499%、\(f=20\) Hz(\(f_c\) の1/5)でも誤差1.980%と、カットオフの数分の1以下の帯域では近似が極めて良く成り立つことを確認しました。一般に\(N\) 次バターワースハイパスフィルタでは \(\omega \ll \omega_c\) で \(\lvert H_{HP}(j\omega)\rvert \approx (\omega/\omega_c)^N\) という低域漸近則が成り立ち(-20N dB/decadeのロールオフに対応)、次数ごとに数値検証しても比が0.87〜0.97の範囲で理論値に収束することを確認しています。

微分器は「信号の変化率」を強調する演算ですが、これは測定ノイズも同じように強調してしまうという代償を伴います。低周波の目的信号にノイズが混入した状態でハイパスフィルタを適用し、SN比の変化を実測しました。

import numpy as np
from scipy import signal

np.random.seed(42)
fs = 1000.0
t = np.arange(0, 2.0, 1 / fs)

clean = np.sin(2 * np.pi * 1.0 * t)          # 目的信号(1Hzの緩やかな変動)
noise = 0.05 * np.random.randn(len(t))        # 広帯域ノイズ

fc_diff = 50.0  # 目的信号帯域よりずっと高いカットオフ = 微分器として使う設定
b, a = signal.butter(1, fc_diff / (fs / 2), btype='high')

skip = 500  # 過渡応答区間を除外
clean_f = signal.lfilter(b, a, clean)[skip:]
noise_f = signal.lfilter(b, a, noise)[skip:]

snr_in = np.var(clean[skip:]) / np.var(noise[skip:])
snr_out = np.var(clean_f) / np.var(noise_f)
print(f"SNR_in  = {10*np.log10(snr_in):.2f} dB")
print(f"SNR_out = {10*np.log10(snr_out):.2f} dB")
print(f"劣化量  = {10*np.log10(snr_in) - 10*np.log10(snr_out):.2f} dB")
print(f"信号の振幅比 std(clean_f)/std(clean) = {np.std(clean_f)/np.std(clean[skip:]):.5f}"
      f"(理論ゲイン@1Hz = {abs((1j*2*np.pi*1.0)/(1j*2*np.pi*1.0+2*np.pi*fc_diff)):.5f})")
print(f"ノイズの振幅比 std(noise_f)/std(noise) = {np.std(noise_f)/np.std(noise[skip:]):.5f}")

実行結果は次の通りです。

  • SNR(入力): 22.68 dB → SNR(出力): -10.34 dB(約33 dB劣化
  • 信号の振幅比: 0.02079(理論ゲイン0.02000とほぼ一致)
  • ノイズの振幅比: 0.93057

目的信号(1Hz)はカットオフ(50Hz)よりずっと低いため理論通り約1/50に強く減衰される一方、広帯域ノイズはカットオフ付近・以上の成分が減衰されずにほぼ素通りするため、相対的にノイズが支配的になります。これが「ハイパスフィルタ=微分器はノイズを増幅する」と言われる所以です(正確には信号成分より相対的にノイズが目立つようになる現象で、フィルタの最大ゲインは1を超えません)。なお、filtfilt(順方向・逆方向の2回通し)を用いる場合は振幅ゲインが2乗されるため、上記と同条件でSNR劣化は約67 dBとさらに大きくなることも確認済みです。実務でノイズ下での微分(変化率推定)が必要な場合は、ハイパスフィルタ単体ではなく、平滑化と組み合わせた設計(例: Savitzky-Golay微分フィルタや、後段にローパスを追加したバンドパス的構成)を検討すべきです。

1次RC型ハイパスフィルタと高次設計の使い分け

式 \((3)\) ・\((5)\) の1次ハイパスフィルタは、アナログ回路ではコンデンサと抵抗1つずつで構成できる最も単純な「DCブロッキングフィルタ」に対応します(\(\omega_c = 1/RC\) )。マイクプリアンプの直流バイアス除去やオーディオのAC結合など、位相特性やリンギングを気にせず、ともかく直流だけ落とせればよい用途では、この1次RC型で十分です。実装もシンプルで、位相遅れは緩やかかつ単調(前節で見た通りアンダーシュートも生じません)です。

一方、心電図のベースラインドリフト除去や振動解析のように、目的信号の帯域とノイズ帯域が近接していて急峻な遷移帯域が必要な場面では、4〜8次程度のバターワース/チェビシェフ設計が使われます。ただし前節で確認した通り、次数を上げるほどステップ状の変化に対するリンギング(オーバーシュート・アンダーシュート)が増大するため、次数選定は「遷移帯域の急峻さ」と「時間領域での波形歪み」のトレードオフとして扱う必要があります。位相歪みが問題になる場合は本記事のfiltfilt(ゼロ位相化)や、姉妹記事で扱うベッセルフィルタ(群遅延平坦化)との併用も検討してください。

Pythonによる実装

scipy.signalを用いたIIRハイパスフィルタ

import numpy as np
import matplotlib.pyplot as plt
from scipy import signal

# --- パラメータ設定 ---
fs = 1000.0      # サンプリング周波数 [Hz]
fc = 100.0       # 遮断周波数 [Hz]
order = 4        # フィルタ次数

# --- バターワース ハイパスフィルタの設計 ---
nyq = fs / 2.0   # ナイキスト周波数
wn = fc / nyq    # 正規化遮断周波数

b, a = signal.butter(order, wn, btype='high')

# --- 周波数応答の計算 ---
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(fc, color='r', linestyle='--', label=f'$f_c$ = {fc} 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 Highpass 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(fc, color='r', 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('highpass_response.png', dpi=150, bbox_inches='tight')
plt.show()

FIRハイパスフィルタの設計

IIRフィルタは位相が非線形ですが、FIRフィルタは線形位相を実現できます。音声・医療信号処理など位相の保存が重要な場面で有用です。

from scipy.signal import firwin, freqz

# --- FIR ハイパスフィルタ ---
numtaps = 101    # フィルタ係数の数(奇数推奨)
fir_hpf = firwin(
    numtaps,
    fc,
    pass_zero=False,  # ハイパス指定
    fs=fs,
    window='hamming'
)

w_fir, h_fir = freqz(fir_hpf, 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(fc, color='r', linestyle=':', alpha=0.7, label=f'$f_c$ = {fc} Hz')
plt.axhline(-3, color='gray', linestyle=':', alpha=0.7)
plt.xlabel('Frequency [Hz]')
plt.ylabel('Gain [dB]')
plt.title('Highpass 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秒分

# 目的信号: 200 Hz のサイン波(高周波成分)
signal_pure = np.sin(2 * np.pi * 200 * t)

# ノイズ: 5 Hz の低周波ドリフト + ホワイトノイズ
noise = (
    2.0 * np.sin(2 * np.pi * 5 * 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[:300], x_noisy[:300], alpha=0.8, label='ノイズ混じり信号(低周波ドリフトあり)')
axes[0].set_ylabel('Amplitude')
axes[0].set_title('Input Signal (with low-frequency drift)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].plot(t[:300], y_lfilter[:300], color='orange', label='lfilter (因果的)')
axes[1].plot(t[:300], signal_pure[:300], color='gray', linestyle='--', alpha=0.6, label='真の信号')
axes[1].set_ylabel('Amplitude')
axes[1].set_title('After lfilter (位相遅れあり)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

axes[2].plot(t[:300], y_filtfilt[:300], color='green', label='filtfilt (ゼロ位相)')
axes[2].plot(t[:300], signal_pure[:300], color='gray', linestyle='--', alpha=0.6, label='真の信号')
axes[2].set_ylabel('Amplitude')
axes[2].set_xlabel('Time [s]')
axes[2].set_title('After filtfilt (ゼロ位相)')
axes[2].legend()
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('highpass_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 [1, 2, 4, 8]:
    b_n, a_n = signal.butter(ord_n, wn, btype='high')
    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(fc, color='r', linestyle='--', alpha=0.5, label=f'$f_c$ = {fc} Hz')
ax.set_xlabel('Frequency [Hz]')
ax.set_ylabel('Gain [dB]')
ax.set_title('Effect of Filter Order on Highpass 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()

次数1では遷移帯域が緩やかで、次数8では急峻な遮断特性が得られます。ただし IIR フィルタでは次数が高くなるほど位相遅れと数値的不安定性のリスクが増すため、次数8以下が実用的な目安です。

近年の研究動向:生体信号処理・画像フォレンジックにおけるハイパスフィルタ

前節で示したステップ応答のオーバーシュート・アンダーシュート(リンギング)は、生体信号処理の分野で近年活発に再検討されているテーマです。

脳波(EEG)のERP解析では、2023年に Psychophysiology 誌で発表された研究(Tanner et al. のフィルタ最適化研究の後継、bioRxivプレプリント2023年6月)が、ハイパスフィルタのカットオフを上げすぎると本来のピークの前後に逆極性の偽ピークが生じることを定量的に示しました。この研究は「ハイパスフィルタは反転しているため、真のピークの前後に偽の逆極性ピークを作り出す」と明記した上で、偽ピークの振幅を真のピークの5%以内に収める閾値を設定し、成分ごとに異なる推奨カットオフ(N170で0.9Hz、MMN/N2pcで0.5Hz、P3/N400で0.2Hzなど)を提示しています。論文は「過度なハイパスフィルタリングによって真のP600効果が見かけ上N400的な効果を生み出しうる」という具体例も示しており、本記事で数値的に確認した「次数を上げるほどアンダーシュートが増大する」という性質が、実データの解釈を誤らせるリスクとして実証的に扱われていることが分かります。同じく2023年に Scientific Reports 誌に掲載された別の研究では、タスク種別ごとに最適なカットオフ(Faceタスクで0.1Hz、Oddballタスクで0.5Hz、Go/No-goタスクで0.75Hz)が異なることが報告されており、ハイパスフィルタの設計が信号の性質に強く依存することが近年のEEG研究でも再確認されています。

心電図(ECG)のベースラインドリフト除去では、0.5〜1Hz程度のカットオフが定番ですが、位相歪みを避けるためにfiltfiltのような双方向(ゼロ位相)フィルタの使用が引き続き標準的な指針とされています。カットオフを上げるほどドリフト除去は改善する一方、S-T部分など臨床的に重要な低周波成分への影響とのトレードオフが継続的な検討課題です。

画像処理・画像フォレンジック分野では、生成画像(GAN・拡散モデル)の検出において、高周波成分に着目したハイパスフィルタ的な前処理が引き続き活用されています。2024年に発表された学習不要(training-free)の画像鑑識手法は、単純なハンドクラフトフィルタで生成画像特有の高周波アーティファクトを捉えられることを示しており、周波数領域での高域強調が偽造検出の有力な手がかりであり続けていることがうかがえます。ただし個別の実装詳細(フィルタ次数やカットオフの具体的な設計)は手法ごとに異なるため、興味のある方は各論文を参照してください。

Sources:

ハイパスフィルタの実用例

用途\(f_c\) の目安備考
音声のDC除去(マイク補正)20〜80 HzDC 成分による信号飽和を防ぐ
加速度センサのドリフト除去0.1〜1 Hz重力成分(定常成分)を除去
心電図(ECG)のベースライン0.5〜1 Hz呼吸による低周波変動を除去
画像のエッジ強調2次元HPFで輪郭を強調(鮮鋭化)
振動解析の低周波ノイズ除去1〜10 Hz機械振動の直流バイアスや電源ハム波を除去

関連記事

参考文献


関連ツール