Python으로 배우는 DSP/Part 0. 시작하기: DSP와 Python

[04] Python 신호 처리 생태계: 기초 파라미터 이해와 첫 번째 신호 생성

multimedia 2026. 3. 1. 10:00
반응형

들어가며

실험실 구축을 마쳤다면, 이제 본격적으로 디지털 신호라는 재료를 다뤄볼 시간입니다. 우리가 일상에서 듣는 소리나 보는 영상은 모두 연속적인 아날로그 형태이지만, 컴퓨터가 이를 처리하기 위해서는 일정한 규칙에 따라 '숫자의 나열'로 변환해야 합니다. 이번 글에서는 신호 생성의 가장 기본이 되는 파라미터를 이해하고, Python 코드로 우리의 첫 번째 디지털 신호를 만들어 보겠습니다.

 

1. Python 신호 처리 생태계 

과거 DSP는 C/C++ 또는 MATLAB으로 구현하고 결과를 확인했습니다. 하지만 현재는 Python으로 대체하고 있으며, 신호처리를 위해 주로 사용하게 될 도구는 아래와 같이 3가지로 정리할 수 있습니다.

  • NumPy: 모든 신호를 저장하는 메모리(배열)와 빠른 벡터연산을 지원합니다. "디지털 샘플들의 집합"을 다루는 표준 도구입니다.
  • SciPy (scipy.signal): DSP 알고리즘들이 구현되어 있는 라이브러리입니다. 필터 설계, 컨볼루션, 스펙트럼 분석 등 다양한 DSP 함수들이 구현되어 있습니다.
  • Matplotlib: 각종 신호와 결과를 시각적으로 확인하기 위한 도구로 사용할 것입니다. 파형, 스펙트럼, 응답신호를 시각적으로 도시하는 것을 지원합니다.

즉, "NumPy로 신호를 생성하고, SciPy로 신호를 처리하고, Matplotlib로 시각화해서 검증한다"라고 생각하면 됩니다.

 

2. 디지털 신호를 결정짓는 6개 파라미터

본격적인 실험에 앞서 디지털 신호를 구성하기 위해 자주 등장하는 6개 파라미터를 살펴보겠습니다.

  • 샘플링 주파수 (Sampling Rate, $f_s$): 아날로그 신호를 1초 동안 몇 개의 숫자로 쪼개어 저장할 것인가를 의미합니다. 오디오 CD 규격인 44.1kHz는 1초를 44,100개로 나누었다는 뜻입니다. 샘플링 주파수가 높을수록 원본 신호를 더 정밀하게 재현할 수 있습니다. 마치 영화의 프레임 레이트(fps)가 높을수록 부드러운 영상을 얻는 것과 같습니다.
    • 시간축의 해상도($\Delta t = 1/f_s$)를 의미합니다.
  • 지속 시간 (Duration): 관측시간을 나타내며, 관측시간 동안 샘플의 수($N$)는 다음과 같습니다.
    • $N = f_s \times Duration$의 관계로 설명됩니다.
  • 시간축 배열 (Time Array, $t$): 신호는 시간에 따라 변하는 값이므로 각 샘플이 어느 시점에 해당하는지 시간축이 필요합니다. 
    • 샘플이 찍힌 시간 좌표를 의미합니다.
    • $t$의 정의가 틀어지면 모든 결과가 틀어집니다.
  • 진폭 (Amplitude, $A$): 신호의 세기나 크기를 나타냅니다. 오디오에서는 볼륨, 영상에서는 밝기에 해당합니다.
  • 주파수 (Frequency, $f$): 초당 진동 횟수(Hz)입니다. 신호가 얼마나 빠르게 변하는지를 결정합니다. 라디오의 주파수나 소리의 톤에 해당합니다.
  • 위상 (Phase, $\phi$): 파형이 어디에서 시작하는 가를 나타냅니다. 파형의 시간 이동(Time Shift)을 표현합니다.

사인파의 표준형태는 아래 수식과 같습니다.
                                    $x(t) = A\sin(2{\pi}ft+\phi)$
실험에서는 연속 시간 $t$가 아닌 샘플링된 시간축 $t[n]$을 사용하므로, 아래 수식으로 표현합니다.
                                    $x[n] = A\sin(2{\pi}ft[n]+\phi)$
