利用 “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,一经查实,立即删除!

相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

husky结合commitlint审查commit信息

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

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…

OpenGL 入门(一)— 创建窗口

文章目录 前言创建一个窗口视口动态调整输入控制渲染 完整代码 前言 关键词介绍&#xff1a; OpenGL&#xff1a; 一个定义了函数布局和输出的图形API的正式规范。GLFW&#xff1a;一个专门针对OpenGL的C语言库&#xff0c;它提供了一些渲染物体所需的最低限度的接口。它允许…

2024美赛数学建模思路 - 案例:最短时间生产计划安排

文章目录 0 赛题思路1 模型描述2 实例2.1 问题描述2.2 数学模型2.2.1 模型流程2.2.2 符号约定2.2.3 求解模型 2.3 相关代码2.4 模型求解结果 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 模型…

【doghead】2: 数据产生及pacing发送

默认采用fake的数据生产者 FakeDataProducer也可以读取h264文件生成:H264FileDataProducerUSE_FAKE_DATA_PRODUCER G:\CDN\BWE-DEV\Bifrost\worker\src\bifrost\bifrost_send_algorithm\bifrost_pacer.cpp FakeDataProducer 生产制造rtp包 ExperimentDumpData : 可用带宽、发…

【C/C++】详解程序环境和预处理(什么是程序环境?为什么要有程序环境?如何理解程序环境?)

目录 一、前言 二、 什么是程序环境&#xff1f; 三、 为什么要有程序环境&#xff1f; 四、如何理解程序环境&#xff1f; &#x1f34e; ANSI C 标准 &#x1f350; 翻译环境和执行环境 五、详解翻译环境和执行环境 &#x1f347;翻译环境&#xff08;重点&#xff01…

3ds Max宣传片怎么提升渲染速度?从硬件升级到云渲染,全面提升你的渲染速度!

在3ds Max中&#xff0c;渲染是一项耗时的任务&#xff0c;尤其是对于大型场景和复杂的动画。然而&#xff0c;通过一些优化策略和技巧&#xff0c;你可以显著加速渲染过程。以下是一些建议和技巧&#xff0c;帮助你提高3ds Max的渲染速度&#xff1a; 1.升级硬件&#xff1a; …

leetcode88合并两个有序数组

力扣&#xff08;LeetCode&#xff09;-合并两个有序数组 方法一 | 合并后排序 题目要求将两个有序数组合并并保证合并后的数组仍然有序。 观察题目可以看出&#xff0c;nums1的容量大小总是 mn&#xff0c;所以 nums2能够合并到 nums1中。 那就将 nums1中未赋值的地方赋上 …

AI Agents系列—— 探究大模型的推理能力,关于Chain-of-Thought的那些事儿

一、写在前面&#xff1a;关于AI Agents与CoT 本文是2023.07.24发表在同名公众号「陌北有棵树」上的一篇文章&#xff0c;个人观点是基础理论的学习现在仍是有必要的&#xff0c;所以搬运过来。 今天要读的论文是《Chain-of-Thought Prompting Elicits Reasoning in Large La…