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

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

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

前言

Open AI在2022年9月21日开源了号称其英文语音辨识能力已达到人类水准的 Whisper 神经网络,且它亦支持其它98种语言的自动语音辨识。 Whisper系统所提供的自动语音辨识(Automatic Speech Recognition,ASR)模型是被训练来运行语音辨识与翻译任务的,它们能将各种语言的语音变成文本,也能将这些文本翻译成英文。

>> 测试离线音频转文本模型Whisper.net的基本用法_ggml-large.bin-ZEEKLOG博客

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】。

image-20230530162444326

下载模型文件

前往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下:

image-20230530165740596

新建Whisper帮助类

WhisperHelper.cs

image-20230530170227200
using Whisper.net; using System.IO; using System.Collections.Generic; using Market.Core.Enum; namespace Market.Core.Util { public class WhisperHelper { public static List<SegmentData> Segments { get; set; } public static WhisperProcessor Processor { get; set; } public WhisperHelper(ASRModelType modelType) { if(Segments == null || Processor == null) { Segments = new List<SegmentData>(); var binName = "ggml-large.bin"; switch (modelType) { case ASRModelType.WhisperTiny: binName = "ggml-tiny.bin"; break; case ASRModelType.WhisperBase: binName = "ggml-base.bin"; break; case ASRModelType.WhisperSmall: binName = "ggml-small.bin"; break; case ASRModelType.WhisperMedium: binName = "ggml-medium.bin"; break; case ASRModelType.WhisperLarge: binName = "ggml-large.bin"; break; default: break; } var modelFilePath = $"wwwroot/WhisperModel/{binName}"; var factory = WhisperFactory.FromPath(modelFilePath); var builder = factory.CreateBuilder() .WithLanguage("zh") //中文 .WithSegmentEventHandler(Segments.Add); var processor = builder.Build(); Processor = processor; } } /// <summary> /// 完整的语音识别 单例实现 /// </summary> /// <returns></returns> public string FullDetection(Stream speechStream) { Segments.Clear(); var txtResult = string.Empty; //开始识别 Processor.Process(speechStream); //识别结果处理 foreach (var segment in Segments) { txtResult += segment.Text + "\n"; } Segments.Clear(); return txtResult; } } } 

写代码c#运行

ModelType.cs

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

image-20230530170534542
using System.ComponentModel; namespace Market.Core.Enum { /// <summary> /// ASR模型类型 /// </summary> [Description("ASR模型类型")] public enum ASRModelType { /// <summary> /// ASRT /// </summary> [Description("ASRT")] ASRT = 0, /// <summary> /// WhisperTiny /// </summary> [Description("WhisperTiny")] WhisperTiny = 100, /// <summary> /// WhisperBase /// </summary> [Description("WhisperBase")] WhisperBase = 110, /// <summary> /// WhisperSmall /// </summary> [Description("WhisperSmall")] WhisperSmall = 120, /// <summary> /// WhisperMedium /// </summary> [Description("WhisperMedium")] WhisperMedium = 130, /// <summary> /// WhisperLarge /// </summary> [Description("WhisperLarge")] WhisperLarge = 140, /// <summary> /// PaddleSpeech /// </summary> [Description("PaddleSpeech")] PaddleSpeech = 200, } } 

后端接受音频并识别

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

image-20230530171221152

关键代码如下:

public class ASRModel { public string samples { get; set; } } /// <summary> /// 语音识别 /// </summary> [HttpPost] [Route("/auth/speechRecogize")] public async Task<IActionResult> SpeechRecogizeAsync([FromBody] ASRModel model) { ResultDto result = new ResultDto(); byte[] wavData = Convert.FromBase64String(model.samples); model.samples = null; //内存回收 // 使用Whisper模型进行语音识别 var speechStream = new MemoryStream(wavData); var whisperManager = new WhisperHelper(model.ModelType); var textResult = whisperManager.FullDetection(speechStream); speechStream.Dispose();//内存回收 speechStream = null; wavData = null; //内存回收 result.Data = textResult; return Json(result.OK()); } 

AI写代码c#运行

前端页面上传音频

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

前端页面如下:

image-20230530134802045

页面代码如下:

@{ Layout = null; } @using Karambolo.AspNetCore.Bundling.ViewHelpers @addTagHelper *, Karambolo.AspNetCore.Bundling @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>语音录制</title> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <environment names="Development"> <link href="~/content/plugins/element-ui/index.css" rel="stylesheet" /> <script src="~/content/plugins/jquery/jquery-3.4.1.min.js"></script> <script src="~/content/js/matomo.js"></script> <script src="~/content/js/slick.min.js"></script> <script src="~/content/js/masonry.js"></script> <script src="~/content/js/instafeed.min.js"></script> <script src="~/content/js/headroom.js"></script> <script src="~/content/js/readingTime.min.js"></script> <script src="~/content/js/script.js"></script> <script src="~/content/js/prism.js"></script> <script src="~/content/js/recorder-core.js"></script> <script src="~/content/js/wav.js"></script> <script src="~/content/js/waveview.js"></script> <script src="~/content/js/vue.js"></script> <script src="~/content/plugins/element-ui/index.js"></script> <script src="~/content/js/request.js"></script> </environment> <environment names="Stage,Production"> @await Styles.RenderAsync("~/bundles/login.css") @await Scripts.RenderAsync("~/bundles/login.js") </environment> <style> html, body { margin: 0; height: 100%; } body { padding: 20px; box-sizing: border-box; } audio { display:block; } audio + audio { margin-top: 20px; } .el-textarea .el-textarea__inner { color: #000 !important; font-size: 18px; font-weight: 600; } #app { height: 100%; } .content { height: calc(100% - 130px); overflow: auto; } .content > div { margin: 10px 0 20px; } .press { height: 40px; line-height: 40px; border-radius: 5px; border: 1px solid #dcdfe6; cursor: pointer; width: 100%; text-align: center; background: #fff; } </style> </head> <body> <div> <div> <center>{{isPC? '我是电脑版' : '我是手机版'}}</center> <center> <el-radio-group v-model="modelType"> <el-radio :label="0">ASRT</el-radio> <el-radio :label="100">WhisperTiny</el-radio> <el-radio :label="110">WhisperBase</el-radio> <el-radio :label="120">WhisperSmall</el-radio> <el-radio :label="130">WhisperMedium</el-radio> <el-radio :label="140">WhisperLarge</el-radio> <el-radio :label="200">PaddleSpeech</el-radio> </el-radio-group> </center> <el-button type="primary" size="small" onclick="window.location.href = '/'">返回</el-button> </div> <div> @*{{textarea}}*@ </div> <div></div> <center><h4 v-if="messageSatuts">{{message}}</h4></center> <button v-on:touchstart="start" v-on:touchend="end" v-if="!isPC"> 按住 说话 </button> <button v-on:mousedown="start" v-on:mouseup="end" v-else> 按住 说话 </button> </div> </body> </html> <script> var blob_wav_current; var rec; var recOpen = function (success) { rec = Recorder({ type: "wav", sampleRate: 16000, bitRate: 16, onProcess: (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) => { } }); rec.open(() => { success && success(); }, (msg, isUserNotAllow) => { app.textarea = (isUserNotAllow ? "UserNotAllow," : "") + "无法录音:" + msg; }); }; var app = new Vue({ el: '#app', data: { textarea: '', message: '', messageSatuts: false, modelType: 0, }, computed: { isPC() { var userAgentInfo = navigator.userAgent; var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPod", "iPad"]; var flag = true; for (var i = 0; i < Agents.length; i++) { if (userAgentInfo.indexOf(Agents[i]) > 0) { flag = false; break; } } return flag; } }, methods: { start() { app.message = "正在录音..."; app.messageSatuts = true; recOpen(function() { app.recStart(); }); }, end() { if (rec) { rec.stop(function (blob, duration) { app.messageSatuts = false; rec.close(); rec = null; blob_wav_current = blob; var audio = document.createElement("audio"); audio.controls = true; var dom = document.getElementById("wav_pannel"); dom.appendChild(audio); audio.src = (window.URL || webkitURL).createObjectURL(blob); //audio.play(); app.messageSatuts = false; app.upload(); }, function (msg) { console.log("录音失败:" + msg); rec.close(); rec = null; }); app.message = "录音停止"; } }, upload() { app.message = "正在上传识别..."; app.messageSatuts = true; var blob = blob_wav_current; var reader = new FileReader(); reader.onloadend = function(){ var data = { samples: (/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1], sample_rate: 16000, channels: 1, byte_width: 2, modelType: app.modelType } $.post('/auth/speechRecogize', data, function(res) { if (res.data && res.data.statusCode == 200000) { app.messageSatuts = false; app.textarea = res.data.text == '' ? '暂未识别出来,请重新试试' : res.data.text; } else { app.textarea = "识别失败"; } var dom = document.getElementById("wav_pannel"); var div = document.createElement("div"); div.innerHTML = app.textarea; dom.appendChild(div); $('#wav_pannel').animate({ scrollTop: $('#wav_pannel')[0].scrollHeight - $('#wav_pannel')[0].offsetHeight }); }) }; reader.readAsDataURL(blob); }, recStart() { rec.start(); }, } }) </script> 

引用

whisper官网

测试离线音频转文本模型Whisper.net的基本用法

whisper.cpp的github

whisper.net的github

whisper模型下载

Read more

《发现了一种本地AI服务远程管理难题与一种加密隧道解决方案!》

《发现了一种本地AI服务远程管理难题与一种加密隧道解决方案!》

现在用着开源大语言模型、Stable Diffusion这类AI工具的人越来越多了,不少开发者都选在自己家或者公司的本地硬件上搭AI服务,比如带显卡的台式机、Linux服务器,还有NAS设备都行。这么弄确实能完全自己掌控隐私,数据也全在自己手里,但麻烦事儿也来了:怎么才能安全又方便地从外面的网络远程访问、管理这些本地的AI服务呢? 以前常用的端口映射办法吧,不安全;要搭VPN的话,步骤又太复杂,一般人搞不定。今天咱们就聊聊用P2P虚拟组网技术做的那种简单好用的解决办法。 本地部署AI后,常见的远程访问需求包括: 1. 状态监控:在外查看服务的CPU/GPU占用、日志和运行状态。 2. 交互操作:远程使用WebUI(如ChatGPT-Next-Web、Stable Diffusion WebUI)进行推理或生图。 3. 文件管理:安全地传输生成的文件或更新模型。 直接通过公网IP+端口暴露服务,相当于将内网服务置于公网扫描之下,极易成为攻击目标。而商用远程桌面软件通常延迟较高,且不适合长期后台服务管理。 一种思路:如果构建加密的虚拟局域网呢? 理想的方案是,让远程设

微信4.1.5.16 UI树“消失”?UIAutomation实战复现+AI驱动RPA落地方案

微信4.1.5.16 UI树“消失”?UIAutomation实战复现+AI驱动RPA落地方案

适用人群:桌面RPA开发者、自动化测试工程师、GUI Agent搭建者 关键词:微信4.1.5.X、UIAutomation、UI树恢复、微信RPA、AI私域运营 用过PC微信4.1.x版本的开发者大概率都遇到过一个棘手问题:升级前用Inspect、FlaUI或pywinauto能轻松抓取完整UI树,控件定位、脚本执行行云流水;升级后UI树几乎“清空”,仅剩一两个根节点,之前的自动化脚本全部失效。这并非工具故障,而是微信在界面架构和无障碍暴露策略上的重大调整。本文将从原理拆解、技术实现到实战落地,带你彻底解决UI树“消失”问题,还会附上可直接运行的代码和AI+RPA的进阶方案。 一、核心问题:微信4.1.5.16为何隐藏UI树? PC微信从4.0版本开启了多端UI框架统一重构,4.1.5.16更是在UIAutomation暴露机制上做了关键优化,这也是UI树“消失”的根本原因。 1.

【粉丝福利社】扣子(Coze) Skills+OpenClaw 实战:零基础玩转AI智能体

【粉丝福利社】扣子(Coze) Skills+OpenClaw 实战:零基础玩转AI智能体

💎【行业认证·权威头衔】 ✔ 华为云天团核心成员:特约编辑/云享专家/开发者专家/产品云测专家 ✔ 开发者社区全满贯:ZEEKLOG博客&商业化双料专家/阿里云签约作者/腾讯云内容共创官/掘金&亚马逊&51CTO顶级博主 ✔ 技术生态共建先锋:横跨鸿蒙、云计算、AI等前沿领域的技术布道者 🏆【荣誉殿堂】 🎖 连续三年蝉联"华为云十佳博主"(2022-2024) 🎖 双冠加冕ZEEKLOG"年度博客之星TOP2"(2022&2023) 🎖 十余个技术社区年度杰出贡献奖得主 📚【知识宝库】 覆盖全栈技术矩阵: ◾ 编程语言:.NET/Java/Python/Go/Node… ◾ 移动生态:HarmonyOS/iOS/Android/小程序 ◾ 前沿领域:

Python + Ollama 本地跑大模型:零成本打造私有 AI 助手

Python + Ollama 本地跑大模型:零成本打造私有 AI 助手

零 API 费用、零数据泄露风险、完全离线可用。本文带你从安装到实战,30 分钟跑起一个本地 AI 助手。 一、为什么要在本地跑大模型? 对比维度云端 API(ChatGPT / Claude)本地模型(Ollama)费用按量付费,$20/月起完全免费数据隐私数据上传到云端数据留在本地网络依赖必须联网离线可用模型选择固定自由切换开源模型硬件要求无需要一定配置 38%27%18%12%5%选择本地大模型的理由(2026年开发者调查)数据隐私与安全零成本长期使用离线可用可自由定制微调其他 二、Ollama 是什么? Ollama 是一个开源的本地大模型运行框架,核心特点: * 一键拉取模型:类似 docker pull 的体验 * 自动适配硬件:根据你的显存/内存自动量化 * 兼容 OpenAI API 格式:现有代码几乎不用改 * 跨平台:Windows