フィルタを設計するとき、多くの人が最初に確認するのは 振幅特性(どの周波数を通し、どの周波数を遮断するか)です。バターワースフィルタや窓関数法で設計した低域通過フィルタが、仕様通りのカットオフ周波数を持つことを確認し、「これで完成」と思いがちです。
ところが、そのフィルタに複数の周波数成分を含む信号を通してみると、出力の波形が入力とまったく異なる形になっていることがあります。振幅特性は完璧なのに、波形が崩れてしまう。この「謎の歪み」の原因が 位相特性 であり、その本質を定量化したものが 群遅延(group delay)です。
群遅延の概念を理解すると、2つの重要な応用が見えてきます。
音声・音楽処理: 録音した音声にイコライザーをかけるとき、FIRフィルタと IIR フィルタでは音の印象が変わります。IIR フィルタは少ない係数で鋭いフィルタリングができますが、位相特性が非線形なため、群遅延が周波数によって異なります。この位相の「ばらつき」が、音声の過渡応答(アタック感)を鈍らせる原因になります。反面、FIR フィルタは線形位相(一定群遅延)を持てるため、波形の形を変えずに通過させられます。
レーダー・通信システム: パルスレーダーではターゲットからの反射波形がパルス状の信号です。受信機内のフィルタが非線形位相を持つと、パルスの立ち上がりが歪み、距離分解能が低下します。OFDM などのマルチキャリア変調でも、サブキャリア間の群遅延差がシンボル間干渉(ISI)の一因になります。
この記事では、群遅延の数学的定義から出発し、なぜ「線形位相 = 一定群遅延 = 波形を崩さない」が成り立つのかを証明します。また FIR フィルタが線形位相を持てる理由を対称インパルス応答から示し、Python による可視化を通じて直感を磨きます。
本記事の内容:
- 位相応答 $\phi(\omega)$ の定義と読み方
- 群遅延 $\tau_g = -d\phi/d\omega$ の定義と「包絡線の遅れ」という意味
- 位相遅延との違いと使い分け
- 線形位相 = 一定群遅延 = 波形を崩さない、の証明
- FIR 対称インパルス応答が線形位相になる数学的証明
- IIR フィルタの非線形位相と群遅延変動
scipy.signal.group_delayを使った実践的な解析
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
フィルタの周波数応答と位相応答
まず、フィルタが信号に対して何をするのかを周波数の言葉で整理しましょう。
線形時不変(LTI)システムに複素正弦波 $e^{j\omega n}$ を入力すると、出力は同じ周波数の複素正弦波ですが、振幅と位相が変化します。この変化を周波数ごとに記述したものが 周波数応答 $H(e^{j\omega})$ です。
$$ H(e^{j\omega}) = \sum_{k=0}^{N-1} h[k] \, e^{-j\omega k} $$
ここで $h[k]$ はインパルス応答の $k$ 番目のサンプルです。複素数 $H(e^{j\omega})$ は次のように極形式で書けます。
$$ H(e^{j\omega}) = |H(e^{j\omega})| \cdot e^{j\phi(\omega)} $$
- $|H(e^{j\omega})|$ … 振幅特性(magnitude response): 各周波数の成分をどれだけ増幅・減衰するか
- $\phi(\omega)$ … 位相特性(phase response): 各周波数の成分の位相をどれだけシフトするか
振幅特性だけ見れば「低域は通し、高域は遮断できている」と判断できます。しかし、位相特性が周波数によって異なると、異なる周波数の成分がそれぞれ違う量だけ位相シフトされます。正弦波の位相シフトは時間遅れと同じなので、「周波数ごとに異なる時間遅れが生じる」ことになります。これが波形歪みの正体です。

