Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일

(시리즈 글이 2개 있습니다.)
.NET Framework: 300. C#으로 만드는 음성인식/TTS 프로그램
; https://www.sysnet.pe.kr/2/0/1228

닷넷: 2363. C# - Whisper.NET Library를 이용해 음성을 텍스트로 변환 및 번역하는 예제
; https://www.sysnet.pe.kr/2/0/14013




C# - Whisper.NET Library를 이용해 음성을 텍스트로 변환 및 번역하는 예제

고맙게도, OpenAI에서 Whisper 모델을 공개했는데요,

openai/whisper-large-v3
; https://huggingface.co/openai/whisper-large-v3

더욱 고맙게도, ^^ .NET 환경을 위한 라이브러리가 nuget에 공개돼 있어 이걸 사용하면 쉽게 음성을 텍스트로 변환할 수 있습니다.

Whisper.net
; https://www.nuget.org/packages/Whisper.net/

github에 보면, 대략적인 사용법도 알 수 있는데요,

sandrohanea/whisper.net
; https://github.com/sandrohanea/whisper.net

이걸로 간단하게 실습해 보겠습니다. (사실상 베끼는 것에 불과합니다. ^^)




우선 nuget으로부터 아래의 패키지를 설치하는 것으로 시작할 수 있습니다.

Install-Package Whisper.net.AllRuntimes

이름에서 알 수 있듯이 다양한 런타임을 한 번에 설치하는데요, AI 모델을 로컬에서 실행하기 때문에 특정 런타임을 제약할 수 있다면 개별로 선택해서 설치하는 것도 가능합니다.

// CPU만 사용하는 경우,
Install-Package Whisper.net
Install-Package Whisper.net.Runtime

// Cuda GPU를 사용하는 경우,
Install-Package Whisper.net
Install-Package Whisper.net.Runtime.Cuda.Windows

대충 감이 오시죠? ^^ 일단 위의 패키지 설치로 Whisper 라이브러리와 런타임은 설치하지만 정작 Model 파일은 별도로 다운로드해야 합니다. 재미있는 건, 이러한 모델 파일을 다운로드하는 기능도 라이브러리에서 제공한다는 점인데, 대충 아래와 같이 코딩할 수 있습니다.

using Whisper.net.Ggml;

namespace ConsoleApp1;

// Install-Package Whisper.net.AllRuntimes
internal class Program
{
    static async Task Main(string[] args)
    {
        var modelName = "ggml-base.bin";
        if (!File.Exists(modelName))
        {
            using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(GgmlType.Base);
            using var fileWriter = File.OpenWrite(modelName);
            await modelStream.CopyToAsync(fileWriter);
        }
    }
}

실행하면, EXE 파일이 있는 디렉터리에 ggml-base.bin 파일이 다운로드되고, WhisperFactory 타입을 이용해 해당 모델을 로드할 수 있습니다.

using var whisperFactory = WhisperFactory.FromPath(modelName);

using var processor = whisperFactory.CreateBuilder()
    .WithLanguage("auto")
    .Build();




자, 그럼 이제 음성을 텍스트로 변환하는 작업이 남았는데요, Whisper.NET 라이브러리는 wave 포맷을 지원하므로 다음과 같이 ProcessAsync 메서드에 전달하는 것으로 마무리할 수 있습니다.

string waveFileName = "audio.wav";

using var fileStream = File.OpenRead(wavFileName);

await foreach (var result in processor.ProcessAsync(fileStream))
{
    Console.WriteLine($"{result.Start}->{result.End}: {result.Text}");
}

실제로 결과의 품질까지 확인해 볼까요? ^^ 이에 대한 실습을 위해 한글 음성이 포함된 audio 파일을 구해야 하는데... Kaggle을 이용해 보는 것도 좋을 듯합니다.

Korean Single Speaker Speech Dataset
; https://www.kaggle.com/datasets/bryanpark/korean-single-speaker-speech-dataset

3GB 정도의 zip 파일을 해제하면 4GB 정도의 wav 파일들과 각각의 wav 음성에 대한 텍스트가 대응되는 정보를 가진 transcript.v.1.4.txt 파일이 나옵니다.

