チェビシェフフィルタの設計原理とPython実装

scipy.signal.cheby1 / scipy.signal.cheby2 / scipy.signal.cheb1ord でチェビシェフフィルタをPython実装。I型/II型の等リップル比較とバターワースとの違い、次数決定式、LPF/HPF/BPF設計コードと周波数応答の可視化を解説します。

はじめに

チェビシェフフィルタは、通過域(I型)または阻止域(II型)に等リップルを許容することで、同次数のバターワースフィルタよりも急峻な遷移帯域を実現するIIRフィルタです。

バターワースフィルタ が通過域で最大限に平坦な特性を持つのに対し、チェビシェフフィルタはその平坦性を犠牲にして遷移の鋭さを優先します。本記事ではI型とII型それぞれの数学的基礎から、SciPyを使った実践的な設計・実装までを解説します。

チェビシェフ多項式

チェビシェフフィルタの周波数特性は、チェビシェフ多項式 \(T_N(x)\) に基づいています。

\[T_N(x) = \begin{cases} \cos(N \arccos x) & |x| \leq 1 \\ \cosh(N \operatorname{arccosh} x) & |x| > 1 \end{cases} \tag{1}\]

低次のチェビシェフ多項式は次のとおりです。

次数 \(N\)\(T_N(x)\)
0\(1\)
1\(x\)
2\(2x^2 - 1\)
3\(4x^3 - 3x\)
4\(8x^4 - 8x^2 + 1\)

\(|x| \leq 1\) の範囲でチェビシェフ多項式は \(-1\) から \(+1\) の間を等振幅で振動する性質があり、これがフィルタに等リップル特性をもたらします。

なぜチェビシェフ多項式なのか:最小最大性(ミニマックス性)

チェビシェフフィルタが等リップル多項式としてチェビシェフ多項式を選ぶのは、単に「振動する関数だから」ではありません。その背後には、チェビシェフ自身が1854年に証明した次の定理があります。

チェビシェフの最小最大定理:区間 \([-1,1]\) 上で、最高次係数が \(1\) の(モニックな)\(N\) 次多項式のうち、絶対値の最大値(sup ノルム)を最小化するものは

\[ \tilde{T}_N(x) = \frac{1}{2^{N-1}} T_N(x) \]

であり、そのときの最小値は \(2^{1-N}\) である。

証明の骨子(背理法):\(T_N(x) = \cos(N\arccos x)\) は、区間内の \(N+1\) 個の点で交互に符号が反転する極値を取ります(等振幅極値、equioscillation)。極値点とそこでの値を次のように書きます。

\[ x_m = \cos\!\left(\frac{m\pi}{N}\right), \qquad T_N(x_m) = (-1)^m \qquad (m=0,1,\ldots,N) \]

いま \(\tilde{T}_N\) より小さい sup ノルムを持つモニック多項式 \(q(x)\) が存在すると仮定し、差を

\[ \Delta(x) = \tilde{T}_N(x) - q(x) \]

とおきます。両者ともモニックなので最高次項が相殺し、\(\Delta(x)\) の次数は \(N-1\) 以下です。\(q\) の sup ノルムは極値の絶対値(\(=1\) )より小さいという仮定なので、上式の \(N+1\) 個の点における \(\Delta\) の値の符号は、そこでの \(T_N\) の符号のまま交互に反転します。符号が \(N+1\) 点で交互に反転する連続関数は区間内に少なくとも \(N\) 個の零点を持たねばなりませんが、これは次数 \(N-1\) 以下の多項式が持てる零点数の上限(\(N-1\) 個)を超えており矛盾します。したがって \(\tilde{T}_N\) が唯一の最小化多項式です。

この定理が意味するのは、「通過域リップル \(R_p\) の範囲に収めるという制約の下で、遷移帯域を最も急峻にする \(N\) 次多項式近似はチェビシェフ多項式以外にあり得ない」ということです。つまりチェビシェフフィルタは、多項式で表現される振幅特性の中で理論上最適な選択です(有理関数まで許せば、さらに急峻にできる 楕円フィルタ が存在します。楕円フィルタはこのチェビシェフの理論を、単一周期・多項式から二重周期・有理関数へ一般化したゾロタレフ有理関数に基づいており、詳細は楕円フィルタの記事にまとめています)。

