음성 인식은 모델만의 문제가 아니다. Whisper 같은 STT 모델을 쓰면 음성을 텍스트로 바꾸는 핵심 추론은 모델이 처리하지만, 모델 앞에 들어가는 음성이 어떤 상태인지에 따라 결과가 크게 달라진다. 같은 문장이라도 마이크가 멀리 있거나, 키보드 소리가 섞이거나, 에어컨 저주파가 깔리거나, 말이 너무 작게 녹음되면 모델은 더 어려운 입력을 받는다.
이 글은 STT 시리즈의 첫 번째 글로, 음성 노이즈를 어떻게 바라보고 어떤 순서로 전처리하면 좋은지 정리한다. 목표는 초보자가 파이프라인의 전체 모양을 잡는 것이다. 특정 라이브러리 사용법보다 “무엇을 왜 하는지”를 먼저 설명한다.

자동 음성 인식은 오디오 입력을 특징으로 바꾸고 모델을 거쳐 텍스트를 출력하는 흐름으로 이해할 수 있다. 출처: NVIDIA Technical Blog
음성 전처리는 모델 앞의 입력 정리다
STT 모델은 소리를 그대로 문장으로 바꾸지 않는다. 일반적으로 입력 오디오는 일정한 샘플링 레이트와 채널 형태로 정리되고, 짧은 시간 단위로 잘린 뒤, 주파수 성분을 나타내는 특징으로 변환된다. Whisper 논문은 30초 오디오 구간을 log-Mel spectrogram으로 변환해 Transformer encoder-decoder 모델에 넣는 구조를 사용한다.
여기서 중요한 점은 모델이 듣는 것은 사람이 느끼는 “소리” 자체가 아니라, 일정한 규칙으로 숫자화된 신호라는 점이다. 녹음 품질이 나쁘면 숫자화된 특징에도 잡음이 남고, 그 잡음은 모델이 음소와 단어를 구분하는 일을 방해한다.
전처리의 목적은 단순히 소리를 예쁘게 만드는 것이 아니다. STT 모델이 음성 정보를 더 안정적으로 읽을 수 있게 입력 신호를 정리하는 것이다.
노이즈는 하나가 아니다
음성 노이즈라고 하면 보통 “시끄러운 소리”를 떠올리지만, 실제로는 여러 종류가 있다. 종류를 나누어야 처리 방법도 달라진다.
- 지속 노이즈: 에어컨, 선풍기, PC 팬, 냉장고처럼 계속 깔리는 소리다.
- 순간 노이즈: 키보드, 클릭, 문 닫힘, 컵 소리처럼 짧게 튀는 소리다.
- 배경 음성: 주변 사람이 말하는 소리다. 음악보다 더 까다로운 경우가 많다.
- 반향: 방 안에서 목소리가 벽에 반사되어 늦게 섞이는 현상이다.
- 클리핑: 입력이 너무 커서 파형 위아래가 잘리는 왜곡이다.
- 저음 럼블: 책상 진동, 마이크 터치, 바람 소리처럼 낮은 주파수에 몰린 잡음이다.
지속 노이즈는 필터링이나 노이즈 프로파일 기반 제거가 비교적 잘 먹힌다. 순간 노이즈와 배경 음성은 훨씬 어렵다. 특히 다른 사람의 말소리는 STT 입장에서 “노이즈”가 아니라 또 다른 음성 정보이기 때문에 단순 필터로 분리하기 어렵다.

배경 노이즈가 섞이면 파형의 침묵 구간에도 에너지가 남는다. 출처: audiomentations documentation
가장 먼저 확인할 것은 파형이다
초보자는 음성 전처리를 바로 모델이나 복잡한 알고리듬으로 시작하기 쉽다. 하지만 첫 단계는 파형을 보는 것이다. 파형은 시간에 따라 소리의 크기가 어떻게 바뀌는지 보여준다.
파형에서 확인할 수 있는 것은 단순하지만 중요하다.
- 말이 너무 작게 녹음되었는지 볼 수 있다.
- 입력이 너무 커서 클리핑이 생겼는지 볼 수 있다.
- 말하지 않는 구간에 노이즈가 얼마나 깔려 있는지 볼 수 있다.
- 갑자기 튀는 클릭이나 충격음이 있는지 볼 수 있다.
- 침묵 구간과 발화 구간이 대략적으로 구분되는지 볼 수 있다.
파형이 이미 심하게 잘려 있다면 이후 단계에서 완전히 복구하기 어렵다. 클리핑은 단순히 볼륨이 큰 문제가 아니라 원래 신호 일부가 사라진 상태다. 그래서 녹음 단계에서는 너무 크게 받는 것보다 약간 여유를 두고 받는 편이 안전하다.
스펙트로그램은 소리를 주파수별로 펼친 지도다
파형만 보면 소리의 높낮이와 잡음의 종류를 구분하기 어렵다. 이때 스펙트로그램을 사용한다. 스펙트로그램은 시간축과 주파수축을 함께 보여주고, 색의 밝기로 에너지 크기를 표현한다.
사람 목소리는 넓은 주파수 영역을 쓰지만, 말소리의 핵심 정보는 특정 대역에 많이 모인다. 반대로 바람 소리나 책상 진동은 낮은 주파수에 몰리는 경우가 많고, 전기적 히스 노이즈는 높은 주파수에 얇게 깔리는 경우가 많다.

