Python으로 배우는 DSP/Part 3. 디지털 필터 설계와 구현

[16] Z-변환 응용: 전달함수 H(z), 극점·영점과 필터 안정성

multimedia 2026. 5. 23. 10:00
반응형

들어가며

이전 시간에는 시간 영역의 복잡한 컨볼루션(Convolution) 연산을 주파수 영역의 단순한 곱셈으로 바꾸어 주는 마법 같은 수학 도구, Z-변환(Z-Transform)의 기초를 다루었습니다. 복소평면 위에서 수렴 영역(ROC)을 정의하고, 단위 원(Unit Circle)을 따라 걸을 때 얻어지는 값이 바로 시스템의 주파수 응답(DTFT)이라는 중요한 사실을 확인했습니다.

하지만 Z-변환의 진정한 위력은 단순히 계산을 편하게 만들어 주는 데서 끝나지 않습니다. 우리는 이 2차원 Z-평면을 도화지 삼아, 우리가 원하는 특성을 가진 필터를 자유자재로 설계(Design)할 수 있습니다.

이번 글에서는 그 연장선에서, Z-변환이 실제 필터 설계에 어떻게 연결되는지를 다룹니다. 핵심 도구는 전달함수 H(z)입니다. 차분방정식으로 표현된 필터를 H(z)로 변환하면, 시스템의 성질을 수식 한 줄로 요약할 수 있습니다. 그리고 H(z)의 분자·분모를 0으로 만드는 복소수 값, 즉 영점(Zero)과 극점(Pole)을 복소평면 위에 그리는 것만으로도 필터의 주파수 응답과 안정성을 한눈에 파악할 수 있습니다.

이번 글에서 다루는 내용은 다음과 같습니다.

  • 차분방정식에서 전달함수 H(z)를 도출하는 과정
  • 영점과 극점의 정의 및 극점·영점 플롯(Pole-Zero Plot) 해석
  • 안정 조건: 왜 모든 극점이 단위원 안에 있어야 하는가
  • FIR과 IIR 필터의 구조적 차이를 H(z)로 이해하기
  • Python 실습: 직접 설계한 H(z)의 주파수 응답과 안정성 확인

 

1. 차분 방정식에서 전달함수 $H(z)$ 도출하기

디지털 신호 처리(DSP)에서 필터를 하드웨어나 소프트웨어 코드로 구현할 때, 가장 근간이 되는 수식은 현재와 과거의 입출력 데이터를 결합하는 차분 방정식(Difference Equation)입니다. 일반적인 선형 시불변(LTI) 시스템의 차분 방정식은 다음과 같습니다.

$$y[n] = \sum_{k=0}^{M} b_k x[n-k] - \sum_{k=1}^{N} a_k y[n-k]$$
  • $x[n]$: 입력 신호
  • $y[n]$: 출력 신호
  • $b_k$: 피드포워드(Feedforward) 필터의 계수(Coefficients)
  • $a_k$: 피드백(Feedback) 필터의 계수

이 복잡한 수식을 분석하기 위해 양변에 Z-변환을 적용해 보겠습니다. Z-변환의 핵심 성질인 시간 지연 성질 $\mathcal{Z}\{x[n - k]\} = z^{-k} X(z)$을 이용하면 수식이 매우 단순한 대수 방정식으로 바뀝니다.

$$Y(z) = X(z) \sum_{k=0}^{M} b_k z^{-k} - Y(z) \sum_{k=1}^{N} a_k z^{-k}$$

이제 $Y(z)$끼리 묶어주고, 입력 $X(z)$ 대비 출력 $Y(z)$의 비율을 구해보면 우리가 찾던 전달함수(Transfer Function, $H(z)$)가 도출됩니다.

$$H(z) = \frac{Y(z)}{X(z)} = \frac{\sum_{k=0}^{M} b_k z^{-k}}{1 + \sum_{k=1}^{N} a_k z^{-k}}$$

전달함수 $H(z)$는 입력 신호가 시스템을 통과하면서 어떻게 변형되는지를 완벽하게 기술하는 함수입니다. 입력 $X(z)$에 $H(z)$를 곱하면 출력 $Y(z)$가 나오고, 단위 임펄스를 입력했을 때의 출력, 즉 임펄스 응답 $h[n]$도 $H(z)$의 역변환으로 구할 수 있습니다.

 