数値検証:上の証明の主張(モニックなチェビシェフ多項式から少しでもずれると sup ノルムが増加する)を、実際に確かめます。\(N=5\) のモニック・チェビシェフ多項式

\[ \tilde{T}_5(x) = T_5(x)/2^4 \]

に、ランダムな低次多項式を微小係数 \(\varepsilon_p\) で加えて sup ノルムの変化を見ます。

import numpy as np
from numpy.polynomial import chebyshev as C

N = 5
x = np.linspace(-1, 1, 400001)

coeffs = np.zeros(N + 1); coeffs[N] = 1.0
monic_cheb = C.chebval(x, coeffs) / 2**(N - 1)
sup_cheb = np.max(np.abs(monic_cheb))

rng = np.random.default_rng(1)
direction = rng.normal(size=N)
direction /= np.linalg.norm(direction)
powers = np.vstack([x**i for i in range(N)])

print(f"モニック・チェビシェフ T_5/2^4 の sup ノルム = {sup_cheb:.8f}  (理論値 2^-4 = {2**(1-N):.8f})")
for eps_p in [0.0, 0.001, 0.005, 0.01, 0.05, -0.001, -0.01]:
    perturbed = monic_cheb + eps_p * (direction @ powers)
    sup_p = np.max(np.abs(perturbed))
    print(f"  eps={eps_p:+.4f}: sup ノルム = {sup_p:.8f}  (差分 {sup_p - sup_cheb:+.8f})")

実行結果:

モニック・チェビシェフ T_5/2^4 の sup ノルム = 0.06250000  (理論値 2^-4 = 0.06250000)
  eps=+0.0000: sup ノルム = 0.06250000  (差分 +0.00000000)
  eps=+0.0010: sup ノルム = 0.06309459  (差分 +0.00059459)
  eps=+0.0050: sup ノルム = 0.06547294  (差分 +0.00297294)
  eps=+0.0100: sup ノルム = 0.06844589  (差分 +0.00594589)
  eps=+0.0500: sup ノルム = 0.09222943  (差分 +0.02972943)
  eps=-0.0010: sup ノルム = 0.06361524  (差分 +0.00111524)
  eps=-0.0100: sup ノルム = 0.07365243  (差分 +0.01115243)

ランダムに選んだどの摂動方向・符号でも sup ノルムは厳密に増加しており(差分は常に正)、\(\varepsilon_p=0\) (=チェビシェフ多項式そのもの)が局所的な最小点になっていることが数値的に確認できます。

下図は \(N=6\) のチェビシェフ多項式 \(T_6(x)\) の等振幅極値(\(N+1=7\) 個)を示したものです。\(|x|\leq 1\) では \(\pm 1\) の間を等振幅で往復し、\(|x|>1\) では急峻に発散する様子が確認できます。

チェビシェフ多項式T6(x)の等振幅振動と急峻な発散

チェビシェフI型フィルタ

振幅特性

\(N\) 次チェビシェフI型ローパスフィルタの振幅二乗関数は次の式で定義されます。

\[|H(j\Omega)|^2 = \frac{1}{1 + \varepsilon^2 T_N^2\!\left(\dfrac{\Omega}{\Omega_p}\right)} \tag{2}\]

ここで、

  • \(\varepsilon\) : リップル係数(\(\varepsilon > 0\) )
  • \(\Omega_p\) : 通過域端の角周波数
  • \(T_N\) : \(N\) 次チェビシェフ多項式

通過域(\(\Omega \leq \Omega_p\) )では、

\[ T_N(\Omega/\Omega_p) \in [-1, 1] \]

となり、振幅は \(1/\sqrt{1+\varepsilon^2}\) から \(1\) の間を等振幅でリップルします。通過域リップル \(R_p\) [dB] とリップル係数の関係は次のとおりです。

\[\varepsilon = \sqrt{10^{R_p/10} - 1} \tag{3}\]

エッジケース:DC利得は次数の偶奇で変わる

式(2)で \(\Omega=0\) を代入すると、DC(直流、\(\Omega=0\) )における利得は \(T_N(0)\) の値だけで決まります。\(T_N(0)=\cos(N\arccos 0)=\cos(N\pi/2)\) なので、

  • \(N\) が奇数:\(N\pi/2\) は \(\pi/2\) の奇数倍で \(\cos(N\pi/2)=0\) となり、\(|H(j0)|^2=1\) (DC利得はちょうど \(0\) dB、完全通過)
  • \(N\) が偶数:\(\cos(N\pi/2)=\pm 1\) となり、\(|H(j0)|^2=1/(1+\varepsilon^2)\) (DC利得は通過域リップルの下限 \(-R_p\) dB そのもの)

