음성처리

[음성 처리]음성신호 전처리, Librosa 활용 및 프로젝트 데이터

Uno_says 2025. 7. 12. 15:45
728x90
728x90

 

 

 

1. 음성 신호 전처리

 

📌 Pre-emphasis

  • 고주파 성분을 강조하는 1차 FIR 필터이다.
  • 일반적으로 음성처리에서 음성의 고주파 성분이 약해지기 때문에 이를 보정하기 위해 사용한다.

 

※ 백색잡음에 pre-emphasis 필터를 적용했을 때의 스펙트럼 변화 시각화

import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq

# 1. 파라미터 설정
fs = 16000            # 샘플링 주파수 (Hz)
duration = 1.0        # 신호 길이 (초)
N = int(fs * duration)

# 2. 랜덤 노이즈 생성 (백색잡음)
noise = np.random.randn(N)

# 3. pre-emphasis 필터 적용(고주파 성분을 강조) 
alpha = 0.97
noise_pre = np.append(noise[0], noise[1:] - alpha * noise[:-1])

# 4. DFT 계산
NFFT = 2048
freqs = fftfreq(NFFT, 1/fs)[:NFFT//2]

spec = np.abs(fft(noise, NFFT))[:NFFT//2]
spec_pre = np.abs(fft(noise_pre, NFFT))[:NFFT//2]

# 5. dB 스케일로 변환
spec_db     = 20 * np.log10(spec     + 1e-8)
spec_pre_db = 20 * np.log10(spec_pre + 1e-8)

# 6. 스펙트럼 시각화
plt.figure()
plt.plot(freqs, spec_db,     label='원본 노이즈')
plt.plot(freqs, spec_pre_db, label='프리엠파시스 적용')
plt.title('프리엠파시스 필터 효과 (스펙트럼 비교)')
plt.xlabel('주파수 (Hz)')
plt.ylabel('크기 (dB)')
plt.legend()
plt.tight_layout()
plt.show()

저주파 성분은 감소하고, 고주파 성분은 강화됨

 

 

 

📌 Hamming window

  • 각 프레임 양 끝을 부드럽게 0으로 만드는 윈도우
  • 이를 통해 프레임 간 겹침 시 생길 수 있는 불연속성을 완

 

※ 1kHz 정현파에 pre-emphasis, framing, hamming window, overlap-add을 적용하여 음성 처리에서의 시간 도메인 프레임 합성을 비교

import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
from scipy.io.wavfile import write
from IPython.display import Audio, display

# 1. 파라미터 설정
fs = 8000               # 샘플링 주파수 (Hz)
f0 = 1000               # 톤 주파수 (Hz)
duration = 0.1          # 신호 길이 (초)
t = np.linspace(0, duration, int(fs*duration), endpoint=False)

# 2. 1 kHz 정현파 생성
x = np.sin(2 * np.pi * f0 * t)

# 3. 프리엠파시스
pre_emphasis = 0.97
x_pre = np.append(x[0], x[1:] - pre_emphasis * x[:-1])   

# 4. 프레이밍 및 호핑
frame_len = 200         # 프레임 길이 (샘플, 약 25 ms)
hop_len   = 100         # 호핑 길이 (샘플, 약 12.5 ms)

# 두 개의 연속된 프레임 추출
frame0 = x_pre[:frame_len]
frame1 = x_pre[hop_len:hop_len+frame_len]

# 5. 해밍 윈도우 적용(프레임의 양 끝이 부드럽게 0에 수렴) -> 프레임 간 겹침 시 생길 수 있는 불연속성을 완화 
win = np.hamming(frame_len)
frame0_win = frame0 * win
frame1_win = frame1 * win

# 6. 겹치는 구간 추출(두 프레임에서 겹치는 100샘플 추출)
reg0_no  = frame0[-hop_len:]       # 윈도우 전, 프레임0 끝부분
reg1_no  = frame1[:hop_len]        # 윈도우 전, 프레임1 시작부분
reg0_win = frame0_win[-hop_len:]   # 윈도우 후, 프레임0 끝부분
reg1_win = frame1_win[:hop_len]    # 윈도우 후, 프레임1 시작부분

# 7. 겹침 합산
overlap_sum_no  = reg0_no  + reg1_no     # 윈도우 없을 때는 불연속 발생
overlap_sum_win = reg0_win + reg1_win    # 윈도우 적용 후에는 자연스럽게 이어짐

# 8. 시간축 (ms)
time_hop = np.arange(hop_len) / fs * 1000

# 9. 시각화
plt.figure(figsize=(10, 6))

plt.subplot(2,1,1)
plt.plot(time_hop, reg0_no,  label='Frame0 end (no window)')
plt.plot(time_hop, reg1_no,  label='Frame1 start (no window)')
plt.plot(time_hop, overlap_sum_no, '--', label='Sum (no window)')
plt.title('윈도우 없이 중첩 시 불연속점')
plt.xlabel('시간 (ms)')
plt.ylabel('진폭')
plt.legend()

plt.subplot(2,1,2)
plt.plot(time_hop, reg0_win,  label='Frame0 end (Hamming)')
plt.plot(time_hop, reg1_win,  label='Frame1 start (Hamming)')
plt.plot(time_hop, overlap_sum_win, '--', label='Sum (Hamming)')
plt.title('해밍 윈도우 적용 후 중첩 시 왜곡 감소')
plt.xlabel('시간 (ms)')
plt.ylabel('진폭')
plt.legend()

plt.tight_layout()
plt.show()

# 10. 재생 및 저장
display(Audio(x, rate=fs))
write('1kHz_tone_hopping_demo.wav', fs, (x * 32767).astype(np.int16))
print('파일로 저장됨: 1kHz_tone_hopping_demo.wav')

 

 

 

※ 주파수를 1kHz -> 500Hz로 낮춘 정현파에 대해 pre-emphasis, framing, hamming window, overlap-add 적용

import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
from scipy.io.wavfile import write
from IPython.display import Audio, display

# 1. 파라미터 설정
fs = 8000               # 샘플링 주파수 (Hz)
f0 = 500               # 톤 주파수 (Hz)
duration = 0.1          # 신호 길이 (초)
t = np.linspace(0, duration, int(fs*duration), endpoint=False)

# 2. 1 kHz 정현파 생성
x = np.sin(2 * np.pi * f0 * t)

# 3. pre-emphasis(고주파 성분 강조)
pre_emphasis = 0.97
x_pre = np.append(x[0], x[1:] - pre_emphasis * x[:-1])

# 4. 프레이밍 및 호핑
frame_len = 200         # 프레임 길이 (샘플, 약 25 ms)
hop_len   = 100         # 호핑 길이(겹치는 구간)(샘플, 약 12.5 ms)

# 두 개의 연속된 프레임 추출
frame0 = x_pre[:frame_len]
frame1 = x_pre[hop_len:hop_len+frame_len]

# 5. 해밍 윈도우 적용(프레임의 양 끝이 부드럽게 0에 수렴)
win = np.hamming(frame_len)
frame0_win = frame0 * win
frame1_win = frame1 * win

# 6. 겹치는 구간 추출
reg0_no  = frame0[-hop_len:]       # 윈도우 전, 프레임0 끝부분
reg1_no  = frame1[:hop_len]        # 윈도우 전, 프레임1 시작부분
reg0_win = frame0_win[-hop_len:]   # 윈도우 후, 프레임0 끝부분
reg1_win = frame1_win[:hop_len]    # 윈도우 후, 프레임1 시작부분

# 7. 겹침 합산
overlap_sum_no  = reg0_no  + reg1_no
overlap_sum_win = reg0_win + reg1_win

# 8. 시간축 (ms)
time_hop = np.arange(hop_len) / fs * 1000

# 9. 시각화
plt.figure(figsize=(10, 6))

plt.subplot(2,1,1)
plt.plot(time_hop, reg0_no,  label='Frame0 end (no window)')
plt.plot(time_hop, reg1_no,  label='Frame1 start (no window)')
plt.plot(time_hop, overlap_sum_no, '--', label='Sum (no window)')
plt.title('윈도우 없이 중첩 시 불연속점')
plt.xlabel('시간 (ms)')
plt.ylabel('진폭')
plt.legend()

plt.subplot(2,1,2)
plt.plot(time_hop, reg0_win,  label='Frame0 end (Hamming)')
plt.plot(time_hop, reg1_win,  label='Frame1 start (Hamming)')
plt.plot(time_hop, overlap_sum_win, '--', label='Sum (Hamming)')
plt.title('해밍 윈도우 적용 후 중첩 시 왜곡 감소')
plt.xlabel('시간 (ms)')
plt.ylabel('진폭')
plt.legend()

plt.tight_layout()
plt.show()

# 10. 재생 및 저장
display(Audio(x, rate=fs))
write('0.5kHz_tone_hopping_demo.wav', fs, (x * 32767).astype(np.int16))
print('파일로 저장됨: 0.5kHz_tone_hopping_demo.wav')

 

 

 

✔️ 1kHz VS 500Hz 비교

  • 주파수가 높을수록(1kHz) 프레임 간 위상 충돌이 더 심해지고, 오버랩 합산 시 불연속점이 뚜렷하게 나타난다.
  • 주파수가 낮을수록(500Hz) 프레임 간 변화가 적고, 해밍 윈도우 적용 시 거의 자연스러운 이어짐을 얻을 수 있다.

 

 

 

📌 Hopping에 의한 Overlap 왜곡

  • hopping은 다음 프레임을 어디서 시작할지를 나타내는 간격이다.

 

※ 1kHz 정현파 신호를 50ms 길이로 생성한 후, framing과 1/2 hopping(128샘플 겹침)을 적용한 뒤, 그래프를 시각화하고 MSE로 수치화하기

import numpy as np
import matplotlib.pyplot as plt

# 1. 파라미터 설정
fs       = 8000               # 샘플링 주파수 (Hz)
f0       = 1000               # 톤 주파수 (Hz)
duration = 0.05               # 신호 길이 (초)
t        = np.linspace(0, duration, int(fs*duration), endpoint=False)
x        = np.sin(2 * np.pi * f0 * t)

# 2. 프레임 & 호핑 설정 (1/2 호핑)
frame_len = 256              # 프레임 길이 (샘플)
hop_len   = frame_len //2   # 1/2 호핑 = 128 샘플 겹침

# 3. 두 프레임 추출
frame0 = x[:frame_len]
frame1 = x[hop_len:hop_len+frame_len]

# 4. 겹침 영역 계산 (원본)
orig_region    = x[hop_len:frame_len]  # 길이 = frame_len - hop_len

# 5. 윈도우 없이 중첩
overlap_no = frame0[hop_len:] + frame1[:frame_len-hop_len]

# 6. 해밍 윈도우 적용 후 중첩
win        = np.hamming(frame_len)
f0_win     = frame0 * win
f1_win     = frame1 * win
overlap_win = f0_win[hop_len:] + f1_win[:frame_len-hop_len]

# 7. 시간축 (ms)
time_ms = np.arange(len(orig_region)) / fs * 1000

# 8. 시각화
plt.figure(figsize=(8, 4))
plt.plot(time_ms, orig_region,      label='원본 신호 구간')
plt.plot(time_ms, overlap_no, '--', label='윈도우 없이 중첩')
plt.plot(time_ms, overlap_win, '-.', label='해밍 윈도우 적용 후')
plt.title('1/2 호핑 시 중첩 왜곡 비교')
plt.xlabel('시간 (ms)')
plt.ylabel('진폭')
plt.legend()
plt.tight_layout()
plt.show()

# 9. 왜곡 정도 계산 (MSE)
mse_no  = np.mean((overlap_no  - orig_region)**2)
mse_win = np.mean((overlap_win - orig_region)**2)
print(f'MSE without window: {mse_no:.6f}')
print(f'MSE with Hamming:  {mse_win:.6f}')

 

  • 윈도우 미적용 시 진폭이 크게 틀어지고 해밍 윈도우를 적용 시 원본과 매우 비슷하게 복원된다.

 

 

※ hopping 간격을 1/2 -> 1/3으로 줄였을 때

# 2. 프레임 & 호핑 설정 (1/3 호핑)
frame_len = 256              # 프레임 길이 (샘플)
hop_len   = frame_len //3   # 1/3 호핑 = 85

 

 

 

hopping 간격을 1/5으로 줄였을 때

# 2. 프레임 & 호핑 설정 (1/5 호핑)
frame_len = 256              # 프레임 길이 (샘플)
hop_len   = frame_len //5   # 1/5 호핑 = 205 샘플 겹침

 

=> 1/2 hopping이 가장 최적의 값

 

 

✔️ hopping 간격을 줄여가며 중첩 구간을 늘린 그래프 비교

  • 윈도우 없이 중첩
    • 오버랩 비율이 높을수록 윈도우 없이 중첩 시 왜곡이 더 심해짐
  • 해밍 윈도우 적용 후
    • 오버랩 비율이 높아질수록 오히려 부드러운 이어짐의 효과가 더 커짐  
    • 윈도우가 있을 경우 오버랩 비율을 높여도 왜곡 없이 부드럽게 연결 가능 

 

 

 

📌 프레임 윈도우 효과

※ 44100Hz로 샘플링된 100Hz 사인파 신호를 생성하고, pre-emphasis -> framing -> windowing -> DFT(푸리에 변환) 처리 과정

import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
from scipy.io.wavfile import write
from IPython.display import Audio, display

# 1. 파라미터 설정
fs = 44100           # 샘플링 주파수 (Hz)
f0 = 100           # 톤 주파수 (Hz)
duration = 1.0       # 신호 길이 (초)
t = np.linspace(0, duration, int(fs*duration), endpoint=False)

# 2. 10 kHz 정현파 생성
x = np.sin(2 * np.pi * f0 * t)

# 3. 프리엠파시스 (차분 고역 강조 → 음성 특징 추출 전 필수 전처리)
pre_emphasis = 0.97
x_pre = np.append(x[0], x[1:] - pre_emphasis * x[:-1])

# 4. 프레이밍 (25 ms 프레임, 10 ms 스텝)
frame_size = 0.025   # 초    1102샘플
frame_step = 0.01    # 초    441샘플
frame_len = int(frame_size * fs)
step_len  = int(frame_step * fs)

num_frames = int(np.ceil((len(x_pre) - frame_len) / step_len)) + 1
pad_len = (num_frames - 1) * step_len + frame_len
x_padded = np.append(x_pre, np.zeros(pad_len - len(x_pre)))

indices = (np.tile(np.arange(frame_len), (num_frames, 1)) +
           np.tile(np.arange(0, num_frames*step_len, step_len), (frame_len, 1)).T)
frames = x_padded[indices.astype(int)]

# 5. 해밍 윈도우 적용 (NumPy) -> 윈도우를 곱해 경계 불연속 완화
win = np.hamming(frame_len)
frames_win = frames * win

# 6. DFT (첫 번째 프레임에 대해)
NFFT = 512
spec = fft(frames_win[0], n=NFFT)
mag = np.abs(spec)[:NFFT//2]
freqs = fftfreq(NFFT, 1/fs)[:NFFT//2]

# 7. 시각화
plt.figure(figsize=(10, 9))

# 원 신호 (잡음 없는 순수 사인파)
plt.subplot(4,1,1)
plt.plot(t, x)
plt.title('원신호 (10 kHz sine)')
plt.xlabel('시간 [s]')
plt.ylabel('진폭')

# 프리엠파시스 신호 (처음 5 ms) -> 신호 변화가 더 뾰족하고 급해짐 (고주파 강조)
plt.subplot(4,1,2)
plt.plot(t[:int(0.005*fs)], x_pre[:int(0.005*fs)])
plt.title('프리엠파시스 적용 신호 (처음 5 ms)')
plt.xlabel('시간 [s]')
plt.ylabel('진폭')

# 첫 프레임 (윈도우 전/후)
# 파란색: 윈도우 적용 전 → 프레임의 양 끝이 잘려 있음
# 주황색: 해밍 윈도우 적용 후 → 양 끝이 자연스럽게 0으로 수렴
frame_t = np.arange(frame_len) / fs
plt.subplot(4,1,3)
plt.plot(frame_t, frames[0], label='원프레임')
plt.plot(frame_t, frames_win[0], label='해밍 윈도우 적용 후')
plt.title('첫 번째 프레임 (25 ms)')
plt.xlabel('시간 [s]')
plt.ylabel('진폭')
plt.legend()

# 스펙트럼
# 스펙트럼 상에서 100Hz 근처에서 피크가 나타나야 함 → 입력 신호가 100Hz 사인파였기 때문
plt.subplot(4,1,4)
plt.plot(freqs, mag)
plt.title('첫 번째 프레임 DFT (magnitude)')
plt.xlabel('주파수 [Hz]')
plt.ylabel('크기')

plt.tight_layout()
plt.show()

# 8. 재생 및 저장
display(Audio(x, rate=fs))            # 노트북에서 바로 듣기
write('10kHz_tone.wav', fs,           # 16-bit PCM WAV 파일로 저장
      (x * 32767).astype(np.int16))
print('10kHz_tone.wav 파일로 저장되었습니다.')

 

 

 

 

 


 

 

 

2. Librosa 활용

 

📌 Librosa

  • 오디오 분석을 위한 파이썬 라이브러리
  • Numpy, SciPy, scikit-learn, soundfile 등과 통합
  • 음성 인식(STT,ASR), 화자 식별/감정 인식, 음악 분석, 환경 소리 분류, 딥러닝 입력 피처 생성 등에 활용
# =============================
# FSDD 음원 다운로드 및 준비
# =============================
# FSDD(Free Spoken Digit Dataset) GitHub에서 클론
!git clone https://github.com/Jakobovski/free-spoken-digit-dataset.git

# recordings 폴더만 이동
!mv free-spoken-digit-dataset/recordings recordings

# =============================
# 라이브러리 로드
# =============================
import librosa                   # 오디오 처리 라이브러리
import librosa.display           # 시각화 유틸리티
import soundfile as sf          # 오디오 저장
import numpy as np              # 수치 계산
import matplotlib.pyplot as plt # 시각화

# =============================
# 0. FSDD 음원 로드
# =============================
audio_path = 'recordings/0_jackson_0.wav'  # 0번 숫자를 발음한 파일
y, sr = librosa.load(audio_path, sr=16000) # 16kHz로 리샘플링하여 로딩

# =============================
# 2-1. 오디오 입출력
# =============================
print("샘플레이트:", sr)                           # 샘플레이트 출력(초당 샘플 수)
print("신호 길이:", len(y))                        # 총 샘플 수 출력
print("오디오 길이(초):", librosa.get_duration(y=y, sr=sr))  # 총 재생 시간(초)

sf.write("fsdd_output.wav", y, sr)                # 오디오를 파일로 저장

# =============================
# 2-2. 신호 처리 (STFT(시간에 따라 주파수 스펙트럼을 계산) 및 복원):
# =============================
n_fft = 1024                      # 프레임당 FFT 크기
hop_length = 256                 # 프레임 간의 이동 간격(홉 크기)
D = librosa.stft(y, n_fft=n_fft, hop_length=hop_length)  # STFT 계산
magnitude = np.abs(D)            # 복소수 STFT에서 진폭만 추출
y_reconstructed = librosa.istft(D, hop_length=hop_length) # 역변환 수행하여 waveform 복원

# STFT 결과 시각화(시간-주파수 영역에서 진폭의 변화를 시각적으로 표현)
plt.figure(figsize=(10, 4))
librosa.display.specshow(librosa.amplitude_to_db(magnitude, ref=np.max),
                         sr=sr, hop_length=hop_length, x_axis='time', y_axis='linear')
plt.title("STFT Magnitude")      # 제목
plt.colorbar(format="%+2.0f dB") # 데시벨 범례
plt.tight_layout()
plt.show()

# =============================
# 2-3. 특징 추출 (MFCC, ZCR, Centroid)
# =============================
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)        # MFCC(멜 주파수 켑스트럼 계수) 13차원 추출
zcr = librosa.feature.zero_crossing_rate(y)               # 제로 크로싱 비율(0을 기준으로 신호가 몇 번 바뀌는지)
centroid = librosa.feature.spectral_centroid(y=y, sr=sr)  # 스펙트럼 중심 -> 음색 높낮이 느낌

# 특징 시각화
plt.figure(figsize=(10, 6))
plt.subplot(3, 1, 1)
librosa.display.specshow(mfcc, x_axis='time')     # MFCC 시각화
plt.title('MFCC')
plt.colorbar()

plt.subplot(3, 1, 2)
plt.plot(zcr[0])                                  # ZCR 시각화
plt.title('Zero Crossing Rate')

plt.subplot(3, 1, 3)
plt.plot(centroid[0])                             # 스펙트럼 중심 시각화
plt.title('Spectral Centroid')
plt.tight_layout()
plt.show()

# =============================
# 2-4. 단위 변환 (Power ↔ dB)
# =============================
mel = librosa.feature.melspectrogram(y=y, sr=sr, n_fft=n_fft,
                                     hop_length=hop_length, n_mels=80)  # 멜 스펙트로그램 계산
mel_db = librosa.power_to_db(mel, ref=np.max)                           # dB 스케일로 변환

# 멜 스펙트로그램 시각화
plt.figure(figsize=(10, 4))
librosa.display.specshow(mel_db, sr=sr, hop_length=hop_length, x_axis='time', y_axis='mel')    # 멜 주파수 축 사용 
plt.colorbar(format='%+2.0f dB')
plt.title('Mel-Spectrogram (dB)')
plt.tight_layout()
plt.show()

# =============================
# 2-5. 시간-프레임 변환
# =============================
frame_idx = librosa.time_to_frames([0.5, 1.0], sr=sr, hop_length=hop_length)  # 0.5초, 1초 → 프레임 인덱스
print("0.5초, 1.0초에 해당하는 프레임:", frame_idx)

times = librosa.frames_to_time(frame_idx, sr=sr, hop_length=hop_length)      # 프레임 인덱스 → 시간
print("프레임 → 시간:", times)

# =============================
# 2-6. 시각화 (Waveform)
# =============================
plt.figure(figsize=(10, 3))
librosa.display.waveshow(y, sr=sr)  # 파형 시각화
plt.title("Waveform")
plt.tight_layout()
plt.show()

# =============================
# 2-7. 유틸리티 함수 (정규화, 고정 길이, 프레이밍)
# =============================
from librosa.util import normalize, fix_length, frame

y_norm = normalize(y)                             # 신호를 정규화하여 전체 에너지를 1로 맞춤
y_fixed = fix_length(y_norm, size=sr * 2)         # 2초 길이로 고정
frames = frame(y_fixed, frame_length=400, hop_length=160)  # 프레임 단위로 신호 분할 
print("프레임 수:", frames.shape[1])             # 프레임 개수 출력

 

 

 

 

💡 문제

1. 오디오 앞부분 1초 자르고 새로운 파일 'new_output.wav'로 저장

# 1초 구간 자르기
y_trimmed = y[:sr]  # sr은 1초에 해당하는 샘플 수

# 새로운 파일로 저장
sf.write("new_output.wav", y_trimmed, sr)

 

 

2. MFCC 평균값 계산

  • librosa.feature.mfcc()를 사용해 각 차원별 평균값을 구하고 (13,) 형태의 벡터로 출력
import numpy as np

# 각 MFCC 차원별 평균값 계산,
mfcc_mean = np.mean(mfcc, axis=1)   # axis=1(시간축을 따라 평)

# 결과 출력
print("MFCC 평균 벡터 (shape: (13,)):")
print(mfcc_mean)

 

 

3. ZCR이 높은 프레임 수 구하기

  • ZCR이 0.1이상인 프레임의 개수 출력
# ZCR이 0.1 이상인 프레임 개수 계산
zcr_count = np.sum(zcr[0] >= 0.1)

# 결과 출력
print("ZCR이 0.1 이상인 프레임 수:", zcr_count)

 

 

4. 멜 스펙트로그램 평균 벡터 만들기

  • 시간 축으로 평균 내어 80차원의 벡터 생성
# 시간 축(axis=1)을 따라 평균
mel_mean = np.mean(mel_db, axis=1)

# 결과 출력
print("Mel-spectrogram 평균 벡터 (shape: (80,)):")
print(mel_mean)

 

 

5. 0.5초와 1.0초에 해당하는 샘플 인덱스 구하기

# 시간(초) × 샘플레이트 = 샘플 인덱스
sample_idx_0_5 = int(0.5 * sr)
sample_idx_1_0 = int(1.0 * sr)

# 결과 출력
print("0.5초에 해당하는 샘플 인덱스:", sample_idx_0_5)
print("1.0초에 해당하는 샘플 인덱스:", sample_idx_1_0)

 

 

 

 

 

📌 librosa로 Mel-spectrogram 계산

  • 정현파이기 때문에 해당 주파수에서 연속적인 높은 에너지(밝은색)가 생성됨
  • 밝은 수평 띠: 특정 Mel 주파수에 지속적인 에너지 존재
  • 파형 로드 -> pre-emphasis -> STFT 내부 호출 -> Mel 필터뱅크 적용 -> 로그/정규화 -> 시각화

 

주파수 = 4kHz 인 정현파 신호에 대해 pre-emphasis, framing, hamming window, overlap-add 를 시연하고 시각화

import numpy as np
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq
from scipy.io.wavfile import write
from IPython.display import Audio, display

# 1. 파라미터 설정
fs = 16000               # 샘플링 주파수 (Hz)
f0 = 4000               # 톤 주파수 (Hz)
duration = 0.1          # 신호 길이 (초)
t = np.linspace(0, duration, int(fs*duration), endpoint=False)

# 2. 1 kHz 정현파 생성(4000Hz의 정현파를 0.1초 동안 생성)
x = np.sin(2 * np.pi * f0 * t)

# 3. 프리엠파시스(고주파 영역 강조를 위한 1차 FIR 필터 적용)
pre_emphasis = 0.97
x_pre = np.append(x[0], x[1:] - pre_emphasis * x[:-1])

# 4. 프레이밍 및 호핑
frame_len = 200         # 프레임 길이 (샘플, 약 25 ms)
hop_len   = 100         # 호핑 길이 (샘플, 약 12.5 ms)

# 두 개의 연속된 프레임 추출(50% overlap)
frame0 = x_pre[:frame_len]
frame1 = x_pre[hop_len:hop_len+frame_len]

# 5. 해밍 윈도우 적용(프레임 경계에서 불연속점을 줄이기 위해)
win = np.hamming(frame_len)
frame0_win = frame0 * win
frame1_win = frame1 * win

# 6. 겹치는 구간 추출(100샘플)
reg0_no  = frame0[-hop_len:]       # 윈도우 전, 프레임0 끝부분
reg1_no  = frame1[:hop_len]        # 윈도우 전, 프레임1 시작부분
reg0_win = frame0_win[-hop_len:]   # 윈도우 후, 프레임0 끝부분
reg1_win = frame1_win[:hop_len]    # 윈도우 후, 프레임1 시작부분

# 7. 겹침 합산
overlap_sum_no  = reg0_no  + reg1_no
overlap_sum_win = reg0_win + reg1_win

# 8. 시간축 (ms)
time_hop = np.arange(hop_len) / fs * 1000

# 9. 시각화
plt.figure(figsize=(10, 6))

plt.subplot(2,1,1)
plt.plot(time_hop, reg0_no,  label='Frame0 end (no window)')
plt.plot(time_hop, reg1_no,  label='Frame1 start (no window)')
plt.plot(time_hop, overlap_sum_no, '--', label='Sum (no window)')
plt.title('윈도우 없이 중첩 시 불연속점')
plt.xlabel('시간 (ms)')
plt.ylabel('진폭')
plt.legend()

plt.subplot(2,1,2)
plt.plot(time_hop, reg0_win,  label='Frame0 end (Hamming)')
plt.plot(time_hop, reg1_win,  label='Frame1 start (Hamming)')
plt.plot(time_hop, overlap_sum_win, '--', label='Sum (Hamming)')
plt.title('해밍 윈도우 적용 후 중첩 시 왜곡 감소')
plt.xlabel('시간 (ms)')
plt.ylabel('진폭')
plt.legend()

plt.tight_layout()
plt.show()

# 10. 재생 및 저장
display(Audio(x, rate=fs))
write('4kHz_tone_hopping_demo.wav', fs, (x * 32767).astype(np.int16))
print('파일로 저장됨: 4kHz_tone_hopping_demo.wav')

해밍 윈도우를 쓰지 않으면 두 프레임 합산 시 급격한 파형 변화 발생

import librosa, librosa.display
import numpy as np
import matplotlib.pyplot as plt

# 1) 음원 로드 (예: 16 kHz, 모노로 유지)
wav_path = "/content/4kHz_tone_hopping_demo.wav"
y, sr = librosa.load(wav_path, sr=16000)

# 2) 전처리: pre-emphasis + 정규화
y = np.append(y[0], y[1:] - 0.97 * y[:-1])
y = y / np.abs(y).max()

# 3) Mel-Spectrogram 파라미터
n_fft      = 1024          # 64 ms
hop_length = 160           # 10 ms
n_mels     = 80            # Mel 필터 수

# 4) Mel-Spectrogram
mel = librosa.feature.melspectrogram(
    y=y, sr=sr,
    n_fft=n_fft,
    hop_length=hop_length,
    n_mels=n_mels,
    fmin=0.0, fmax=sr/2,
    power=2.0               # |X|²
)

# 5) dB 변환과 표준화
mel_db  = librosa.power_to_db(mel, ref=np.max)
mel_std = (mel_db - mel_db.mean()) / mel_db.std()

# 6) 시각화 (해당 주파수에 지속적으로 에너지가 집중되어 있음)
plt.figure(figsize=(9, 3))
librosa.display.specshow(
    mel_std,
    sr=sr, hop_length=hop_length,
    x_axis="time", y_axis="mel",
    cmap="viridis"
)
plt.title("Normalized Mel-Spectrogram (80 bins)")
plt.tight_layout(); plt.show()

4kHz에서 에너지가 강하게 집중된 형태

 

 

 

주파수 = 2kHz

# 1. 파라미터 설정
fs = 16000               # 샘플링 주파수 (Hz)
f0 = 2000               # 톤 주파수 (Hz)
duration = 0.1          # 신호 길이 (초)
t = np.linspace(0, duration, int(fs*duration), endpoint=False)

해밍 윈도우의 효과가 고주파일수록 더 두드러짐

 

import librosa, librosa.display
import numpy as np
import matplotlib.pyplot as plt

# 1) 음원 로드 (예: 16 kHz, 모노로 유지)
wav_path = "/content/2kHz_tone_hopping_demo.wav"
y, sr = librosa.load(wav_path, sr=16000)

# 2) 전처리: 프리-엠퍼시스(선택) + 정규화
y = np.append(y[0], y[1:] - 0.97 * y[:-1])
y = y / np.abs(y).max()

# 3) Mel-Spectrogram 파라미터
n_fft      = 1024          # 64 ms
hop_length = 160           # 10 ms
n_mels     = 80            # Mel 필터 수

# 4) Mel-Spectrogram
mel = librosa.feature.melspectrogram(
    y=y, sr=sr,
    n_fft=n_fft,
    hop_length=hop_length,
    n_mels=n_mels,
    fmin=0.0, fmax=sr/2,
    power=2.0               # |X|²
)

# 5) dB 변환과 표준화
mel_db  = librosa.power_to_db(mel, ref=np.max)
mel_std = (mel_db - mel_db.mean()) / mel_db.std()

# 6) 시각화 (해당 주파수에 지속적으로 에너지가 집중되어 있음)
plt.figure(figsize=(9, 3))
librosa.display.specshow(
    mel_std,
    sr=sr, hop_length=hop_length,
    x_axis="time", y_axis="mel",
    cmap="viridis"
)
plt.title("Normalized Mel-Spectrogram (80 bins)")
plt.tight_layout(); plt.show()

 

2kHz에서 에너지가 강하게 집중된 형태

 

 

 

 

📌 STFT 결과를 직접 활용하여 Mel 필터 적용하기

※ "음성인식" 녹음

!pip install -q soundfile

from google.colab import output
from base64 import b64decode
from IPython.display import Audio, display
import io
import soundfile as sf

RECORD_SEC = 3

# JavaScript 코드: 마이크 녹음 및 base64 반환
record_js = f"""
async function recordAudio() {{
  const sleep = time => new Promise(resolve => setTimeout(resolve, time));
  const b2text = blob => new Promise(resolve => {{
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.readAsDataURL(blob);
  }});

  let stream = await navigator.mediaDevices.getUserMedia({{ audio: true }});
  let recorder = new MediaRecorder(stream);
  let data = [];

  recorder.ondataavailable = event => data.push(event.data);
  recorder.start();

  await sleep({RECORD_SEC * 1000});
  recorder.stop();

  await new Promise(resolve => recorder.onstop = resolve);
  let blob = new Blob(data);
  return await b2text(blob);
}}
recordAudio();
"""

print(f"▶ 마이크 녹음을 {RECORD_SEC}초 동안 시작합니다...")
audio_base64 = output.eval_js(record_js)

# base64 디코딩 및 저장
audio_bytes = b64decode(audio_base64.split(',')[1])
with open("recorded.wav", "wb") as f:
    f.write(audio_bytes)

display(Audio("recorded.wav"))
# original WAV으로 변환해야 올바른 배열로 실행 가능
# STEP 1: 설치 (최초 1회만 실행)
!apt-get install -y ffmpeg
!pip install pydub

# STEP 2: 변환 (WebM/Opus → WAV)
from pydub import AudioSegment

# WebM 형식으로 저장된 파일을 WAV로 변환
input_path = "recorded.wav"         # 실질적으로 WebM 또는 Opus
converted_path = "converted.wav"    # WAV 파일로 저장

audio = AudioSegment.from_file(input_path)
audio = audio.set_frame_rate(16000).set_channels(1)
audio.export(converted_path, format="wav")

print("변환 완료: converted.wav")
import librosa, numpy as np

y, sr = librosa.load("converted.wav", sr=16000)
y = y / np.abs(y).max()

# STFT
n_fft, hop = 1024, 160
stft = librosa.stft(y, n_fft=n_fft, hop_length=hop, window="hann", center=False)
power_spec = np.abs(stft) ** 2

# Mel 필터뱅크 행렬(필터는 삼각형 구조이며, 저역은 조밀하고 고역은 넓게 분포)
mel_fb = librosa.filters.mel(sr=sr, n_fft=n_fft, n_mels=80, fmin=0.0, fmax=sr/2)
mel_spec = np.dot(mel_fb, power_spec)        # Mel필터를 파워 스펙트럼에 적용해 (80, frame)의 구조로 출력

mel_db = librosa.power_to_db(mel_spec, ref=np.max)     # dB 단위로 변환(log 스케일)
# 정규화 추가 (없으면 mel_std 에러 발생함)
mel_std = (mel_db - mel_db.mean()) / mel_db.std()
# 시각화 (해당 주파수에 지속적으로 에너지가 집중되어 있음)
plt.figure(figsize=(9, 3))
librosa.display.specshow(    # 2차원 스펙트로그램 이미지 출력
    mel_std,
    sr=sr, hop_length=hop_length,
    x_axis="time", y_axis="mel",
    cmap="viridis"
)
plt.title("Normalized Mel-Spectrogram (80 bins)")
plt.tight_layout(); plt.show()

Mel 주파수 대역 별 에너지 강도

 

 

 

 


 

 

 

3. 프로젝트용 공개 데이터셋

데이터셋 내용 샘플레이트 규모
Free Spoken Digit Dataset 0-9 숫자 발화 8kHz 2시간 미만, 1만 샘플
Speech commands V2 35개 단어(yes, no 등) 16kHz 105만 샘플
UrbanSound8K 10종 도시 소리 44.1kHz 8732 파일
MUSDB 18 음악 분리용 멀티트랙 44.1kHz 150곡
LibriSpeech (train-clean-100) 오디오북 음성 16kHz 100시간

 

 

 

📌 FSDD 실습

# FSDD 음원 다운로드
!git clone https://github.com/Jakobovski/free-spoken-digit-dataset.git
!mv free-spoken-digit-dataset/recordings recordings
#FSDD 데이터 웨이브데이터 로딩 후 멜 스펙트럼 변환
import librosa, glob
import numpy as np

files = glob.glob("FSDD/recordings/*.wav")
mel_list = []

for fp in files:
    y, _ = librosa.load(fp, sr=16000)
    mel = librosa.feature.melspectrogram(y=y, sr=16000, n_fft=1024,
                                         hop_length=160, n_mels=80, power=2)
    mel_db = librosa.power_to_db(mel, ref=np.max)
    mel_list.append(mel_db)
num_samples = min(5, len(mel_list))  # 최대 5개만

plt.figure(figsize=(12, 2.5 * num_samples))

for i in range(num_samples):
    plt.subplot(num_samples, 1, i + 1)
    librosa.display.specshow(mel_list[i], sr=16000, hop_length=160,
                             x_axis='time', y_axis='mel', cmap='magma')
    plt.colorbar(format='%+2.0f dB')
    plt.title(f'Mel-Spectrogram #{i + 1}')

plt.tight_layout()
plt.show

 

 

 

728x90