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

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...
NoWriterDateCnt.TitleFile(s)
1796정성태10/30/201428240개발 환경 구성: 249. Visual Studio 2013에서 Mono 컴파일하는 방법
1795정성태10/29/201430122개발 환경 구성: 248. Lync 2013 서버 설치 방법
1794정성태10/29/201425661개발 환경 구성: 247. "Microsoft Office 365 Enterprise E3" 서비스에 대한 간략 소개
1793정성태10/27/201426741.NET Framework: 474. C# - chromiumembedded 사용 - 두 번째 이야기 [2]파일 다운로드1
1792정성태10/27/201426799.NET Framework: 473. WebClient 객체에 쿠키(Cookie)를 사용하는 방법
1791정성태10/22/201426233VC++: 83. G++ - 템플릿 클래스의 iterator 코드 사용에서 발생하는 컴파일 오류 [5]
1790정성태10/22/201420979오류 유형: 254. NETLOGON Service is paused on [... AD Server...]
1789정성태10/22/201424880오류 유형: 253. 이벤트 로그 - The client-side extension could not remove user policy settings for '...'
1788정성태10/22/201426203VC++: 82. COM 프로그래밍에서 HRESULT 타입의 S_FALSE는 실패일까요? 성공일까요? [2]
1787정성태10/22/201434931오류 유형: 252. COM 개체 등록시 0x8002801C 오류가 발생한다면?
1786정성태10/22/201436417디버깅 기술: 65. 프로세스 비정상 종료 시 "Debug Diagnostic Tool"를 이용해 덤프를 남기는 방법 [3]파일 다운로드1
1785정성태10/22/201425632오류 유형: 251. 이벤트 로그 - Load control template file /_controltemplates/TaxonomyPicker.ascx failed [1]
1784정성태10/22/201432569.NET Framework: 472. C/C++과 C# 사이의 메모리 할당/해제 방법파일 다운로드1
1783정성태10/21/201426630VC++: 81. 프로그래밍에서 borrowing의 개념
1782정성태10/21/201423593오류 유형: 250. 이벤트 로그 - Application Server job failed for service instance Microsoft.Office.Server.Search.Administration.SearchServiceInstance
1781정성태10/21/201424669디버깅 기술: 64. new/delete의 짝이 맞는 경우에도 메모리 누수가 발생한다면?
1780정성태10/15/201428232오류 유형: 249. The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
1779정성태10/15/201423305오류 유형: 248. Active Directory에서 OU가 지워지지 않는 경우
1778정성태10/10/201421736오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201424782오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201421591오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201423166오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201429655개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201433188.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201422078.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201430496개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
... 121  122  123  124  125  126  127  128  129  130  131  132  133  [134]  135  ...