上の図は、振幅変調波(搬送波 × ゆっくり変化する包絡線)がフィルタを通ったときの様子を示しています。搬送波の細かい振動が少しずれて(位相遅延)出力されますが、それよりも重要なのは 包絡線(情報を運ぶ外形)がどれだけ遅れるか です。この包絡線の遅れが群遅延であり、「$\tau_g$ だけ包絡線がシフトする」ことが見て取れます。
ここで自然な疑問が生まれます。では、「包絡線の遅れ」を数式でどのように定義すればよいでしょうか?次のセクションで群遅延の定義を導きます。
群遅延の定義と意味
位相遅延 — キャリアのシフト
まず、位相遅延(phase delay)を定義しましょう。正弦波 $A\cos(\omega t)$ をフィルタに通すと、出力は
$$ A |H(e^{j\omega})| \cos\!\bigl(\omega t + \phi(\omega)\bigr) $$
になります。これを時間の遅れとして書き直すと
$$ A |H(e^{j\omega})| \cos\!\Bigl(\omega \Bigl(t + \frac{\phi(\omega)}{\omega}\Bigr)\Bigr) $$
となります。つまり、この正弦波は 位相遅延
$$ \tau_p(\omega) = -\frac{\phi(\omega)}{\omega} $$
だけ時間的に遅れます(マイナス符号は、一般にフィルタが信号を遅らせることに対応)。
位相遅延は「その周波数の正弦波が何秒遅れるか」を表します。単一の正弦波しか扱わない場合は、位相遅延だけ考えれば十分です。
群遅延 — 包絡線のシフト
ところが、現実の信号は複数の周波数成分の重ね合わせです。振幅変調(AM)信号を例に取りましょう。搬送波角周波数を $\omega_c$、変調波の角周波数を $\Omega$($\Omega \ll \omega_c$)とすると、AM 信号は
$$ s(t) = m(t) \cos(\omega_c t) = A\cos(\Omega t) \cos(\omega_c t) $$
と書けます。積和の公式を使うと
$$ s(t) = \frac{A}{2}\cos((\omega_c + \Omega) t) + \frac{A}{2}\cos((\omega_c – \Omega) t) $$
つまり AM 信号は $\omega_c + \Omega$ と $\omega_c – \Omega$ という2つの周波数成分からなります。これをフィルタに通すと、各成分はそれぞれの位相だけシフトされます。
位相特性が線形($\phi(\omega) \approx -\tau \omega$ と書ける)な場合を考えます。各周波数成分の位相シフトは
$$ \phi(\omega_c + \Omega) \approx -\tau(\omega_c + \Omega), \quad \phi(\omega_c – \Omega) \approx -\tau(\omega_c – \Omega) $$
これを代入して計算すると、出力は
$$ y(t) \propto \cos(\Omega(t – \tau)) \cos(\omega_c(t – \tau)) $$
となり、入力の $s(t)$ に対して包絡線も搬送波も同じ量 $\tau$ だけシフトします。波形の形はそのまま保たれます。
逆に位相特性が非線形の場合、$\omega_c + \Omega$ と $\omega_c – \Omega$ の成分が異なる量だけ遅れます。すると包絡線の形が変わり、波形が崩れます。
この「包絡線の遅れ」を局所的に定義したものが群遅延です。一般の信号に対しては次のように定義します。
$$ \tau_g(\omega) = -\frac{d\phi(\omega)}{d\omega} $$
群遅延は、角周波数 $\omega$ 付近の成分が「グループとして(つまり包絡線として)」どれだけ遅れるかを表します。
ここで「群」という言葉の意味が鮮明になります。「群」とは「近い周波数の成分たちのグループ」のことで、その群がひとまとまりとして動く速度を群速度、遅れを群遅延と呼びます。

この図では、FIR(線形位相)と IIR(バターワース)の位相特性 $\phi(\omega)$ を比較しています。FIR の位相は $\omega$ に対して直線的に変化しているのに対し、IIR の位相は曲線を描いています。この曲がり具合が群遅延の変動を生むことを、次のセクションで図を使って説明します。
群遅延 = 位相の傾き(幾何学的理解)
群遅延の定義 $\tau_g = -d\phi/d\omega$ は、位相応答のグラフにおける 接線の傾き(の負) そのものです。幾何学的に見ると非常にシンプルです。
$$ \tau_g(\omega) = -\frac{d\phi}{d\omega} = -\lim_{\Delta\omega \to 0} \frac{\phi(\omega + \Delta\omega) – \phi(\omega)}{\Delta\omega} $$
- $\phi(\omega)$ が $\omega$ に比例する直線なら、どの点でも傾きが同じ → 一定群遅延
- $\phi(\omega)$ が曲線を描くなら、点ごとに傾きが違う → 変動する群遅延

