找回密码
 立即注册
首页 业界区 业界 C#AI系列(5): 从零开始 C# 轻松语音识别

C#AI系列(5): 从零开始 C# 轻松语音识别

梦霉 12 小时前
人工智能历经多年演进,昔日高门槛的图像与语音识别任务,如今已有成熟的开源框架可供免费使用,只要花点时间,就可以零成本部署。本文以语音识别为例,看如何高效的将语音识别功能集成至C#系统中,后续大家可以继续完善扩展,去处理如语音指令、语音交互、字幕生成、会议纪要分析、语音翻译等相关任务。
1.gif

本文项目在笔记本电脑上用cpu就可以自己动手轻松实现,所有代码均已开源,仅需关注 萤火初芒 公众号回复AISharp即可查看仓库地址,供学习交流使用,无套路。
一、环境配置基础

语音识别的方案有很多,windows系统本身也自带有语音识别的方案(System.Speech.Recognition),但是效果查强人意。既要简单好用,又要功能强大效果好,我们选择基于Whisper(MIT,https://github.com/openai/whisper)的Whisper.Net(MIT,https://github.com/sandrohanea/whisper.net)来实现。
2.png

Whisper 是2022年OpenAI发布的一个通用语音识别模型。它基于大量多样化的音频数据进行训练,同时也是一个多任务模型,能够执行多语言语音识别、语音翻译和语言识别任务。项目创建完成后直接在nuget拉取Whisper.net(1.9.0)和Whisper.net.Runtime(1.9.0)即可。
另外为了实现语音交互,还要在nuget拉取NAudio(2.2.1)(MIT,https://github.com/naudio/NAudio),以实现通过麦克风设备对声音的捕获。
二、核心代码实现

2.1 四行核心代码实现语音转文本

上面的控制台demo的main函数代码如下:
  1. private static readonly string OutFile = "d:\\record.wav";  //临时输出文件
  2. // 读取json配置文件,nuget拉取LumConfig,往期文章有介绍
  3. private static LumConfigManager con = new LumConfigManager("model.conf");
  4. private static void Main()
  5. {
  6.     // 1. 加载模型
  7.     // 输入模型地址,model文件夹下面提供了一个tiny版模型可以直接用
  8.     var wm = new WhisperManager((string)con.Get("modelPath"));
  9.     while (true)
  10.     {
  11.         bool started = false;
  12.         Console.WriteLine("按 Space 开始/停止录音");
  13.         while (true)
  14.         {
  15.             var key = Console.ReadKey(true).Key;
  16.             if (key == ConsoleKey.Spacebar)
  17.             {
  18.                 if (started)
  19.                 {
  20.                     // 2. 开始录音
  21.                     StopRec();
  22.                     break;
  23.                 }
  24.                 else
  25.                 {
  26.                     // 3. 结束录音
  27.                     StartRec();
  28.                 }
  29.                 started=!started;
  30.             }
  31.         }
  32.         // 4. 转文本
  33.         var res = wm.RunAsync(OutFile).GetAwaiter().GetResult();
  34.         Console.WriteLine(res);            
  35.     }
  36. }
复制代码
代码通过循环控制来实现反复读取语音再转换为文本的动作。其实核心代码只有四行,分别是加载模型、开始录音、停止录音、语音转文本。后面我们将对其一一拆解。
2.2 麦克风录制声音

麦克风的录制很简单,我们只需要完成2个动作,一个是开始录制声音,另一个是停止录制声音。WaveFileWriter方法两个重载,可以选择把声音录制到文件,也可以录制到流(stream)中。以录制到文件为例具体代码如下:
  1. private static void StartRec()
  2. {            
  3.     waveIn = new WaveInEvent
  4.     {
  5.         DeviceNumber = 0,                      // 默认麦克风
  6.         WaveFormat = new WaveFormat(16000, 1), // 44.1kHz 单声道
  7.         BufferMilliseconds = 100
  8.     };
  9.     writer = new WaveFileWriter(OutFile, waveIn.WaveFormat);
  10.     // 数据到达事件
  11.     waveIn.DataAvailable += (s, e) =>
  12.     {
  13.         writer.Write(e.Buffer, 0, e.BytesRecorded);
  14.     };
  15.     // 录音停止事件(负责释放 writer)
  16.     waveIn.RecordingStopped += (s, e) =>
  17.     {
  18.         writer?.Dispose();
  19.         writer = null;
  20.         waveIn?.Dispose();
  21.         waveIn = null;
  22.         stop.Set();
  23.     };
  24.     waveIn.StartRecording();
  25.     Console.WriteLine(">>> 正在录音……");
  26. }
  27. private static void StopRec()
  28. {
  29.     Console.WriteLine("<<< 停止录音");
  30.     waveIn?.StopRecording();   // 触发 RecordingStopped 事件
  31.     // 停止录制时,流数据的处理不一定已经完成
  32.     // 我们额外使用一个信号量来实现简单的同步
  33.     stop.WaitOne();  
  34. }
复制代码
Whisper重要基础参数配置:
WithLanguage,指定输入语音的语言,如zh、en、ja、auto(自动);
WithPrompt,输出提示词,这里与大语言模型不同,只能说引导模型生成,比如“以下是一个访谈。”。此处需要与指定语言一致,否则可能会强行切换,50~200 个字符足够,最好出现"标点符号"(不然可能会无标点)、"口语词、专有名词"(适配语境)。
另外还有很多与线程、输出相关的控制,大伙可根据需要自行研究了解。
2.4 模型、输入与输出

c#使用的Whisper模型是.bin的二进制格式的,现在可以在https://huggingface.co/ggerganov/whisper.cpp/tree/main下载。本项目里用到的是好久以前下载的ggml-model-whisper-tiny.bin,74mb,速度很快但效果中等。
本项目对Whisper的输入用的是wav格式的文件,且采样率必须为16kHz,否则会报错。如果导入音频时采样频率不对,可以用NAudio通过下代码进行转换:
  1. internal class WhisperManager:IDisposable
  2. {
  3.     WhisperFactory whisperFactory;
  4.     WhisperProcessor processor;
  5.     public WhisperManager(string path)
  6.     {
  7.         whisperFactory = WhisperFactory.FromPath(path);
  8.         processor = whisperFactory.CreateBuilder()
  9.             .WithLanguage("zh")
  10.             .WithPrompt("以下是,简体中文普通话的句子。")  // 否则大概率会输出繁体中文
  11.             .WithThreads(16)
  12.             .Build();            
  13.     }
  14.    
  15.     public async Task<string> RunAsync(string path)
  16.     {
  17.         try
  18.         {
  19.             var sb = new StringBuilder();
  20.             using var fileStream = File.OpenRead(path);
  21.             //  异步获取识别后的分段结果
  22.             await foreach (var seg in processor.ProcessAsync(fileStream))
  23.             {
  24.                 var str = $"{seg.Start}->{seg.End}: {seg.Text}\r\n";
  25.                 sb.AppendLine(str);
  26.                 var per = (fileStream.Position / fileStream.Length*100).ToString("N2");
  27.             }
  28.             return sb.ToString();
  29.         }
  30.         catch (Exception ex)
  31.         {
  32.             return ex.Message;
  33.         }
  34.     }
  35.     public void Dispose()
  36.     {
  37.         processor.Dispose();
  38.         whisperFactory.Dispose();
  39.     }
  40. }
复制代码
Whisper输出是分多个段(IAsyncEnumerable)输出的,包含如起始时间,文本,语言等多个信息,我们可以选择性的进行导出查看。
  1. public void Execute()
  2. {
  3.     //using (Mp3FileReader reader = new Mp3FileReader("D:\\Documents\\Temp\\WhisperDesktop\\上午对接结构录音.mp3"))
  4.     using (AudioFileReader reader = new AudioFileReader("D:\\Documents\\Temp\\WhisperDesktop\\录音.m4a"))
  5.     {
  6.       // 16kHz, 16bit,单声道
  7.       var newFormat = new WaveFormat(16000, 16, 1);
  8.       using (var conversionStream = new WaveFormatConversionStream(newFormat, reader))
  9.       {
  10.           WaveFileWriter.CreateWaveFile("D:\\Documents\\Temp\\WhisperDesktop\\录音.wav", conversionStream);
  11.       }            
  12.     }
  13. }
复制代码
打印的结果示意:
  1. public class SegmentData
  2. {
  3.     public string Text { get; } 文本
  4.     public TimeSpan Start { get; }
  5.     public TimeSpan End { get; }
  6.     public float MinProbability { get; }
  7.     public float MaxProbability { get; }
  8.     public float Probability { get; }
  9.     public string Language { get; }
  10.     public WhisperToken[] Tokens { get; }
  11.     public float NoSpeechProbability { get; }
  12. }
复制代码
三、最后


  • 在使用tiny模型下,整体速度和质量还是基本可以的,如果追求更好的效果则考虑使用更大模型,但与之对内存的需求及运算时间会相应大幅增加。
  • 在转换时,语音越长,转换速度越慢,因此建议在转换前,主动对长语音进行分段。尽管这样可能导致上下文丢失造成一定程度的不精确,但却能显著提高转换速度。
  • 语音识别是支持多语言混合的。但翻译的功能,试了下,失灵时不灵,可能和模型本身及大小有关。
感谢您的阅读,本案例及更加完整丰富的机器学习模型案例的代码已全部开源,新朋友们可以关注公众号回复AISharp查看仓库地址,本期相关代码在仓库下面的Voic文件夹里,在model文件夹内可以找到案例使用的74mb大小的ggml-model-whisper-tiny.bin小模型,大家可以自行尝试。
3.jpeg


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册