스펙트로그램은 시간에 따른 주파수 성분의 변화를 색으로 보여준다. 출처: Wikimedia Commons
스펙트로그램을 보면 다음 판단이 가능하다.
- 낮은 주파수에만 잡음이 몰려 있으면 high-pass filter를 고려할 수 있다.
- 특정 주파수 줄이 계속 보이면 hum 제거 또는 notch filter를 고려할 수 있다.
- 말하지 않는 구간에도 전체 대역이 밝으면 지속 노이즈가 큰 상태다.
- 발화와 배경 음악이 겹치면 단순 노이즈 제거보다 분리 문제가 된다.
Whisper 같은 모델도 내부적으로 오디오를 log-Mel spectrogram 형태로 바꾸어 처리한다. Whisper 구현은 오디오를 16 kHz로 다루고 Mel spectrogram을 만든 뒤 모델 입력으로 사용한다. 스펙트로그램을 이해하면 STT 모델이 보는 입력의 모양도 같이 이해할 수 있다.
기본 파이프라인은 단순하게 잡는 것이 좋다
처음부터 복잡한 음성 향상 모델을 붙이는 것은 좋은 출발점이 아니다. 전처리는 단계가 많아질수록 음성 자체를 망가뜨릴 가능성도 커진다. 기본 파이프라인은 단순해야 한다.
입력 규격 정리, 약한 필터링, 음량 정규화, VAD, STT 입력 순서로 파이프라인을 단순하게 잡는다.
내가 기본값으로 잡는 흐름은 다음과 같다.
- 입력 파일을 읽는다.
- 모노 채널로 변환한다.
- STT 모델에 맞는 샘플링 레이트로 리샘플링한다.
- 너무 낮은 주파수의 럼블을 줄인다.
- 음량을 적정 범위로 정규화한다.
- VAD로 말하는 구간과 말하지 않는 구간을 나눈다.
- 필요한 경우에만 노이즈 감소를 적용한다.
- 너무 긴 음성은 모델 입력 단위에 맞게 자른다.
- STT 모델에 넣는다.
이 흐름의 핵심은 “무조건 강하게 지우기”가 아니라 “모델이 필요로 하는 입력 형태에 맞게 정리하기”다.
리샘플링과 모노 변환은 입력 규격을 맞추는 일이다
샘플링 레이트는 1초에 소리를 몇 번 측정했는지를 뜻한다. 48 kHz 오디오는 1초에 48,000번 측정된 신호이고, 16 kHz 오디오는 1초에 16,000번 측정된 신호다. STT 모델이나 전처리 라이브러리는 특정 샘플링 레이트를 기준으로 동작하는 경우가 많다.
Whisper는 오디오를 16 kHz 기준으로 처리한다. 그래서 44.1 kHz나 48 kHz 녹음을 그대로 넣기보다 16 kHz로 리샘플링해 입력을 맞추는 흐름이 일반적이다. librosa의 resample 같은 함수는 오디오 시계열을 다른 샘플링 레이트로 변환하는 데 쓰인다.
스테레오 오디오는 좌우 채널이 나뉘어 있다. STT에서는 보통 모노로 합쳐도 충분한 경우가 많다. 회의 녹음처럼 채널별 화자가 분리되어 있으면 별도 처리가 필요하지만, 일반 마이크 입력에서는 모노 변환이 파이프라인을 단순하게 만든다.
필터링은 특정 주파수 대역을 줄이는 작업이다
필터는 주파수 기준으로 소리를 정리하는 방법이다. 모든 노이즈를 없애는 마법 같은 단계는 아니지만, 특정 상황에서는 매우 효과적이다.
- high-pass filter: 너무 낮은 주파수를 줄인다. 마이크 터치, 바람, 책상 진동 같은 럼블을 줄일 때 쓴다.
- low-pass filter: 너무 높은 주파수를 줄인다. 고주파 히스가 심할 때 제한적으로 쓴다.
- band-pass filter: 특정 대역만 남긴다. 음성 대역에 집중하고 싶을 때 쓴다.
- notch filter: 특정 주파수만 좁게 깎는다. 전원 hum처럼 한 줄로 박힌 잡음을 줄일 때 쓴다.