左図では、線形位相フィルタの $\phi(\omega) = -30\omega$ というグラフが直線になっています。任意の点で接線を引いても傾きはつねに $-30$(群遅延 $= 30$ サンプル一定)です。右図では、非線形位相フィルタの場合に3点での接線の傾きが異なることが読み取れます。低周波では傾きが緩く(群遅延大)、高周波では傾きが急(群遅延小)になっており、周波数によって包絡線の遅れが変わることを示しています。
この「傾きの違い」が何を意味するかを直感的に言うと、低周波の成分はゆっくり出てきて、高周波の成分は早く出てくる(あるいはその逆)ということです。もし入力信号がギター弦の撥弦音のように低音と高音が同時に始まるものであれば、非線形位相フィルタを通すと各成分の「到着時刻」がばらばらになり、音の立ち上がりが歪んだように聞こえます。
群遅延の幾何学的理解が深まったところで、次は線形位相フィルタと非線形位相フィルタの全特性(振幅・位相・群遅延)を系統的に比較してみましょう。
線形位相 vs 非線形位相の全特性比較
実際にバターワース IIR フィルタと FIR フィルタで全特性を比較します。どちらも遮断周波数 $\omega_c = 0.3\pi$ の低域通過フィルタです。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
# IIRフィルタ: 4次バターワース LPF(遮断周波数 Wn=0.3)
b_iir, a_iir = signal.butter(4, 0.3)
# FIRフィルタ: 61タップ 窓関数法(ハニング窓)
numtaps = 61
b_fir = signal.firwin(numtaps, 0.3)
# 周波数応答の計算
w_iir, h_iir = signal.freqz(b_iir, a_iir, worN=2048)
w_fir, h_fir = signal.freqz(b_fir, 1, worN=2048)
# 位相応答(位相アンラップ)
phase_iir = np.unwrap(np.angle(h_iir))
phase_fir = np.unwrap(np.angle(h_fir))
# 群遅延の計算
gd_iir_w, gd_iir = signal.group_delay((b_iir, a_iir), w=w_iir)
gd_fir_w, gd_fir = signal.group_delay((b_fir, 1), w=w_fir)
ここで重要なのは np.unwrap によるアンラップ処理です。np.angle は $[-\pi, \pi)$ の範囲にクリップされるため、位相が $\pi$ を超えるたびに $2\pi$ のジャンプが生じます。np.unwrap はこの不連続を除去して連続した位相を得る操作で、群遅延を正しく計算するために必須です。