여기서
                                    $t[n]=\frac{n}{f_s}$
입니다.

 

3. 실습 코드: 정현파(Sine Wave) 생성하기

아래 코드는 10Hz의 사인파를 1000Hz로 샘플링한 파형을 생성하고 시각화하는 Python 예제입니다.

import numpy as np
import matplotlib.pyplot as plt

# 파라미터 설정
fs = 1000.0               # 샘플링 주파수 (1000 Hz)
duration = 1.0            # 관찰 시간 (1 sec)
N = int(fs * duration)    # 총 샘플 수 
f = 10.0                    # 신호 주파수 (10 Hz)
phi = 0.0                 # 위상 (rad)
A = 1.0                   # 진폭   

# 1. 시간축 생성
t = np.linspace(0, duration, N, endpoint=False)

# 2. 신호 생성 
sin_wave = A * np.sin(2 * np.pi * f * t + phi)

# 3. 시각화 (처음 200개만 stem으로)
plt.figure(figsize=(10, 4))
plt.stem(t[:200], sin_wave[:200], basefmt=' ')
plt.title(f'Digital Sine Wave: {f}Hz (fs={fs}Hz) - First 200 Samples')
plt.xlabel('Time [s]')
plt.ylabel('Amplitude')
plt.grid(True)
plt.tight_layout()
plt.show()

# 4. 결과 확인
print(f"생성된 샘플 수: {len(sin_wave)}")
print(f"신호 범위: [{np.min(sin_wave):.3f}, {np.max(sin_wave):.3f}]")

여기서 우리는  np.arange를 사용하는 아래 코드를 사용하지 않았습니다.

t = np.arange(0, duration, 1/fs)

 np.arange는 부동소수점 오차로 인해 끝점이 포함 될 수도, 안 될 수도 있어 샘플 수가 정확하지 않을 수 있습니다. 반면 위의 실습 코드처럼 np.linspace사용한다면 샘플의 개수를 정확히 제어할 수 있습니다.

아래 그림은 위의 코드로 시각화된 10Hz 사인파입니다. 처음 200개 샘플(0.2초 구간)에서 정확히 2번의 완전한 주기가 나타나는 것을 확인할 수 있으며, 이는 1초 동안 10번의 주기가 생성됨을 의미합니다. 

 

[Insight] 

방금 생성한 sin_wave 변수는 단순한 NumPy 배열에 불과합니다. 하지만 여기에 '시간'이라는 개념인 샘플링 주파수($f_s$)가 부여되는 순간, 이 숫자의 나열은 물리적인 의미를 갖는 신호(Signal)가 됩니다. 개발 환경 구축만큼이나, DSP 실험에서 실제로 시간을 잡아 먹는 것은 파라미터의 설정입니다.

  • 샘플링 주파수를 너무 낮게 잡으면 앨리어싱이 발생하고
  • Duration을 너무 짧게 잡으면 스펙트럼이 흐려지고
  • 시간축 배열의 정의가 애매하면 주파수, 위상, 필터 결과가 전부 오류일 수 있습니다.

실제 개발에서 신호 처리가 어려운 이유는 알고리즘 그 자체보다, 현실 세계의 아날로그 데이터를 컴퓨터의 제한된 자원(비트 깊이, 샘플링 제한) 속으로 가져올 때 발생하는 왜곡을 얼마나 잘 통제하느냐에 달려 있습니다. 실무 환경에서 신호 처리는 단순히 수식을 코드로 옮기는 과정이 아닙니다. Python 라이브러리 생태계의 의존성과 호환성을 얼마나 잘 관리하느냐의 싸움이기도 합니다. 환경 관리가 시스템 안정성의 시작이라면, 생태계에 대한 이해는 효율적인 파이프라인 구축의 시작이고, 정확한 파라미터 설정은 데이터 신뢰성의 시작입니다.

 

다음 글 예고

기본적인 사인파를 그려보았으니, 이제 더 복잡한 형태의 신호들을 다뤄볼 차례입니다. 다음 글 [05. 디지털 신호의 기초: 기본 파형 생성 및 시각화]에서는 다양한 기본 파형들을 Python으로 구현하고, 이들이 합쳐졌을 때 어떤 특성을 갖게 되는지 살펴보겠습니다.

 

📌 이전 연재 글 보기

 

반응형