これは初学者が見落としやすい落とし穴です。「通過域では利得が \(0\) dB 付近でリップルする」と考えて、偶数次のフィルタでも \(\Omega=0\) で \(0\) dB が出ると誤解しがちですが、偶数次では DC ゲインそのものが \(-R_p\) dB からスタートします。実際に数値で確認します。

import numpy as np
from scipy import signal

fs = 100_000
fp = 100
rp = 1.0
eps = np.sqrt(10**(rp / 10) - 1)

print(f"{'N':<4}{'T_N(0)':>10}{'理論DC利得[dB]':>18}{'実測DC利得[dB]':>18}")
for N in [3, 4, 5, 6, 7, 8]:
    TN0 = np.cos(N * np.pi / 2)
    H2 = 1 / (1 + eps**2 * TN0**2)
    gain_theory_db = 10 * np.log10(H2)

    sos = signal.cheby1(N, rp, fp, btype='low', fs=fs, output='sos')
    _, h = signal.sosfreqz(sos, worN=[1e-6], fs=fs)
    gain_meas_db = 20 * np.log10(np.abs(h[0]))

    print(f"{N:<4}{TN0:>10.3f}{gain_theory_db:>18.4f}{gain_meas_db:>18.4f}")

実行結果:

N       T_N(0)        理論DC利得[dB]        実測DC利得[dB]
3       -0.000            0.0000            0.0000
4        1.000           -1.0000           -1.0000
5        0.000            0.0000           -0.0000
6       -1.000           -1.0000           -1.0000
7       -0.000            0.0000            0.0000
8        1.000           -1.0000           -1.0000

理論値と scipy.signal.cheby1 の実測値が完全に一致し、奇数次(\(N=3,5,7\) )は DC利得ちょうど \(0\) dB、偶数次(\(N=4,6,8\) )は DC利得ちょうど \(-R_p=-1\) dB になることが確認できます。この偶奇差はチェビシェフI型に固有の性質で、バターワース(式(1)より \(\Omega=0\) で常に \(|H|=1\) )にはありません。直流成分を正確に \(0\) dB で通したい用途(DC結合の計装アンプ段など)では、偶数次のチェビシェフI型を選ぶ際にこの減衰を見込んでおく必要があります。

極の配置

チェビシェフI型フィルタの極は、楕円上に配置されます。\(k = 1, 2, \ldots, N\) について

\[\sigma_k = -\sinh\!\left(\frac{\operatorname{arcsinh}(1/\varepsilon)}{N}\right)\sin\theta_k \tag{4}\]

\[\omega_k = \cosh\!\left(\frac{\operatorname{arcsinh}(1/\varepsilon)}{N}\right)\cos\theta_k \tag{5}\]

ただし \(\theta_k = \dfrac{\pi(2k-1)}{2N}\) です。安定な左半平面の極は

\[ s_k = \sigma_k + j\omega_k \]

のみを採用します。

式(4)(5)の導出

正規化(\(\Omega_p=1\) )した振幅二乗関数の分母を \(0\) にする \(s\) (極)を求めます。\(H(s)H(-s)\) の分母は解析接続 \(\Omega \to -js\) により \(1+\varepsilon^2 T_N^2(-js)\) となるので、極は次の方程式の解です。

\[ T_N(-js) = \pm \frac{j}{\varepsilon} \]

ここで \(-js = \cos\theta\) (\(\theta = u+jv\) 、\(u,v\) は実数)と置換します。チェビシェフ多項式の定義 \(T_N(\cos\theta)=\cos(N\theta)\) は複素 \(\theta\) へ解析接続してもそのまま成立するため、

\[ \cos(Nu+jNv) = \cos(Nu)\cosh(Nv) - j\sin(Nu)\sinh(Nv) = \pm\frac{j}{\varepsilon} \]

を得ます。右辺は純虚数なので実部はゼロでなければなりません。\(\cosh(Nv) > 0\) は常に成り立つため、

\[ \cos(Nu) = 0 \ \Longrightarrow\ u_k = \theta_k = \frac{(2k-1)\pi}{2N} \qquad (k=1,\ldots,N) \]