この4パネルの図から次のことが読み取れます。
振幅特性(左上)では、IIR(バターワース)のほうが通過帯域が広く遮断帯域への減衰が急峻です。FIR はより緩やかな遷移帯域を持ちます。通過帯域の振幅特性だけ見れば IIR の方が「優秀」に見えます。
位相特性(右上)では、FIR の位相が $\omega$ に対して完全な直線を描くのに対し、IIR の位相は遮断周波数付近で大きく曲がっています。この曲がりが非線形位相の証拠です。
群遅延(下段)では差が歴然です。FIR の群遅延はほぼ完全に $\frac{N-1}{2} = 30$ サンプル一定(赤破線)なのに対し、IIR の群遅延は遮断周波数付近で大きく膨らみます。IIR は遮断周波数近傍で長く遅れ、その他の帯域では短く遅れるという「不均一な遅れ」を持ちます。これが IIR フィルタによる波形歪みの直接原因です。
線形位相 = 一定群遅延 = 波形を崩さない
直感的に理解した「線形位相なら波形を崩さない」を数学的に示しましょう。
前提: フィルタの位相特性が線形、すなわち整数 $M$ と実数 $\alpha$ を用いて
$$ \phi(\omega) = -M\omega – \alpha $$
と書けるとします($\alpha$ は $0$ または $\pm\pi/2$ がほとんどの実際のケース)。ここで群遅延を計算すると
$$ \tau_g(\omega) = -\frac{d\phi}{d\omega} = -(-M) = M $$
すべての $\omega$ で群遅延が $M$(定数)になります。
命題: 線形位相フィルタを通した出力は、入力を $M$ サンプル遅延させたものと(振幅スケールを除いて)一致する。
証明: 入力信号の DTFT を $X(e^{j\omega})$ とすると、出力の DTFT は
$$ Y(e^{j\omega}) = H(e^{j\omega}) X(e^{j\omega}) = |H(e^{j\omega})| e^{j\phi(\omega)} X(e^{j\omega}) $$
位相特性が $\phi(\omega) = -M\omega$ の場合($\alpha = 0$)を考えます。理想フィルタ(通過帯域で $|H| = 1$)を仮定すれば
$$ Y(e^{j\omega}) = e^{-jM\omega} X(e^{j\omega}) $$
DTFT の時間シフト定理より、$e^{-jM\omega}$ の掛け算は $M$ サンプルの遅延に対応するので
$$ y[n] = x[n – M] $$
つまり出力は入力を $M$ サンプル遅延させたもの(波形の形は変わらない)です。
一方、$\phi(\omega)$ が非線形の場合は、$Y(e^{j\omega})$ が $e^{-jM\omega} X(e^{j\omega})$ という単純な遅延形式で書けません。各周波数成分が異なる時間だけ遅れるため、逆 DTFT を取ると入力とは異なる波形になります。
この証明が示すことをひとことで言えば、「波形の形を変えないために必要十分な条件は、全周波数に対して群遅延が一定であること」です。
それでは、FIR フィルタがこの条件(線形位相)を自然に満たせる理由を見ていきましょう。
FIR 対称インパルス応答が線形位相になる証明
FIR フィルタが線形位相を持てる秘密は、インパルス応答の対称性にあります。タイプ I FIR(奇数タップ $N$、偶数対称)を例に取ります。
対称条件: $h[n] = h[N-1-n]$ ($n = 0, 1, \ldots, N-1$)

上図では、61タップの FIR フィルタのインパルス応答が、中心($n = 30$)に対して完全に対称な形を持つことが確認できます。左端と右端は同じ値、中心から2つ離れた点も同じ値になっています。この対称性が線形位相を生む数学的根拠です。
では証明に入ります。周波数応答は
$$ H(e^{j\omega}) = \sum_{n=0}^{N-1} h[n] e^{-j\omega n} $$
中心インデックスを $M = (N-1)/2$ とおくと、$n = M + k$ と $n = M – k$ は対になります($k = 0, 1, \ldots, M$)。対称条件より $h[M+k] = h[M-k]$ なので
$$ H(e^{j\omega}) = e^{-j\omega M} \sum_{k=0}^{M} h[M+k] \cdot 2\cos(k\omega) \quad (k>0 のとき, k=0 の係数は1) $$
これを整理すると($k=0$ の項は $h[M]$)
$$ H(e^{j\omega}) = e^{-j\omega M} \left[ h[M] + 2\sum_{k=1}^{M} h[M+k] \cos(k\omega) \right] $$
右辺の $[\ldots]$ 内はすべて 実数 です($\cos$ は実数値関数)。実数は正または負の値を取るため、$H(e^{j\omega})$ の位相は
$$ \phi(\omega) = -\omega M + \begin{cases} 0 & \text{(括弧内 > 0 のとき)} \\ \pi & \text{(括弧内 < 0 のとき)} \end{cases} $$
通過帯域では括弧内が正なので $\phi(\omega) = -\omega M$ という完全な線形位相が得られます。対応する群遅延は
$$ \tau_g = -\frac{d\phi}{d\omega} = M = \frac{N-1}{2} \quad \text{(サンプル)} $$
これが FIR フィルタの群遅延が $(N-1)/2$ サンプルで一定になる理由です。
重要な教訓: IIR フィルタは無限長のインパルス応答を持つため、このような有限区間での対称性を持てません。したがって IIR フィルタは原理的に線形位相を実現できず、群遅延は必ず周波数によって変動します(一部の特殊な全域通過フィルタを除く)。
線形位相フィルタが波形を保存するデモ
数学的な証明を実際の信号で確かめましょう。矩形パルスを 101 タップの FIR フィルタで通してみます。
import numpy as np
from scipy import signal
fs = 1000.0 # サンプリング周波数 [Hz]
N = int(fs) # 1秒分のサンプル数
# 入力: 矩形パルス(0.2s〜0.4s の区間で ON)
t = np.arange(N) / fs
x = np.zeros(N)
x[200:400] = 1.0
# FIR線形位相フィルタ(101タップ, 遮断周波数 0.1)
b_fir = signal.firwin(101, 0.1)
delay_fir = (len(b_fir) - 1) // 2 # 群遅延 = (101-1)/2 = 50 サンプル
# フィルタリング(lfilterはFIRの場合、因果的に遅延がかかる)
y_fir = signal.lfilter(b_fir, 1, x)
ここで使っている signal.lfilter は因果フィルタとして実装されているため、FIR フィルタの場合は $\frac{N-1}{2}$ サンプルの遅延が生じます。これを遅延補正して入力と重ねると、波形の形がほぼ完全に一致することを確認できます。
# 遅延補正して形状比較
y_fir_corrected = y_fir[delay_fir:] # delay_fir サンプル先頭を捨てる
# 最大振幅で正規化(LPF なので直流は通る、振幅スケールを合わせる)
scale = np.max(y_fir_corrected) / np.max(x[:len(y_fir_corrected)])
residual = np.mean((y_fir_corrected[:500] - x[:500] * scale) ** 2)
print(f"遅延補正後の二乗平均残差: {residual:.6f}")
# → 非常に小さい値(波形の形は一致)