2. 극점(Pole)과 영점(Zero)의 물리적 의미

도출된 전달함수 $H(z)$는 분자 다항식과 분모 다항식의 분수 형태로 이루어져 있습니다. 이 다항식을 인수분해하면 시스템의 특성을 지배하는 두 가지 중요한 좌표를 얻을 수 있습니다.

$$H(z) = \frac{B(z)}{A(z)} = g \cdot \frac{(z - z_1)(z - z_2) \cdots (z - z_M)}{(z - p_1)(z - p_2) \cdots (z - p_N)}$$

① 영점 (Zero)

  • 정의: 전달함수의 분자를 0으로 만드는 $z$ 값. 즉, $H(z) = 0$이 되는 지점입니다.
  • 물리적 의미: Z-평면 위에서 주파수 응답을 바닥으로 끌어내리는 '골짜기' 역할을 합니다. 특정 주파수 성분을 완벽하게 제거(Notch)하고 싶을 때 영점을 해당 주파수 위치의 단위 원 위에 배치합니다. 플롯에서는 주로 $\text{O}$ 기호로 표시합니다.

② 극점 (Pole)

  • 정의: 전달함수의 분모를 0으로 만드는 $z$ 값. 즉, $H(z) = \infty$로 발산하게 만드는 지점입니다.
  • 물리적 의미: Z-평면 위에서 주파수 응답을 위로 강하게 솟구치게 만드는 '산봉우리' 역할을 합니다. 특정 주파수 대역을 공진(Resonance)시켜 증폭하고 싶을 때 극점을 해당 주파수 근처에 배치합니다. 플롯에서는 주로 $\text{X}$ 기호로 표시합니다.

주파수 응답은 $z = e^{j\omega}$, 즉 단위원 위를 따라가며 $H(z)$를 평가한 값입니다. 단위원 위의 한 점에서 각 영점까지의 거리는 분자의 크기에, 각 극점까지의 거리는 분모의 크기에 기여합니다.

$$|H(e^{j\omega})| = g \cdot \frac{\prod_k |e^{j\omega} - z_k|}{\prod_k |e^{j\omega} - p_k|}$$

즉, 극점이 단위원에 가까울수록 그 근방의 주파수 성분이 크게 증폭되고, 영점이 단위원 위에 놓이면 그 주파수가 완전히 차단됩니다. 이것이 극점·영점 배치로 원하는 주파수 응답을 설계할 수 있는 핵심 원리입니다.

 

3. 필터의 안정성(Stability) 조건: 단위 원과 극점

필터를 설계할 때 가장 우선적으로 고려해야 할 절대 원칙이 있습니다. 바로 시스템은 안정적(Stable)이어야 한다는 것입니다. 안정적인 시스템이란, 제한된 크기의 입력을 넣었을 때 출력 역시 제한된 크기로 나오는 시스템을 말합니다. 이러한 시스템을 BIBO 안정(Bounded Input Bounded Output Stable) 시스템이라고 합니다.

Z-도메인에서 시스템의 안정성은 전적으로 극점(Pole)의 위치에 의해 결정됩니다.

  • 안정 (Stable): 모든 극점이 단위 원의 엄격한 내부에 존재할 때 ($|z| < 1$). 극점이 원점(0)에 가까울수록 임펄스 응답이 빠르게 소멸합니다.
  • 불안정 (Unstable): 단 하나의 극점이라도 단위 원의 외부에 존재할 때 ($|z| > 1$). 이 경우 피드백 에너지가 계속 증폭되어 시스템의 출력이 무한대로 발산(폭발)합니다.
  • 임계 안정 (Marginally Stable): 극점이 단위 원 경계선 위에 있을 때 ($|z| = 1$). 신호가 감쇠하지 않고 영원히 진동하는 발진기(Oscillator) 상태가 됩니다.

※ 주의: 영점(Zero)은 단위 원 밖으로 나가더라도 신호의 위상(Phase)만 바꿀 뿐 시스템의 안정성 자체에는 전혀 영향을 주지 않습니다.

위의 내용을 임펄스 응답의 관점에서 살펴보겠습니다. 단일 실수 극점 $p$를 가진 시스템의 임펄스 응답은 다음과 같습니다.

