.Net 使用OpenAI开源语音识别模型Whisper

[复制链接]
查看949 | 回复0 | 2023-8-9 02:06:30 来自手机 | 显示全部楼层 |阅读模式
.Net 使用OpenAI开源语音识别模型 Whisper
前言

Open AI在2022年9月21日开源了号称其英文语音辨识能力已达到人类水准的 Whisper 神经网络,且它亦支持其它98种语言的自动语音辨识。 Whisper系统所提供的自动语音辨识(Automatic Speech Recognition,ASR)模型是被训练来运行语音辨识与翻译任务的,它们能将各种语言的语音变成文本,也能将这些文本翻译成英文。
whisper的核心功能语音识别,对于大部分人来说,可以帮助我们更快捷的将会议、讲座、课堂录音整理成文字稿;对于影视爱好者,可以将无字幕的资源自动生成字幕,不用再苦苦等待各大字幕组的字幕资源;对于外语口语学习者,使用whisper翻译你的发音练习录音,可以很好的检验你的口语发音水平。 当然,各大云平台都提供语音识别服务,但是基本都是联网运行,个人隐私安全总是有隐患,而whisper完全不同,whisper完全在本地运行,无需联网,充分保障了个人隐私,且whisper识别准确率相当高。
Whisper是C++写的,sandrohanea 对其进行了.Net封装。
本文旨在梳理我在.net web 项目中使用开源语音识别模型Whisper的过程,方便下次翻阅,如对您有所帮助不胜荣幸~
.Net Web 项目版本为:.Net 6.0

  
安装Whisper.net包

首先我们在Core项目中安装Whisper.net包。在NuGet包管理器中搜索并安装【Whisper.net】包,如下图所示:
注意,我们要找的是【Whisper.net】,不是【Whisper.net.Runtime】、【WhisperNet】、【Whisper.Runtime】。

下载模型文件

前往Hugging Face下载Whisper的模型文件,一共有 ggml-tiny.bin、ggml-base.bin、ggml-small.bin、ggml-medium.bin、ggml-large.bin 5个模型,文件大小依次变大,识别率也依次变大。此外,【xxx.en.bin】是英文模型,【xxx.bin】支持各国语言。
我们将模型文件放到项目中即可,我这里是放到Web项目的wwwroot下:

新建Whisper帮助类

WhisperHelper.cs


  1. using Whisper.net;
  2. using System.IO;
  3. using System.Collections.Generic;
  4. using Market.Core.Enum;
  5. namespace Market.Core.Util
  6. {
  7.     public class WhisperHelper
  8.     {
  9.         public static List<SegmentData> Segments { get; set; }
  10.         public static WhisperProcessor Processor { get; set; }
  11.         public WhisperHelper(ASRModelType modelType)
  12.         {
  13.             if(Segments == null || Processor == null)
  14.             {
  15.                 Segments = new List<SegmentData>();
  16.                 var binName = "ggml-large.bin";
  17.                 switch (modelType)
  18.                 {
  19.                     case ASRModelType.WhisperTiny:
  20.                         binName = "ggml-tiny.bin";
  21.                         break;
  22.                     case ASRModelType.WhisperBase:
  23.                         binName = "ggml-base.bin";
  24.                         break;
  25.                     case ASRModelType.WhisperSmall:
  26.                         binName = "ggml-small.bin";
  27.                         break;
  28.                     case ASRModelType.WhisperMedium:
  29.                         binName = "ggml-medium.bin";
  30.                         break;
  31.                     case ASRModelType.WhisperLarge:
  32.                         binName = "ggml-large.bin";
  33.                         break;
  34.                     default:
  35.                         break;
  36.                 }
  37.                 var modelFilePath = $"wwwroot/WhisperModel/{binName}";
  38.                 var factory = WhisperFactory.FromPath(modelFilePath);
  39.                 var builder = factory.CreateBuilder()
  40.                                      .WithLanguage("zh") //中文
  41.                                      .WithSegmentEventHandler(Segments.Add);
  42.                 var processor = builder.Build();
  43.                 Processor = processor;
  44.             }
  45.         }
  46.         /// <summary>
  47.         /// 完整的语音识别 单例实现
  48.         /// </summary>
  49.         /// <returns></returns>
  50.         public string FullDetection(Stream speechStream)
  51.         {
  52.             Segments.Clear();
  53.             var txtResult = string.Empty;
  54.             //开始识别
  55.             Processor.Process(speechStream);
  56.             //识别结果处理
  57.             foreach (var segment in Segments)
  58.             {
  59.                 txtResult += segment.Text + "\n";
  60.             }
  61.             Segments.Clear();
  62.             return txtResult;
  63.         }
  64.     }
  65. }
复制代码
ModelType.cs

