Projects/AI 아이트래킹 모델 프로젝트

[도메인 공부] 파이썬 음성 처리 공부

ha2yong 2025. 9. 4. 17:45

좋은 질문이에요! 👌
OpenCV가 영상 처리에 특화된 라이브러리라면, 파이썬에서 음성(오디오) 처리를 할 때는 여러 모듈들이 있습니다. 용도별로 정리해드릴게요:


🎙️ 음성 입력/출력(녹음·재생)

  • PyAudio : 마이크 입력, 스피커 출력 같은 저수준 오디오 입출력 라이브러리
  • sounddevice : NumPy 배열 기반으로 간단히 녹음/재생 가능
  • playsound : 단순히 mp3/wav 같은 오디오 파일을 재생할 때 사용

🔊 음성 신호 처리(파형·스펙트럼)

  • librosa : 음성 신호 분석, 특징 추출(MFCC, 스펙트로그램 등)
  • scipy.signal : 필터링, 푸리에 변환 등 기본 신호처리 기능 제공
  • torchaudio : PyTorch와 연동해 딥러닝용 오디오 전처리에 많이 사용

🗣️ 음성 인식(STT: Speech To Text)

  • SpeechRecognition : 구글/비스퍼 등 다양한 STT API 연결 지원
  • whisper (OpenAI Whisper) : 최신 음성 인식 모델, 한글도 잘 지원

🔈 음성 합성(TTS: Text To Speech)

  • pyttsx3 : 오프라인 TTS 라이브러리 (윈도우/맥/리눅스 지원)
  • gTTS : 구글 TTS API 기반, 한국어 지원, MP3로 저장 가능
  • Coqui TTS : 딥러닝 기반 고품질 오픈소스 TTS

🤖 고급/딥러닝 기반

  • transformers (Hugging Face) : Wav2Vec2, Whisper, TTS 모델 제공
  • ESPnet / fairseq : 음성 인식·합성 연구용 프레임워크
  • torchaudio : STT/TTS 모델 학습에 활용

👉 정리하면:

  • 입출력 → sounddevice, PyAudio
  • 신호처리 → librosa, scipy.signal
  • 음성인식 → SpeechRecognition, whisper
  • TTS → pyttsx3, gTTS

 

큰 그림

  1. 마이크 캡처: sounddevice로 16kHz 모노 스트리밍
  2. VAD(음성/무음 구분): webrtcvad로 말할 때만 청크를 모아 지연 최소화
  3. STT: faster-whisper(Whisper 가속 구현)로 청크 단위 즉시 인식
  4. TTS: 빠르게는 pyttsx3(윈도우 SAPI5, 오프라인)로 말하기
  5. (선택) 인식 텍스트 → NLU/명령 처리 → 답변 텍스트 → TTS

Windows 설치 (Python 3.10~3.11 권장)

# 새 가상환경(권장)
python -m venv venv
.\venv\Scripts\activate

# 필수
pip install --upgrade pip
pip install sounddevice numpy webrtcvad faster-whisper pyttsx3

# (선택) PyTorch가 필요한 건 아니지만 나중에 torchaudio 쓰면 설치
# CUDA가 없다면 cpu 전용으로 설치됩니다.
# pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu

 

예제: 실시간 STT + 즉시 TTS 에코

  • 20ms 프레임 단위로 VAD 적용
  • 말이 멈추면(무음 0.6초) 모인 청크를 STT → 바로 pyttsx3로 읽어줌
  • Whisper 모델 크기: tiny/base/small/medium/large-v3 (작을수록 지연↓, 정확도↓)
# realtime_stt_tts.py
import queue
import sys
import time
import numpy as np
import sounddevice as sd
import webrtcvad
import pyttsx3
from faster_whisper import WhisperModel

#####################
# 설정값 (지연/정확도 트레이드오프)
#####################
SAMPLE_RATE = 16000            # Whisper 권장
CHANNELS = 1
FRAME_DURATION_MS = 20         # 10/20/30ms만 가능(webrtcvad 제약)
VAD_AGGRESSIVENESS = 2         # 0~3 (클수록 더 공격적으로 음성으로 판단)
MAX_SILENCE_SECS = 0.6         # 이만큼 무음이면 한 문장 끝으로 간주
WHISPER_MODEL_SIZE = "small"   # "tiny", "base", "small" 권장(실시간)
WHISPER_DEVICE = "cpu"         # "cpu" 또는 "cuda" (CUDA 빌드 세팅 시)
LANGUAGE = "ko"                # 한국어 고정(자동감지 원하면 None)
BEAM_SIZE = 1                  # 속도 우선

#####################
# 준비: TTS
#####################
tts = pyttsx3.init()           # Windows: SAPI5
tts.setProperty("rate", 180)   # 말속도
tts.setProperty("volume", 1.0)

#####################
# 준비: Whisper
#####################
print("Loading Whisper model... (first time can take a bit)")
model = WhisperModel(
    WHISPER_MODEL_SIZE,
    device=WHISPER_DEVICE,
    compute_type="int8" if WHISPER_DEVICE == "cpu" else "float16"  # CPU는 int8 추천
)

#####################
# 준비: 오디오 & VAD
#####################
audio_q = queue.Queue()
frame_len = int(SAMPLE_RATE * FRAME_DURATION_MS / 1000)  # 샘플 수(20ms=320)
vad = webrtcvad.Vad(VAD_AGGRESSIVENESS)