$$h[n] = p^n \cdot u[n]$$

  • $|p| < 1$이면 $h[n]$은 $n \to \infty$일 때 0으로 수렴 $\to$ 안정
  • $|p| = 1$이면 $h[n]$은 진동하며 발산하지 않으나 수렴도 하지 않음 $\to$ 임계 안정(경계선)
  • $|p| > 1$이면 $h[n]$은 기하급수적으로 발산 $\to$ 불안정

복소 켤레 극점 쌍 $p = re^{\pm j\omega_0}$의 경우 임펄스 응답은 진동하는 형태이며, 반지름 $r = |p|$가 감쇠율을 결정합니다. 극점이 단위원에 가까울수록 해당 진동이 오래 지속됩니다.

 

4. 극점·영점 플롯(Pole-Zero Plot)

극점·영점 플롯은 복소평면에 극점(×)과 영점(○)을 표시한 그래프입니다. 반지름 1의 단위원을 기준으로 극점과 영점의 위치를 보면 시스템의 안정성과 주파수 응답 특성을 직관적으로 파악할 수 있습니다.

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

rcParams['font.family'] = 'Malgun Gothic'
rcParams['axes.unicode_minus'] = False

# --- 예시 1: 2차 IIR 시스템 (영점·극점 각 2개) ---
# H(z) = (1 - 0.9z^{-1} + 0.8z^{-2}) / (1 - 1.5z^{-1} + 0.7z^{-2})
b = [1.0, -0.9, 0.8]
a = [1.0, -1.5, 0.7]

zeros, poles, gain = signal.tf2zpk(b, a)

fig, ax = plt.subplots(figsize=(5, 5))

# 단위원
theta = np.linspace(0, 2 * np.pi, 300)
ax.plot(np.cos(theta), np.sin(theta), 'k--', linewidth=0.8, label='단위원')

# 극점·영점
ax.plot(np.real(zeros), np.imag(zeros), 'o', markersize=10,
        color='royalblue', label=f'영점 ({len(zeros)}개)')
ax.plot(np.real(poles), np.imag(poles), 'x', markersize=12,
        markeredgewidth=2, color='crimson', label=f'극점 ({len(poles)}개)')

# 보조선
ax.axhline(0, color='gray', linewidth=0.5)
ax.axvline(0, color='gray', linewidth=0.5)

# 영점 좌표 주석
for z in zeros:
    ax.annotate(f'  ({z.real:.3f}{z.imag:+.3f}j)',
                xy=(z.real, z.imag), fontsize=9, color='royalblue', va='bottom')

# 극점 좌표 주석
for p in poles:
    ax.annotate(f'  ({p.real:.3f}{p.imag:+.3f}j)',
                xy=(p.real, p.imag), fontsize=9, color='crimson', va='top')

ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_aspect('equal')
ax.set_xlabel('실수 축')
ax.set_ylabel('허수 축')
ax.set_title('극점·영점 플롯 (예시 1: 2차 IIR)')
ax.legend(loc='upper right')
ax.grid(True, linestyle=':', alpha=0.5)
plt.tight_layout()
plt.savefig('pz_plot_ex1.png', dpi=150, bbox_inches='tight')
plt.show()

위 코드를 실행하면 켤레 복소 영점 2개와 켤레 복소 극점 2개가 복소평면에 함께 표시됩니다. 극점은 단위원 내부에 위치하며, 영점은 단위원 외부 근방에 배치되어 있습니다. 극점이 단위원 안에 있다는 사실이 바로 이 시스템이 안정하다는 것을 의미합니다.

다음은 극점의 위치에 따른 시스템의 안정성을 확인하는 예제입니다. 이 예제는 피드백만 존재하는 1차 시스템으로, 유한한 영점이 없어 극점의 위치만으로 안정성이 결정됩니다.

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

rcParams['font.family'] = 'Malgun Gothic'
rcParams['axes.unicode_minus'] = False

n = np.arange(40)

pole_cases = [
    (0.85, '|p| = 0.85 (안정)',   'royalblue'),
    (1.00, '|p| = 1.00 (임계)',   'orange'),
    (1.08, '|p| = 1.08 (불안정)', 'crimson'),
]

fig, axes = plt.subplots(2, 3, figsize=(13, 8))

theta = np.linspace(0, 2 * np.pi, 300)