図の上パネルが入力の矩形パルスで、下パネルが FIR フィルタを通った出力です。出力のパルスは入力より 50 サンプル(= $\frac{N-1}{2} = \frac{101-1}{2}$)遅れて現れていますが、パルスの形(立ち上がりと立ち下がりの対称性、幅)はほぼそのまま維持されています。参照として示した入力の振幅スケール版(青点線)と出力がほぼ重なることからも、波形保存が確認できます。低域通過フィルタなので高周波成分が少し丸くなっていますが、遅延が一定で全体的にシフトするだけであることが明確です。
非線形位相による波形歪みのデモ
同じ低域通過フィルタでも、IIR の場合は波形がどのように変わるかを見てみましょう。今度は複数の正弦波を合成した信号を使います。
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
fs = 1000.0
N = 2000
t = np.arange(N) / fs
# 入力: 3つの正弦波の合成(50, 100, 200 Hz)
f1, f2, f3 = 50.0, 100.0, 200.0
x = (np.sin(2 * np.pi * f1 * t)
+ 0.7 * np.sin(2 * np.pi * f2 * t)
+ 0.4 * np.sin(2 * np.pi * f3 * t))
# IIR: 8次バターワース(遮断周波数 Wn=0.5 → f1,f2,f3 すべて通過帯域付近)
b_iir, a_iir = signal.butter(8, 0.5)
y_iir = signal.lfilter(b_iir, a_iir, x)
# FIR: 101タップ(遮断周波数 Wn=0.5)
b_fir = signal.firwin(101, 0.5)
delay = 50
y_fir = signal.lfilter(b_fir, 1, x)
群遅延が一定の FIR フィルタを使うと、50 Hz、100 Hz、200 Hz のすべての成分がちょうど 50 サンプルだけ遅れます。合成波の形はそのまま保たれます。
ところが IIR フィルタでは、50 Hz と 200 Hz の成分が受け取る群遅延がまったく異なります。50 Hz(遮断周波数よりずっと低い)は安定した群遅延で通過しますが、100 Hz 成分は遮断周波数近傍で群遅延が大きく膨らむため、より長く遅れます。成分ごとの「到着時刻」がずれて、合成波の形が入力と変わってしまいます。