def audio_callback(indata, frames, time_info, status):
    if status:
        print(status, file=sys.stderr)
    # float32 -> int16 변환 (webrtcvad는 16bit PCM 필요)
    pcm16 = np.clip(indata[:, 0], -1.0, 1.0)
    pcm16 = (pcm16 * 32767).astype(np.int16)
    audio_q.put(pcm16.tobytes())
    return

#####################
# 유틸: VAD 프레이밍
#####################
def bytes_to_vad_frames(byte_buffer):
    # 20ms 단위로 자르기
    chunk_size = frame_len * 2  # int16 = 2 bytes
    for i in range(0, len(byte_buffer), chunk_size):
        yield byte_buffer[i:i+chunk_size]

def is_speech(frame_bytes):
    # webrtcvad는 16kHz mono 16bit PCM little-endian 필요
    if len(frame_bytes) != frame_len * 2:
        return False
    try:
        return vad.is_speech(frame_bytes, SAMPLE_RATE)
    except Exception:
        return False

#####################
# 메인 루프
#####################
def main():
    print("Starting audio stream...")
    with sd.InputStream(
        channels=CHANNELS,
        samplerate=SAMPLE_RATE,
        dtype="float32",
        blocksize=frame_len,   # 20ms 콜백
        callback=audio_callback,
        latency="low"
    ):
        voiced_bytes = bytearray()
        last_voice_time = None
        buffering = bytearray()

        while True:
            # 큐에서 받아 프레임 묶기
            try:
                block = audio_q.get(timeout=1.0)  # 1초 대기
                buffering.extend(block)
            except queue.Empty:
                continue

            # 20ms 단위로 검사
            for f in bytes_to_vad_frames(buffering):
                if len(f) < frame_len * 2:
                    # 다음 블록과 합치기 위해 루프 종료
                    buffering = bytearray(f)
                    break

                speech = is_speech(f)
                if speech:
                    voiced_bytes.extend(f)
                    last_voice_time = time.time()
                else:
                    # 무음
                    pass

                # 문장 끝났는지 체크
                if last_voice_time is not None and (time.time() - last_voice_time) > MAX_SILENCE_SECS and len(voiced_bytes) > 0:
                    # STT 수행
                    segment_pcm = np.frombuffer(voiced_bytes, dtype=np.int16).astype(np.float32) / 32768.0
                    # faster-whisper에 직접 배열 전달 가능
                    # vad로 이미 잘랐으므로 no_speech_threshold 완화
                    segments, info = model.transcribe(
                        segment_pcm,
                        language=LANGUAGE,
                        beam_size=BEAM_SIZE,
                        vad_filter=False,          # 이미 수동 VAD 적용
                        temperature=0.0,
                        no_speech_threshold=0.2,
                        condition_on_previous_text=False
                    )

                    text_out = ""
                    for seg in segments:
                        text_out += seg.text

                    text_out = text_out.strip()
                    if text_out:
                        print(f"[STT] {text_out}")

                        # (여기서 NLU/명령 처리 로직을 넣을 수 있음)
                        # reply_text = handle_command(text_out)
                        reply_text = text_out  # 데모: 그대로 따라 말하기

                        # TTS
                        tts.say(reply_text)
                        tts.runAndWait()

                    # 다음 문장 준비
                    voiced_bytes = bytearray()
                    last_voice_time = None

            else:
                # 프레임 루프를 정상 종료한 경우, 버퍼 비움
                buffering = bytearray()

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nBye")

 

# 실행
python realtime_stt_tts.py
# 말하고 멈추면(무음 0.6초) 즉시 인식 → 스피커로 읽어줍니다.

 

튜닝 포인트(중요)

  • 지연 줄이기
    • WHISPER_MODEL_SIZE: tiny 또는 base로 ↓
    • MAX_SILENCE_SECS: 0.6 → 0.3~0.5로 ↓
    • FRAME_DURATION_MS: 20ms 유지(10ms는 CPU 부하↑)
    • 가능하면 CUDA 빌드로 WHISPER_DEVICE="cuda", compute_type="float16"
  • 정확도 올리기
    • 모델을 small 이상으로 ↑
    • LANGUAGE=None로 자동감지(영/한 혼용 시 유리)
    • VAD aggressiveness를 1~2로 조절(3은 침소봉대 가능)
  • 말 잘 끊기게
    • MAX_SILENCE_SECS 조절 + VAD 강도 조절
    • 긴 문장 실시간 자막 느낌이면, 무음 임계값을 짧게 잡아 “짧게-짧게” 인식

모델 연결 확장

  • NLU/명령 처리: STT 결과를 키워드 매칭, 정규식, 또는 LLM API로 처리
  • 고급 TTS: 더 자연스러운 음색이 필요하면
    • Coqui TTS 로컬 모델(오프라인),
    • Windows SAPI5 보이스(설정 → 접근성 → 내레이터 → 보이스 설치 후 pyttsx3에서 선택)
  • 오디오 전처리: pydub/scipy.signal로 노멀라이즈, HPF/LPF 필터, AGC 등
  • 핀치 포인트: 에코·하울링 방지 필요하면 헤드셋 사용 or WebRTC AEC(별도 구현)

원하시면 위 데모를 **“명령어 반응 봇”**으로 바꿔서
예) “원, 100픽셀 그려” → OpenCV로 도형 그리기,
“녹화 시작” → 화면/카메라 녹화 시작,
같은 액션을 바로 붙여드릴게요.