이것을 참조하면, "".\kss\1\1_0000.wav"" 경로의 파일은 "그는 괜찮은 척하려고 애쓰는 것 같았다."라는 텍스트에 해당하는 것을 알 수 있는데요, 이걸 whisper.net의 입력으로 쓰면 다음과 같은 결과가 나옵니다.

00:00:00->00:00:03.4800000:  그는 괜찮은 척하려고 S는 것 같았다.

"애쓰"라는 단어를 영문자 "S"로 인식했는데, 비록 원하는 결과와는 다르지만 어쨌든 음성 자체로는 비슷한 발음이라 나름대로 그럴싸한 결과가 나온 것 같습니다. ^^

혹시 명시적으로 언어를 지정하면 더 나은 결과가 나올까요?

using var processor = whisperFactory.CreateBuilder()
    .WithLanguage("ko")
    .Build();

아쉽게도 결과는 동일했습니다. 즉 "auto"로 지정한 경우 결국 "ko"로 인식한 것과 같다는 의미입니다. 참고로 이것을 "en"으로 지정하면,

00:00:00->00:00:03.5200000:  I think it's okay to use it.

이번에는 아예 번역까지 해버립니다. ^^; ASR 기능에 더해 번역 기능까지 있다니 놀라울 따름입니다.




"애쓰"를 "S"로 인식한 것은 locale 문제는 아닌 것 같으니, 그렇다면 모델의 크기를 키우면 좀 더 나아지지 않을까요? ^^

Whisper.NET이 다운로드한 모델 파일(위의 코드의 경우 "ggml-base.bin")은 140MB 정도에 불과한 매우 작은 크기의 모델입니다. 실제로 해당 모델은 Hugging Face의 whisper.cpp repo에서,

ggerganov/whisper.cpp
; https://huggingface.co/ggerganov/whisper.cpp/tree/main

ggml-base.bin 파일을 다운로드한 것인데요, 따라서 그것보다 좀 더 큰 모델, 가령 "ggml-large-v1.bin" 파일을 직접 다운로드하거나, 코드에서 그 모델을 다운로드하도록 바꾸면,

var modelName = "ggml-large-v1.bin";

if (!File.Exists(modelName))
{
    using var modelStream = await WhisperGgmlDownloader.Default.GetGgmlModelAsync(GgmlType.LargeV1);
    using var fileWriter = File.OpenWrite(modelName);
    await modelStream.CopyToAsync(fileWriter);
}

음성 인식뿐만 아니라 번역까지도 더 정확해집니다.

// "ko"인 경우,
00:00:00->00:00:03.5000000:  그는 괜찮은 척 하려고 애쓰는 것 같았다.

// "en"인 경우,
00:00:00->00:00:03.5000000:  He seemed to be trying to act okay.

오호~~~ 이제야 좀 쓸만하군요. ^^ (물론, 그만큼의 GPU 메모리와 연산 시간이 필요하다는 것은 감안해야 합니다.)

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




본문의 테스트를 "Korean Single Speaker Speech Dataset"의 wav 파일들을 대상으로 직접 테스트하면 이런 오류가 발생합니다.