不同的模型名字不一样,需要用一个枚举类作区分:

  1. using System.ComponentModel;
  2. namespace Market.Core.Enum
  3. {
  4.     /// <summary>
  5.     /// ASR模型类型
  6.     /// </summary>
  7.     [Description("ASR模型类型")]
  8.     public enum ASRModelType
  9.     {
  10.         /// <summary>
  11.         /// ASRT
  12.         /// </summary>
  13.         [Description("ASRT")]
  14.         ASRT = 0,
  15.         /// <summary>
  16.         /// WhisperTiny
  17.         /// </summary>
  18.         [Description("WhisperTiny")]
  19.         WhisperTiny = 100,
  20.         /// <summary>
  21.         /// WhisperBase
  22.         /// </summary>
  23.         [Description("WhisperBase")]
  24.         WhisperBase = 110,
  25.         /// <summary>
  26.         /// WhisperSmall
  27.         /// </summary>
  28.         [Description("WhisperSmall")]
  29.         WhisperSmall = 120,
  30.         /// <summary>
  31.         /// WhisperMedium
  32.         /// </summary>
  33.         [Description("WhisperMedium")]
  34.         WhisperMedium = 130,
  35.         /// <summary>
  36.         /// WhisperLarge
  37.         /// </summary>
  38.         [Description("WhisperLarge")]
  39.         WhisperLarge = 140,
  40.         /// <summary>
  41.         /// PaddleSpeech
  42.         /// </summary>
  43.         [Description("PaddleSpeech")]
  44.         PaddleSpeech = 200,
  45.     }
  46. }
复制代码
后端接受音频并识别

后端接口接受音频二进制字节码,并使用Whisper帮助类进行语音识别。

关键代码如下:
  1. public class ASRModel
  2. {
  3.         public string samples { get; set; }
  4. }
  5. /// <summary>
  6. /// 语音识别
  7. /// </summary>
  8. [HttpPost]
  9. [Route("/auth/speechRecogize")]
  10. public async Task<IActionResult> SpeechRecogizeAsync([FromBody] ASRModel model)
  11. {
  12.     ResultDto result = new ResultDto();
  13.     byte[] wavData = Convert.FromBase64String(model.samples);
  14.     model.samples = null;   //内存回收
  15.     // 使用Whisper模型进行语音识别
  16.     var speechStream = new MemoryStream(wavData);
  17.     var whisperManager = new WhisperHelper(model.ModelType);
  18.     var textResult = whisperManager.FullDetection(speechStream);
  19.     speechStream.Dispose();//内存回收
  20.     speechStream = null;
  21.     wavData = null; //内存回收
  22.     result.Data = textResult;
  23.     return Json(result.OK());
  24. }
复制代码
前端页面上传音频

前端主要做一个音频采集的工作,然后将音频文件转化成二进制编码传输到后端Api接口中
前端页面如下:

