FIR ํํฐ ์ค๊ณ(FIR Filter Design)
FIR ํํฐ ์ค๊ณ(FIR Filter Design)¶
ํ์ต ๋ชฉํ¶
- ๋์งํธ ํํฐ ์ค๊ณ์ ์ฌ์๊ณผ ์ฉ์ด๋ฅผ ์ดํดํ๋ค
- FIR ํํฐ ์ค๊ณ๋ฅผ ์ํ ์ฐฝํจ์๋ฒ(Window Method)์ ๋ง์คํฐํ๊ณ ์ฐฝํจ์๋ค์ ๋น๊ตํ๋ค
- ์ฃผํ์ ํ๋ณธํ(Frequency Sampling) ๋ฐ ์ต์ ๋ฑ๋ฆฌํ(Parks-McClellan) ์ค๊ณ ๋ฐฉ๋ฒ์ ํ์ตํ๋ค
- ์ ํ ์์(Linear Phase) FIR ํํฐ ์ ํ๊ณผ ๊ทธ ์ ์ฝ ์กฐ๊ฑด์ ์ดํดํ๋ค
- Python์
scipy.signal๋ชจ๋์ ์ฌ์ฉํ์ฌ FIR ํํฐ๋ฅผ ์ค๊ณํ๋ค - ์ง์ ํฉ์ฑ๊ณฑ(Direct Convolution), ์ค์ฒฉ ๊ฐ์ฐ๋ฒ(Overlap-Add), ์ค์ฒฉ ์ ์ฅ๋ฒ(Overlap-Save)์ ์ด์ฉํ FIR ํํฐ๋ง์ ๊ตฌํํ๋ค
- ์ ์ด ๋์ญํญ(Transition Width), ์ ์ง๋์ญ ๊ฐ์ (Stopband Attenuation), ์ฐ์ฐ ๋น์ฉ ์ธก๋ฉด์์ ์ค๊ณ ๋ฐฉ๋ฒ๋ค์ ๋น๊ตํ๋ค
๋ชฉ์ฐจ¶
- FIR ํํฐ ์ค๊ณ ์ฌ์
- ์ด์์ ์ธ ํํฐ์ ์ํ์ค ์๋ต
- ์ฐฝํจ์๋ฒ
- ์ฐฝํจ์
- ์นด์ด์ ์ฐฝํจ์ ์ค๊ณ
- ์ฃผํ์ ํ๋ณธํ๋ฒ
- ์ต์ ๋ฑ๋ฆฌํ ์ค๊ณ: Parks-McClellan ์๊ณ ๋ฆฌ์ฆ
- ์ ํ ์์ FIR ํํฐ
- ์ค๊ณ ๋ฐฉ๋ฒ ๋น๊ต
- FIR ํํฐ ๊ตฌํ
- Python ๊ตฌํ
- ์ฐ์ต ๋ฌธ์
1. FIR ํํฐ ์ค๊ณ ์ฌ์¶
1.1 ํํฐ ์ฉ์ด¶
๋์งํธ ํํฐ๋ ์ฃผํ์ ์๋ต(Frequency Response) $H(e^{j\omega})$๋ก ํน์ฑํ๋๋ฉฐ, ์ด๋ ์ ํธ์ ๊ฐ ์ฃผํ์ ์ฑ๋ถ์ด ์ด๋ป๊ฒ ๋ณํ๋๋์ง๋ฅผ ๋ํ๋ธ๋ค. ์ฃผ์ ์ฌ์์ ๋ค์๊ณผ ๊ฐ๋ค:
์ฃผํ์ ๋์ญ: - ํต๊ณผ๋์ญ(Passband): ์ต์ํ์ ์๊ณก์ผ๋ก ํต๊ณผ์์ผ์ผ ํ ์ฃผํ์, $0 \leq \omega \leq \omega_p$ - ์ ์ง๋์ญ(Stopband): ๊ฐ์ ์์ผ์ผ ํ ์ฃผํ์, $\omega_s \leq \omega \leq \pi$ - ์ ์ด ๋์ญ(Transition Band): ํต๊ณผ๋์ญ๊ณผ ์ ์ง๋์ญ ์ฌ์ด์ ์์ญ, $\omega_p < \omega < \omega_s$
ํ์ฉ ์ค์ฐจ(Tolerances): - ํต๊ณผ๋์ญ ๋ฆฌํ(Passband Ripple) $\delta_1$: ํต๊ณผ๋์ญ์์ ๋จ์๋ก๋ถํฐ์ ์ต๋ ํธ์ฐจ - ์ ์ง๋์ญ ๊ฐ์ (Stopband Attenuation) $\delta_2$: ์ ์ง๋์ญ์์์ ์ต๋ ํฌ๊ธฐ
์งํญ ์๋ต ์ฌ์(Magnitude Response Specification)
|H(e^jฯ)|
โ
1+ฮดโ โ โ โ โโ
โ โ
1 โโโโโโโโโโค
โ โ
1-ฮดโ โ โ โ โโ โฒ
โ โฒ
ฮดโ โ โ โ โ โ โ โ โ โ โโฌโโโโโโโโโ
โ โ
0 โโโโโโโโฌโโโโโโโฌโโโโโโโโโฌโโโโโโ ฯ
0 ฯp ฯs ฯ
ํต๊ณผ๋์ญโ์ ์ด โ ์ ์ง๋์ญ
โ๋์ญ โ
1.2 ๋ฐ์๋ฒจ ์ฌ์¶
์ค์ ๋ก ํํฐ ์ฌ์์ ์ข ์ข ๋ฐ์๋ฒจ(dB)๋ก ํํ๋๋ค:
$$A_p = -20\log_{10}(1 - \delta_1) \quad \text{(ํต๊ณผ๋์ญ ๋ฆฌํ, dB)}$$
$$A_s = -20\log_{10}(\delta_2) \quad \text{(์ ์ง๋์ญ ๊ฐ์ , dB)}$$
์ผ๋ฐ์ ์ธ ์ฌ์ ์์: - ํต๊ณผ๋์ญ ๋ฆฌํ: $A_p \leq 0.1$ dB ($\delta_1 \approx 0.0115$ ์๋ฏธ) - ์ ์ง๋์ญ ๊ฐ์ : $A_s \geq 60$ dB ($\delta_2 = 0.001$ ์๋ฏธ)
1.3 FIR vs IIR ํธ๋ ์ด๋์คํ¶
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FIR vs IIR ๋น๊ต โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโค
โ ํน์ฑ โ FIR โ IIR โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโค
โ ์์ ์ฑ โ ํญ์ ์์ โ ๋ถ์์ ๊ฐ๋ฅ โ
โ ์ ํ ์์ โ ์ฝ๊ฒ ๋ฌ์ฑ โ ๋ถ๊ฐ๋ฅ โ
โ ํํฐ ์ฐจ์ โ ๋์ โ ๋ฎ์ โ
โ ์ฐ์ฐ๋ โ ๊ณฑ์
๋ง์ โ ๊ณฑ์
์ ์ โ
โ ์ค๊ณ ๋ฐฉ๋ฒ โ Window, PM, FS โ ์๋ ๋ก๊ทธ ์ํ โ
โ ๊ตฐ์ง์ฐ โ ์ผ์ โ ๋น์ผ์ โ
โ ๋ฐ์ฌ๋ฆผ ์ค์ฐจ โ ๋ ๋ฏผ๊ฐ โ ๋ ๋ฏผ๊ฐ โ
โ ๊ตฌํ โ ํผ๋๋ฐฑ ์์ โ ํผ๋๋ฐฑ ํ์ โ
โโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโ
์ฐจ์ $M$์ FIR ํํฐ๋ ๋ค์์ ์ ๋ฌ ํจ์(Transfer Function)๋ฅผ ๊ฐ๋๋ค:
$$H(z) = \sum_{n=0}^{M} h[n] z^{-n}$$
์ถ๋ ฅ์ ์ ํ ํฉ์ฑ๊ณฑ(Finite Convolution)์ผ๋ก ํํ๋๋ค:
$$y[n] = \sum_{k=0}^{M} h[k] x[n-k]$$
2. ์ด์์ ์ธ ํํฐ์ ์ํ์ค ์๋ต¶
2.1 ์ด์์ ์ธ ์ ์ญํต๊ณผ ํํฐ¶
์ด์์ ์ธ ์ ์ญํต๊ณผ ํํฐ(Ideal Lowpass Filter)์ ์ฃผํ์ ์๋ต์:
$$H_d(e^{j\omega}) = \begin{cases} 1, & |\omega| \leq \omega_c \\ 0, & \omega_c < |\omega| \leq \pi \end{cases}$$
์ํ์ค ์๋ต์ ์ญ DTFT๋ฅผ ํตํด ๊ตฌํ๋ค:
$$h_d[n] = \frac{1}{2\pi} \int_{-\pi}^{\pi} H_d(e^{j\omega}) e^{j\omega n} d\omega = \frac{\sin(\omega_c n)}{\pi n}$$
์ด๋ ๋ค์๊ณผ ๊ฐ์ ํน์ฑ์ ๊ฐ์ง sinc ํจ์์ด๋ค: - ์ง์ ์๊ฐ์ด ๋ฌดํํ๋ค (๋น์ธ๊ณผ์ , ์คํ ๋ถ๊ฐ) - $n = 0$์ ๋ํด ๋์นญ์ด๋ค
2.2 ๋ค๋ฅธ ์ด์์ ์ธ ํํฐ ์ ํ¶
์ด์์ ์ธ ๊ณ ์ญํต๊ณผ ํํฐ(Ideal Highpass Filter):
$$h_d[n] = \delta[n] - \frac{\sin(\omega_c n)}{\pi n}$$
์ด์์ ์ธ ๋์ญํต๊ณผ ํํฐ(Ideal Bandpass Filter) ($\omega_{c1} < |\omega| < \omega_{c2}$):
$$h_d[n] = \frac{\sin(\omega_{c2} n)}{\pi n} - \frac{\sin(\omega_{c1} n)}{\pi n}$$
์ด์์ ์ธ ๋์ญ์ ์ง ํํฐ(Ideal Bandstop Filter):
$$h_d[n] = \delta[n] - \frac{\sin(\omega_{c2} n)}{\pi n} + \frac{\sin(\omega_{c1} n)}{\pi n}$$
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
def ideal_lowpass(omega_c, M):
"""Compute ideal lowpass filter impulse response (truncated)."""
n = np.arange(-M, M + 1)
h = np.zeros_like(n, dtype=float)
# Handle n = 0 case (sinc(0) = omega_c / pi)
center = M
h[center] = omega_c / np.pi
# Non-zero indices
nonzero = np.concatenate([np.arange(0, center), np.arange(center + 1, 2 * M + 1)])
h[nonzero] = np.sin(omega_c * n[nonzero]) / (np.pi * n[nonzero])
return n, h
# Plot ideal lowpass impulse responses for different cutoff frequencies
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, wc, title in zip(axes, [np.pi/4, np.pi/2, 3*np.pi/4],
['ฯc = ฯ/4', 'ฯc = ฯ/2', 'ฯc = 3ฯ/4']):
n, h = ideal_lowpass(wc, 30)
ax.stem(n, h, linefmt='b-', markerfmt='bo', basefmt='k-')
ax.set_xlabel('n')
ax.set_ylabel('h[n]')
ax.set_title(f'Ideal LP Impulse Response ({title})')
ax.set_xlim(-32, 32)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('ideal_lowpass_responses.png', dpi=150)
plt.close()
3. ์ฐฝํจ์๋ฒ¶
3.1 ๊ธฐ๋ณธ ์๋ฆฌ¶
์ฐฝํจ์๋ฒ(Window Method)์ FIR ์ค๊ณ์์ ๊ฐ์ฅ ๋จ์ํ ๋ฐฉ๋ฒ์ด๋ค. ๊ธฐ๋ณธ ์์ด๋์ด๋:
- ์ด์์ ์ธ (๋ฌดํ) ์ํ์ค ์๋ต $h_d[n]$์์ ์์ํ๋ค
- ๊ธธ์ด $N = M + 1$์ธ ์ ํ ์ง์ ์๊ฐ ์ฐฝํจ์ $w[n]$์ ๊ณฑํ๋ค
- ํํฐ๋ฅผ ์ธ๊ณผ์ (Causal)์ผ๋ก ๋ง๋ค๊ธฐ ์ํด ๊ฒฐ๊ณผ๋ฅผ ์ด๋์ํจ๋ค
$$h[n] = h_d[n - M/2] \cdot w[n], \quad n = 0, 1, \ldots, M$$
์ฃผํ์ ์์ญ์์ ์ด ๊ณฑ์ ์ ํฉ์ฑ๊ณฑ(Convolution)์ ํด๋นํ๋ค:
$$H(e^{j\omega}) = \frac{1}{2\pi} H_d(e^{j\omega}) * W(e^{j\omega})$$
3.2 ์ฐฝํจ์ ์ ์ฉ์ ํจ๊ณผ¶
์ฐฝํจ์์ ์ฃผํ์ ์๋ต $W(e^{j\omega})$์ ๋ค์์ ๊ฒฐ์ ํ๋ค:
- ์ฃผ์ฝ ํญ(Mainlobe Width): ์ ์ด ๋์ญํญ์ ์ ์ดํ๋ค -- ์ฃผ์ฝ์ด ๋์์๋ก ์ ์ด ๋์ญ์ด ๋์ด์ง๋ค
- ๋ถ์ฝ ๋ ๋ฒจ(Sidelobe Level): ์ ์ง๋์ญ ๊ฐ์ ๋ฅผ ๊ฒฐ์ ํ๋ค -- ๋ถ์ฝ์ด ๋์์๋ก ์ ์ง๋์ญ ๊ฐ์ ๊ฐ ์์์ง๋ค
- ๋ถ์ฝ ๊ฐ์์จ(Sidelobe Decay Rate): ์ฃผํ์์ ๋ฐ๋ผ ๋ถ์ฝ์ด ์ผ๋ง๋ ๋นจ๋ฆฌ ๊ฐ์ํ๋์ง
์ฐฝํจ์ ์คํํธ๋ผ ํน์ฑ(Window Spectrum Properties)
|W(e^jฯ)|
โ
โ โฑโฒ ์ฃผ์ฝ(Mainlobe)
โ โฑ โฒ ํญ = ฮฯ_ML
โ โฑ โฒ
โโฑ โฒ โฑโฒ
โ โฒโฑ โฒ โฑโฒ ์ต๋ ๋ถ์ฝ
โ โ โฒโฑ โฒ ๋ ๋ฒจ (dB)
โ โ โ โฒโฑโฒ
โโโโโโโโโโโผโโโโโผโโโโโโผโโโ ฯ
0 ฮฯ_ML/2 ฯ
3.3 ๊น์ค ํ์(Gibbs Phenomenon)¶
์ง์ฌ๊ฐํ ์ฐฝ(Rectangular Window)์ผ๋ก ์ด์์ ์ธ ์ํ์ค ์๋ต์ ์ ๋จํ๋ฉด ๊น์ค ํ์(Gibbs Phenomenon)์ด ๋ฐ์ํ์ฌ ํํฐ ์ฐจ์ $M$์ ๊ด๊ณ์์ด ๋์ญ ๊ฒฝ๊ณ์์ ์ฝ 9%์ ์ค๋ฒ์ํธ(์ฝ $-21$ dB ์ ์ง๋์ญ ๊ฐ์ )๊ฐ ๋ฐ์ํ๋ค. ํํฐ ์ฐจ์ $M$์ ๋๋ฆฌ๋ฉด ์ ์ด ํญ์ ์ข์์ง์ง๋ง ๋ฆฌํ ์งํญ์ ์ค์ด๋ค์ง ์๋๋ค.
def windowed_lowpass(omega_c, M, window_type='rectangular'):
"""Design lowpass FIR filter using window method."""
n = np.arange(0, M + 1)
alpha = M / 2 # Delay for linear phase
# Ideal lowpass impulse response (shifted for causality)
h_d = np.zeros(M + 1)
for i in range(M + 1):
if n[i] == alpha:
h_d[i] = omega_c / np.pi
else:
h_d[i] = np.sin(omega_c * (n[i] - alpha)) / (np.pi * (n[i] - alpha))
# Apply window
if window_type == 'rectangular':
w = np.ones(M + 1)
elif window_type == 'hamming':
w = signal.windows.hamming(M + 1)
elif window_type == 'hanning':
w = signal.windows.hann(M + 1)
elif window_type == 'blackman':
w = signal.windows.blackman(M + 1)
else:
w = np.ones(M + 1)
h = h_d * w
return h
# Demonstrate Gibbs phenomenon with rectangular window
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
omega_c = np.pi / 2
for idx, M in enumerate([10, 20, 50, 100]):
ax = axes[idx // 2, idx % 2]
h = windowed_lowpass(omega_c, M, 'rectangular')
# Frequency response
w_freq, H = signal.freqz(h, worN=2048)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)
ax.plot(w_freq / np.pi, H_dB, 'b-', linewidth=1.5)
ax.axhline(-21, color='r', linestyle='--', alpha=0.5, label='-21 dB (Gibbs)')
ax.axvline(0.5, color='g', linestyle='--', alpha=0.5, label='ฯc = ฯ/2')
ax.set_xlabel('Normalized Frequency (รฯ rad/sample)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title(f'Rectangular Window, M = {M}')
ax.set_ylim(-80, 5)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.suptitle('Gibbs Phenomenon: Stopband ripple remains ~-21 dB', fontsize=12)
plt.tight_layout()
plt.savefig('gibbs_phenomenon.png', dpi=150)
plt.close()
4. ์ฐฝํจ์¶
4.1 ์ผ๋ฐ์ ์ธ ์ฐฝํจ์¶
๊ฐ ์ฐฝํจ์๋ ์ฃผ์ฝ ํญ๊ณผ ๋ถ์ฝ ๋ ๋ฒจ ์ฌ์ด์ ํธ๋ ์ด๋์คํ๋ฅผ ์กฐ์ ํ๋ค. ๋ ๋งค๋๋ฌ์ด ์ฐฝํจ์์ผ์๋ก ์ฃผ์ฝ์ด ๋์ง๋ง ๋ถ์ฝ์ ๋ฎ๋ค.
์ง์ฌ๊ฐํ ์ฐฝ(Rectangular Window)¶
$$w[n] = 1, \quad 0 \leq n \leq M$$
- ์ฃผ์ฝ ํญ: $\Delta\omega_\text{ML} = 4\pi / (M+1)$
- ์ต๋ ๋ถ์ฝ: $-13$ dB
- ์ต์ ์ ์ง๋์ญ ๊ฐ์ : $-21$ dB
- ์ ์ด ํญ: $\Delta\omega \approx 0.92 \cdot 2\pi / (M+1)$
ํ(Hann) ์ฐฝ (ํด๋(Hanning) ์ฐฝ)¶
$$w[n] = 0.5 - 0.5\cos\left(\frac{2\pi n}{M}\right), \quad 0 \leq n \leq M$$
- ์ฃผ์ฝ ํญ: $\Delta\omega_\text{ML} = 8\pi / (M+1)$
- ์ต๋ ๋ถ์ฝ: $-31$ dB
- ์ต์ ์ ์ง๋์ญ ๊ฐ์ : $-44$ dB
- ์ ์ด ํญ: $\Delta\omega \approx 3.11 \cdot 2\pi / (M+1)$
ํด๋ฐ ์ฐฝ(Hamming Window)¶
$$w[n] = 0.54 - 0.46\cos\left(\frac{2\pi n}{M}\right), \quad 0 \leq n \leq M$$
- ์ฃผ์ฝ ํญ: $\Delta\omega_\text{ML} = 8\pi / (M+1)$
- ์ต๋ ๋ถ์ฝ: $-41$ dB
- ์ต์ ์ ์ง๋์ญ ๊ฐ์ : $-53$ dB
- ์ ์ด ํญ: $\Delta\omega \approx 3.32 \cdot 2\pi / (M+1)$
๋ธ๋๋ง ์ฐฝ(Blackman Window)¶
$$w[n] = 0.42 - 0.5\cos\left(\frac{2\pi n}{M}\right) + 0.08\cos\left(\frac{4\pi n}{M}\right), \quad 0 \leq n \leq M$$
- ์ฃผ์ฝ ํญ: $\Delta\omega_\text{ML} = 12\pi / (M+1)$
- ์ต๋ ๋ถ์ฝ: $-57$ dB
- ์ต์ ์ ์ง๋์ญ ๊ฐ์ : $-74$ dB
- ์ ์ด ํญ: $\Delta\omega \approx 5.56 \cdot 2\pi / (M+1)$
4.2 ์ฐฝํจ์ ๋น๊ตํ¶
โโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
โ ์ฐฝํจ์ โ ์ต๋ ๋ถ์ฝ โ ์ต์ ์ ์ง๋์ญ โ ๊ทผ์ฌ ์ ์ดํญ โ
โ โ ๋ ๋ฒจ (dB) โ ๊ฐ์ (dB) โ (rad) โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโค
โ Rectangular โ -13 โ -21 โ 0.92ยท(2ฯ/M) โ
โ Hann โ -31 โ -44 โ 3.11ยท(2ฯ/M) โ
โ Hamming โ -41 โ -53 โ 3.32ยท(2ฯ/M) โ
โ Blackman โ -57 โ -74 โ 5.56ยท(2ฯ/M) โ
โ Kaiser(ฮฒ=6) โ -44 โ -60 โ adjustable โ
โ Kaiser(ฮฒ=9) โ -69 โ -90 โ adjustable โ
โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโ
4.3 ์ฐฝํจ์์ ์คํํธ๋ผ ์๊ฐํ¶
def compare_windows(M=50):
"""Compare common window functions in time and frequency domains."""
windows = {
'Rectangular': np.ones(M + 1),
'Hann': signal.windows.hann(M + 1),
'Hamming': signal.windows.hamming(M + 1),
'Blackman': signal.windows.blackman(M + 1),
}
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
colors = ['blue', 'orange', 'green', 'red']
# Time domain
ax = axes[0]
for (name, w), color in zip(windows.items(), colors):
ax.plot(np.arange(M + 1), w, color=color, linewidth=2, label=name)
ax.set_xlabel('Sample n')
ax.set_ylabel('w[n]')
ax.set_title('Window Functions (Time Domain)')
ax.legend()
ax.grid(True, alpha=0.3)
# Frequency domain (log magnitude)
ax = axes[1]
nfft = 4096
for (name, w), color in zip(windows.items(), colors):
W = np.fft.fft(w, nfft)
W_shift = np.fft.fftshift(W)
freq = np.linspace(-np.pi, np.pi, nfft)
W_dB = 20 * np.log10(np.abs(W_shift) / np.max(np.abs(W_shift)) + 1e-15)
ax.plot(freq / np.pi, W_dB, color=color, linewidth=1.5, label=name)
ax.set_xlabel('Normalized Frequency (รฯ rad/sample)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title('Window Spectra (Frequency Domain)')
ax.set_xlim(-0.5, 0.5)
ax.set_ylim(-100, 5)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('window_comparison.png', dpi=150)
plt.close()
compare_windows(M=50)
4.4 ์ฐฝํจ์๋ฒ ์ค๊ณ ์ ์ฐจ¶
- ์ฌ์ ๊ฒฐ์ : $\omega_p$, $\omega_s$, $\delta_1$, $\delta_2$
- ์ฐฝํจ์ ์ ํ ์ ํ: ์๊ตฌ๋๋ ์ ์ง๋์ญ ๊ฐ์ ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ํ
- ์ ์ด ํญ ๊ณ์ฐ: $\Delta\omega = \omega_s - \omega_p$
- ํํฐ ์ฐจ์ ๊ฒฐ์ : ์ฐฝํจ์์ ์ ์ด ํญ ๊ณต์์ผ๋ก๋ถํฐ $M$ ๊ฒฐ์
- ์ฐจ๋จ ์ฃผํ์ ์ค์ : $\omega_c = (\omega_p + \omega_s) / 2$
- ์ด์์ ์ธ ์ํ์ค ์๋ต ๊ณ์ฐ: $M/2$๋งํผ ์ด๋๋ $h_d[n]$ ๊ณ์ฐ
- ์ฐฝํจ์ ์ ์ฉ: $h[n] = h_d[n] \cdot w[n]$ ๊ณ์ฐ
def design_fir_window(wp, ws, delta_s, window_type='hamming'):
"""
Design FIR lowpass filter using window method.
Parameters:
wp: passband edge (normalized, 0 to pi)
ws: stopband edge (normalized, 0 to pi)
delta_s: stopband attenuation (linear)
window_type: 'rectangular', 'hann', 'hamming', or 'blackman'
Returns:
h: filter coefficients
M: filter order
"""
# Transition width
delta_w = ws - wp
# Cutoff frequency (midpoint)
wc = (wp + ws) / 2
# Determine filter order M from window type
transition_coefficients = {
'rectangular': 0.92,
'hann': 3.11,
'hamming': 3.32,
'blackman': 5.56,
}
coeff = transition_coefficients[window_type]
M = int(np.ceil(coeff * 2 * np.pi / delta_w))
if M % 2 == 1:
M += 1 # Ensure even order for Type I filter
# Compute windowed ideal lowpass
n = np.arange(0, M + 1)
alpha = M / 2
h_d = np.where(
n == alpha,
wc / np.pi,
np.sin(wc * (n - alpha)) / (np.pi * (n - alpha))
)
# Apply window
windows = {
'rectangular': np.ones(M + 1),
'hann': signal.windows.hann(M + 1),
'hamming': signal.windows.hamming(M + 1),
'blackman': signal.windows.blackman(M + 1),
}
w = windows[window_type]
h = h_d * w
return h, M
# Example: design lowpass filter
wp = 0.3 * np.pi # Passband edge
ws = 0.5 * np.pi # Stopband edge
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
window_types = ['rectangular', 'hann', 'hamming', 'blackman']
for ax, wtype in zip(axes.flat, window_types):
h, M = design_fir_window(wp, ws, 0.001, wtype)
w_freq, H = signal.freqz(h, worN=4096)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)
ax.plot(w_freq / np.pi, H_dB, 'b-', linewidth=1.5)
ax.axvline(0.3, color='g', linestyle='--', alpha=0.7, label=f'ฯp = 0.3ฯ')
ax.axvline(0.5, color='r', linestyle='--', alpha=0.7, label=f'ฯs = 0.5ฯ')
ax.set_xlabel('Normalized Frequency (รฯ rad/sample)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title(f'{wtype.capitalize()} Window, M = {M}')
ax.set_ylim(-100, 5)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.suptitle('FIR Lowpass Filter Design: Window Method Comparison', fontsize=13)
plt.tight_layout()
plt.savefig('fir_window_method.png', dpi=150)
plt.close()
5. ์นด์ด์ ์ฐฝํจ์ ์ค๊ณ¶
5.1 ์นด์ด์ ์ฐฝํจ์ ์ ์¶
์นด์ด์ ์ฐฝํจ์(Kaiser Window)๋ ์ฃผ์ฝ ํญ๊ณผ ๋ถ์ฝ ๋ ๋ฒจ ์ฌ์ด์ ํธ๋ ์ด๋์คํ๋ฅผ ์ ์ดํ๋ ์ฐ์ ์กฐ์ ๊ฐ๋ฅํ ํ๋ผ๋ฏธํฐ $\beta$๋ฅผ ์ ๊ณตํ๋ค:
$$w[n] = \frac{I_0\left(\beta\sqrt{1 - \left(\frac{2n}{M} - 1\right)^2}\right)}{I_0(\beta)}, \quad 0 \leq n \leq M$$
์ฌ๊ธฐ์ $I_0(\cdot)$๋ ์ 1์ข 0์ฐจ ๋ณํ ๋ฒ ์ ํจ์(Zeroth-Order Modified Bessel Function of the First Kind)์ด๋ค.
5.2 ์นด์ด์ ์ ๊ฒฝํ์ ๊ณต์¶
์ํ๋ ์ ์ง๋์ญ ๊ฐ์ $A_s$ (dB)๊ฐ ์ฃผ์ด์ก์ ๋:
ํ๋ผ๋ฏธํฐ $\beta$:
$$\beta = \begin{cases} 0.1102(A_s - 8.7), & A_s > 50 \\ 0.5842(A_s - 21)^{0.4} + 0.07886(A_s - 21), & 21 \leq A_s \leq 50 \\ 0, & A_s < 21 \end{cases}$$
ํํฐ ์ฐจ์ $M$:
$$M = \left\lceil \frac{A_s - 7.95}{2.285 \cdot \Delta\omega} \right\rceil$$
์ฌ๊ธฐ์ $\Delta\omega = \omega_s - \omega_p$๋ ์ ์ด ํญ์ด๋ค.
5.3 ์นด์ด์ ์ฐฝํจ์ ์ค๊ณ ์ ์ฐจ¶
def kaiser_fir_design(wp, ws, As_dB):
"""
Design FIR lowpass filter using Kaiser window.
Parameters:
wp: passband edge (normalized, 0 to pi)
ws: stopband edge (normalized, 0 to pi)
As_dB: stopband attenuation in dB
Returns:
h: filter coefficients
M: filter order
beta: Kaiser window parameter
"""
# Transition width
delta_w = ws - wp
# Cutoff frequency
wc = (wp + ws) / 2
# Kaiser's empirical formula for beta
if As_dB > 50:
beta = 0.1102 * (As_dB - 8.7)
elif As_dB >= 21:
beta = 0.5842 * (As_dB - 21)**0.4 + 0.07886 * (As_dB - 21)
else:
beta = 0.0
# Filter order
M = int(np.ceil((As_dB - 7.95) / (2.285 * delta_w)))
if M % 2 == 1:
M += 1 # Ensure even order for Type I
# Ideal lowpass impulse response
n = np.arange(0, M + 1)
alpha = M / 2
h_d = np.where(
n == alpha,
wc / np.pi,
np.sin(wc * (n - alpha)) / (np.pi * (n - alpha))
)
# Kaiser window
w = signal.windows.kaiser(M + 1, beta)
h = h_d * w
return h, M, beta
# Design filters with different stopband attenuations
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
specs = [
(0.3 * np.pi, 0.5 * np.pi, 30),
(0.3 * np.pi, 0.5 * np.pi, 50),
(0.3 * np.pi, 0.5 * np.pi, 70),
(0.3 * np.pi, 0.5 * np.pi, 90),
]
for ax, (wp, ws, As) in zip(axes.flat, specs):
h, M, beta = kaiser_fir_design(wp, ws, As)
w_freq, H = signal.freqz(h, worN=4096)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)
ax.plot(w_freq / np.pi, H_dB, 'b-', linewidth=1.5)
ax.axhline(-As, color='r', linestyle='--', alpha=0.5, label=f'-{As} dB')
ax.set_xlabel('Normalized Frequency (รฯ rad/sample)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title(f'Kaiser: As={As} dB, ฮฒ={beta:.2f}, M={M}')
ax.set_ylim(-As - 20, 5)
ax.legend()
ax.grid(True, alpha=0.3)
plt.suptitle('Kaiser Window FIR Design: Varying Stopband Attenuation', fontsize=13)
plt.tight_layout()
plt.savefig('kaiser_design.png', dpi=150)
plt.close()
5.4 scipy.signal.kaiserord ์ฌ์ฉ¶
# scipy provides convenient functions for Kaiser window design
from scipy.signal import kaiserord, firwin
# Specification
fs = 8000 # Sample rate (Hz)
f_pass = 1000 # Passband edge (Hz)
f_stop = 1500 # Stopband edge (Hz)
A_stop = 60 # Stopband attenuation (dB)
# Compute transition width in normalized frequency
nyquist = fs / 2
width = (f_stop - f_pass) / nyquist # Normalized transition width
# Kaiser window parameters
numtaps, beta = kaiserord(A_stop, width)
if numtaps % 2 == 0:
numtaps += 1 # firwin needs odd number of taps for Type I
# Design the filter
cutoff = (f_pass + f_stop) / 2 / nyquist # Normalized cutoff
h = firwin(numtaps, cutoff, window=('kaiser', beta))
print(f"Filter order: {numtaps - 1}")
print(f"Number of taps: {numtaps}")
print(f"Kaiser beta: {beta:.4f}")
# Frequency response
w, H = signal.freqz(h, worN=4096, fs=fs)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Magnitude response
axes[0].plot(w, H_dB, 'b-', linewidth=1.5)
axes[0].axhline(-A_stop, color='r', linestyle='--', label=f'-{A_stop} dB')
axes[0].axvline(f_pass, color='g', linestyle='--', alpha=0.5, label=f'fp={f_pass} Hz')
axes[0].axvline(f_stop, color='orange', linestyle='--', alpha=0.5, label=f'fs={f_stop} Hz')
axes[0].set_xlabel('Frequency (Hz)')
axes[0].set_ylabel('Magnitude (dB)')
axes[0].set_title('Magnitude Response')
axes[0].set_ylim(-100, 5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# Impulse response
axes[1].stem(h, linefmt='b-', markerfmt='bo', basefmt='k-')
axes[1].set_xlabel('Sample n')
axes[1].set_ylabel('h[n]')
axes[1].set_title(f'Impulse Response (M={numtaps - 1})')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('kaiser_scipy.png', dpi=150)
plt.close()
6. ์ฃผํ์ ํ๋ณธํ๋ฒ¶
6.1 ์๋ฆฌ¶
์ฃผํ์ ํ๋ณธํ๋ฒ(Frequency Sampling Method)์ $N$๊ฐ์ ๋ฑ๊ฐ๊ฒฉ ์ฃผํ์ ์ ์์ ์ํ๋ ์ฃผํ์ ์๋ต์ ์ง์ ํ๊ณ ์ญ DFT๋ฅผ ํตํด ํํฐ ๊ณ์๋ฅผ ๊ณ์ฐํ์ฌ FIR ํํฐ๋ฅผ ์ค๊ณํ๋ ๋ฐฉ๋ฒ์ด๋ค:
$$H[k] = H(e^{j 2\pi k / N}), \quad k = 0, 1, \ldots, N-1$$
$$h[n] = \frac{1}{N} \sum_{k=0}^{N-1} H[k] e^{j 2\pi k n / N}, \quad n = 0, 1, \ldots, N-1$$
6.2 ์ ์ด ํ๋ณธ(Transition Samples)¶
ํต์ฌ ์์ด๋์ด๋ ์ ์ด ๋์ญ ํ๋ณธ์ ์ต์ ํํ์ฌ ์ ์ง๋์ญ ๋ฆฌํ์ ์ค์ผ ์ ์๋ค๋ ๊ฒ์ด๋ค. 1์์ 0์ผ๋ก ๊ธ๊ฒฉํ ์ ํํ๋ ๋์ , ์ค๊ฐ ๊ฐ $T_1, T_2, \ldots$๋ฅผ ์ ์ด ๋์ญ์ ๋ฐฐ์นํ๋ค.
์ฃผํ์ ํ๋ณธ H[k]:
H[k]
โ
1 โ โ โ โ โ โ โ โ
โ โฒ
Tโโ โ โ ์ ์ด ํ๋ณธ (์ต์ ํ๋จ)
โ โฒ
0 โ โ โ โ โ โ โ โ โ โ โโ โ โ โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ k
6.3 ๊ตฌํ¶
def freq_sampling_lowpass(N, cutoff_bin, transition_values=None):
"""
FIR filter design via frequency sampling.
Parameters:
N: number of samples (filter length)
cutoff_bin: passband ends at bin index cutoff_bin
transition_values: list of transition band sample values
Returns:
h: filter coefficients (real)
"""
H = np.zeros(N, dtype=complex)
# Passband (bins 0 to cutoff_bin)
H[:cutoff_bin + 1] = 1.0
# Mirror for negative frequencies (ensure real h[n])
H[N - cutoff_bin:] = 1.0
# Transition band samples
if transition_values is not None:
for i, T in enumerate(transition_values):
H[cutoff_bin + 1 + i] = T
H[N - cutoff_bin - 1 - i] = T
# Inverse DFT to get filter coefficients
h = np.real(np.fft.ifft(H))
# Circular shift for causal filter
h = np.roll(h, N // 2)
return h
# Compare with and without optimized transition samples
N = 33
cutoff_bin = 5
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Without transition samples
h1 = freq_sampling_lowpass(N, cutoff_bin)
w1, H1 = signal.freqz(h1, worN=4096)
# With optimized transition sample
h2 = freq_sampling_lowpass(N, cutoff_bin, transition_values=[0.5])
w2, H2 = signal.freqz(h2, worN=4096)
# With two optimized transition samples
h3 = freq_sampling_lowpass(N, cutoff_bin, transition_values=[0.59, 0.11])
w3, H3 = signal.freqz(h3, worN=4096)
axes[0].plot(w1 / np.pi, 20 * np.log10(np.abs(H1) + 1e-15), label='No transition')
axes[0].plot(w2 / np.pi, 20 * np.log10(np.abs(H2) + 1e-15), label='T=[0.5]')
axes[0].plot(w3 / np.pi, 20 * np.log10(np.abs(H3) + 1e-15), label='T=[0.59, 0.11]')
axes[0].set_xlabel('Normalized Frequency (รฯ rad/sample)')
axes[0].set_ylabel('Magnitude (dB)')
axes[0].set_title('Frequency Sampling: Effect of Transition Values')
axes[0].set_ylim(-100, 5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].stem(np.arange(N), h3, linefmt='b-', markerfmt='bo', basefmt='k-')
axes[1].set_xlabel('Sample n')
axes[1].set_ylabel('h[n]')
axes[1].set_title('Impulse Response (Optimized Transition)')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('freq_sampling.png', dpi=150)
plt.close()
7. ์ต์ ๋ฑ๋ฆฌํ ์ค๊ณ: Parks-McClellan ์๊ณ ๋ฆฌ์ฆ¶
7.1 ๋ฏธ๋๋งฅ์ค ์ต์ ํ(Minimax Optimization)¶
Parks-McClellan (PM) ์๊ณ ๋ฆฌ์ฆ์ Remez ๊ตํ ์๊ณ ๋ฆฌ์ฆ(Remez Exchange Algorithm)์ผ๋ก๋ ์๋ ค์ ธ ์์ผ๋ฉฐ, ์ฃผํ์ ์์ญ์์ ์ต๋ ๊ฐ์ค ์ค์ฐจ๋ฅผ ์ต์ํํ๋ FIR ํํฐ๋ฅผ ์ค๊ณํ๋ค:
$$\min_{h} \max_{\omega \in \text{bands}} \left| W(\omega) \left[ H_d(\omega) - H(\omega) \right] \right|$$
์ฌ๊ธฐ์: - $H_d(\omega)$๋ ์ํ๋ ์ฃผํ์ ์๋ต - $H(\omega) = \sum_{n=0}^{M} h[n] e^{-j\omega n}$๋ ์ค์ ์๋ต - $W(\omega)$๋ ๊ฐ์ค ํจ์
7.2 ๋ฑ๋ฆฌํ ํน์ฑ(Equiripple Property)¶
์ฒด๋น์ผํ ์ ๋ฆฌ(Chebyshev Theorem)๋ ์ต์ ํด๊ฐ ๋ฑ๋ฆฌํ(Equiripple) ์ค์ฐจ๋ฅผ ๋ํ๋์ ๋ณด์ฅํ๋ค -- ์ค์ฐจ๋ ์ง์ ๋ ๋ชจ๋ ๋์ญ์์ ๋์ผํ ์งํญ์ผ๋ก $\pm\delta$ ์ฌ์ด๋ฅผ ์ง๋ํ๋ค:
๋ฑ๋ฆฌํ ์ค์ฐจ ๊ฑฐ๋:
ํต๊ณผ๋์ญ ์ ์ง๋์ญ
โ +ฮดโ โ โ โ โฑโฒ โฑโฒ
H(ฯ)โ โฑ โฒ โฑ โฒ
โ โโโโโโฑโโโโโฒโฑโโโโโฒโโโโ 1.0
โ
โ โฑโฒ โฑโฒ โฑโฒ
โ -ฮดโ โ โ โ โโโฒโโโฑโโโฒโโโฑโโโฒโโโฑโโ
โ +ฮดโ โ โโฒโฑโ โ โฒโฑโ โ โฒโฑโ
โ -ฮดโ โ โ โ โ โ โ โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ ฯ
7.3 Remez ๊ตํ ์๊ณ ๋ฆฌ์ฆ¶
์๊ณ ๋ฆฌ์ฆ์ ๋ฐ๋ณต์ ์ผ๋ก:
- $(M/2 + 2)$๊ฐ์ ๊ทน์ ์ฃผํ์(Extremal Frequency) ์ ์ผ๋ก ์ด๊ธฐํํ๋ค
- ๋ฑ๋ฆฌํ ๊ฑฐ๋์ ๋ง๋ค์ด๋ด๋ ๋คํญ์์ ๊ตฌํ๋ ๋ณด๊ฐ ๋ฌธ์ ๋ฅผ ํ์ดํ๋ค
- ์ค์ฐจ๊ฐ ์ต๋์ธ ์๋ก์ด ๊ทน์ ์ ํ์ํ๋ค
- ํ์ฌ ๊ทน์ ์งํฉ์ ์ ์งํฉ์ผ๋ก ๊ตํํ๋ค
- ์๋ ดํ ๋๊น์ง (๊ทน์ ์ด ์์ ๋ ๋๊น์ง) ๋ฐ๋ณตํ๋ค
7.4 scipy.signal.remez๋ฅผ ์ด์ฉํ ๊ตฌํ¶
from scipy.signal import remez, freqz
def parks_mcclellan_lowpass(numtaps, fp, fs, fs_hz=1.0, weight=None):
"""
Design lowpass FIR filter using Parks-McClellan algorithm.
Parameters:
numtaps: number of filter taps (M + 1)
fp: passband edge frequency (Hz)
fs_freq: stopband edge frequency (Hz)
fs_hz: sampling frequency (Hz)
weight: weighting [W_pass, W_stop]
Returns:
h: filter coefficients
"""
if weight is None:
weight = [1, 1]
# Bands: [0, fp, fs, fs_hz/2]
bands = [0, fp, fs, fs_hz / 2]
desired = [1, 0] # Passband = 1, Stopband = 0
h = remez(numtaps, bands, desired, weight=weight, fs=fs_hz)
return h
# Example: lowpass filter design
fs_hz = 8000
numtaps = 51
fp = 1000 # Passband edge (Hz)
f_stop = 1500 # Stopband edge (Hz)
# Different weighting schemes
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
weights = [
([1, 1], 'Equal weight'),
([1, 10], 'Stopband 10ร weight'),
([10, 1], 'Passband 10ร weight'),
([1, 100], 'Stopband 100ร weight'),
]
for ax, (w, title) in zip(axes.flat, weights):
h = parks_mcclellan_lowpass(numtaps, fp, f_stop, fs_hz, weight=w)
freq, H = signal.freqz(h, worN=4096, fs=fs_hz)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)
ax.plot(freq, H_dB, 'b-', linewidth=1.5)
ax.axvline(fp, color='g', linestyle='--', alpha=0.5, label=f'fp={fp} Hz')
ax.axvline(f_stop, color='r', linestyle='--', alpha=0.5, label=f'fs={f_stop} Hz')
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title(f'PM Design: {title}')
ax.set_ylim(-80, 5)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.suptitle('Parks-McClellan Filter Design with Different Weights', fontsize=13)
plt.tight_layout()
plt.savefig('parks_mcclellan.png', dpi=150)
plt.close()
7.5 ๋์ญํต๊ณผ ํํฐ ์์¶
# Bandpass filter using Parks-McClellan
fs_hz = 8000
numtaps = 101
# Bandpass: pass 1000-2000 Hz, stop below 500 and above 2500
bands = [0, 500, 1000, 2000, 2500, fs_hz / 2]
desired = [0, 1, 0]
weight = [1, 1, 1]
h_bp = remez(numtaps, bands, desired, weight=weight, fs=fs_hz)
# Frequency response
freq, H_bp = signal.freqz(h_bp, worN=4096, fs=fs_hz)
H_bp_dB = 20 * np.log10(np.abs(H_bp) + 1e-15)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(freq, H_bp_dB, 'b-', linewidth=1.5)
axes[0].axvspan(1000, 2000, alpha=0.1, color='green', label='Passband')
axes[0].set_xlabel('Frequency (Hz)')
axes[0].set_ylabel('Magnitude (dB)')
axes[0].set_title('Bandpass FIR (Parks-McClellan)')
axes[0].set_ylim(-80, 5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].plot(freq, np.abs(H_bp), 'b-', linewidth=1.5)
axes[1].axvspan(1000, 2000, alpha=0.1, color='green', label='Passband')
axes[1].set_xlabel('Frequency (Hz)')
axes[1].set_ylabel('|H(f)|')
axes[1].set_title('Magnitude Response (Linear)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('bandpass_pm.png', dpi=150)
plt.close()
8. ์ ํ ์์ FIR ํํฐ¶
8.1 ์ ํ ์์์ ์ค์์ฑ¶
ํํฐ๊ฐ ๋ค์๊ณผ ๊ฐ์ด ํํ๋ ์ ์์ ๋ ์ ํ ์์(Linear Phase)์ ๊ฐ๋๋ค๊ณ ํ๋ค:
$$H(e^{j\omega}) = |H(e^{j\omega})| \cdot e^{-j\omega \tau}$$
์ฌ๊ธฐ์ $\tau$๋ ์ผ์ ํ ๊ตฐ์ง์ฐ(Group Delay)์ด๋ค. ์ ํ ์์์ ๋ชจ๋ ์ฃผํ์ ์ฑ๋ถ์ด ๋์ผํ ์๊ฐ๋งํผ ์ง์ฐ๋จ์ ์๋ฏธํ๋ฉฐ, ํํ ๋ชจ์์ ๋ณด์กดํ๋ค.
๊ตฐ์ง์ฐ:
$$\tau(\omega) = -\frac{d\angle H(e^{j\omega})}{d\omega} = \text{์์} = \frac{M}{2}$$
8.2 ๋์นญ ์กฐ๊ฑด¶
์ ํ ์์ FIR ํํฐ๋ ๋์นญ ๋๋ ๋ฐ๋์นญ ์ํ์ค ์๋ต์ ํ์๋ก ํ๋ค:
๋์นญ(Symmetric) (ํ์ I ๋ฐ II): $h[n] = h[M - n]$
๋ฐ๋์นญ(Antisymmetric) (ํ์ III ๋ฐ IV): $h[n] = -h[M - n]$
8.3 ์ ํ ์์ FIR ํํฐ์ ๋ค ๊ฐ์ง ์ ํ¶
โโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
โ ์ ํ โ ๋์นญ์ฑ โ ์ฐจ์ M โ ์ ํฉํ ํํฐ ์ ํ โ
โโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโค
โ I โ ๋์นญ โ ์ง์ โ ๋ชจ๋ ํํฐ ์ ํ โ
โ II โ ๋์นญ โ ํ์ โ ์ ์ญํต๊ณผ, ๋์ญํต๊ณผโ
โ III โ ๋ฐ๋์นญ โ ์ง์ โ ๋์ญํต๊ณผ, ํ๋ฒํธ, โ
โ โ โ โ ๋ฏธ๋ถ๊ธฐ โ
โ IV โ ๋ฐ๋์นญ โ ํ์ โ ๊ณ ์ญํต๊ณผ, ๋์ญํต๊ณผโ
โ โ โ โ ํ๋ฒํธ โ
โโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโ
ํ์ I (์ง์ ์ฐจ์, ๋์นญ): - $H(e^{j0}) = \sum h[n]$ (์ ์ฝ ์์) - $H(e^{j\pi}) = \sum (-1)^n h[n]$ (์ ์ฝ ์์) - ๋ชจ๋ ํํฐ ์ ํ์ ์ ํฉ
ํ์ II (ํ์ ์ฐจ์, ๋์นญ): - $H(e^{j\pi}) = 0$ ํญ์ ์ฑ๋ฆฝ - ๊ณ ์ญํต๊ณผ(Highpass) ๋๋ ๋์ญ์ ์ง(Bandstop) ํํฐ์ ์ฌ์ฉ ๋ถ๊ฐ
ํ์ III (์ง์ ์ฐจ์, ๋ฐ๋์นญ): - $H(e^{j0}) = 0$ ๋ฐ $H(e^{j\pi}) = 0$ ํญ์ ์ฑ๋ฆฝ - ๋์ญํต๊ณผ, ํ๋ฒํธ ๋ณํ(Hilbert Transform), ๋ฏธ๋ถ๊ธฐ(Differentiator)์ ์ ํฉ
ํ์ IV (ํ์ ์ฐจ์, ๋ฐ๋์นญ): - $H(e^{j0}) = 0$ ํญ์ ์ฑ๋ฆฝ - ์ ์ญํต๊ณผ ํํฐ์ ์ฌ์ฉ ๋ถ๊ฐ
8.4 ์งํญ ์๋ต ๊ณต์¶
ํ์ I ํํฐ (์ง์ $M$, ๋์นญ)์ ๊ฒฝ์ฐ, ์ฃผํ์ ์๋ต์:
$$H(e^{j\omega}) = e^{-j\omega M/2} A(\omega)$$
์ฌ๊ธฐ์ ์งํญ ์๋ต(Amplitude Response)์:
$$A(\omega) = h[M/2] + 2 \sum_{k=1}^{M/2} h[M/2 - k] \cos(k\omega)$$
์ด๋ ์ค์ ๊ฐ ํจ์๋ก, ํต๊ณผ๋์ญ/์ ์ง๋์ญ ๊ฑฐ๋ ๋ถ์์ด ์ฉ์ดํ๋ค.
def analyze_linear_phase(h, filter_type=None):
"""Analyze linear phase properties of FIR filter."""
M = len(h) - 1
# Check symmetry
is_symmetric = np.allclose(h, h[::-1])
is_antisymmetric = np.allclose(h, -h[::-1])
if is_symmetric and M % 2 == 0:
ftype = "Type I"
elif is_symmetric and M % 2 == 1:
ftype = "Type II"
elif is_antisymmetric and M % 2 == 0:
ftype = "Type III"
elif is_antisymmetric and M % 2 == 1:
ftype = "Type IV"
else:
ftype = "Not linear phase"
# Frequency response
w, H = signal.freqz(h, worN=4096)
# Group delay
w_gd, gd = signal.group_delay((h, [1]), w=4096)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Magnitude
axes[0, 0].plot(w / np.pi, 20 * np.log10(np.abs(H) + 1e-15), 'b-')
axes[0, 0].set_ylabel('Magnitude (dB)')
axes[0, 0].set_title(f'{ftype}: Magnitude Response')
axes[0, 0].grid(True, alpha=0.3)
# Phase
axes[0, 1].plot(w / np.pi, np.unwrap(np.angle(H)), 'r-')
axes[0, 1].set_ylabel('Phase (radians)')
axes[0, 1].set_title(f'{ftype}: Phase Response')
axes[0, 1].grid(True, alpha=0.3)
# Group delay
axes[1, 0].plot(w_gd / np.pi, gd, 'g-')
axes[1, 0].axhline(M / 2, color='r', linestyle='--', alpha=0.5,
label=f'M/2 = {M/2}')
axes[1, 0].set_ylabel('Group Delay (samples)')
axes[1, 0].set_title('Group Delay')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3)
# Impulse response (with symmetry visualization)
axes[1, 1].stem(h, linefmt='b-', markerfmt='bo', basefmt='k-')
axes[1, 1].axvline(M / 2, color='r', linestyle='--', alpha=0.5,
label='Center of symmetry')
axes[1, 1].set_ylabel('h[n]')
axes[1, 1].set_title(f'Impulse Response (M={M})')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)
for ax in [axes[1, 0], axes[1, 1]]:
ax.set_xlabel('Normalized Frequency (รฯ)' if ax == axes[1, 0] else 'Sample n')
for ax in [axes[0, 0], axes[0, 1]]:
ax.set_xlabel('Normalized Frequency (รฯ)')
plt.suptitle(f'Linear Phase Analysis: {ftype}', fontsize=13)
plt.tight_layout()
return ftype
# Example: Type I lowpass
h_type1 = firwin(51, 0.4) # 50th order, symmetric
ftype = analyze_linear_phase(h_type1)
plt.savefig('linear_phase_analysis.png', dpi=150)
plt.close()
print(f"Detected filter type: {ftype}")
9. ์ค๊ณ ๋ฐฉ๋ฒ ๋น๊ต¶
9.1 ์ง์ ๋น๊ต¶
def compare_design_methods(numtaps=51, wp=0.3, ws=0.5):
"""
Compare window, Kaiser, and Parks-McClellan methods for the same specs.
"""
wc_norm = (wp + ws) / 2 # Cutoff for window methods
# Method 1: Hamming window
h_hamming = firwin(numtaps, wc_norm, window='hamming')
# Method 2: Kaiser window (60 dB attenuation)
h_kaiser = firwin(numtaps, wc_norm, window=('kaiser', 5.65))
# Method 3: Parks-McClellan
h_pm = remez(numtaps, [0, wp / 2, ws / 2, 0.5], [1, 0])
# Frequency responses
methods = [
('Hamming Window', h_hamming),
('Kaiser Window (ฮฒ=5.65)', h_kaiser),
('Parks-McClellan', h_pm),
]
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
colors = ['blue', 'green', 'red']
# Overlay magnitude responses
ax = axes[0, 0]
for (name, h), color in zip(methods, colors):
w, H = signal.freqz(h, worN=4096)
ax.plot(w / np.pi, 20 * np.log10(np.abs(H) + 1e-15),
color=color, linewidth=1.5, label=name)
ax.set_xlabel('Normalized Frequency (รฯ)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title('Magnitude Response Comparison')
ax.set_ylim(-80, 5)
ax.legend()
ax.grid(True, alpha=0.3)
# Passband detail
ax = axes[0, 1]
for (name, h), color in zip(methods, colors):
w, H = signal.freqz(h, worN=4096)
ax.plot(w / np.pi, 20 * np.log10(np.abs(H) + 1e-15),
color=color, linewidth=1.5, label=name)
ax.set_xlabel('Normalized Frequency (รฯ)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title('Passband Detail')
ax.set_xlim(0, wp + 0.05)
ax.set_ylim(-1, 0.5)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
# Transition band detail
ax = axes[1, 0]
for (name, h), color in zip(methods, colors):
w, H = signal.freqz(h, worN=4096)
ax.plot(w / np.pi, np.abs(H), color=color, linewidth=1.5, label=name)
ax.set_xlabel('Normalized Frequency (รฯ)')
ax.set_ylabel('|H(f)|')
ax.set_title('Transition Band Detail')
ax.set_xlim(wp - 0.05, ws + 0.05)
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
# Impulse responses
ax = axes[1, 1]
n = np.arange(numtaps)
for (name, h), color in zip(methods, colors):
ax.plot(n, h, color=color, linewidth=1.5, marker='o',
markersize=3, label=name)
ax.set_xlabel('Sample n')
ax.set_ylabel('h[n]')
ax.set_title('Impulse Responses')
ax.legend(fontsize=8)
ax.grid(True, alpha=0.3)
plt.suptitle(f'FIR Design Comparison (N={numtaps})', fontsize=13)
plt.tight_layout()
plt.savefig('design_comparison.png', dpi=150)
plt.close()
compare_design_methods(numtaps=51, wp=0.3, ws=0.5)
9.2 ์ค๊ณ ๋ฐฉ๋ฒ ์์ฝ¶
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ
โ ๋ฐฉ๋ฒ โ ์ต์ ์ฑ โ ์ ์ด โ ์ต์ ์ฉ๋ โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ ์ฐฝํจ์๋ฒ โ ์ค์ต์ โ ์ ํ์ โ ๋น ๋ฅธ ์ค๊ณ, โ
โ โ โ (์ด์ฐ ์ฐฝํจ์ โ ๊ต์ก์ฉ โ
โ โ โ ์ ํ) โ โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ ์นด์ด์ ์ฐฝํจ์ โ ์ค์ต์ ์ ๊ทผ์ โ ์ฐ์ ฮฒ โ ์ ์ด์ ๋จ์์ฑ์ โ
โ โ โ ํ๋ผ๋ฏธํฐ โ ์ข์ ๊ท ํ โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ ์ฃผํ์ ํ๋ณธํ โ ์ค์ต์ โ ์ ์ด ํ๋ณธ โ ํ๋์จ์ด ๊ตฌํ, โ
โ โ โ โ DFT ๊ธฐ๋ฐ ํํฐ โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ Parks-McClellan โ ์ต์ โ ๊ฐ์ค ํจ์, โ ์ค์ ์ ํ, โ
โ โ (๋ฏธ๋๋งฅ์ค) โ ๋ค์ค ๋์ญ โ ์๊ฒฉํ ์ฌ์ โ
โโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโ
ํต์ฌ ์ธ์ฌ์ดํธ: ๋์ผํ ํํฐ ์ฐจ์์์ Parks-McClellan์ ๊ฐ์ฅ ์์ ์ต๋ ์ค์ฐจ(๋ฑ๋ฆฌํ)๋ฅผ ๋ฌ์ฑํ๋ค. ์ฐฝํจ์๋ฒ์ ๋ ๋์ ์ฃผํ์์์ ๋จ์กฐ ์ฆ๊ฐํ๋ ์ ์ง๋์ญ ๊ฐ์ ๋ฅผ ๊ฐ๋ ํํฐ๋ฅผ ์์ฑํ๋๋ฐ, ์ด๋ ๋์ ์ฃผํ์์์์ ๊ฐ์ ๋ฅผ "๋ญ๋น"ํ๋ ์ ์ด๋ค.
10. FIR ํํฐ ๊ตฌํ¶
10.1 ์ง์ ํฉ์ฑ๊ณฑ(Direct Convolution)¶
๊ฐ์ฅ ๊ฐ๋จํ ๊ตฌํ ๋ฐฉ๋ฒ์ ์ถ๋ ฅ์ ์ง์ ํฉ์ฑ๊ณฑ์ผ๋ก ๊ณ์ฐํ๋ค:
$$y[n] = \sum_{k=0}^{M} h[k] x[n-k]$$
๋ณต์ก๋: $M$์ฐจ ํํฐ๋ก $N$๊ฐ์ ํ๋ณธ์ ํํฐ๋งํ๋ ๋ฐ $O(MN)$.
def fir_direct(x, h):
"""Direct-form FIR filter implementation."""
M = len(h)
N = len(x)
y = np.zeros(N + M - 1)
for n in range(N + M - 1):
for k in range(M):
if 0 <= n - k < N:
y[n] += h[k] * x[n - k]
return y
# Using numpy's convolve (optimized)
x = np.random.randn(1000)
h = firwin(51, 0.3)
y_direct = np.convolve(x, h, mode='full')
print(f"Input length: {len(x)}, Filter length: {len(h)}, Output length: {len(y_direct)}")
10.2 FFT ๊ธฐ๋ฐ ํํฐ๋ง¶
๊ธด ์ ํธ์ ๊ฒฝ์ฐ FFT ๊ธฐ๋ฐ ํฉ์ฑ๊ณฑ์ด ํจ์ฌ ๋น ๋ฅด๋ค:
$$y = \text{IFFT}[\text{FFT}(x) \cdot \text{FFT}(h)]$$
๋ณต์ก๋: $O(MN)$ ๋์ $O(N \log N)$.
FFT๊ฐ ๋ ๋นจ๋ผ์ง๋ ๊ต์ฐจ์ ์ ์ผ๋ฐ์ ์ผ๋ก $M \approx 50-100$์ด๋ค.
10.3 ์ค์ฒฉ ๊ฐ์ฐ๋ฒ(Overlap-Add Method)¶
์ ๋ ฅ์ด ์ธ๊ทธ๋จผํธ๋ก ๋์ฐฉํ๋ ์ค์๊ฐ ๋๋ ๋ธ๋ก ๊ธฐ๋ฐ ์ฒ๋ฆฌ๋ฅผ ์ํ ๋ฐฉ๋ฒ:
- ์ ๋ ฅ $x[n]$์ ๊ธธ์ด $L$์ ๋น์ค์ฒฉ ๋ธ๋ก์ผ๋ก ๋ถํ ํ๋ค
- ๊ฐ ๋ธ๋ก์ ๊ธธ์ด $L + M$์ผ๋ก ์ ํจ๋ฉํ๋ค
- ๊ฐ ๋ธ๋ก์ ํํฐ $h[n]$๊ณผ FFT ํฉ์ฑ๊ณฑํ๋ค
- ์ธ์ ์ถ๋ ฅ ๋ธ๋ก๋ค์ ์ค์ฒฉ ๊ฐ์ฐํ๋ค (๋ง์ง๋ง $M$๊ฐ์ ํ๋ณธ์ด ์ค์ฒฉ๋จ)
def overlap_add(x, h, L=256):
"""
Overlap-add FIR filtering.
Parameters:
x: input signal
h: FIR filter coefficients
L: block size
Returns:
y: filtered output
"""
M = len(h)
N = len(x)
N_fft = L + M - 1
# Zero-pad filter to FFT length
H = np.fft.fft(h, N_fft)
# Output array
y = np.zeros(N + M - 1)
# Process blocks
num_blocks = int(np.ceil(N / L))
for i in range(num_blocks):
# Extract block
start = i * L
end = min(start + L, N)
x_block = np.zeros(L)
x_block[:end - start] = x[start:end]
# FFT convolution
X_block = np.fft.fft(x_block, N_fft)
y_block = np.real(np.fft.ifft(X_block * H))
# Overlap-add
y[start:start + N_fft] += y_block
return y[:N + M - 1]
# Verify overlap-add produces same result as direct convolution
x = np.random.randn(5000)
h = firwin(101, 0.3)
y_direct = np.convolve(x, h)
y_ola = overlap_add(x, h, L=256)
error = np.max(np.abs(y_direct - y_ola))
print(f"Maximum error between direct and overlap-add: {error:.2e}")
10.4 ์ค์ฒฉ ์ ์ฅ๋ฒ(Overlap-Save Method)¶
์ค์ฒฉ ๊ฐ์ฐ๋ฒ์ ๋์:
- ์ ๋ ฅ ๋ธ๋ก์ $M-1$๊ฐ์ ํ๋ณธ๋งํผ ์ค์ฒฉ์ํจ๋ค (๊ฐ ๋ธ๋ก์ ์ด์ ๋ธ๋ก์ $M-1$๊ฐ ํ๋ณธ ํฌํจ)
- ์ํ ํฉ์ฑ๊ณฑ(Circular Convolution)์ ์ด์ฉํ์ฌ FFT ํฉ์ฑ๊ณฑํ๋ค (๊ธธ์ด $L+M-1$)
- ๊ฐ ์ถ๋ ฅ ๋ธ๋ก์ ์ฒซ $M-1$๊ฐ ํ๋ณธ์ ๋ฒ๋ฆฐ๋ค (์ํ ๊ฐ๊น์ ์ํด ์ค์ผ๋จ)
- ๋๋จธ์ง ํ๋ณธ๋ค์ ์ฐ๊ฒฐํ๋ค
def overlap_save(x, h, L=256):
"""
Overlap-save FIR filtering.
Parameters:
x: input signal
h: FIR filter coefficients
L: block size (output samples per block)
Returns:
y: filtered output
"""
M = len(h)
N = len(x)
N_fft = L + M - 1
# Zero-pad filter to FFT length
H = np.fft.fft(h, N_fft)
# Prepend M-1 zeros to input
x_padded = np.concatenate([np.zeros(M - 1), x])
N_padded = len(x_padded)
# Output
y = np.zeros(N + M - 1)
output_idx = 0
block_start = 0
while block_start < N_padded:
# Extract overlapping block of length N_fft
block_end = min(block_start + N_fft, N_padded)
x_block = np.zeros(N_fft)
x_block[:block_end - block_start] = x_padded[block_start:block_end]
# Circular convolution via FFT
X_block = np.fft.fft(x_block, N_fft)
y_block = np.real(np.fft.ifft(X_block * H))
# Keep only valid samples (discard first M-1)
valid = y_block[M - 1:]
valid_len = min(len(valid), N + M - 1 - output_idx)
y[output_idx:output_idx + valid_len] = valid[:valid_len]
output_idx += L
block_start += L # Advance by L (not N_fft)
return y[:N + M - 1]
# Verify overlap-save
y_ols = overlap_save(x, h, L=256)
error_ols = np.max(np.abs(y_direct[:len(y_ols)] - y_ols[:len(y_direct)]))
print(f"Maximum error between direct and overlap-save: {error_ols:.2e}")
10.5 ์ฑ๋ฅ ๋น๊ต¶
import time
def benchmark_methods(signal_lengths, filter_order=100):
"""Benchmark different FIR filtering methods."""
h = firwin(filter_order + 1, 0.3)
results = {name: [] for name in ['Direct (np.convolve)', 'Overlap-Add', 'scipy.fftconvolve']}
for N in signal_lengths:
x = np.random.randn(N)
# Direct convolution
t0 = time.time()
_ = np.convolve(x, h)
results['Direct (np.convolve)'].append(time.time() - t0)
# Overlap-add
t0 = time.time()
_ = overlap_add(x, h, L=1024)
results['Overlap-Add'].append(time.time() - t0)
# scipy FFT convolve
from scipy.signal import fftconvolve
t0 = time.time()
_ = fftconvolve(x, h)
results['scipy.fftconvolve'].append(time.time() - t0)
return results
signal_lengths = [1000, 5000, 10000, 50000, 100000, 500000]
results = benchmark_methods(signal_lengths)
plt.figure(figsize=(10, 6))
for name, times in results.items():
plt.loglog(signal_lengths, times, 'o-', linewidth=2, label=name)
plt.xlabel('Signal Length N')
plt.ylabel('Time (seconds)')
plt.title('FIR Filtering: Performance Comparison')
plt.legend()
plt.grid(True, which='both', alpha=0.3)
plt.tight_layout()
plt.savefig('fir_performance.png', dpi=150)
plt.close()
11. Python ๊ตฌํ¶
11.1 ์์ ํ ์ค๊ณ ์์¶
import numpy as np
from scipy import signal
from scipy.signal import firwin, remez, freqz, kaiserord
import matplotlib.pyplot as plt
def complete_fir_design(fs, f_pass, f_stop, A_stop_dB, method='kaiser'):
"""
Complete FIR lowpass filter design workflow.
Parameters:
fs: sampling frequency (Hz)
f_pass: passband edge (Hz)
f_stop: stopband edge (Hz)
A_stop_dB: stopband attenuation (dB)
method: 'kaiser', 'hamming', or 'remez'
Returns:
h: filter coefficients
info: design information dictionary
"""
nyq = fs / 2
if method == 'kaiser':
width = (f_stop - f_pass) / nyq
numtaps, beta = kaiserord(A_stop_dB, width)
if numtaps % 2 == 0:
numtaps += 1
cutoff = (f_pass + f_stop) / 2 / nyq
h = firwin(numtaps, cutoff, window=('kaiser', beta))
info = {'method': 'Kaiser', 'numtaps': numtaps, 'beta': beta}
elif method == 'hamming':
# Estimate order from transition width
delta_f = (f_stop - f_pass) / fs
numtaps = int(np.ceil(3.32 / delta_f)) + 1
if numtaps % 2 == 0:
numtaps += 1
cutoff = (f_pass + f_stop) / 2 / nyq
h = firwin(numtaps, cutoff, window='hamming')
info = {'method': 'Hamming', 'numtaps': numtaps}
elif method == 'remez':
# Start with estimated order, iterate if needed
delta_f = (f_stop - f_pass) / fs
numtaps = int(np.ceil((-20 * np.log10(np.sqrt(0.001 * 0.001)) - 13) /
(14.6 * delta_f))) + 1
if numtaps % 2 == 0:
numtaps += 1
delta_s = 10**(-A_stop_dB / 20)
weight = [1, 1 / delta_s] # Weight stopband more
h = remez(numtaps, [0, f_pass, f_stop, nyq], [1, 0],
weight=[1, 1], fs=fs)
info = {'method': 'Parks-McClellan', 'numtaps': numtaps}
return h, info
# Design and analyze
fs = 16000
f_pass = 2000
f_stop = 3000
A_stop = 60
fig, axes = plt.subplots(3, 2, figsize=(14, 14))
methods = ['kaiser', 'hamming', 'remez']
for i, method in enumerate(methods):
h, info = complete_fir_design(fs, f_pass, f_stop, A_stop, method=method)
# Frequency response
w, H = freqz(h, worN=4096, fs=fs)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)
# Magnitude
ax = axes[i, 0]
ax.plot(w, H_dB, 'b-', linewidth=1.5)
ax.axhline(-A_stop, color='r', linestyle='--', alpha=0.5, label=f'-{A_stop} dB')
ax.axvline(f_pass, color='g', linestyle='--', alpha=0.5)
ax.axvline(f_stop, color='orange', linestyle='--', alpha=0.5)
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('Magnitude (dB)')
ax.set_title(f"{info['method']} (N={info['numtaps']})")
ax.set_ylim(-100, 5)
ax.legend()
ax.grid(True, alpha=0.3)
# Group delay
w_gd, gd = signal.group_delay((h, [1]), w=4096, fs=fs)
ax = axes[i, 1]
ax.plot(w_gd, gd, 'g-', linewidth=1.5)
ax.set_xlabel('Frequency (Hz)')
ax.set_ylabel('Group Delay (samples)')
ax.set_title(f"{info['method']}: Group Delay")
ax.grid(True, alpha=0.3)
plt.suptitle('Complete FIR Filter Design Comparison', fontsize=14)
plt.tight_layout()
plt.savefig('complete_fir_design.png', dpi=150)
plt.close()
11.2 ์ค์ ์ ํธ ํํฐ๋ง¶
def demonstrate_filtering():
"""Demonstrate FIR filtering on a multi-tone signal."""
fs = 8000
t = np.arange(0, 1, 1/fs)
# Create signal: 200 Hz + 800 Hz + 2000 Hz + noise
x = (np.sin(2 * np.pi * 200 * t) +
0.5 * np.sin(2 * np.pi * 800 * t) +
0.3 * np.sin(2 * np.pi * 2000 * t) +
0.1 * np.random.randn(len(t)))
# Design lowpass filter (cutoff ~1000 Hz)
h = firwin(101, 1000, fs=fs, window='hamming')
# Apply filter
y = signal.lfilter(h, 1, x)
# Compensate for group delay
delay = (len(h) - 1) // 2
fig, axes = plt.subplots(3, 1, figsize=(14, 10))
# Time domain
t_ms = t * 1000
axes[0].plot(t_ms[:500], x[:500], 'b-', alpha=0.7, label='Original')
axes[0].plot(t_ms[:500], y[delay:delay + 500], 'r-', linewidth=2,
label='Filtered (delay compensated)')
axes[0].set_xlabel('Time (ms)')
axes[0].set_ylabel('Amplitude')
axes[0].set_title('Time Domain')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# Spectrum comparison
from scipy.fft import fft, fftfreq
N = len(x)
freq = fftfreq(N, 1/fs)[:N//2]
X = 2 / N * np.abs(fft(x))[:N//2]
Y = 2 / N * np.abs(fft(y))[:N//2]
axes[1].plot(freq, X, 'b-', alpha=0.7, label='Original')
axes[1].plot(freq, Y, 'r-', linewidth=2, label='Filtered')
axes[1].axvline(1000, color='g', linestyle='--', alpha=0.5, label='Cutoff')
axes[1].set_xlabel('Frequency (Hz)')
axes[1].set_ylabel('Magnitude')
axes[1].set_title('Frequency Domain')
axes[1].set_xlim(0, fs / 2)
axes[1].legend()
axes[1].grid(True, alpha=0.3)
# Spectrogram
f_spec, t_spec, Sxx = signal.spectrogram(x, fs, nperseg=256)
f_spec_f, t_spec_f, Syy = signal.spectrogram(y, fs, nperseg=256)
axes[2].pcolormesh(t_spec * 1000, f_spec, 10 * np.log10(Sxx + 1e-15),
shading='gouraud', cmap='viridis')
axes[2].set_xlabel('Time (ms)')
axes[2].set_ylabel('Frequency (Hz)')
axes[2].set_title('Spectrogram (Original Signal)')
plt.tight_layout()
plt.savefig('fir_filtering_demo.png', dpi=150)
plt.close()
demonstrate_filtering()
11.3 ๋ค์ค ๋์ญ ํํฐ ์ค๊ณ¶
# Design a multi-band filter using firwin2
from scipy.signal import firwin2
# Arbitrary magnitude response
numtaps = 201
freq_points = [0, 0.1, 0.15, 0.3, 0.35, 0.5, 0.55, 0.7, 0.75, 1.0]
gain_points = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0]
h_multiband = firwin2(numtaps, freq_points, gain_points)
w, H = freqz(h_multiband, worN=4096)
H_dB = 20 * np.log10(np.abs(H) + 1e-15)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Desired vs actual
axes[0].plot(np.array(freq_points) / 2, gain_points, 'ro-',
linewidth=2, label='Desired', markersize=8)
axes[0].plot(w / np.pi / 2, np.abs(H), 'b-', linewidth=1.5, label='Actual')
axes[0].set_xlabel('Normalized Frequency')
axes[0].set_ylabel('Magnitude')
axes[0].set_title('Multi-band Filter: Magnitude')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].plot(w / np.pi, H_dB, 'b-', linewidth=1.5)
axes[1].set_xlabel('Normalized Frequency (รฯ)')
axes[1].set_ylabel('Magnitude (dB)')
axes[1].set_title('Multi-band Filter: dB Scale')
axes[1].set_ylim(-80, 5)
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('multiband_fir.png', dpi=150)
plt.close()
12. ์ฐ์ต ๋ฌธ์ ¶
์ฐ์ต ๋ฌธ์ 1: ์ฐฝํจ์ ์ ํ¶
๋ค์ ์ฌ์์ผ๋ก ์ ์ญํต๊ณผ FIR ํํฐ๋ฅผ ์ค๊ณํ๋ผ: - ํ๋ณธํ ์ฃผํ์: $f_s = 10000$ Hz - ํต๊ณผ๋์ญ ๊ฒฝ๊ณ: $f_p = 1500$ Hz - ์ ์ง๋์ญ ๊ฒฝ๊ณ: $f_s = 2000$ Hz - ์ต์ ์ ์ง๋์ญ ๊ฐ์ : $60$ dB
(a) ์ด๋ค ์ฐฝํจ์ ์ ํ์ด ์ ์ง๋์ญ ์ฌ์์ ์ถฉ์กฑํ ์ ์๋๊ฐ?
(b) ๊ฐ ๊ฐ๋ฅํ ์ฐฝํจ์์ ๋ํด ํ์ํ ํํฐ ์ฐจ์ $M$์ ๊ณ์ฐํ๋ผ.
(c) scipy.signal.firwin์ ์ฌ์ฉํ์ฌ ํํฐ๋ฅผ ์ค๊ณํ๊ณ ์ฌ์์ด ์ถฉ์กฑ๋๋์ง ๊ฒ์ฆํ๋ผ.
(d) ํต๊ณผ๋์ญ ๋ฆฌํ๊ณผ ์ ์ด ํญ ์ธก๋ฉด์์ ๊ฒฐ๊ณผ ํํฐ๋ค์ ๋น๊ตํ๋ผ.
์ฐ์ต ๋ฌธ์ 2: ์นด์ด์ ์ฐฝํจ์ ์ค๊ณ¶
๋์งํธ ํํฐ๊ฐ ๋ค์์ ๋ง์กฑํด์ผ ํ๋ค: - ํต๊ณผ๋์ญ: $0 \leq f \leq 3$ kHz, ๋ฆฌํ $\leq 0.1$ dB - ์ ์ง๋์ญ: $f \geq 4$ kHz, ๊ฐ์ $\geq 50$ dB - ํ๋ณธํ์จ: $f_s = 20$ kHz
(a) ๊ฒฝํ์ ๊ณต์์ ์ด์ฉํ์ฌ ์นด์ด์ ์ฐฝํจ์ ํ๋ผ๋ฏธํฐ $\beta$๋ฅผ ๊ณ์ฐํ๋ผ.
(b) ์ต์ ํํฐ ์ฐจ์ $M$์ ๊ณ์ฐํ๋ผ.
(c) ํํฐ๋ฅผ ์ค๊ณํ๊ณ ์งํญ ์๋ต, ์์ ์๋ต, ๊ตฐ์ง์ฐ์ ๊ทธ๋ํ๋ก ๋ํ๋ด๋ผ.
(d) ์ค์ ํต๊ณผ๋์ญ ๋ฆฌํ๊ณผ ์ ์ง๋์ญ ๊ฐ์ ๋ฅผ ์ธก์ ํ์ฌ ์ค๊ณ๊ฐ ์ฌ์์ ์ถฉ์กฑํ๋์ง ๊ฒ์ฆํ๋ผ.
์ฐ์ต ๋ฌธ์ 3: Parks-McClellan ๋์ญํต๊ณผ ํํฐ¶
Parks-McClellan ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ์ฌ ๋์ญํต๊ณผ FIR ํํฐ๋ฅผ ์ค๊ณํ๋ผ: - $f_s = 44100$ Hz (CD ์ค๋์ค ๋ ์ดํธ) - ์ ์ง๋์ญ 1: $0$์์ $800$ Hz (๊ฐ์ $\geq 40$ dB) - ํต๊ณผ๋์ญ: $1000$์์ $3000$ Hz (๋ฆฌํ $\leq 0.5$ dB) - ์ ์ง๋์ญ 2: $3500$์์ $22050$ Hz (๊ฐ์ $\geq 40$ dB)
(a) ์์ชฝ ์ ์ง๋์ญ์์ ๋ฑ๋ฆฌํ์ ์ํ ์ ์ ํ ๊ฐ์ค์น๋ฅผ ์ ํํ๋ผ.
(b) ์ต์ ํํฐ ์ฐจ์๋ฅผ ๊ฒฐ์ ํ๋ผ.
(c) ์งํญ ์๋ต์ ๊ทธ๋ํ๋ก ๋ํ๋ด๊ณ ์ฌ์์ ๊ฒ์ฆํ๋ผ.
(d) ์ฒํ ์ ํธ(100 Hz์์ 10 kHz๋ก ์ค์)์ ํํฐ๋ฅผ ์ ์ฉํ๊ณ ํํฐ๋ง ์ ํ์ ์คํํธ๋ก๊ทธ๋จ์ ํ์ํ๋ผ.
์ฐ์ต ๋ฌธ์ 4: ์ค์ฒฉ ๊ฐ์ฐ๋ฒ ๊ตฌํ¶
์ค์ฒฉ ๊ฐ์ฐ๋ฒ์ ์ฌ์ฉํ๋ ์ค์๊ฐ FIR ํํฐ ํ๋ก์ธ์๋ฅผ ๊ตฌํํ๋ผ:
(a) ๋ธ๋ก ๋จ์๋ก ์ค๋์ค๋ฅผ ์ฒ๋ฆฌํ๋ StreamingFIRFilter ํด๋์ค๋ฅผ ์์ฑํ๋ผ.
(b) 64, 256, 1024, 4096 ํ๋ณธ์ ๋ธ๋ก ํฌ๊ธฐ๋ก ํ ์คํธํ๋ผ.
(c) ์ถ๋ ฅ์ด ์ง์ ํฉ์ฑ๊ณฑ (np.convolve)๊ณผ ์ผ์นํ๋์ง ๊ฒ์ฆํ๋ผ.
(d) ๋ธ๋ก ํฌ๊ธฐ์ ํจ์๋ก ๋ธ๋ก๋น ์ฒ๋ฆฌ ์๊ฐ์ ์ธก์ ํ๊ณ ๊ทธ๋ํ๋ก ๋ํ๋ด๋ผ.
(e) 44100 Hz์์ 10์ด ์ค๋์ค ์ ํธ์ 200ํญ ํํฐ๋ฅผ ์ ์ฉํ ๋ ์ต์ ๋ธ๋ก ํฌ๊ธฐ๋ฅผ ์ฐพ์๋ผ.
์ฐ์ต ๋ฌธ์ 5: ์ ํ ์์ ์ ์ฝ¶
(a) ํ์ II FIR ํํฐ (ํ์ ์ฐจ์, ๋์นญ ๊ณ์)๊ฐ ํญ์ $H(e^{j\pi}) = 0$์์ ์ฆ๋ช ํ๋ผ. ์ ์ด๊ฒ์ด ๊ณ ์ญํต๊ณผ ํํฐ ์ค๊ณ์ ๋ถ์ ํฉํ๊ฐ?
(b) ์ฐจ๋จ ์ฃผํ์ $0.6\pi$์์ ๊ณ ์ญํต๊ณผ FIR ํํฐ๋ฅผ ๋ค์์ผ๋ก ์ค๊ณํ๋ผ: - ํ์ I (์ง์ ์ฐจ์) - ํ์ IV (ํ์ ์ฐจ์, ๋ฐ๋์นญ)
๊ฒฐ๊ณผ๋ฅผ ๋น๊ตํ๋ผ. ์ด๋ ๊ฒ์ด $\omega = \pi$ ๊ทผ์ฒ์์ ๋ ์ข์ ์ฑ๋ฅ์ ๋ณด์ด๋๊ฐ?
(c) ์ฐจ์ 30์ ํ๋ฒํธ ๋ณํ ํํฐ(ํ์ III)๋ฅผ ์ค๊ณํ๋ผ. ์งํญ ๋ฐ ์์ ์๋ต์ ๊ทธ๋ํ๋ก ๋ํ๋ด๋ผ. $\omega = 0$๊ณผ $\omega = \pi$์์ ์ด๋ค ์ผ์ด ๋ฐ์ํ๋๊ฐ?
์ฐ์ต ๋ฌธ์ 6: ๋น๊ต ์ฐ๊ตฌ¶
์ฌ์: $f_s = 16000$ Hz, $f_p = 2000$ Hz, $f_\text{stop} = 2500$ Hz, $A_s = 50$ dB:
(a) (i) ํด๋ฐ ์ฐฝํจ์, (ii) ์นด์ด์ ์ฐฝํจ์, (iii) Parks-McClellan์ ์ฌ์ฉํ์ฌ ํํฐ๋ฅผ ์ค๊ณํ๋ผ.
(b) ๊ฐ ๋ฐฉ๋ฒ์ ๋ํด ๋ค์์ ๋ณด๊ณ ํ๋ผ: ํํฐ ์ฐจ์, ํต๊ณผ๋์ญ ๋ฆฌํ (dB), ์ค์ ์ ์ง๋์ญ ๊ฐ์ (dB), ์ ์ด ํญ (Hz).
(c) 4๊ฐ์ ์๋ธํ๋กฏ์ด ํฌํจ๋ ๋จ์ผ ๊ทธ๋ฆผ์ ์์ฑํ๋ผ: ์งํญ ์๋ต ์ค๋ฒ๋ ์ด, ํต๊ณผ๋์ญ ์์ธ, ์ํ์ค ์๋ต, ์์ -๊ทน์ ์ ๋(Pole-Zero Plot).
(d) ์ด๋ค ๋ฐฉ๋ฒ์ด ๊ฐ์ฅ ๋ฎ์ ํํฐ ์ฐจ์์์ ์ต์ ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํ๋๊ฐ? ๋ต์ ์ ๋นํํ๋ผ.
์ฐ์ต ๋ฌธ์ 7: ์ต์ ์์ FIR ํํฐ¶
(a) ํด๋ฐ ์ฐฝํจ์๋ฒ์ ์ฌ์ฉํ์ฌ ์ฐจ์ 40์ ์ ํ ์์ ์ ์ญํต๊ณผ FIR ํํฐ๋ฅผ ์ค๊ณํ๋ผ.
(b) ์ผ์คํธ๋ผ ๋ฐฉ๋ฒ(Cepstral Method)์ ์ฌ์ฉํ์ฌ ์ต์ ์์(Minimum-Phase) FIR ํํฐ๋ก ๋ณํํ๋ผ: - ์ผ์คํธ๋ผ $\hat{h}[n] = \text{IFFT}(\log|\text{FFT}(h)|)$ ๊ณ์ฐ - ์ต์ ์์ ๋ฒ์ ๊ตฌ์ฑ
(c) ๋ ํํฐ์ ์งํญ ์๋ต, ์์ ์๋ต, ๊ตฐ์ง์ฐ์ ๋น๊ตํ๋ผ.
(d) ์ธ์ ์ต์ ์์์ ์ ํ ์์๋ณด๋ค ์ ํธํ๋๊ฐ? ์์๋ฅผ ๋ค์ด ๋ ผ์ํ๋ผ.
์ฐธ๊ณ ๋ฌธํ¶
- Oppenheim, A. V., & Schafer, R. W. (2010). Discrete-Time Signal Processing (3rd ed.). Pearson. Chapters 7-8.
- Proakis, J. G., & Manolakis, D. G. (2007). Digital Signal Processing (4th ed.). Pearson. Chapter 10.
- Parks, T. W., & Burrus, C. S. (1987). Digital Filter Design. Wiley.
- Kaiser, J. F. (1974). "Nonrecursive Digital Filter Design Using the I0-sinh Window Function." Proceedings of the 1974 IEEE International Symposium on Circuits and Systems.
- McClellan, J. H., Parks, T. W., & Rabiner, L. R. (1973). "A Computer Program for Designing Optimum FIR Linear Phase Digital Filters." IEEE Trans. Audio Electroacoustics, 21(6), 506-526.
- SciPy Documentation --
scipy.signalmodule: https://docs.scipy.org/doc/scipy/reference/signal.html
ํ์¶
- ์ด์ : 08. Z ๋ณํ๊ณผ ์ ๋ฌ ํจ์
- ๋ค์: 10. IIR ํํฐ ์ค๊ณ
- ๊ฐ์๋ก ๋์๊ฐ๊ธฐ