for col, (r, label, color) in enumerate(pole_cases):

    # ── 행 0: 극점·영점 플롯 ──────────────────────────────
    ax_pz = axes[0, col]

    # 전달함수: H(z) = 1 / (1 - r·z^{-1})  →  극점: z = r, 영점: 없음
    b = [1.0]
    a = [1.0, -r]
    zeros, poles, _ = signal.tf2zpk(b, a)

    ax_pz.plot(np.cos(theta), np.sin(theta), 'k--', linewidth=0.8, label='단위원')
    ax_pz.plot(np.real(zeros), np.imag(zeros), 'o', markersize=10,
               color='royalblue', label='영점 (없음)', zorder=3)
    ax_pz.plot(np.real(poles), np.imag(poles), 'x', markersize=14,
               markeredgewidth=2.5, color=color, label='극점', zorder=3)

    # 극점 좌표 주석
    for p in poles:
        ax_pz.annotate(f'  z={p.real:.2f}', xy=(p.real, p.imag),
                       fontsize=8.5, color=color, va='center')

    ax_pz.axhline(0, color='gray', linewidth=0.5)
    ax_pz.axvline(0, color='gray', linewidth=0.5)
    ax_pz.set_xlim(-1.8, 1.8)
    ax_pz.set_ylim(-1.5, 1.5)
    ax_pz.set_aspect('equal')
    ax_pz.set_title(f'극점·영점 플롯\n{label}', fontsize=10)
    ax_pz.set_xlabel('실수 축', fontsize=9)
    ax_pz.set_ylabel('허수 축', fontsize=9)
    ax_pz.legend(fontsize=8, loc='upper right')
    ax_pz.grid(True, linestyle=':', alpha=0.5)

    # ── 행 1: 임펄스 응답 ────────────────────────────────
    ax_ir = axes[1, col]

    h = r ** n
    ax_ir.stem(n, h, linefmt=color, markerfmt='o', basefmt='k-')
    ax_ir.set_title(f'임펄스 응답\n{label}', fontsize=10)
    ax_ir.set_xlabel('n', fontsize=9)
    ax_ir.set_ylabel('h[n]', fontsize=9)
    ax_ir.set_ylim(-0.5, max(3, h.max() * 1.1))
    ax_ir.grid(True, linestyle=':', alpha=0.5)

plt.suptitle('극점 크기에 따른 극점·영점 플롯과 임펄스 응답', fontsize=13)
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.savefig('stability_comparison.png', dpi=150, bbox_inches='tight')
plt.show()

실행 결과에서 $|p| = 0.85$인 경우 임펄스 응답이 점차 $0$으로 수렴하고, $|p| = 1.08$인 경우 폭발적으로 증가하는 것을 명확히 확인할 수 있습니다.

지금까지 살펴본 극점·영점의 개념을 아래 설계기에서 직접 실험해 보시기 바랍니다. 극점을 단위원 밖으로 끌어내면 주파수 응답이 어떻게 발산하는지, 영점을 단위원 위에 올리면 해당 주파수가 어떻게 차단되는지를 실시간으로 확인할 수 있습니다. 복소수인 극점이나 영점을 추가하면 켤레 복소수의 극점이나 영점이 자동으로 추가됩니다.

필터 설계기: 극점 및 영점 (Pole-Zero Designer)
상태
안정 (Stable)
극점 수
0
영점 수
0
Z-평면 (극점/영점)
 
 

 

5. FIR 필터와 IIR 필터의 구조적 차이

5.1. FIR 필터 (Finite Impulse Response)

$$H_{\text{FIR}}(z) = \sum_{k=0}^{M} b_k z^{-k} = b_0 + b_1 z^{-1} + \dots + b_M z^{-M}$$

  • 피드백(과거의 출력 $y[n-k]$) 항이 없는 필터입니다. 따라서 분모가 상수(= 1)이므로 극점은 원점에만 존재합니다.
  • 원점의 극점은 $|p| = 0 < 1$이므로 FIR 필터는 구조적으로 100% 안정적이라는 엄청난 장점을 가집니다.
  • 임펄스 응답의 길이가 $M + 1$개로 유한합니다.
  • 피드백이 없으므로 구현이 단순하고 선형 위상 설계가 용이합니다.

5.2. IIR 필터 (Infinite Impulse Response)

