利用 “diart“ 和 OpenAI 的 Whisper 简化实时转录

利用 "diart" 和 OpenAI 的 Whisper 简化实时转录

工作原理

Diart 是一个基于人工智能的 Python 库,用于实时记录说话者语言(即 "谁在什么时候说话"),它建立在 pyannote.audio 模型之上,专为实时音频流(如麦克风)而设计。
只需几行代码,diart 就能让您获得类似这样的实时发言者标签:

1 KYmVPiXfLf7fWf2akWUk2A

与此同时,Whisper 是 OpenAI 最新推出的一种为自动语音识别(ASR)而训练的模型,它对嘈杂环境的适应能力特别强,非常适合现实生活中的使用案例。

准备工作

  1. 按照此处的说明安装 diart
  2. 使用 pip install git+https://github.com/linto-ai/whisper-timestamped 安装 whisper-timestamped

在这篇文章的其余部分,我将使用 RxPY(Python 的反应式编程扩展)来处理流媒体部分。如果你对它不熟悉,我建议你看看这个文档页面,了解一下基本知识。

简而言之,反应式编程就是对来自给定源(在我们的例子中是麦克风)的发射项(在我们的例子中是音频块)进行操作。

结合听和写

让我们先概述一下源代码,然后将其分解成若干块,以便更好地理解它。