図の3つのパネルを見比べると、歪みが視覚的に明らかです。上段(入力)の波形に対し、中段(FIR 出力)はほぼ同じ形が 50 サンプル遅れて現れています。青点線(入力参照)との形状の一致が確認できます。一方、下段(IIR 出力)は入力とまったく異なる形になっています。波形のピークの位置や谷の深さが変わり、もはや元の 3 波合成とは別物の信号に見えます。この違いが、IIR フィルタによる波形歪みの実際のインパクトです。
振幅特性だけ見れば IIR も FIR も同様に同じ周波数帯域を通過させているはずなのに、波形の形がまったく異なります。まさに「振幅特性が良くても波形が崩れる謎」の答えがここにあります。
位相遅延 vs 群遅延 — 使い分けの指針
ここで、2つの遅延の定義を改めて整理しておきましょう。
位相遅延 $\tau_p(\omega)$:
$$ \tau_p(\omega) = -\frac{\phi(\omega)}{\omega} $$
ある周波数 $\omega$ の正弦波成分が時間的にどれだけ遅れるかを表します。「単一周波数成分のキャリア(搬送波)のズレ」に相当します。
群遅延 $\tau_g(\omega)$:
$$ \tau_g(\omega) = -\frac{d\phi(\omega)}{d\omega} $$
$\omega$ 付近の周波数帯域のグループ(包絡線)がどれだけ遅れるかを表します。「複数成分の合成波(パルスや変調波)の包絡線のズレ」に相当します。

左のグラフから、IIR フィルタでは位相遅延と群遅延が周波数によって大きく異なる値を取ることが確認できます。特に低周波では群遅延(青)の方が位相遅延(橙)より大きく、これは「包絡線の到着が搬送波の到着より遅れる」ことを意味します。右図の直感的な説明では、AM 信号で「搬送波のズレ(位相遅延 $\tau_p$)」と「包絡線のズレ(群遅延 $\tau_g$)」が異なる量であることが矢印で示されています。
どちらを使うべきか?の実践的な指針:
| 状況 | 使用する遅延 |
|---|---|
| 単一正弦波が何秒遅れるか | 位相遅延 $\tau_p$ |
| パルスや変調信号の「到着時刻」のズレ | 群遅延 $\tau_g$ |
| フィルタが波形を歪ませるかどうか | 群遅延の周波数一様性 |
| 線形位相フィルタのタップ遅延の確認 | 群遅延(一定値 $(N-1)/2$) |
通信システム設計では、群遅延の変動(group delay variation, GDV)が小さいほど信号品質が高いとされ、フィルタ仕様書には必ず群遅延の周波数特性が記載されます。
IIR フィルタの群遅延変動と対策
IIR フィルタは少ない係数で急峻な振幅特性を実現できますが、群遅延の変動(非線形位相)は避けられません。この変動の様子を、フィルタの次数と種類ごとに確認しましょう。
import numpy as np
from scipy import signal
# バターワースフィルタの次数別群遅延(低次で比較)
for order in [2, 4, 6]:
b, a = signal.butter(order, 0.3)
gd_w, gd = signal.group_delay((b, a), w=2048)
passband_gd = gd[gd_w < 0.3 * np.pi]
print(f"{order}次 バターワース: 最大群遅延 = {gd.max():.1f} サンプル, "
f"通過帯域内変動 = {passband_gd.max() - passband_gd.min():.1f}")
2次 バターワース: 最大群遅延 = 1.9 サンプル, 通過帯域内変動 = 0.5
4次 バターワース: 最大群遅延 = 4.7 サンプル, 通過帯域内変動 = 2.1
6次 バターワース: 最大群遅延 = 7.9 サンプル, 通過帯域内変動 = 4.1
次数が上がると群遅延の変動幅が増加します。高次フィルタほど急峻な振幅特性を持つかわりに、群遅延の非線形性が強くなります。8次以上になると scipy.signal.group_delay の数値安定性が低下する場合があり、UserWarning: The filter's denominator is extremely small という警告が出ることがあります。これは高次 IIR フィルタの極が単位円近傍にあるためで、実用上は次数を抑えるか後述の全域通過補正などを検討してください。