Unhandled exception. Whisper.net.Wave.NotSupportedWaveException: Only 16KHz sample rate is supported.
   at Whisper.net.Wave.WaveParser.InternalInitialize(Boolean useAsync, CancellationToken cancellationToken)
   at Whisper.net.Wave.WaveParser.GetAvgSamplesAsync(CancellationToken cancellationToken)
   at Whisper.net.WhisperProcessor.ProcessAsync(Stream waveStream, CancellationToken cancellationToken)+MoveNext()
   at Whisper.net.WhisperProcessor.ProcessAsync(Stream waveStream, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   ...[생략]...

즉, 16KHz 샘플링 레이트만 지원한다는 건데요, 어쩔 수 없습니다. ^^ ffmpeg 등을 이용해 다음과 같이 (원본은 44KHz이므로) 16KHz로 변환해 두어야 합니다.

ffmpeg -i .\kss\1\1_0000.wav -ar 16000 1_0000_16khz.wav

혹은, NAudio 등의 라이브러리를 이용해 wav 파일의 포맷을 16KHz로 변환하면 될 텐데요, 이에 대해서는 whisper.net의 예제에도 잘 나와 있으니 참고하시고!

NAudio integration for resampled wav (whisper.net/examples/NAudioResampleWav/Program.cs)
; https://github.com/sandrohanea/whisper.net/blob/main/examples/NAudioResampleWav/Program.cs

덧붙이면 wav 포맷뿐만 아니라 mp3 등의 포맷도 결국 원시 스트림만 얻을 수 있다면 whisper.net에서 사용할 수 있습니다. 그리고 그에 대한 예제도 제공하므로 ^^ 쉽게 코딩할 수 있을 것입니다.

NAudio integration for mp3 (whisper.net/examples/NAudioMp3/Program.cs)
; https://github.com/sandrohanea/whisper.net/blob/main/examples/NAudioMp3/Program.cs




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 9/15/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 181  182  183  184  185  186  187  188  189  190  [191]  192  193  194  195  ...
NoWriterDateCnt.TitleFile(s)
287정성태6/24/200619341    답변글 Team Foundation Server: 7.1. 외부 빌드 머신 구성 - 다른 블로그 자료
275정성태6/7/200627358디버깅 기술: 4. VC++ 8.0 원격 디버깅 구성 - Side-by-Side DLL 문제.
269정성태6/6/200624250Team Foundation Server: 6. HTTPS를 통한 Team Server 접근 [1]
270정성태6/5/200621664    답변글 Team Foundation Server: 6.1. HTTPS를 통한 Team Server 접근 [1]
273정성태6/6/200624335    답변글 Team Foundation Server: 6.2. 두번째 방법 - HTTPS 를 통한 Team Server 접근 [1]
267정성태6/4/200623710Team Foundation Server: 5. 인터넷으로 Team Server 접근 [2]
266정성태6/8/200620016오류 유형: 7. [설치] mpoai9.dll 관련 오류
265정성태6/1/200627763디버깅 기술: 3. 원격 컴퓨터 디버깅 - VPC 설정
314정성태8/11/200625085    답변글 디버깅 기술: 3.1. Managed 원격 디버깅과 WinDBG 원격 디버깅
264정성태6/1/200634481오류 유형: 6. [VC++ 컴파일] already defined in ntdll.lib(ntdll.dll)
263정성태6/1/200635170디버깅 기술: 2. 커널 구조체 살펴보기 [5]
262정성태6/1/200627982오류 유형: 5. [설치] WinFX Beta2 - 설치시 문제점 해결
261정성태6/1/200623912웹: 3. IIS 6.0 - AppPool을 활용하여 실 서버(운영 서버)에서 디버깅
258정성태6/1/200631860디버깅 기술: 1. 디버깅 방법 - CLR 프로파일러 [1]파일 다운로드1
274정성태6/7/200624760    답변글 디버깅 기술: 1.1. 디버깅 방법 - CLR 프로파일러 ( on Vista )
254정성태6/1/200621104개발 환경 구성: 2. VPC에 Vista 설치하는 방법 [2]
255정성태6/1/200621118    답변글 개발 환경 구성: 2.1. msconfig 설정과 Windows Activation
259정성태6/1/200619974    답변글 개발 환경 구성: 2.2. Vista VPC에 터미널 서비스 - 원격 접속
253정성태6/1/200617724기타: 14. .NET 2.0 이 지원되는 NDoc 2.0 을 배포합니다.
251정성태6/1/200621130오류 유형: 4. [OS 지원 API] SHParseDisplayName과 Windows 2000
252정성태6/1/200620457    답변글 오류 유형: 4.1. NET BCL 에서 제공되는 FolderBrowserDialog [2]
249정성태6/1/200620464.NET Framework: 71. VB.NET 이외의 언어에서 My 네임스페이스 사용
250정성태6/1/200623467    답변글 .NET Framework: 71.1. VB.NET 이외의 언어에서 My 네임스페이스 사용
248정성태6/1/200621821기타: 13. Code Center Premium에서 Win32 API 소스 찾기
245정성태6/1/200629150오류 유형: 3. [C# / VC++] error C2146: syntax error : missing ';' before identifier 'GetType'
247정성태5/3/200626532    답변글 .NET Framework: 3.1. Interface를 사용하면. [1]
... 181  182  183  184  185  186  187  188  189  190  [191]  192  193  194  195  ...