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

비밀번호

댓글 작성자
 




... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11575정성태7/2/201824328Math: 28. GeoGebra 기하 (5) - 선분을 n 등분하는 방법파일 다운로드1
11574정성태7/2/201822980Math: 27. GeoGebra 기하 (4) - 선분을 n 배 늘이는 방법파일 다운로드1
11573정성태7/2/201820650Math: 26. GeoGebra 기하 (3) - 평행선
11572정성태7/1/201819952.NET Framework: 783. C# 컴파일러가 허용하지 않는 (유효한) 코드를 컴파일해 테스트하는 방법
11571정성태7/1/201821274.NET Framework: 782. C# - JIRA에 등록된 Project의 Version 항목 추가하는 방법파일 다운로드1
11570정성태7/1/201822733Math: 25. GeoGebra 기하 (2) - 임의의 선분과 특정 점을 지나는 수직선파일 다운로드1
11569정성태7/1/201820657Math: 24. GeoGebra 기하 (1) - 수직 이등분선파일 다운로드1
11568정성태7/1/201833275Math: 23. GeoGebra 기하 - 컴퍼스와 자를 이용한 작도 프로그램 [1]
11567정성태6/28/201823419.NET Framework: 781. C# - OpenCvSharp 사용 시 포인터를 이용한 속도 향상파일 다운로드1
11566정성태6/28/201828653.NET Framework: 780. C# - JIRA REST API 사용 정리 (1) Basic 인증 [4]파일 다운로드1
11565정성태6/28/201826492.NET Framework: 779. C# 7.3에서 enum을 boxing 없이 int로 변환하기 - 세 번째 이야기파일 다운로드1
11564정성태6/27/201824944.NET Framework: 778. (Unity가 사용하는) 모노 런타임의 __makeref 오류
11563정성태6/27/201821604개발 환경 구성: 386. .NET Framework Native compiler 프리뷰 버전 사용법 [2]
11562정성태6/26/201821294개발 환경 구성: 385. 레지스트리에 등록된 원격지 스크립트 COM 객체 실행 방법
11561정성태6/26/201834493.NET Framework: 777. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! [8]파일 다운로드1
11560정성태6/25/201825902.NET Framework: 776. C# 7.3 - 초기화 식에서 변수 사용 가능(expression variables in initializers)파일 다운로드1
11559정성태6/25/201833572개발 환경 구성: 384. 영문 설정의 Windows 10 명령행 창(cmd.exe)의 한글 지원 [6]
11558정성태6/24/201827071.NET Framework: 775. C# 7.3 - unmanaged(blittable) 제네릭 제약파일 다운로드1
11557정성태6/22/201824494.NET Framework: 774. C# - blittable 타입이란?파일 다운로드1
11556정성태6/19/201832755.NET Framework: 773. C# 7.3 - 구조체의 고정 크기를 갖는 fixed 배열 필드에 대한 직접 접근 가능 [1]파일 다운로드1
11555정성태6/18/201824741.NET Framework: 772. C# 7.3 - 사용자 정의 타입에 fixed 적용 가능(Custom fixed)파일 다운로드1
11554정성태6/17/201826427.NET Framework: 771. C# 7.3 - 자동 구현 속성에 특성 적용 가능(Attribute on backing field)
11553정성태6/15/201825022.NET Framework: 770. C# 7.3 - 개선된 메서드 선택 규칙 3가지(Improved overload candidates)파일 다운로드1
11552정성태6/15/201828234.NET Framework: 769. C# 7.3에서 개선된 문법 4개(Support == and != for tuples, Ref Reassignment, Constraints, Stackalloc initializers)파일 다운로드1
11551정성태6/14/201823979개발 환경 구성: 383. BenchmarkDotNet 사용 시 주의 사항
11550정성태6/13/201823088.NET Framework: 768. BenchmarkDotNet으로 Span<T> 성능 측정 [2]
... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...