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

news2024/11/27 23:41:21

.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包
  • 下载模型文件
  • 新建Whisper帮助类
    • WhisperHelper.cs
    • ModelType.cs
  • 后端接受音频并识别
  • 前端页面上传音频
  • 引用

安装Whisper.net包

首先我们在Core项目中安装Whisper.net包。在NuGet包管理器中搜索并安装【Whisper.net】包,如下图所示:

注意,我们要找的是【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;
        }
    }
}

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());
}

前端页面上传音频

前端主要做一个音频采集的工作,然后将音频文件转化成二进制编码传输到后端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 id="app">
        <div style="display: flex; justify-content: space-between; align-items: center;">
            <center>{{isPC? '我是电脑版' : '我是手机版'}}</center>
            <center style="margin: 10px 0">
                <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 class="content" id="wav_pannel">
            @*{{textarea}}*@
        </div>
        <div style="margin-top: 20px"></div>
        <center style="height: 40px;"><h4 id="msgbox" v-if="messageSatuts">{{message}}</h4></center>
        <button class="press" v-on:touchstart="start" v-on:touchend="end" v-if="!isPC">
            按住 说话
        </button>
        <button class="press" 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模型下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/590595.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python基础知识(四):input语句、if语句和pass语句

目录 1. input语句2. 强制转换3. if语句4. pass语句 1. input语句 input语句是程序获取从键盘输入的内容&#xff0c;会把输入的内容自动转换成字符串。 使用方法: 变量名 input(“提示语”) 例如 language input("你最爱什么语言?") print(language)这两行代码…

RK3588平台开发系列讲解(项目篇)常见模型结构

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、DNN二、CNN三、RNN沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 AI 模型常采用人工神经网络来模拟人脑神经的记忆和处理信号的能力。常见的人工神经网络类型有深度神经网络(Deep Neural Network,DNN)…

Vue中组件的几个重要点

1 单词命名组件名称 vue推荐的命名组件名称有以下几种&#xff1a; 首先看下组件有几个单词构成 单个单词 如果只有一个单词&#xff0c;那么建议全部小写&#xff0c;用的时候也是全部小写的&#xff0c;或者首字母大写 有人喜欢哪怕只有一个单词也首字母大写&#xff0c;…

读论文-GPRAR

论文&#xff1a;GPRAR: Graph Convolutional Network based Pose Reconstruction and Action Recognition for Human Trajectory Prediction&#xff08;2016&#xff09; 摘要 高精度的预测对于自动驾驶等各种应用至关重要。现有的预测模型在现实世界中很容易出错&#xff0…

linux【网络编程】之HTTP协议

一文了解应用层协议&#xff1a;HTTP协议 一、HTTP协议二、URL2.1 urlencode和urldecode 三、HTTP协议格式3.1 HTTP请求方法3.2 HTTP状态码3.3 HTTP响应报头 四、结合代码理解HTTP通信流程五、长连接六、http会话保持七、postman和fiddler 一、HTTP协议 在上篇文章中我们模拟了…

YOLOV5 + PYQT5双目测距

YOLOV5 PYQT5双目测距 1. 测距源码2. 测距原理3. PYQT环境配置4. 实验结果 1. 测距源码 详见文章 YOLOV5 双目测距&#xff08;python&#xff09; 2. 测距原理 如果想了解双目测距原理&#xff0c;请移步该文章 双目三维测距&#xff08;python&#xff09; 3. PYQT环境…

大数据需要学习哪些内容

Python 已成利器 在大数据领域中大放异彩 Python&#xff0c;成为职场人追求效率的利器&#xff0c;因为不管什么工作&#xff0c;数据都会是工作的一部分&#xff0c;有数据的地方&#xff0c;就有Python&#xff01; 我们知道&#xff0c;随着互联网的发展&#xff0c;线上…

数睿通2.0数据血缘、标准、质量功能更新发布

文章目录 引言数据血缘数据标准数据质量结语 引言 这段时间工作繁忙&#xff0c;琐事较多&#xff0c;加上二阳的冲击&#xff0c;导致数睿通 2.0 的更新进度缓慢&#xff0c;深表歉意&#xff0c;还望大家可以理解。本次更新主要包含数据治理模块的血缘&#xff0c;标准&…

阿里云的云安全审计可以用于哪些安全事件与合规审核?

阿里云的云安全审计可以用于哪些安全事件与合规审核&#xff1f; [本文由阿里云代理商[聚搜云www.4526.cn]撰写] 随着互联网的高速发展&#xff0c;各种互联网应用和服务也在不断涌现。但在运营过程中&#xff0c;安全事件和合规审核问题也不断出现。如何及时、准确地识别并解决…