が要求されます。\(\theta_k \in (0,\pi)\) の範囲でこれは \(N\) 個の解を与えます。虚部の等式

\[ -\sin(N\theta_k)\sinh(Nv)=\pm \frac{1}{\varepsilon} \]

に \(\sin(N\theta_k)=\sin\!\left(\frac{(2k-1)\pi}{2}\right)=\pm 1\) を代入すると \(\sinh(Nv)=1/\varepsilon\) (符号は \(v\) の符号選択に吸収されます)となり、

\[ v = \frac{1}{N}\operatorname{arcsinh}\!\left(\frac{1}{\varepsilon}\right) \]

が定まります。最後に \(s = j\cos\theta = j\cos(u+jv) = \sin u \sinh v + j\cos u \cosh v\) を展開すると、\(\theta_k\in(0,\pi)\) の範囲では常に \(\sin\theta_k>0\) なので

\[ \sigma_k = -\sinh(v)\sin\theta_k \leq 0, \qquad \omega_k = \cosh(v)\cos\theta_k \]

となり、\(k=1,\ldots,N\) の解がそのまま安定な左半平面の極(式(4)(5))を与えることが分かります(\(2N\) 個ある解のうち、\(\theta_k\in(0,\pi)\) を満たす半分が自動的に安定側として選ばれています)。

数値検証:この導出式と scipy.signal.cheb1ap が返す解析的プロトタイプの極を、\(N=6\) 、\(R_p=1\) dB で比較します。

import numpy as np
from scipy import signal

N = 6
rp = 1.0
eps = np.sqrt(10**(rp / 10) - 1)

v = np.arcsinh(1 / eps) / N
k = np.arange(1, N + 1)
theta = np.pi * (2 * k - 1) / (2 * N)
poles_formula = -np.sinh(v) * np.sin(theta) + 1j * np.cosh(v) * np.cos(theta)

_, poles_scipy, _ = signal.cheb1ap(N, rp)

print("導出式による極   :", np.round(np.sort_complex(poles_formula), 6))
print("cheb1apによる極   :", np.round(np.sort_complex(poles_scipy), 6))
print("最大絶対誤差      :", np.max(np.abs(np.sort_complex(poles_formula) - np.sort_complex(poles_scipy))))

実行結果:

導出式による極   : [-0.232063-0.727227j -0.232063-0.266184j -0.169882+0.266184j
 -0.169882+0.727227j -0.062181-0.993411j -0.062181+0.993411j]
cheb1apによる極   : [-0.232063-0.727227j -0.232063-0.266184j -0.169882+0.266184j
 -0.169882+0.727227j -0.062181-0.993411j -0.062181+0.993411j]
最大絶対誤差      : 1.3472894910096233e-16

導出式とSciPyの結果は浮動小数点誤差(\(10^{-16}\) オーダー)の範囲で完全に一致しました。下図は、この \(N=6\) のチェビシェフI型の極(楕円上)と、同次数のバターワースの極(円上)を重ねて描いたものです。チェビシェフI型の極は虚軸に近い狭い楕円上に集まっており、これが同次数でもバターワースより急峻な遷移帯域を生む理由です。

チェビシェフI型とバターワースの極配置比較

エッジケース:高次・低リップルでの極の虚軸接近:楕円の半短軸は \(\sinh(v)=\sinh(\operatorname{arcsinh}(1/\varepsilon)/N)\) で決まります。\(N\) を大きくする、あるいは \(R_p\) (したがって \(\varepsilon\) )を小さくすると \(v\to 0\) に近づき、\(\sinh(v)\approx v\) と近似できるため極の実部(減衰)は \(O(1/N)\) で小さくなります。つまり高次・低リップル設計ほど極が虚軸に接近し、Q値(共振の鋭さ)が高くなります。これは実装上、直接形(Direct Form)の伝達関数係数が桁落ちしやすくなることを意味し、 楕円フィルタ の記事と同様、\(N\) が大きい場合は二次セクション(SOS)形式での実装を推奨します。

チェビシェフII型フィルタ

チェビシェフII型(逆チェビシェフ)フィルタは、阻止域に等リップルを持ち、通過域は単調です。振幅二乗関数は次のとおりです。

\[|H(j\Omega)|^2 = \frac{1}{1 + \left[\varepsilon^2 T_N^2\!\left(\dfrac{\Omega_s}{\Omega}\right)\right]^{-1}} \tag{6}\]

