ToB企服应用市场:ToB评测及商务社交产业平台

标题: .Net 使用OpenAI开源语音辨认模子Whisper [打印本页]

作者: 写过一篇    时间: 2024-7-13 20:58
标题: .Net 使用OpenAI开源语音辨认模子Whisper
.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.Runtime】包,如下图所示:
注意,我们要找的是【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 src="~/content/plugins/jquery/jquery-3.4.1.min.js"></script>
  16.         <script src="~/content/js/matomo.js"></script>
  17.         <script src="~/content/js/slick.min.js"></script>
  18.         <script src="~/content/js/masonry.js"></script>
  19.         <script src="~/content/js/instafeed.min.js"></script>
  20.         <script src="~/content/js/headroom.js"></script>
  21.         <script src="~/content/js/readingTime.min.js"></script>
  22.         <script src="~/content/js/script.js"></script>
  23.         <script src="~/content/js/prism.js"></script>
  24.         <script src="~/content/js/recorder-core.js"></script>
  25.         <script src="~/content/js/wav.js"></script>
  26.         <script src="~/content/js/waveview.js"></script>
  27.         <script src="~/content/js/vue.js"></script>
  28.         <script src="~/content/plugins/element-ui/index.js"></script>
  29.         <script src="~/content/js/request.js"></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.     <style>
  36.         html,
  37.         body {
  38.             margin: 0;
  39.             height: 100%;
  40.         }
  41.         body {
  42.             padding: 20px;
  43.             box-sizing: border-box;
  44.         }
  45.         audio {
  46.             display:block;
  47.         }
  48.         audio + audio {
  49.             margin-top: 20px;
  50.         }
  51.         .el-textarea .el-textarea__inner {
  52.             color: #000 !important;
  53.             font-size: 18px;
  54.             font-weight: 600;
  55.         }
  56.         #app {
  57.             height: 100%;
  58.         }
  59.         .content {
  60.             height: calc(100% - 130px);
  61.             overflow: auto;
  62.         }
  63.         .content > div {
  64.             margin: 10px 0 20px;
  65.         }
  66.         .press {
  67.             height: 40px;
  68.             line-height: 40px;
  69.             border-radius: 5px;
  70.             border: 1px solid #dcdfe6;
  71.             cursor: pointer;
  72.             width: 100%;
  73.             text-align: center;
  74.             background: #fff;
  75.         }
  76.     </style>
  77. </head>
  78. <body>
  79.     <div id="app">
  80.         <div style="display: flex; justify-content: space-between; align-items: center;">
  81.             <center>{{isPC? '我是电脑版' : '我是手机版'}}</center>
  82.             <center style="margin: 10px 0">
  83.                 <el-radio-group v-model="modelType">
  84.                     <el-radio :label="0">ASRT</el-radio>
  85.                     <el-radio :label="100">WhisperTiny</el-radio>
  86.                     <el-radio :label="110">WhisperBase</el-radio>
  87.                     <el-radio :label="120">WhisperSmall</el-radio>
  88.                     <el-radio :label="130">WhisperMedium</el-radio>
  89.                     <el-radio :label="140">WhisperLarge</el-radio>
  90.                     <el-radio :label="200">PaddleSpeech</el-radio>
  91.                 </el-radio-group>
  92.             </center>
  93.             <el-button type="primary" size="small" onclick="window.location.href = '/'">返回</el-button>
  94.         </div>
  95.         <div class="content" id="wav_pannel">
  96.             @*{{textarea}}*@
  97.         </div>
  98.         <div style="margin-top: 20px"></div>
  99.         <center style="height: 40px;"><h4 id="msgbox" v-if="messageSatuts">{{message}}</h4></center>
  100.         <button class="press" v-on:touchstart="start" v-on:touchend="end" v-if="!isPC">
  101.             按住 说话
  102.         </button>
  103.         <button class="press" v-on:mousedown="start" v-on:mouseup="end" v-else>
  104.             按住 说话
  105.         </button>
  106.     </div>
  107. </body>
  108. </html>
  109. <script>
  110.     var blob_wav_current;
  111.     var rec;
  112.     var recOpen = function (success) {
  113.         rec = Recorder({
  114.             type: "wav",
  115.             sampleRate: 16000,
  116.             bitRate: 16,
  117.             onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) => {
  118.             }
  119.         });
  120.         rec.open(() => {
  121.             success && success();
  122.         }, (msg, isUserNotAllow) => {
  123.             app.textarea = (isUserNotAllow ? "UserNotAllow," : "") + "无法录音:" + msg;
  124.         });
  125.     };
  126.     var app = new Vue({
  127.         el: '#app',
  128.         data: {
  129.             textarea: '',
  130.             message: '',
  131.             messageSatuts: false,
  132.             modelType: 0,
  133.         },
  134.         computed: {
  135.             isPC() {
  136.                 var userAgentInfo = navigator.userAgent;
  137.                 var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPod", "iPad"];
  138.                 var flag = true;
  139.                 for (var i = 0; i < Agents.length; i++) {
  140.                     if (userAgentInfo.indexOf(Agents[i]) > 0) {
  141.                         flag = false;
  142.                         break;
  143.                     }
  144.                 }
  145.                 return flag;
  146.             }
  147.         },
  148.         methods: {
  149.             start() {
  150.                 app.message = "正在录音...";
  151.                 app.messageSatuts = true;
  152.                 recOpen(function() {
  153.                     app.recStart();
  154.                 });
  155.             },
  156.             end() {
  157.                 if (rec) {
  158.                     rec.stop(function (blob, duration) {
  159.                         app.messageSatuts = false;
  160.                         rec.close();
  161.                         rec = null;
  162.                         blob_wav_current = blob;
  163.                         var audio = document.createElement("audio");
  164.                         audio.controls = true;
  165.                         var dom = document.getElementById("wav_pannel");
  166.                         dom.appendChild(audio);
  167.                         audio.src = (window.URL || webkitURL).createObjectURL(blob);
  168.                         //audio.play();
  169.                         app.messageSatuts = false;
  170.                         app.upload();
  171.                     }, function (msg) {
  172.                         console.log("录音失败:" + msg);
  173.                         rec.close();
  174.                         rec = null;
  175.                     });
  176.                     app.message = "录音停止";
  177.                 }
  178.             },
  179.             upload() {
  180.                 app.message = "正在上传识别...";
  181.                 app.messageSatuts = true;
  182.                 var blob = blob_wav_current;
  183.                 var reader = new FileReader();
  184.                 reader.onloadend = function(){
  185.                     var data = {
  186.                         samples: (/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1],
  187.                         sample_rate: 16000,
  188.                         channels: 1,
  189.                         byte_width: 2,
  190.                         modelType: app.modelType
  191.                     }
  192.                     $.post('/auth/speechRecogize', data, function(res) {
  193.                         if (res.data && res.data.statusCode == 200000) {
  194.                             app.messageSatuts = false;
  195.                             app.textarea = res.data.text == '' ? '暂未识别出来,请重新试试' : res.data.text;
  196.                         } else {
  197.                             app.textarea = "识别失败";
  198.                         }
  199.                         var dom = document.getElementById("wav_pannel");
  200.                         var div = document.createElement("div");
  201.                         div.innerHTML = app.textarea;
  202.                         dom.appendChild(div);
  203.                         $('#wav_pannel').animate({ scrollTop: $('#wav_pannel')[0].scrollHeight - $('#wav_pannel')[0].offsetHeight });
  204.                     })
  205.                 };
  206.                 reader.readAsDataURL(blob);
  207.             },
  208.             recStart() {
  209.                 rec.start();
  210.             },
  211.         }
  212.     })
  213. </script>
复制代码
引用

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4