页面代码如下:
  1. @{
  2.     Layout = null;
  3. }
  4. @using Karambolo.AspNetCore.Bundling.ViewHelpers
  5. @addTagHelper *, Karambolo.AspNetCore.Bundling
  6. @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
  7. <!DOCTYPE html>
  8. <html>
  9. <head>
  10.     <meta charset="utf-8" />
  11.     <title>语音录制</title>
  12.     <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  13.     <environment names="Development">
  14.         <link href="~/content/plugins/element-ui/index.css" rel="stylesheet" />
  15.         <script></script>
  16.         <script></script>
  17.         <script></script>
  18.         <script></script>
  19.         <script></script>
  20.         <script></script>
  21.         <script></script>
  22.         <script></script>
  23.         <script></script>
  24.         <script></script>
  25.         <script></script>
  26.         <script></script>
  27.         <script></script>
  28.         <script></script>
  29.         <script></script>
  30.     </environment>
  31.     <environment names="Stage,Production">
  32.         @await Styles.RenderAsync("~/bundles/login.css")
  33.         @await Scripts.RenderAsync("~/bundles/login.js")
  34.     </environment>
  35.    
  36. </head>
  37. <body>
  38.    
  39.         
  40.             <center>{{isPC? '我是电脑版' : '我是手机版'}}</center>
  41.             <center style="margin: 10px 0">
  42.                 <el-radio-group v-model="modelType">
  43.                     <el-radio :label="0">ASRT</el-radio>
  44.                     <el-radio :label="100">WhisperTiny</el-radio>
  45.                     <el-radio :label="110">WhisperBase</el-radio>
  46.                     <el-radio :label="120">WhisperSmall</el-radio>
  47.                     <el-radio :label="130">WhisperMedium</el-radio>
  48.                     <el-radio :label="140">WhisperLarge</el-radio>
  49.                     <el-radio :label="200">PaddleSpeech</el-radio>
  50.                 </el-radio-group>
  51.             </center>
  52.             <el-button type="primary" size="small" onclick="window.location.href = '/'">返回</el-button>
  53.         
  54.         
  55.             @*{{textarea}}*@
  56.         
  57.         
  58.         <center style="height: 40px;"><h4 id="msgbox" v-if="messageSatuts">{{message}}</h4></center>
  59.         <button class="press" v-on:touchstart="start" v-on:touchend="end" v-if="!isPC">
  60.             按住 说话
  61.         </button>
  62.         <button class="press" v-on:mousedown="start" v-on:mouseup="end" v-else>
  63.             按住 说话
  64.         </button>
  65.    
  66. </body>
  67. </html>
  68. <script>
  69.     var blob_wav_current;
  70.     var rec;
  71.     var recOpen = function (success) {
  72.         rec = Recorder({
  73.             type: "wav",
  74.             sampleRate: 16000,
  75.             bitRate: 16,
  76.             onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) => {
  77.             }
  78.         });
  79.         rec.open(() => {
  80.             success && success();
  81.         }, (msg, isUserNotAllow) => {
  82.             app.textarea = (isUserNotAllow ? "UserNotAllow," : "") + "无法录音:" + msg;
  83.         });
  84.     };
  85.     var app = new Vue({
  86.         el: '#app',
  87.         data: {
  88.             textarea: '',
  89.             message: '',
  90.             messageSatuts: false,
  91.             modelType: 0,
  92.         },
  93.         computed: {
  94.             isPC() {
  95.                 var userAgentInfo = navigator.userAgent;
  96.                 var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPod", "iPad"];
  97.                 var flag = true;
  98.                 for (var i = 0; i < Agents.length; i++) {
  99.                     if (userAgentInfo.indexOf(Agents[i]) > 0) {
  100.                         flag = false;
  101.                         break;
  102.                     }
  103.                 }
  104.                 return flag;
  105.             }
  106.         },
  107.         methods: {
  108.             start() {
  109.                 app.message = "正在录音...";
  110.                 app.messageSatuts = true;
  111.                 recOpen(function() {
  112.                     app.recStart();
  113.                 });
  114.             },
  115.             end() {
  116.                 if (rec) {
  117.                     rec.stop(function (blob, duration) {
  118.                         app.messageSatuts = false;
  119.                         rec.close();
  120.                         rec = null;
  121.                         blob_wav_current = blob;
  122.                         var audio = document.createElement("audio");
  123.                         audio.controls = true;
  124.                         var dom = document.getElementById("wav_pannel");
  125.                         dom.appendChild(audio);
  126.                         audio.src = (window.URL || webkitURL).createObjectURL(blob);
  127.                         //audio.play();
  128.                         app.messageSatuts = false;
  129.                         app.upload();
  130.                     }, function (msg) {
  131.                         console.log("录音失败:" + msg);
  132.                         rec.close();
  133.                         rec = null;
  134.                     });
  135.                     app.message = "录音停止";
  136.                 }
  137.             },
  138.             upload() {
  139.                 app.message = "正在上传识别...";
  140.                 app.messageSatuts = true;
  141.                 var blob = blob_wav_current;
  142.                 var reader = new FileReader();
  143.                 reader.onloadend = function(){
  144.                     var data = {
  145.                         samples: (/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1],
  146.                         sample_rate: 16000,
  147.                         channels: 1,
  148.                         byte_width: 2,
  149.                         modelType: app.modelType
  150.                     }
  151.                     $.post('/auth/speechRecogize', data, function(res) {
  152.                         if (res.data && res.data.statusCode == 200000) {
  153.                             app.messageSatuts = false;
  154.                             app.textarea = res.data.text == '' ? '暂未识别出来,请重新试试' : res.data.text;
  155.                         } else {
  156.                             app.textarea = "识别失败";
  157.                         }
  158.                         var dom = document.getElementById("wav_pannel");
  159.                         var div = document.createElement("div");
  160.                         div.innerHTML = app.textarea;
  161.                         dom.appendChild(div);
  162.                         $('#wav_pannel').animate({ scrollTop: $('#wav_pannel')[0].scrollHeight - $('#wav_pannel')[0].offsetHeight });
  163.                     })
  164.                 };
  165.                 reader.readAsDataURL(blob);
  166.             },
  167.             recStart() {
  168.                 rec.start();
  169.             },
  170.         }
  171.     })
  172. </script>
复制代码
引用

whisper官网
测试离线音频转文本模型Whisper.net的基本用法
whisper.cpp的github
whisper.net的github
whisper模型下载

来源:https://blog.csdn.net/guigenyi/article/details/130955947
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

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

本版积分规则