$$H_{\text{IIR}}(z) = \frac{\sum_{k=0}^{M} b_k z^{-k}}{1 + \sum_{k=1}^{N} a_k z^{-k}}$$

  • 피드백 항이 존재하는 필터이며, 분모에 $z$의 다항식이 있으므로 단위원 근방에 극점이 배치 가능합니다.
  • 극점 위치에 따라 안정성이 결정되므로 설계 시 반드시 검증이 필요합니다.
  • 적은 계수로도 가파른 주파수 응답을 구현할 수 있어 연산 효율이 높습니다.
  • 피드백 구조로 인해 임펄스 응답이 이론상 무한히 지속됩니다.
  • 다만 설계 실수나 양자화 오차로 인해 극점이 단위 원 밖으로 튕겨 나가면 필터가 발산해 버릴 위험을 항상 안고 있습니다.

5.3. FIR 필터와 IIR 필터의 극점 및 영점 배치

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

rcParams['font.family'] = 'Malgun Gothic'
rcParams['axes.unicode_minus'] = False

# FIR: 5탭 이동평균
b_fir = np.ones(5) / 5
a_fir = [1.0]

# IIR: 2차 Butterworth 저역통과 (정규화 차단주파수 0.3)
b_iir, a_iir = signal.butter(2, 0.3)

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

for ax, b, a, title in zip(
    axes,
    [b_fir, b_iir],
    [a_fir, a_iir],
    ['FIR (5탭 이동평균)', 'IIR (2차 Butterworth LPF)']
):
    zeros, poles, _ = signal.tf2zpk(b, a)
    theta = np.linspace(0, 2 * np.pi, 300)
    ax.plot(np.cos(theta), np.sin(theta), 'k--', linewidth=0.8)
    ax.plot(np.real(zeros), np.imag(zeros), 'o', markersize=10,
            color='royalblue', label='영점')
    ax.plot(np.real(poles), np.imag(poles), 'x', markersize=12,
            markeredgewidth=2, color='crimson', label='극점')
    ax.axhline(0, color='gray', linewidth=0.5)
    ax.axvline(0, color='gray', linewidth=0.5)
    ax.set_xlim(-1.8, 1.8)
    ax.set_ylim(-1.8, 1.8)
    ax.set_aspect('equal')
    ax.set_title(title)
    ax.set_xlabel('실수 축')
    ax.set_ylabel('허수 축')
    ax.legend()
    ax.grid(True, linestyle=':', alpha=0.5)

plt.suptitle('FIR vs IIR: 극점·영점 구조 비교', fontsize=13)
plt.tight_layout()
plt.savefig('fir_vs_iir_pz.png', dpi=150)
plt.show()

FIR의 경우 모든 극점이 원점에 겹쳐 있고 영점만 단위원 주변에 분포하는 반면, IIR의 경우 극점이 단위원 내부에 가깝게 배치되어 있음을 확인할 수 있습니다.

 

6. H(z)의 주파수 응답: freqz로 확인하기

설계한 $H(z)$의 주파수 응답을 직접 시각화하는 것은 필터 검증의 기본입니다. scipy.signal.freqz()는 $z = e^{j\omega}$ 위에서 $H(z)$를 평가하여 크기 응답과 위상 응답을 반환합니다.

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

rcParams['font.family'] = 'Malgun Gothic'
rcParams['axes.unicode_minus'] = False

# 2차 Butterworth LPF, Chebyshev Type I LPF 비교
filters = {
    'Butterworth 2차': signal.butter(2, 0.3),
    'Chebyshev I 4차 (리플 1dB)': signal.cheby1(4, 1, 0.3),
    'FIR 이동평균 (5탭)': (np.ones(5) / 5, [1.0]),
}

fig, axes = plt.subplots(2, 1, figsize=(10, 7), sharex=True)

for name, (b, a) in filters.items():
    w, h = signal.freqz(b, a, worN=2048)
    freq = w / np.pi  # 0 ~ 1 (정규화)
    magnitude_db = 20 * np.log10(np.maximum(np.abs(h), 1e-10))
    phase_deg = np.unwrap(np.angle(h)) * 180 / np.pi

    axes[0].plot(freq, magnitude_db, label=name, linewidth=1.5)
    axes[1].plot(freq, phase_deg, label=name, linewidth=1.5)

axes[0].axvline(0.3, color='gray', linestyle='--', linewidth=0.8, label='차단주파수 (0.3π)')
axes[0].set_ylabel('크기 응답 (dB)')
axes[0].set_ylim(-80, 5)
axes[0].legend(fontsize=9)
axes[0].grid(True, linestyle=':', alpha=0.5)
axes[0].set_title('주파수 응답 비교')