필터는 특정 주파수 대역을 줄이거나 통과시키는 방식으로 신호를 정리한다. 출처: Unison Audio
필터를 너무 강하게 걸면 말소리의 일부도 같이 사라진다. 특히 자음 정보는 고주파 성분과 관련이 있기 때문에 무리한 low-pass filter는 발음 구분을 어렵게 만들 수 있다. STT 전처리에서는 사람 귀에 부드럽게 들리는 것보다 모델이 단어를 구분할 수 있는 정보가 남아 있는지가 더 중요하다.
음량 정규화는 크게 만드는 것이 아니라 맞추는 것이다
정규화는 전체 음량을 일정 기준에 맞추는 작업이다. 초보자는 정규화를 “소리를 크게 키우는 것”으로 이해하기 쉽지만, 핵심은 입력 레벨을 안정적으로 맞추는 것이다.
말소리가 너무 작으면 노이즈와 음성의 차이가 줄어든다. 반대로 너무 크면 클리핑이 생기거나 모델 입력 특징이 왜곡될 수 있다. 그래서 정규화는 노이즈 제거와 별개로 중요한 단계다.
여기서 SNR이라는 개념이 나온다. SNR은 signal-to-noise ratio, 즉 신호 대비 잡음 비율이다. 말소리와 배경 잡음의 차이가 클수록 STT가 유리하다. 단순히 전체 음량을 키우면 말소리와 노이즈가 같이 커지기 때문에 SNR 자체가 좋아지는 것은 아니다.

SNR은 말소리 신호와 배경 잡음의 상대적인 차이를 이해하는 기본 개념이다. 출처: Chris's Sound Lab
정규화는 입력 크기를 맞추는 단계이고, 노이즈 감소는 신호와 잡음의 관계를 바꾸려는 단계다. 둘은 목적이 다르다.
VAD는 “말하는 구간”을 찾는 단계다
VAD는 voice activity detection의 약자다. 입력 오디오에서 사람이 말하는 구간과 말하지 않는 구간을 구분한다. STT 전처리에서 VAD는 매우 중요하다.
말하지 않는 구간을 제거하면 모델이 불필요한 노이즈를 처리하는 시간이 줄어든다. 긴 음성에서는 발화 단위로 자르는 기준도 된다. Whisper처럼 긴 입력을 일정 길이 단위로 처리하는 모델에서는 VAD와 chunking 전략이 결과 품질과 처리 속도에 영향을 준다.

VAD는 짧은 프레임 단위로 음성 구간과 비음성 구간을 나누는 데 사용된다. 출처: Introduction to Speech Processing
VAD는 WebRTC VAD를 Python에서 사용할 수 있게 해 주는 대표적인 패키지다. WebRTC VAD는 짧은 프레임 단위로 음성 여부를 판단한다. 보통 10 ms, 20 ms, 30 ms 같은 짧은 조각을 기준으로 판단하고, aggressiveness 설정으로 얼마나 엄격하게 음성만 남길지 조절한다.
VAD를 너무 엄격하게 설정하면 작은 목소리나 문장 끝이 잘릴 수 있다. 너무 느슨하게 설정하면 노이즈 구간이 많이 남는다. 그래서 VAD는 보통 앞뒤에 약간의 여유 구간을 붙여 사용한다. 이 여유를 padding 또는 hangover로 생각하면 된다.
노이즈 감소는 마지막에 조심스럽게 적용한다
노이즈 감소는 매력적인 단계다. 하지만 STT에서는 항상 좋은 결과를 보장하지 않는다. 노이즈 제거 알고리듬이 말소리 일부까지 깎아 버리면 모델이 더 헷갈릴 수 있다.
전통적인 방법으로는 spectral gating이 있다. 말하지 않는 구간에서 노이즈 프로파일을 추정하고, 이후 스펙트럼에서 그 패턴을 줄이는 방식이다. Audacity의 noise reduction도 노이즈 프로파일을 기반으로 감소량, 민감도, 주파수 smoothing 같은 값을 조절하는 구조를 설명한다.
딥러닝 기반 방식도 있다. RNNoise는 순환 신경망을 사용해 실시간 노이즈 억제를 수행하는 예시다. 이런 방식은 지속 노이즈나 일반적인 배경 잡음에는 강할 수 있지만, 음성의 질감이 바뀌거나 자음이 약해지는 부작용이 생길 수 있다.