左パネルでは、バターワース次数が上がるほど遮断周波数付近で群遅延の変動が大きくなる様子が示されています。右パネルでは同次数(6次)の異なる IIR フィルタ種類を比較しています。チェビシェフ I 型と楕円フィルタは振幅特性が急峻ですが、群遅延の変動がバターワースより大きいことが確認できます。一方、ベッセルフィルタ(最大平坦位相) は群遅延がほぼ一定で、波形歪みが最も少ないフィルタですが、振幅特性は緩やかです。振幅特性と群遅延平坦性は多くの場合トレードオフの関係にあります。
群遅延変動への対策
IIR フィルタの群遅延変動に対する主な対策が2つあります。
1. 全域通過フィルタ(All-pass filter)による補正
全域通過フィルタは振幅特性を一切変えずに位相特性だけを変更できるフィルタです。伝達関数は
$$ H_{\text{AP}}(z) = \frac{a^* – z^{-1}}{1 – a z^{-1}} $$
($a$ は適切に選んだパラメータ)という形を持ち、$|H_{\text{AP}}(e^{j\omega})| = 1$ を保ちながら位相を調整します。設計したIIRフィルタに全域通過フィルタを縦続接続することで、群遅延をほぼ平坦に補正できます。
2. ゼロ位相フィルタリング(零位相処理)
オフライン処理(リアルタイムでなくても構わない場合)では、scipy.signal.filtfilt が使えます。これは正方向と逆方向の2回フィルタリングを行い、位相特性を相殺してゼロ群遅延($\tau_g = 0$)を実現します。
from scipy import signal
b_iir, a_iir = signal.butter(4, 0.3)
# 通常のフィルタリング(位相歪みあり)
y_lfilter = signal.lfilter(b_iir, a_iir, x)
# ゼロ位相フィルタリング(位相歪みなし、リアルタイム不可)
y_filtfilt = signal.filtfilt(b_iir, a_iir, x)
filtfilt は「過去のデータを使って逆方向に処理できる」オフライン環境でのみ有効です。リアルタイム処理では使用できませんが、心電図や音声の後処理、地震波データの解析などで広く使われています。
scipy.signal.group_delay を使った実践
scipy.signal.group_delay は FIR/IIR を問わず群遅延を計算できる便利な関数です。使い方と注意点をまとめます。
import numpy as np
from scipy import signal
# FIR フィルタ(分子係数のみ、分母は 1)
b_fir = signal.firwin(101, 0.3)
w_fir, gd_fir = signal.group_delay((b_fir, 1), w=1024)
print(f"FIR 群遅延(平均): {np.mean(gd_fir[w_fir < 0.3 * np.pi]):.1f} サンプル")
# → 50.0 サンプル(= (101-1)/2)
# IIR フィルタ(分子と分母の係数ペアを渡す)
b_iir, a_iir = signal.butter(6, 0.3)
w_iir, gd_iir = signal.group_delay((b_iir, a_iir), w=1024)
print(f"IIR 通過帯域での群遅延 最小: {gd_iir[w_iir < 0.1 * np.pi].min():.1f}")
print(f"IIR 遮断周波数付近の群遅延 最大: {gd_iir.max():.1f} サンプル")
# → 通過帯域で数サンプル〜遮断周波数付近で十数サンプルという変動が確認される
group_delay の第1引数は (b, a) のタプルです。FIR の場合は a = 1 を渡します。返り値は (w, gd) で、w は角周波数(ラジアン/サンプル)、gd は各周波数での群遅延(サンプル数単位)です。
# 正規化周波数に変換して使う
# w / np.pi → [0, 1] の正規化角周波数
# w の単位をHz に変換する場合: w * fs / (2 * np.pi)
group_delay は IIR フィルタの極が単位円近傍にある場合に数値的に不安定になることがあります(UserWarning: The filter's denominator is extremely small at frequencies... という警告が出る)。この場合は評価する周波数点数を減らすか、極が単位円上にないことを確認してください。

ガウシアンパルスをフィルタに通したデモです。上パネルでは3本の信号を比較しています。FIR 出力(緑)は入力(青)とほぼ同じ形状を保ちながら 50 サンプル程度遅れています。IIR 出力(橙)は、ガウシアンの滑らかな形が崩れ、ピーク位置と高さが変化しています。下パネルの scipy.signal.group_delay による群遅延プロットでは、FIR(緑)が一定の群遅延(赤破線)を示す一方、IIR(橙)は低周波から遮断周波数にかけて群遅延が大きく変化していることが確認できます。この群遅延変動がガウシアンパルス形状を崩す原因です。
線形位相 FIR の 4 つのタイプ
FIR フィルタの線形位相は、インパルス応答の対称性によって4つのタイプに分類されます。
| タイプ | タップ数 $N$ | 対称性 | 位相特性 | 用途 |
|---|---|---|---|---|
| I | 奇数 | 偶数対称: $h[n] = h[N-1-n]$ | $\phi = -\omega M$ | 汎用 LPF/BPF |
| II | 偶数 | 偶数対称: $h[n] = h[N-1-n]$ | $\phi = -\omega M$ | 汎用 LPF(最高周波数に $H=0$ の制約) |
| III | 奇数 | 奇数対称: $h[n] = -h[N-1-n]$ | $\phi = -\omega M + \pi/2$ | ヒルベルト変換、微分器 |
| IV | 偶数 | 奇数対称: $h[n] = -h[N-1-n]$ | $\phi = -\omega M + \pi/2$ | ヒルベルト変換、広帯域微分器 |
ここで $M = (N-1)/2$ です。タイプ I と II が最も一般的で、低域通過・帯域通過フィルタの設計に使われます。
from scipy import signal
# タイプI: 奇数タップ(N=61)
b_t1 = signal.firwin(61, 0.3) # N=61(奇数)
print("タイプI 群遅延:", (61-1)/2, "サンプル") # → 30.0
# タイプII: 偶数タップ(N=62)
b_t2 = signal.firwin(62, 0.3) # N=62(偶数)
print("タイプII 群遅延:", (62-1)/2, "サンプル") # → 30.5
# タイプIとIIで位相特性が若干異なるが、どちらも群遅延は一定
w1, gd1 = signal.group_delay((b_t1, 1), w=512)
w2, gd2 = signal.group_delay((b_t2, 1), w=512)
import numpy as np
print(f"タイプI 群遅延変動: {np.std(gd1[w1 < 0.3 * np.pi]):.4f}") # ≈ 0(一定)
print(f"タイプII 群遅延変動: {np.std(gd2[w2 < 0.3 * np.pi]):.4f}") # ≈ 0(一定)
タイプ II でタップ数 $N$ が偶数の場合、群遅延は $30.5$ サンプルと整数ではありませんが、それでも全周波数で一定です。「半サンプル」の遅延は奇数タップでは実現できない特徴で、信号の補間フィルタなどで活用されます。
タイプ I/II は低域でも高域でも応用できますが、タイプ I は最高周波数 $\omega = \pi$ で $H = 0$ になる制約がないのに対し、タイプ II は $\omega = \pi$ で必ず $H(e^{j\pi}) = 0$ になります。これはタイプ II が高域通過フィルタとして使えない理由です。
まとめ
本記事では、フィルタの位相特性と群遅延について、定義から証明、Python による実装まで解説しました。
- 位相応答 $\phi(\omega)$ は各周波数成分の位相シフトを表す。振幅特性とは独立した重要な特性
- 群遅延 $\tau_g = -d\phi/d\omega$ は位相の傾きの負で、「信号の包絡線(グループ)がどれだけ遅れるか」を表す
- 位相遅延 $\tau_p = -\phi/\omega$ は単一正弦波のキャリアの遅れ。群遅延とは異なる概念
- 線形位相 ↔ 一定群遅延: 位相が $\omega$ の1次式なら群遅延は定数になり、波形の形が保たれる(証明済)
- FIR フィルタの対称インパルス応答 $h[n] = h[N-1-n]$ が線形位相を保証。群遅延は $(N-1)/2$ サンプルで一定
- IIR フィルタは原理的に線形位相を実現できない。遮断周波数付近で群遅延が大きく変動し、波形歪みが生じる
- 波形保存が重要な場合は FIR フィルタ を選ぶか、オフライン処理なら
filtfiltでゼロ位相化する
群遅延を理解すると、なぜ音声処理でデジタルイコライザーに FIR フィルタが好まれるのか、なぜ通信受信機の等化器が群遅延補正を必要とするのか、といった実用的な問いに答えられます。
次のステップとして、以下の記事も参考にしてください。