ここで \(\Omega_s\) は阻止域端の角周波数です。通過域が単調なので、位相特性がI型より滑らかになります。

エッジケース:cheby2のWnパラメータが指すのは阻止域端

scipy.signal.cheby1scipy.signal.cheby2はどちらも「カットオフ周波数」に相当する引数Wnを1つ取りますが、その意味はI型とII型で異なります

  • cheby1(N, rp, Wn, ...)Wnは式(2)の \(\Omega_p\) (通過域端、利得が \(-R_p\) dB に達する点)
  • cheby2(N, rs, Wn, ...)Wnは式(6)の \(\Omega_s\) (阻止域端、利得が \(-R_s\) dB に達する点)

同じ「Wn」という引数名なのに指している帯域端が違うため、「cheby1と同じ感覚でWn=通過域端のつもりでcheby2を呼ぶ」という誤りが起こりがちです。実際に阻止域端 150 Hz、阻止域減衰 40 dB で6次のチェビシェフII型を設計し、各周波数での利得を確認します。

import numpy as np
from scipy import signal

fs = 1000
N = 6
rs = 40.0
fs_stop = 150  # cheby2に渡すWnは「阻止域端」=ここでrsぶん減衰する周波数

sos = signal.cheby2(N, rs, fs_stop, btype='low', fs=fs, output='sos')
freqs = np.array([50, 100, 130, 140, 149, 150, 160, 200])
_, h = signal.sosfreqz(sos, worN=freqs, fs=fs)
gdb = 20 * np.log10(np.abs(h))

for f, g in zip(freqs, gdb):
    print(f"f={f:4d} Hz  ゲイン={g:8.3f} dB")

実行結果:

f=  50 Hz  ゲイン=  -0.000 dB
f= 100 Hz  ゲイン=  -0.759 dB
f= 130 Hz  ゲイン= -15.531 dB
f= 140 Hz  ゲイン= -24.903 dB
f= 149 Hz  ゲイン= -37.759 dB
f= 150 Hz  ゲイン= -40.000 dB
f= 160 Hz  ゲイン= -43.434 dB
f= 200 Hz  ゲイン= -66.189 dB

指定したfs_stop=150Hzでちょうど-rs=-40.0dBになっており、Wnが阻止域端であることが確認できます。同時に注目すべきは、通過域は指定したWnよりかなり手前から劣化し始めている点です。100 Hz(阻止域端の2/3の位置)ですでに \(-0.759\) dB の減衰が生じており、「Wnまで平坦」という誤解も禁物です。チェビシェフII型は通過域リップルパラメータを持たず、通過域端を明示的に指定する仕組みがないため、「どこまでが実質的に平坦な通過域か」は \(N\) ・\(R_s\) ・Wnの組み合わせで決まる遷移帯域の形状から読み取る必要があります。厳密に通過域端を指定したい場合は、scipy.signal.cheb2ordに通過域端・阻止域端の両方を渡して次数とWnを自動計算させるのが安全です。

I型とII型の特性比較

特性チェビシェフI型チェビシェフII型
通過域等リップル単調(平坦)
阻止域単調等リップル
遷移帯域バターワースより急峻バターワースより急峻
位相特性非線形(I型より非線形)比較的滑らか
用途ノイズ除去の急峻遮断通過域平坦性重視

フィルタ次数の決定

所望の仕様(通過域リップル \(R_p\) 、阻止域減衰 \(R_s\) 、通過域端 \(\Omega_p\) 、阻止域端 \(\Omega_s\) )から必要な次数を求めます。

\[N \geq \frac{\operatorname{arccosh}\!\left(\sqrt{\dfrac{10^{R_s/10}-1}{10^{R_p/10}-1}}\right)}{\operatorname{arccosh}(\Omega_s/\Omega_p)} \tag{7}\]

同じ仕様に対して、チェビシェフフィルタはバターワースフィルタよりも低い次数で仕様を満足できます。

式(7)の導出

阻止域端 \(\Omega_s\) において、要求される減衰量 \(R_s\) [dB] を満たす条件は、式(2)の \(\Omega_p\) を基準とした \(\varepsilon\) (式(3))に加えて、阻止域減衰から定義される

\[ \varepsilon_s = \sqrt{10^{R_s/10}-1} \]

を導入すると、次のように書けます。