import logging
import traceback
import diart.operators as dops
import rich
import rx.operators as ops
from diart import OnlineSpeakerDiarization, PipelineConfig
from diart.sources import MicrophoneAudioSource# Suppress whisper-timestamped warnings for a clean output
logging.getLogger("whisper_timestamped").setLevel(logging.ERROR)config = PipelineConfig(duration=5,step=0.5,latency="min",tau_active=0.5,rho_update=0.1,delta_new=0.57
)
dia = OnlineSpeakerDiarization(config)
source = MicrophoneAudioSource(config.sample_rate)asr = WhisperTranscriber(model="small")transcription_duration = 2
batch_size = int(transcription_duration // config.step)
source.stream.pipe(dops.rearrange_audio_stream(config.duration, config.step, config.sample_rate),ops.buffer_with_count(count=batch_size),ops.map(dia),ops.map(concat),ops.filter(lambda ann_wav: ann_wav[0].get_timeline().duration() > 0),ops.starmap(asr),ops.map(colorize_transcription),
).subscribe(on_next=rich.print, on_error=lambda _: traceback.print_exc())print("Listening...")
source.read()

创建发言者记录模块

首先,我们创建了流媒体(又称 "在线")扬声器日记系统以及与本地麦克风相连的音频源。

我们将系统配置为使用 5 秒的滑动窗口,步长为 500 毫秒(默认值),并将延迟设置为最小值(500 毫秒),以提高响应速度。

# If you have a GPU, you can also set device=torch.device("cuda")
config = PipelineConfig(duration=5,step=0.5,latency="min",tau_active=0.5,rho_update=0.1,delta_new=0.57
)
dia = OnlineSpeakerDiarization(config)
source = MicrophoneAudioSource(config.sample_rate)

配置中的三个附加参数可调节扬声器识别的灵敏度:

  • tau_active=0.5: 只识别发言概率高于 50% 的发言者。
  • rho_update=0.1: Diart 会自动收集发言者的信息以自我改进(别担心,这是在本地完成的,不会与任何人共享)。在这里,我们只使用每位发言者 100ms 以上的语音进行自我改进。
  • delta_new=0.57:这是一个介于 0 和 2 之间的内部阈值,用于调节新发言人的检测。该值越小,系统对语音差异越敏感。

创建 ASR 模块

接下来,我们使用我为这篇文章创建的 WhisperTranscriber 类加载语音识别模型。

# If you have a GPU, you can also set device="cuda"
asr = WhisperTranscriber(model="small")

该类的定义如下:

import os
import sys
import numpy as np
import whisper_timestamped as whisper
from pyannote.core import Segment
from contextlib import contextmanager@contextmanager
def suppress_stdout():# Auxiliary function to suppress Whisper logs (it is quite verbose)# All credit goes to: https://thesmithfam.org/blog/2012/10/25/temporarily-suppress-console-output-in-python/with open(os.devnull, "w") as devnull:old_stdout = sys.stdoutsys.stdout = devnulltry:yieldfinally:sys.stdout = old_stdoutclass WhisperTranscriber:def __init__(self, model="small", device=None):self.model = whisper.load_model(model, device=device)self._buffer = ""def transcribe(self, waveform):"""Transcribe audio using Whisper"""# Pad/trim audio to fit 30 seconds as required by Whisperaudio = waveform.data.astype("float32").reshape(-1)audio = whisper.pad_or_trim(audio)# Transcribe the given audio while suppressing logswith suppress_stdout():transcription = whisper.transcribe(self.model,audio,# We use past transcriptions to condition the modelinitial_prompt=self._buffer,verbose=True  # to avoid progress bar)return transcriptiondef identify_speakers(self, transcription, diarization, time_shift):"""Iterate over transcription segments to assign speakers"""speaker_captions = []for segment in transcription["segments"]:# Crop diarization to the segment timestampsstart = time_shift + segment["words"][0]["start"]end = time_shift + segment["words"][-1]["end"]dia = diarization.crop(Segment(start, end))# Assign a speaker to the segment based on diarizationspeakers = dia.labels()num_speakers = len(speakers)if num_speakers == 0:# No speakers were detectedcaption = (-1, segment["text"])elif num_speakers == 1:# Only one speaker is active in this segmentspk_id = int(speakers[0].split("speaker")[1])caption = (spk_id, segment["text"])else:# Multiple speakers, select the one that speaks the mostmax_speaker = int(np.argmax([dia.label_duration(spk) for spk in speakers]))caption = (max_speaker, segment["text"])speaker_captions.append(caption)return speaker_captionsdef __call__(self, diarization, waveform):# Step 1: Transcribetranscription = self.transcribe(waveform)# Update transcription bufferself._buffer += transcription["text"]# The audio may not be the beginning of the conversationtime_shift = waveform.sliding_window.start# Step 2: Assign speakersspeaker_transcriptions = self.identify_speakers(transcription, diarization, time_shift)return speaker_transcriptions

转录器执行一个简单的操作,接收音频块及其日记,并按照以下步骤操作:

  1. 用 Whisper 转录音频片段(带单词时间戳)
  2. 通过调整单词和说话人之间的时间戳,为转录的每个片段指定说话人

将两个模块放在一起

既然我们已经创建了日记化和转录模块,那么我们就可以定义对每个音频块应用的操作链:

import traceback
import rich
import rx.operators as ops
import diart.operators as dops# Split the stream into 2s chunks for transcription
transcription_duration = 2
# Apply models in batches for better efficiency
batch_size = int(transcription_duration // config.step)# Chain of operations to apply on the stream of microphone audio
source.stream.pipe(# Format audio stream to sliding windows of 5s with a step of 500msdops.rearrange_audio_stream(config.duration, config.step, config.sample_rate),# Wait until a batch is full# The output is a list of audio chunksops.buffer_with_count(count=batch_size),# Obtain diarization prediction# The output is a list of pairs `(diarization, audio chunk)`ops.map(dia),# Concatenate 500ms predictions/chunks to form a single 2s chunkops.map(concat),# Ignore this chunk if it does not contain speechops.filter(lambda ann_wav: ann_wav[0].get_timeline().duration() > 0),# Obtain speaker-aware transcriptions# The output is a list of pairs `(speaker: int, caption: str)`ops.starmap(asr),# Color transcriptions according to the speaker# The output is plain text with color references for richops.map(colorize_transcription),
).subscribe(on_next=rich.print,  # print colored texton_error=lambda _: traceback.print_exc()  # print stacktrace if error
)

在上述代码中,来自麦克风的所有音频块都将通过我们定义的操作链推送。

在这一系列操作中,我们首先使用 rearrange_audio_stream 将音频格式化为 5 秒钟的小块,小块之间的间隔为 500 毫秒。然后,我们使用 buffer_with_count 填充下一个批次,并应用日记化。请注意,批量大小的定义与转录窗口的大小相匹配。

接下来,我们将批次中不重叠的 500ms 日志化预测连接起来,并应用我们的 WhisperTranscriber,只有在音频包含语音的情况下才能获得说话者感知转录。如果没有检测到语音,我们就跳过这一大块,等待下一块。

最后,我们将使用 rich 库为文本着色并打印到标准输出中。

由于整个操作链可能有点晦涩难懂,我还准备了一个操作示意图,希望能让大家对算法有一个清晰的认识:

1 DTeXXBAuVSESFdemrieV2g

你可能已经注意到,我还没有定义 concat 和 colorize_transcriptions,但它们是非常简单的实用函数:

import numpy as np
from pyannote.core import Annotation, SlidingWindowFeature, SlidingWindowdef concat(chunks, collar=0.05):"""Concatenate predictions and audiogiven a list of `(diarization, waveform)` pairsand merge contiguous single-speaker regionswith pauses shorter than `collar` seconds."""first_annotation = chunks[0][0]first_waveform = chunks[0][1]annotation = Annotation(uri=first_annotation.uri)data = []for ann, wav in chunks:annotation.update(ann)data.append(wav.data)annotation = annotation.support(collar)window = SlidingWindow(first_waveform.sliding_window.duration,first_waveform.sliding_window.step,first_waveform.sliding_window.start,)data = np.concatenate(data, axis=0)return annotation, SlidingWindowFeature(data, window)def colorize_transcription(transcription):"""Unify a speaker-aware transcription represented asa list of `(speaker: int, text: str)` pairsinto a single text colored by speakers."""colors = 2 * ["bright_red", "bright_blue", "bright_green", "orange3", "deep_pink1","yellow2", "magenta", "cyan", "bright_magenta", "dodger_blue2"]result = []for speaker, text in transcription:if speaker == -1:# No speakerfound for this text, use default terminal colorresult.append(text)else:result.append(f"[{colors[speaker]}]{text}")return "\n".join(result)

如果您对 pyannote.audio 中使用的 Annotation 和 SlidingWindowFeature 类不熟悉,我建议您查看一下它们的官方文档页面。

在这里,我们使用 SlidingWindowFeature 作为音频块的 numpy 数组封装器,这些音频块还带有 SlidingWindow 实例提供的时间戳。
我们还使用 Annotation 作为首选数据结构来表示日记化预测。它们可被视为包含说话者 ID 以及开始和结束时间戳的片段有序列表。

结论

在这篇文章中,我们将 diart 流媒体扬声器日记库与 OpenAI 的 Whisper 结合起来,以获得实时的扬声器彩色转录。
为了方便起见,作者在 GitHub gist 中提供了完整的脚本。

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

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

相关文章

Linux CentOS 7 安装mysql的步骤

#wget命令用来从指定的URL下载文件 wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum install mysql-community-server 重启mysql服务 service mysqld restart || systemctl restart mysqld…

RabbitMQ之Window中安装配置与使用

RabbitMQ之Window中安装配置与使用 文章目录 RabbitMQ之Window中安装配置与使用1 安装准备1.1 安装Erlang1.2 ErLnag环境变量配置 2. 安装RabbitMQ2.1 安装2.2 环境变量设置 官网:https://www.rabbitmq.com 下载地址:https://www.rabbitmq.com/download.…

MATLAB中conv和filter函数的区别

文章目录 1.函数简介w conv(u,v)yfilter(b,a,x)讨论情况 2.先说结论3.代码验证 1.函数简介 w conv(u,v) w conv(u,v) 返回向量 u 和 v 的卷积结果,卷积运算是对信号的移位加权求和。 yfilter(b,a,x) 使用由分子和分母系数 b 和 a 定义的有理传递函数对输入数…

Git推送大量内容导致http 413错误

Git推送大量内容导致服务端HTTP 413错误 问题描述 使用git push 大量变更内容(超过60M)时报 http 413错误,详细错误信息: Compressing objects: 100% (2907/2907), done. Writing objects: 100% (6760/6760), 64.18 MiB | 1.18…

【日常学习笔记】gtest

https://zhuanlan.zhihu.com/p/369466622 int Factorial(int n) {int result 1;for (int i 1; i < n; i) {result * i;}return result; }TEST(TestSuiteName, TestName) 第一个参数可以表示为一个对某一个函数进行测试&#xff0c;第二个参数表示对这个函数测试时的分的几…

微信小程序 仿微信聊天界面

1. 需求效果图 2. 方案 为实现这样的效果&#xff0c;首先要解决两个问题&#xff1a; 2.1.点击输入框弹出软键盘后&#xff0c;将已有的少许聊天内容弹出&#xff0c;导致看不到的问题 点击输入框弹出软键盘后&#xff0c;将已有的少许聊天内容弹出&#xff0c;导致看不到的问…

银行数据仓库体系实践(8)--主数据模型设计

主数据区域中保留了数据仓库的所有基础数据及历史数据&#xff0c;是数据仓库中最重要的数据区域之一&#xff0c;那主数据区域中主要分为近源模型区和整合&#xff08;主题&#xff09;模型区。上一节讲到了模型的设计流程如下图所示。那近源模型层的设计在第2.3和3这两个步骤…

前端工程化之:webpack1-5(配置文件)

一、配置文件 webpack 提供的 cli 支持很多的参数&#xff0c;例如 --mode &#xff0c;但更多的时候&#xff0c;我们会使用更加灵活的配置文件来控制 webpack 的行为。 默认情况下&#xff0c; webpack 会读取 webpack.config.js 文件作为配置文件&#xff0c;但也可以通过 C…

《动手学深度学习(PyTorch版)》笔记4.2 4.3

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过。…

85 总结一下最近遇到的一些 jar发布 相关的知识

前言 呵呵 最近有一些构建服务, 发布服务的一些需求 我们这里的服务 一般来说是 java application, spring boot application 针对发布, 当然最好是 增量发布, 尽量的减少需要传递给 发布服务器 的资源的大小 比如 我的这个 java application, 可能会存在很多依赖, 常规…

探讨Go语言在构建HTTP代理时的优势和挑战

亲爱的读者&#xff0c;让我们一起来探讨一下Go语言在构建HTTP代理时的优势和挑战。 首先&#xff0c;让我们来谈谈Go语言在构建HTTP代理时的优势。Go语言是一种高性能的编程语言&#xff0c;它具有简洁、高效的特点&#xff0c;非常适合构建高效的代理服务器。使用Go语言&…

springboot第52集:微服务分布式架构,统一验证,oauth,订单,地区管理周刊

在计算机领域中&#xff0c;FGC 通常代表 Full Garbage Collection&#xff0c;即全垃圾收集。垃圾收集是一种自动管理内存的机制&#xff0c;它负责回收不再被程序使用的内存&#xff0c;以便释放资源和提高程序性能。 当系统执行 Full Garbage Collection 时&#xff0c;它会…

【代码随想录-数组】二分查找

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

Android源码设计模式解析与实战第2版笔记(三)

第三章 自由扩展你的项目–Builder 模式 Builder 模式的定义 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 Builder 模式的使用场景 相同的方法&#xff0c;不同的执行顺序&#xff0c;产生不同的事件结果时 多个部件或零件&…

【驱动系列】C#获取电脑硬件显卡核心代号信息

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《驱动系列》文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点…

msvcp140.dll丢失,有什么好的解决方法?

msvcp140.dll 是 Microsoft Visual C Redistributable Package 的一部分&#xff0c;这是一个由微软开发并发布的运行时库文件。具体而言&#xff1a; 功能与用途&#xff1a; msvcp140.dll 是动态链接库&#xff08;DLL&#xff09;文件&#xff0c;包含了 C 标准库的实现和…

CSS3如何实现从右往左布局的按钮组(固定间距)

可以通过下方CSS实现&#xff0c;下面的CSS表示按钮从右往左布局&#xff0c;且间距为10px: .right-btn {position: relative;float: right;margin-right: 10px; }类似这种&#xff1a; 这种&#xff1a; 注意&#xff1a; 不能使用right:10px代替margin-right:10px&#x…

STM32第三节——点亮第一个LED灯

1 STM32CubeMX新建工程 如果是第一次打开STM32CubeMX&#xff0c;软件会自动下载一些组件&#xff0c;等待下载完成即可。 1.2 点击ACCESS TO MCU SELECTOR 选择CPU型号&#xff0c;我用的是STM32F103ZET6&#xff0c;选择 STM32F103ZETx&#xff0c;可以点击旁边的收藏图标…

husky结合commitlint审查commit信息

commintlint是一个npm包用来规范化我们的commit信息&#xff0c;当然这个行为的操作时期是在git的commit-msg生命周期期间&#xff0c;这一点当然是有husky来控制&#xff0c;需要注意的是commit-msg作为一个git生命周期会被git commit和git merge行为唤醒&#xff0c;并且可以…

flutter tab页面切换练手,手势滑动、禁止滑动、page切换动画,禁止切换动画。

1&#xff1a;AppBar、TabBar、TabBarView实现页面切换&#xff0c;点击tab后tabBarView有左右切换动画&#xff0c;滑动page联动tabBar class DevicePage extends StatefulWidget {const DevicePage({super.key});overrideState<DevicePage> createState() > _Devic…