[Nacos] Nacos Server之间的操作 (十一)

文章目录 1.ServiceManager#init()1.1 定时发送任务1.2 定时更新状态任务1.3 定时清除空service任务 1.ServiceManager#init() PostConstructpublic void init() {// 启动了一个定时任务&#xff1a;每60s当前Server会向其它Nacos Server发送一次本机注册表// 本机注册表是以各…

水声声波频率如何划分?水声功率放大器可将频率放大到20MHz吗?

水声声波频率如何划分&#xff1f;水声功率放大器可将频率放大到20MHz吗&#xff1f; 现如今我们可以在地球任意地区实现通信&#xff0c;是因为电磁波的作用。但是我们都知道海洋占了全球十分之七面积&#xff0c;电磁波在水下衰减速度太快&#xff0c;无法做到远距离传输&am…

linux内核内存管理slab

一、概述 linux内存管理核心是伙伴系统&#xff0c;slab&#xff0c;slub&#xff0c;slob是基于伙伴系统之上提供api&#xff0c;用于内核内存分配释放管理&#xff0c;适用于小内存&#xff08;小于&#xff11;页&#xff09;分配与释放&#xff0c;当然大于&#xff11;页…

Ext JS嵌套分组表格的实现

这里的嵌套分组表格指的是这样一种表格 表格的每一行可以展开下一层的Grid展开的嵌套表格是一个分组的表格显示的效果如下图: 这种显示的方式可以显示 3个层级的数据,比如这里的国家 、 将军等级、将军信息。 如果最外层再使用分组的表格, 则可以显示 4个层级的信息, 这种…

Ethercat学习-从站FOE固件更新(QT上位机)

文章目录 简介1、源码简介1、ec_FOEread2、ec_FOEwrite3、ec_FOEdefinehook 2、程序思路3、修改实现1、ecx_FOEwrite_gxf2、ecx_FOEread_gxf 4、其他5、结果6、源码连接 简介 FOE协议与下位机程序实现过程之前文章有提到&#xff0c;这里不做介绍了。这里主要介绍1、QT上位机通…

Java开发 - 让你少走弯路的Redis的主从复制

前言 大家举举手&#xff0c;让我看看还有多少人不会配置Redis的主从&#xff0c;主主这些的。故事发生在前段时间&#xff0c;小伙伴看到了博主的MySQL主从&#xff0c;就问博主有没有Redis的主从配置教程&#xff0c;本以为网上到处都是教程的博主打开网页一搜&#xff0c;好…

SpringCloud:分布式缓存之Redis主从

1.搭建主从架构 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 2.主从数据同步原理 2.1.全量同步 主从第一次建立连接时&#xff0c;会执行全量同步&#xff0c;将master节点的所有数据…

VSCode+Git+TortoiseGit+Gitee

目录 一、VSCode 1、VSCode(visual studio code)下载安装 2、VSCode使用技巧和经验 2.1、设置字体: 2.2、快捷方式 2.3、安装插件 二、Git下载安装 三、TortoiseGit 1、TortoiseGit 简介 2、下载安装Git及Tortoisegit 3、Tortoisegit拉取gitee仓库到本地 4、Git拉取…

Linux 终端安装并使用tmux管理远程会话 tmux使用教程

文章目录 1 Tmux简介1.1 会话与窗口1.2 tmux功能 2 tmux安装2.1 源码安装2.2 命令行安装 3 基本用法&#xff08;命令行&#xff09;3.1 创建窗口3.2 分离会话 切换会话3.3 连接会话3.4 关闭会话并杀死进行对会话进行重命名 4 Tmux 的快捷键5 窗口操作与窗格操作参考 1 Tmux简介…

Ctfshow基础二刷(1)

前言&#xff1a; 前两天的信安给我整emo了&#xff0c;头一回打正经比赛&#xff0c;结果发现基础太差&#xff0c;代码审计烂得一踏糊涂。 寻思寻思&#xff0c;从头整一遍基础。又买了安恒出的新书。争取7号去吉林打省队选拔不给导儿丢脸吧呜呜 文件包含 web78: 这题一…

前端gojs中禁用指定节点的选中效果

代码思路 适用于禁用某些节点的选中状态&#xff0c;选中节点时判断该节点要不要禁用 点击节点的时候&#xff0c;判断节点要不要禁用选中效果 如果禁用&#xff0c;就在选中时&#xff0c;把选中节点重置为最近一次非禁用的节点 diagram.select&#xff1a;选中节点 diagram.…