\[ |H(j\Omega_s)|^2 = \frac{1}{1+\varepsilon^2 T_N^2(\Omega_s/\Omega_p)} \leq \frac{1}{1+\varepsilon_s^2} \]

両辺の分母を比較すると、

\[ \varepsilon^2 T_N^2(\Omega_s/\Omega_p) \geq \varepsilon_s^2 \]

となります。阻止域端は通過域端より高域(比が \(1\) より大きい)なので \(T_N(x) = \cosh(N\operatorname{arccosh} x)\) が正かつ単調増加であり、そのまま平方根を取って

\[ T_N(\Omega_s/\Omega_p) \geq \frac{\varepsilon_s}{\varepsilon} \quad\Longleftrightarrow\quad \cosh\!\big(N\operatorname{arccosh}(\Omega_s/\Omega_p)\big) \geq \frac{\varepsilon_s}{\varepsilon} \]

を得ます。\(\operatorname{arccosh}\) は単調増加関数なので両辺に適用でき、

\[ N\operatorname{arccosh}(\Omega_s/\Omega_p) \geq \operatorname{arccosh}\!\left(\frac{\varepsilon_s}{\varepsilon}\right) \]

したがって

\[ N \geq \frac{\operatorname{arccosh}(\varepsilon_s/\varepsilon)}{\operatorname{arccosh}(\Omega_s/\Omega_p)} \]

となります。ここで

\[ \frac{\varepsilon_s}{\varepsilon} = \sqrt{\frac{\varepsilon_s^2}{\varepsilon^2}} = \sqrt{\frac{10^{R_s/10}-1}{10^{R_p/10}-1}} \]

を代入すれば式(7)に一致します。等号は仕様をちょうど満たす実数次数を与えるため、実際に採用する次数はこれ以上の最小の整数(切り上げ)です。

Python実装

SciPyによるチェビシェフI型フィルタ設計

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

# --- フィルタ仕様 ---
fs = 1000         # サンプリング周波数 [Hz]
fp = 100          # 通過域端 [Hz]
rp = 1.0          # 通過域リップル [dB]
orders = [2, 4, 6]

fig, axes = plt.subplots(2, 1, figsize=(10, 8))

for N in orders:
    # チェビシェフI型ローパスフィルタ
    sos = signal.cheby1(N, rp, fp, btype='low', fs=fs, output='sos')
    w, h = signal.sosfreqz(sos, worN=4096, fs=fs)

    axes[0].plot(w, 20 * np.log10(np.abs(h) + 1e-12), label=f'N={N}')
    axes[1].plot(w, np.degrees(np.unwrap(np.angle(h))), label=f'N={N}')

axes[0].set_xlabel('Frequency [Hz]')
axes[0].set_ylabel('Magnitude [dB]')
axes[0].set_title('Chebyshev Type I - Magnitude Response (rp=1dB)')
axes[0].set_xlim(0, 500)
axes[0].set_ylim(-80, 5)
axes[0].axvline(fp, color='gray', linestyle='--', alpha=0.5, label=f'fp={fp}Hz')
axes[0].axhline(-rp, color='r', linestyle=':', alpha=0.5, label=f'-{rp}dB ripple')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('Phase [degrees]')
axes[1].set_title('Chebyshev Type I - Phase Response')
axes[1].set_xlim(0, 500)
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

チェビシェフII型フィルタ設計

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

fs = 1000
fs_stop = 150     # 阻止域端 [Hz]
rs = 40.0         # 阻止域減衰量 [dB]
orders = [2, 4, 6]

fig, axes = plt.subplots(2, 1, figsize=(10, 8))

for N in orders:
    # チェビシェフII型ローパスフィルタ
    sos = signal.cheby2(N, rs, fs_stop, btype='low', fs=fs, output='sos')
    w, h = signal.sosfreqz(sos, worN=4096, fs=fs)

    axes[0].plot(w, 20 * np.log10(np.abs(h) + 1e-12), label=f'N={N}')
    axes[1].plot(w, np.degrees(np.unwrap(np.angle(h))), label=f'N={N}')