노이즈 감소는 스펙트럼에서 잡음 성분을 줄이는 방식으로 이해할 수 있다. 출처: DSP Stack Exchange
STT 목적에서는 노이즈 감소를 강하게 거는 것보다 약하게 적용하고, 전후 인식 결과를 비교하는 편이 안전하다. 사람이 듣기 좋은 소리와 STT가 잘 읽는 소리는 항상 같지 않다.
Chunking은 긴 음성을 모델이 읽을 수 있는 단위로 나누는 일이다
긴 파일을 한 번에 넣을 수 없는 경우가 많다. Whisper는 30초 단위 오디오 구간을 기준으로 설계되어 있다. 그래서 긴 회의나 방송 음성을 처리할 때는 적절한 단위로 자르는 chunking이 필요하다.
Chunking에서 중요한 것은 문장을 중간에 자르지 않는 것이다. 단어 중간이나 문장 중간을 잘라 버리면 앞뒤 맥락이 사라지고 인식 결과가 흔들릴 수 있다. 그래서 VAD로 발화 구간을 찾고, 너무 긴 발화는 겹침 구간을 두어 나누는 방식이 자주 쓰인다.
겹침 구간을 overlap이라고 부른다. 예를 들어 30초 단위로 자르더라도 앞뒤 1초 정도를 겹치게 두면 경계에서 잘린 단어를 복구하기 쉬워진다. 대신 결과를 합칠 때 중복 문장을 제거해야 한다.
STT 전처리에서 자주 하는 실수
초보자가 많이 하는 실수는 대체로 비슷하다.
- 노이즈 제거를 너무 강하게 건다.
- 리샘플링 없이 여러 포맷을 섞어 넣는다.
- 스테레오와 모노를 구분하지 않는다.
- 침묵 구간을 그대로 많이 넣는다.
- 클리핑이 있는 파일을 정규화로 해결하려고 한다.
- chunk 경계에서 단어가 잘리는 문제를 무시한다.
- 사람이 듣기에 좋아진 것만 보고 STT 결과 비교를 하지 않는다.
전처리는 항상 결과 비교와 함께 봐야 한다. 같은 파일에 대해 원본, 기본 전처리, 강한 노이즈 제거 버전을 나누고 STT 결과를 비교하면 어떤 처리가 실제로 도움 되는지 알 수 있다.
실전 기준으로 잡는 최소 전처리
처음 구축하는 STT 파이프라인이라면 다음 정도가 현실적인 출발점이다.
- 입력을 mono로 바꾼다.
- 16 kHz로 리샘플링한다.
- 클리핑 여부를 확인한다.
- 너무 낮은 주파수 럼블만 약하게 줄인다.
- 음량을 적정 범위로 맞춘다.
- VAD로 말하는 구간을 찾는다.
- 긴 음성은 발화 기준으로 자른다.
- 노이즈 감소는 필요한 파일에만 약하게 적용한다.
- 전처리 전후 STT 결과를 비교한다.
이 구성은 단순하지만 강하다. 모든 음성을 똑같이 세게 보정하지 않고, 모델이 읽기 쉬운 형태로 정리하는 데 집중하기 때문이다.
코드로 생각하면 이런 구조다
실제 구현은 사용하는 라이브러리에 따라 달라지지만, 흐름은 대체로 비슷하다.
waveform, sr = load_audio(path)
waveform = to_mono(waveform)
waveform = resample(waveform, orig_sr=sr, target_sr=16000)
waveform = high_pass_filter(waveform, cutoff_hz=80)
waveform = normalize_level(waveform)
segments = detect_voice_segments(waveform, sr=16000)
chunks = make_chunks(waveform, segments, max_seconds=30, overlap_seconds=1)
texts = []
for chunk in chunks:
clean_chunk = optional_noise_reduction(chunk)
text = stt_model.transcribe(clean_chunk)
texts.append(text)
result = merge_texts(texts)
이 코드는 완성 코드라기보다 사고 순서에 가깝다. 중요한 것은 각 단계가 왜 존재하는지다. 리샘플링은 입력 규격을 맞추고, 필터는 명확한 주파수 문제를 줄이고, VAD는 말하는 구간을 찾고, chunking은 모델 입력 단위에 맞춘다.
STT에서 좋은 전처리는 보수적이다
음성 전처리는 많이 할수록 좋은 작업이 아니다. 좋은 전처리는 필요한 것만 한다. 특히 STT에서는 음성을 음악처럼 예쁘게 만드는 것이 목표가 아니라, 말소리의 식별 정보를 최대한 보존하면서 모델이 불필요한 잡음에 덜 흔들리게 만드는 것이 목표다.
정리하자면 첫 번째 원칙은 입력 규격을 맞추는 것이다. 두 번째 원칙은 말하지 않는 구간을 줄이는 것이다. 세 번째 원칙은 명확한 노이즈만 조심스럽게 줄이는 것이다. 네 번째 원칙은 전처리 전후의 STT 결과를 반드시 비교하는 것이다.
이 네 가지를 지키면 Whisper, OmniVoice 기반 TTS/STT 실험, 실시간 음성 에이전트, 회의록 자동화 같은 작업에서 음성 입력 품질을 훨씬 안정적으로 다룰 수 있다.