axes[1].axvline(0.3, color='gray', linestyle='--', linewidth=0.8)
axes[1].set_ylabel('위상 응답 (degree)')
axes[1].set_xlabel('정규화 주파수 (× π rad/sample)')
axes[1].legend(fontsize=9)
axes[1].grid(True, linestyle=':', alpha=0.5)

plt.tight_layout()
plt.savefig('freqz_comparison.png', dpi=150)
plt.show()

출력 결과에서 Chebyshev 필터는 통과대역에서 리플이 발생하지만 차단 특성이 Butterworth보다 가파르고, FIR 이동평균 필터는 차단 특성이 완만하지만 완벽한 선형 위상을 유지함을 볼 수 있습니다. 이처럼 극점·영점 배치 전략에 따라 필터의 특성이 달라집니다.

 

7. 종합 실습: 원하는 H(z) 직접 설계하기

이번에는 영점과 극점을 직접 배치하여 $H(z)$를 설계하고, 그 결과를 확인하겠습니다. 단위원 위의 특정 각도에 영점을 놓으면 해당 주파수를 정확히 차단할 수 있습니다. 예를 들어 60Hz(샘플링률 1000Hz 기준, 정규화 주파수 $0.12\pi$) 성분을 제거하는 노치 필터를 설계합니다.

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

rcParams['font.family'] = 'Malgun Gothic'
rcParams['axes.unicode_minus'] = False

fs = 1000          # 샘플링률 (Hz)
f_notch = 60       # 제거할 주파수 (Hz)
r_pole = 0.95      # 극점 반지름 (안정성 확보, < 1)

# 각도 계산
omega_notch = 2 * np.pi * f_notch / fs

# 영점: 단위원 위 (r=1)
zeros = np.array([
    np.exp( 1j * omega_notch),
    np.exp(-1j * omega_notch),
])

# 극점: 단위원 내부 (r=r_pole), 영점과 같은 각도
poles = np.array([
    r_pole * np.exp( 1j * omega_notch),
    r_pole * np.exp(-1j * omega_notch),
])

# 극점·영점 → 전달함수 계수로 변환
b, a = signal.zpk2tf(zeros, poles, 1.0)

# 직류(0Hz) 이득을 1로 정규화
gain_dc = np.abs(np.polyval(b, 1) / np.polyval(a, 1))
b = b / gain_dc

print("분자 계수 b:", np.round(b, 6))
print("분모 계수 a:", np.round(a, 6))

# 주파수 응답
w, h = signal.freqz(b, a, worN=4096, fs=fs)
magnitude_db = 20 * np.log10(np.maximum(np.abs(h), 1e-10))

# 극점·영점 플롯 + 주파수 응답
fig, axes = plt.subplots(1, 2, figsize=(13, 5))

# 극점·영점 플롯
ax = axes[0]
theta = np.linspace(0, 2 * np.pi, 300)
ax.plot(np.cos(theta), np.sin(theta), 'k--', linewidth=0.8, label='단위원')
ax.plot(np.real(zeros), np.imag(zeros), 'o', markersize=12,
        color='royalblue', label='영점')
ax.plot(np.real(poles), np.imag(poles), 'x', markersize=14,
        markeredgewidth=2.5, color='crimson', label='극점')
ax.axhline(0, color='gray', linewidth=0.5)
ax.axvline(0, color='gray', linewidth=0.5)
# 노치 주파수 방향 표시
ax.annotate('', xy=(np.cos(omega_notch) * 1.35, np.sin(omega_notch) * 1.35),
            xytext=(0, 0),
            arrowprops=dict(arrowstyle='->', color='green', lw=1.2))
ax.text(np.cos(omega_notch) * 1.38, np.sin(omega_notch) * 1.38,
        f'{f_notch}Hz', color='green', fontsize=9)
ax.set_xlim(-1.6, 1.6)
ax.set_ylim(-1.6, 1.6)
ax.set_aspect('equal')
ax.set_title(f'노치 필터 극점·영점 플롯\n(f_notch={f_notch}Hz, r_pole={r_pole})')
ax.set_xlabel('실수 축')
ax.set_ylabel('허수 축')
ax.legend(fontsize=9)
ax.grid(True, linestyle=':', alpha=0.5)

