-
음성을 텍스트로, 목소리를 사람으로 — Whisper와 pyannote가 풀어낸 두 가지 문제IT 2026. 3. 19. 21:00
녹음을 다시 듣고 있다. 1시간짜리 음성 파일. 핵심 내용이 어디쯤이었는지 기억이 안 나서 처음부터 재생한다. 빨리감기. 되감기. 또 빨리감기.
이 경험이 익숙하다면, "녹음은 했는데 다시 듣기가 귀찮다" 는 인류 공통의 문제에 공감할 것이다.
이 문제를 해결하려면 두 가지가 필요하다.
- 음성을 텍스트로 바꾸기 (Speech-to-Text)
- 누가 말한 건지 구분하기 (Speaker Diarization)
이 글에서는 이 두 문제를 각각 해결하는 오픈소스 모델 — OpenAI Whisper와 pyannote-audio — 을 다룬다. 어떤 문제를 풀기 위해 나왔고, 어떻게 작동하며, 실제로 어떻게 설치하고 사용하는지까지.
문제 1: 음성을 텍스트로 — OpenAI Whisper
왜 이 모델이 필요한가
음성인식(STT)은 오래된 문제다. 시리, 구글 어시스턴트, 네이버 클로바 — 이미 잘 되고 있지 않나?
문제는 "잘 된다"의 기준이다.
- 조용한 방에서 또박또박 말하면? 잘 된다.
- 여러 사람이 자유롭게 토론하는 자리에서는? 엉망이 된다.
- 한국어로 말하다가 갑자기 영어 용어를 쓰면? 더 엉망이 된다.
기존 음성인식 시스템의 한계는 크게 두 가지였다.
첫째, 언어별로 따로 만들어야 했다. 한국어 모델, 영어 모델, 일본어 모델... 각각 별도의 데이터로 학습하고 별도로 배포했다.
둘째, 학습 데이터가 "깨끗한 음성"에 편향되어 있었다. 스튜디오에서 녹음한 뉴스 앵커의 음성으로 학습하니, 실제 현장의 웅성거림 속에서는 정확도가 급락했다.
Whisper는 어떻게 해결했나
OpenAI는 2022년에 Whisper를 발표하면서 완전히 다른 접근을 했다.
68만 시간의 인터넷 음성 데이터로 학습했다. YouTube 영상의 자막 데이터가 핵심이었다. 99개 언어가 섞여 있고, 배경 소음이 있고, 발음이 불명확한 — 그야말로 "지저분한" 실제 음성 데이터다.
이 접근의 효과는 명확하다.
- 다국어를 하나의 모델로 처리한다. 한국어, 영어, 일본어를 동일한 모델이 인식한다. 한국어로 말하다 "transformer architecture"라고 말해도 자연스럽게 전사된다.
- 실제 환경에서도 정확하다. 학습 데이터 자체가 실제 환경 음성이므로, 세미나장의 배경 소음이나 에어컨 소리에 강하다.
- 무료 오픈소스다. MIT 라이선스. 내 서버에서 돌릴 수 있다. 클라우드 API 비용도, 개인정보 유출 걱정도 없다.
작동 원리를 쉽게 설명하면
Whisper의 구조는 의외로 직관적이다.
음성 → [인코더] → 음성의 "의미 벡터" → [디코더] → 텍스트인코더는 음성 파형을 받아서 "이 구간에서 이런 소리가 났다"를 숫자 벡터로 압축한다. 사람으로 치면, 귀로 소리를 듣고 뇌에서 "아, 이건 '안녕하세요'라는 소리구나"라고 인식하는 과정이다.
디코더는 이 벡터를 보고 텍스트를 하나씩 생성한다. ChatGPT가 글자를 하나씩 출력하는 것과 같은 방식이다. 이전에 생성한 텍스트를 참고하면서 다음 글자를 예측한다.
이 구조 덕분에 Whisper는 문맥을 이해한다. "나는 오늘 ___ 에 갔다"에서 빈칸이 "학교"인지 "학꾜"인지 — 음성이 불명확해도 문맥으로 올바른 단어를 선택할 수 있다.
모델 크기별 선택
Whisper는 여러 크기의 모델을 제공한다.
모델 파라미터 VRAM 용도 tiny 39M ~1GB 빠른 테스트, 실시간 자막 base 74M ~1GB 가벼운 용도 small 244M ~2GB 괜찮은 정확도 medium 769M ~5GB 좋은 정확도 large-v3 1.55B ~10GB 최고 정확도 정확도가 최우선이라면 large-v3가 답이다. GPU 메모리가 10GB 이상이면 충분히 구동할 수 있다.
문제 2: 누가 말한 건지 — pyannote-audio
왜 이 모델이 필요한가
Whisper가 음성을 텍스트로 바꿔줬다. 하지만 결과물은 이렇다.
오늘 세미나 주제는 AI 활용입니다. 네 그 부분에 대해서 질문이 있는데요. 추가로 말씀드리면 작년 사례를 보시면...누가 한 말인지 모른다. 3명이 대화했는데 전부 한 사람이 한 것처럼 보인다. 기록으로 쓸 수 없다.
이 문제를 화자 분리(Speaker Diarization) 라고 한다. "이 음성에서 몇 명이 말했고, 각 구간에서 누가 말했는지"를 알아내는 것이다.
pyannote는 어떻게 해결했나
pyannote-audio는 프랑스 CNRS(국립과학연구센터)에서 개발한 오픈소스 화자 분리 파이프라인이다. 내부적으로 두 개의 모델을 조합한다.
① 세그멘테이션 모델 (pyannote/segmentation-3.0)
이 모델은 음성을 아주 짧은 구간(수십 밀리초)으로 쪼개서, 각 구간마다 세 가지를 판단한다.
- 여기서 누군가 말하고 있나? (Voice Activity Detection)
- 화자가 바뀌었나? (Speaker Change Detection)
- 두 사람이 동시에 말하고 있나? (Overlap Detection)
사람으로 치면, 눈을 감고 소리만 듣면서 "지금 말이 바뀌었다", "지금 두 사람이 겹쳐서 말한다"를 감지하는 과정이다.
② 화자 임베딩 모델 (wespeaker-voxceleb-resnet34)
세그멘테이션이 구간을 잘라주면, 이 모델은 각 구간의 "목소리 지문" 을 추출한다. 사람마다 성대 구조, 발성 습관, 말투가 다르므로 — 이 차이를 숫자 벡터(임베딩)로 표현한다.
같은 사람의 목소리는 비슷한 벡터를 갖고, 다른 사람은 다른 벡터를 갖는다. 이 벡터들을 클러스터링하면 "Speaker 1", "Speaker 2"... 라벨이 만들어진다.
두 모델의 협업
음성 파일 ↓ 세그멘테이션: "0:00~0:15 누군가 말함, 0:15~0:30 다른 사람 말함, 0:28~0:35 겹침" ↓ 임베딩 추출: 각 구간의 목소리 지문 벡터 생성 ↓ 클러스터링: "벡터 A, C, E는 비슷 → Speaker 1 / 벡터 B, D는 비슷 → Speaker 2" ↓ 결과: 시간별 화자 라벨세그멘테이션만으로는 "언제" 누군가 말하는지는 알지만 "누구" 인지 모른다. 임베딩만으로는 "누구" 인지는 알지만 "언제" 말하는지 모른다. 둘을 결합해야 완전한 화자 분리가 된다.
실전: 설치부터 실행까지
환경 준비
Python 가상환경을 만들고 필요한 패키지를 설치한다.
# 가상환경 생성 python3 -m venv ~/voice-env source ~/voice-env/bin/activate # Whisper 설치 pip install openai-whisper # pyannote 설치 pip install pyannote.audio # GPU 사용 시 PyTorch CUDA 버전 필요 pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu128pyannote 사전 준비
pyannote 모델은 HuggingFace에서 제공되며, 라이선스 동의가 필요하다.
- huggingface.co 계정 생성
- Settings → Tokens에서 토큰 발급
- 아래 두 페이지에서 "Agree and access repository" 클릭
- pyannote/speaker-diarization-3.1
- pyannote/segmentation-3.0
Whisper로 전사하기
import whisper # 모델 로드 (최초 실행 시 자동 다운로드, ~3GB) model = whisper.load_model("large-v3", device="cuda") # 전사 result = model.transcribe("meeting.m4a", language="ko") # 결과 출력 for segment in result["segments"]: start = segment["start"] end = segment["end"] text = segment["text"] print(f"[{start:.1f}s ~ {end:.1f}s] {text}")출력 예시:
[0.0s ~ 3.2s] 오늘 세미나 주제는 AI 활용입니다. [3.5s ~ 7.8s] 네 그 부분에 대해서 질문이 있는데요. [8.1s ~ 15.3s] 추가로 말씀드리면 작년 사례를 보시면...language="ko"를 지정하면 한국어로 고정되어 더 정확하다. 생략하면 Whisper가 자동으로 언어를 감지한다.pyannote로 화자 분리하기
from pyannote.audio import Pipeline # 파이프라인 로드 (HuggingFace 토큰 필요) pipeline = Pipeline.from_pretrained( "pyannote/speaker-diarization-3.1", use_auth_token="hf_YOUR_TOKEN_HERE" ) # GPU 사용 import torch pipeline.to(torch.device("cuda")) # 화자 분리 실행 diarization = pipeline("meeting.m4a") # 결과 출력 for turn, _, speaker in diarization.itertracks(yield_label=True): print(f"[{turn.start:.1f}s ~ {turn.end:.1f}s] {speaker}")출력 예시:
[0.0s ~ 3.2s] SPEAKER_00 [3.5s ~ 7.8s] SPEAKER_01 [8.1s ~ 15.3s] SPEAKER_00두 결과를 합치면
Whisper의 전사 결과(텍스트 + 타임스탬프)와 pyannote의 화자 분리 결과(화자 + 타임스탬프)를 타임스탬프 기준으로 매핑하면 완성이다.
# Whisper 세그먼트의 중간 시점이 어떤 화자 구간에 속하는지 매핑 for seg in whisper_segments: mid = (seg["start"] + seg["end"]) / 2 for turn, _, speaker in diarization.itertracks(yield_label=True): if turn.start <= mid <= turn.end: seg["speaker"] = speaker break최종 결과:
[00:00] Speaker 1: 오늘 세미나 주제는 AI 활용입니다. [00:03] Speaker 2: 네 그 부분에 대해서 질문이 있는데요. [00:08] Speaker 1: 추가로 말씀드리면 작년 사례를 보시면...이제 이 텍스트를 LLM에 넘기면 자동 요약, 액션 아이템 추출, 기록 정리까지 가능하다.
실제 운영에서의 팁
성능
내 DGX Spark(NVIDIA GB10, 128GB 통합 메모리) 환경에서의 실측:
- Whisper large-v3: 45분 녹음 파일 전사에 약 6분 소요 (GPU)
- pyannote 화자 분리: 같은 파일에 약 2분 소요 (GPU)
- 전체 파이프라인 (전사 + 화자분리 + LLM 요약): 약 10분
CPU만 사용하면 Whisper만 30분 이상 걸린다. GPU가 있다면 반드시 활용하자.
m4a 포맷 주의
갤럭시 기본 녹음 앱은 m4a(AAC) 포맷으로 저장한다. Whisper는 m4a를 직접 읽을 수 있지만, pyannote의 내부 오디오 로더(soundfile)는 m4a를 지원하지 않는다. ffmpeg로 wav 변환이 필요하다.
ffmpeg -i recording.m4a -ar 16000 -ac 1 recording.wav화자 수 자동 감지
pyannote는 화자 수를 자동으로 감지한다. 미리 알고 있다면 명시할 수도 있다.
# 화자 수를 모를 때 (자동 감지) diarization = pipeline("meeting.m4a") # 화자 수를 알 때 (더 정확) diarization = pipeline("meeting.m4a", num_speakers=3)
마무리
Whisper는 "음성을 텍스트로" 문제를, pyannote는 "누가 말했는지" 문제를 각각 해결한다. 둘 다 오픈소스이고, GPU가 있으면 로컬에서 충분히 돌릴 수 있다.
이 두 모델을 조합하면 녹음 파일 → 화자별 전사 텍스트까지 완전 자동화된다. 여기에 LLM 요약을 붙이면, 1시간짜리 녹음이 2분 만에 깔끔한 요약 문서가 된다.
더 이상 녹음 파일을 처음부터 다시 들을 필요가 없다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
OpenClaw를 250줄 게이트웨이로 교체한 이유 (0) 2026.03.22 GPU 하나로 AI 작업 두 개 돌리기 — 우선순위 스케줄러 만들기 (1) 2026.03.21 OAuth 2.0: 비밀번호를 넘기지 않고 권한만 빌려주는 방법 (3) 2026.03.20 pyannote 화자 분리가 GPU에서 안 돌아갈 때 — Blackwell nvrtc 패치 1줄의 힘 (0) 2026.03.19 녹음 파일을 넣으면 요약이 나온다 — 음성 자동 전사 & 요약 파이프라인 구축기 (0) 2026.03.19 수만 장 가족사진에 AI가 메타데이터를 입히는 과정 — Immich + VLM 파이프라인 해부 (1) 2026.03.18 gogcli에서 gws로: REST API → CLI → AI Agent, 도구의 진화를 따라가다 (0) 2026.03.17 DGX Spark에서 Immich로 가족앨범 GPU 가속 관리하기 (1) 2026.03.16 DGX Spark에서 ONNX Runtime GPU 빌드 성공기 — 8번의 실패와 1번의 성공 (0) 2026.03.16 왜 MoE 아키텍처가 등장했나? - Trinity 모델 툴콜링 이슈에서 출발한 탐구 (0) 2026.03.15