axes[0].set_xlabel('Frequency [Hz]')
axes[0].set_ylabel('Magnitude [dB]')
axes[0].set_title('Chebyshev Type II - Magnitude Response (rs=40dB)')
axes[0].set_xlim(0, 500)
axes[0].set_ylim(-80, 5)
axes[0].axvline(fs_stop, color='gray', linestyle='--', alpha=0.5, label=f'fs={fs_stop}Hz')
axes[0].axhline(-rs, color='r', linestyle=':', alpha=0.5, label=f'-{rs}dB')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('Phase [degrees]')
axes[1].set_title('Chebyshev Type II - Phase Response')
axes[1].set_xlim(0, 500)
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

バターワースとの次数比較

from scipy.signal import buttord, cheb1ord, cheb2ord
import numpy as np

# フィルタ仕様
fp = 100       # 通過域端 [Hz]
fs_stop = 150  # 阻止域端 [Hz]
rp = 1.0       # 通過域リップル [dB]
rs = 40.0      # 阻止域減衰量 [dB]
fs = 1000      # サンプリング周波数

# バターワースの必要次数
N_butter, Wn_butter = buttord(fp, fs_stop, rp, rs, fs=fs)

# チェビシェフI型の必要次数
N_cheby1, Wn_cheby1 = cheb1ord(fp, fs_stop, rp, rs, fs=fs)

# チェビシェフII型の必要次数
N_cheby2, Wn_cheby2 = cheb2ord(fp, fs_stop, rp, rs, fs=fs)

print(f"バターワース    : {N_butter}次")
print(f"チェビシェフI型 : {N_cheby1}次")
print(f"チェビシェフII型: {N_cheby2}次")

実行結果:

バターワース    : 12次
チェビシェフI型 : 6次
チェビシェフII型: 6次

同じ仕様(通過域端100Hz、阻止域端150Hz、通過域リップル1dB、阻止域減衰40dB)に対して、バターワースが12次必要なのに対し、チェビシェフはI型・II型ともに6次で仕様を満たせます。次数はそのまま伝達関数の状態変数の数(=実装コスト)に直結するため、この条件ではチェビシェフを使うことでバターワースの半分の演算量・メモリで同じ仕様を達成できることになります。

この6次のチェビシェフI型フィルタが本当に仕様を満たしているかを検証します。

from scipy import signal
import numpy as np

sos1 = signal.cheby1(N_cheby1, rp, Wn_cheby1, btype='low', fs=fs, output='sos')
_, h = signal.sosfreqz(sos1, worN=[fp, fs_stop], fs=fs)
gdb = 20 * np.log10(np.abs(h))
print(f"cheby1 N={N_cheby1}: gain@fp={gdb[0]:.3f}dB (要求 -{rp}dB), gain@fs_stop={gdb[1]:.3f}dB (要求 <=-{rs}dB)")

実行結果:

cheby1 N=6: gain@fp=-1.000dB (要求 -1.0dB), gain@fs_stop=-41.324dB (要求 <=-40.0dB)

通過域端でちょうど要求どおり \(-1.000\) dB、阻止域端では要求 \(-40.0\) dB に対して \(-41.324\) dB と1.3dB強の余裕を持って仕様を満たしていることが確認できます。なお、バターワース・チェビシェフI型・チェビシェフII型・楕円フィルタの4種を横並びで比較した次数表は 楕円フィルタの記事 にまとめているので、そちらも参照してください(楕円フィルタは同条件でさらに少ない次数になります)。

ノイズ除去への応用

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

fs = 1000
t = np.arange(0, 1, 1/fs)
clean = np.sin(2 * np.pi * 10 * t) + 0.5 * np.sin(2 * np.pi * 30 * t)
noisy = clean + 0.8 * np.random.randn(len(t))

# バターワース(4次)
sos_butter = signal.butter(4, 100, fs=fs, output='sos')
y_butter = signal.sosfiltfilt(sos_butter, noisy)

# チェビシェフI型(4次、1dBリップル)
sos_cheby1 = signal.cheby1(4, 1, 100, fs=fs, output='sos')
y_cheby1 = signal.sosfiltfilt(sos_cheby1, noisy)

# チェビシェフII型(4次、40dB減衰)
sos_cheby2 = signal.cheby2(4, 40, 150, fs=fs, output='sos')
y_cheby2 = signal.sosfiltfilt(sos_cheby2, noisy)

fig, axes = plt.subplots(4, 1, figsize=(10, 12), sharex=True)
axes[0].plot(t, noisy, alpha=0.5, label='Noisy'); axes[0].plot(t, clean, 'k', label='Original')
axes[0].set_title('Input Signal'); axes[0].legend(); axes[0].grid(True, alpha=0.3)