# 주파수 응답
ax2 = axes[1]
ax2.plot(w, magnitude_db, color='steelblue', linewidth=1.8)
ax2.axvline(f_notch, color='crimson', linestyle='--', linewidth=1,
            label=f'노치 주파수 {f_notch}Hz')
ax2.set_xlim(0, fs / 2)
ax2.set_ylim(-60, 5)
ax2.set_xlabel('주파수 (Hz)')
ax2.set_ylabel('크기 응답 (dB)')
ax2.set_title('노치 필터 주파수 응답')
ax2.legend(fontsize=9)
ax2.grid(True, linestyle=':', alpha=0.5)

plt.tight_layout()
plt.savefig('notch_filter_design.png', dpi=150)
plt.show()

# 안정성 검증
print("\n[안정성 검증]")
for i, p in enumerate(poles):
    print(f"  극점 {i+1}: {p:.4f}, |극점| = {abs(p):.4f} → {'✓ 안정' if abs(p) < 1 else '✗ 불안정'}")

실행 결과로 다음을 확인할 수 있습니다.

분자 계수 b: [ 0.9678   -1.799676  0.9678  ]
분모 계수 a: [ 1.       -1.766575  0.9025  ]

[안정성 검증]
  극점 1: 0.8833+0.3497j, |극점| = 0.9500 → ✓ 안정
  극점 2: 0.8833-0.3497j, |극점| = 0.9500 → ✓ 안정

60Hz에서 크기 응답이 $-\infty$ dB(이론상 완전 차단)에 수렴하고, 나머지 주파수 대역의 응답은 0dB(이득 1)에 가깝게 유지됩니다. 두 극점의 절댓값이 모두 0.9500으로 단위원 내부에 위치하므로 시스템은 안정하며, 영점이 단위원 위에 정확히 놓인 덕분에 60Hz 성분이 완전히 제거됩니다.

 

[Insight]

전달함수 $H(z)$를 다루는 것은 거대한 대리석 덩어리를 깎아 아름다운 조각상을 만드는 과정과 같습니다.

잡음이 섞여 들어오는 주파수 대역이 있다면, 설계자는 그 위치의 단위 원 위에 영점(Zero)이라는 '정'을 박아 넣어 소리를 완벽하게 차단합니다. 반대로 우리가 원하는 깨끗한 목소리나 신호 대역이 있다면, 그 근처의 단위 원 안쪽으로 극점(Pole)이라는 '망치'를 밀어 넣어 응답을 원하는 만큼 솟아오르게 만듭니다. 극점을 단위 원 경계에 가깝게 밀어붙일수록 소리는 더욱 날카롭고 예리해지지만, 선을 넘어 단위 원 밖으로 나가는 순간 시스템은 굉음을 내며 폭발(발산)하고 맙니다.

FIR과 IIR의 선택은 트레이드오프의 문제입니다. FIR은 항상 안정적이고 선형 위상이 보장되지만 많은 계수가 필요합니다. IIR은 적은 차수로 날카로운 특성을 구현하지만 안정성 검증과 위상 비선형성을 감수해야 합니다. 응용에 맞는 선택이 중요합니다.

Z-변환은 단순한 수학 도구가 아닙니다. 필터의 과거·현재·미래를 복소평면 위에 시각화하는 언어입니다. 극점·영점 플롯 하나에서 안정성, 주파수 응답, 위상 특성의 힌트를 모두 읽어낼 수 있다는 점에서, Z-변환은 디지털 필터 설계의 핵심 언어라 할 수 있습니다.

 

다음 글 예고

오늘 우리는 Z-변환을 통해 필터의 뼈대가 되는 전달함수 $H(z)$와 극점/영점의 물리적 의미를 완벽하게 이해했습니다. 이제 이 이론적 도구를 실전에 배치할 차례입니다.

다음 시간에는 [17. FIR 필터 설계: 윈도우 기법을 이용한 설계와 노이즈 제거 실전] 편을 통해, 이번 글에서 배운 극점·영점 이론을 바탕으로 실제 FIR 필터를 체계적으로 설계하는 방법을 다룹니다. Hamming, Hann, Kaiser 등 다양한 윈도우 함수를 활용하여 저역통과, 고역통과, 대역통과 필터를 직접 설계하고, 실제 잡음이 섞인 신호에 적용하여 성능을 검증하는 실전 실습을 함께 진행하겠습니다.

 

📌 이전 연재 글 보기

 

 

반응형