axes[1].plot(t, y_butter, label='Butterworth N=4'); axes[1].plot(t, clean, 'k', lw=1.5)
axes[1].set_title('Butterworth (N=4)'); axes[1].legend(); axes[1].grid(True, alpha=0.3)

axes[2].plot(t, y_cheby1, label='Chebyshev I N=4, rp=1dB'); axes[2].plot(t, clean, 'k', lw=1.5)
axes[2].set_title('Chebyshev Type I (N=4, rp=1dB)'); axes[2].legend(); axes[2].grid(True, alpha=0.3)

axes[3].plot(t, y_cheby2, label='Chebyshev II N=4, rs=40dB'); axes[3].plot(t, clean, 'k', lw=1.5)
axes[3].set_title('Chebyshev Type II (N=4, rs=40dB)')
axes[3].set_xlabel('Time [s]'); axes[3].legend(); axes[3].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

他のIIRフィルタとの比較

フィルタ通過域阻止域遷移帯域位相特性
バターワース最大平坦単調減少広い比較的滑らか
チェビシェフI型等リップル単調減少バターワースより狭い非線形
チェビシェフII型単調減少等リップルバターワースより狭い比較的滑らか
楕円(Cauer)等リップル等リップル最も狭い最も非線形

急峻な遮断が必要で通過域のリップルが許容できる場合はI型、通過域の平坦性を保ちつつ阻止域を改善したい場合はII型が適しています。

近年の研究動向

古典的な整数次チェビシェフフィルタは、通過域リップル・阻止域減衰・遷移帯域幅という3つの仕様を式(2)(6)(7)の閉形式で満たす設計として1930年代以降確立していますが、次数 \(N\) は必然的に整数にしかなれず、「\(N\) 次では過剰、\(N+1\) 次では過大」という中途半端な要求に対しては次数を1つ余分に消費せざるを得ません。この制約に対し、2024年に Daryani と Aggarwal は、次数を実数 \(1+\alpha\) (\(0<\alpha<1\) )のように連続的に扱う分数次(fractional-order)チェビシェフローパスフィルタの設計に、粒子群最適化(PSO)・ホタルアルゴリズム(FA)・グレイウルフ最適化(GWO)といった自然界に着想を得たメタヒューリスティック最適化アルゴリズムを適用し、理想的な分数次チェビシェフ特性に近い振幅応答を実現する係数を求める手法を提案しました。さらに、求めた係数を実際にOTA(Operational Transconductance Amplifier)およびCCII(第2世代カレントコンベヤ)ベースのアナログ回路トポロジーで実装し、SPICEシミュレーションにより理想特性との平均二乗誤差がCCII構成で\(-48.67\) dB、OTA構成で\(-62.8\) dBという高精度で一致することを確認しています(Daryani & Aggarwal, 2024, Integration, the VLSI Journal)。

この研究は、本記事で扱った整数次のチェビシェフI型設計を置き換えるものではなく、次数という離散パラメータを連続化することで、整数次では実現できない「中間的な」遷移帯域の鋭さを実現する拡張と位置づけられます。組み込み用途で演算量が固定次数に強く制約される場合は本記事の整数次設計が引き続き主流ですが、アナログ回路設計や、次数を実数パラメータとして最適化ループに組み込みたい研究用途では、分数次アプローチが今後選択肢として広がっていく可能性があります。

まとめ

  • チェビシェフフィルタはチェビシェフ多項式の等振幅特性を利用した等リップルIIRフィルタ
  • I型は通過域に等リップル、II型は阻止域に等リップルを持つ
  • チェビシェフ多項式が等リップルの構成要素に選ばれるのは、区間 \([-1,1]\) 上のモニック多項式の中で sup ノルムを最小化するという最小最大定理(equioscillation theorem)による
  • I型フィルタのDC利得は次数の偶奇で変わり、奇数次は \(0\) dB、偶数次は \(-R_p\) dB になる
  • 同次数でバターワースより急峻な遷移帯域を実現できる(本記事の実行例ではバターワース12次に対しチェビシェフI型・II型はいずれも6次)
  • SciPyでは signal.cheby1() / signal.cheby2() で設計、signal.cheb1ord() / signal.cheb2ord() で必要次数を自動計算できる

関連記事

参考文献


関連ツール