利用 “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.stdout
        sys.stdout = devnull
        try:
            yield
        finally:
            sys.stdout = old_stdout

class 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 Whisper
        audio = waveform.data.astype("float32").reshape(-1)
        audio = whisper.pad_or_trim(audio)

        # Transcribe the given audio while suppressing logs
        with suppress_stdout():
            transcription = whisper.transcribe(
                self.model,
                audio,
                # We use past transcriptions to condition the model
                initial_prompt=self._buffer,
                verbose=True  # to avoid progress bar
            )

        return transcription

    def 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 timestamps
            start = 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 diarization
            speakers = dia.labels()
            num_speakers = len(speakers)
            if num_speakers == 0:
                # No speakers were detected
                caption = (-1, segment["text"])
            elif num_speakers == 1:
                # Only one speaker is active in this segment
                spk_id = int(speakers[0].split("speaker")[1])
                caption = (spk_id, segment["text"])
            else:
                # Multiple speakers, select the one that speaks the most
                max_speaker = int(np.argmax([
                    dia.label_duration(spk) for spk in speakers
                ]))
                caption = (max_speaker, segment["text"])
            speaker_captions.append(caption)

        return speaker_captions

    def __call__(self, diarization, waveform):
        # Step 1: Transcribe
        transcription = self.transcribe(waveform)
        # Update transcription buffer
        self._buffer += transcription["text"]
        # The audio may not be the beginning of the conversation
        time_shift = waveform.sliding_window.start
        # Step 2: Assign speakers
        speaker_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 500ms
    dops.rearrange_audio_stream(
        config.duration, config.step, config.sample_rate
    ),
    # Wait until a batch is full
    # The output is a list of audio chunks
    ops.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 chunk
    ops.map(concat),
    # Ignore this chunk if it does not contain speech
    ops.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 rich
    ops.map(colorize_transcription),
).subscribe(
    on_next=rich.print,  # print colored text
    on_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, SlidingWindow

def concat(chunks, collar=0.05):
    """
    Concatenate predictions and audio
    given a list of `(diarization, waveform)` pairs
    and merge contiguous single-speaker regions
    with 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 as
    a list of `(speaker: int, text: str)` pairs
    into 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 color
            result.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 中提供了完整的脚本。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu 22.04安装Nginx负载均衡

君衍. 一、编译安装Nginx二、轮询算法实现负载均衡三、加权轮询算法实现负载均衡四、ip_hash实现负载均衡 一、编译安装Nginx 这里我们先将环境准备好,我使用的是Ubuntu22.04操作系统: 这个是我刚安装好的,所以首先我们进行保存快照防止安装…

金融OCR领域实习日志(二)——四种OCR模型效果测试(附图)

文章目录 四种模型ocr效果简单测试模型场景1.paddle框架下PP-OCRv31.1.效果如下:1.2.总结 2.paddle框架下ppocr_server_v22.1.效果如下2.2.总结 3.CnOCR3.1.效果如下3.2.总结 4.TesseractOCR4.1.效果如下4.2.总结 5.后续想法 四种模型ocr效果简单测试 模型 PP-OCR…

文件操作---C++

文件操作目录 1.文本文件1.1写文件1.2读文件1.2.1第一种方式:流输入方式1.2.2第二种方式:getline成员函数1.2.3第三种方式:getline全局函数1.2.4第四种方式:按一个一个字符读取 2.二进制文件2.1写文件2.2读文件 程序运行时产生的数…

【C++进阶】STL容器--list使用迭代器问题分析

目录 前言 1. list的基本使用 1.1 list构造函数 1.2 list迭代器 1.3 list capacity 1.4 list元素访问 1.5 list 修改操作 insert erase swap resize clear 2. list失效迭代器问题 3. list使用算法库函数问题 总结 前言 list(链表)在C中非常重要…

分享7种SQL的进阶用法

分享7种SQL的进阶用法 前言 还只会使用SQL进行简单的insert、update、detele吗?本文给大家带来7种SQL的进阶用法,让大家在平常工作中使用SQL简化复杂的代码逻辑。 1.自定义排序(ORDER BY FIELD) 在MySQL中ORDER BY排序除了可以…

vue模拟聊天页面列表:滚动到底部,滚动到顶部触发加载更多

先看下效果&#xff1a; 代码&#xff1a; <template><div><div style"text-align: center"><button click"scrollTop">滚动到顶部</button><button click"scrollBottom">滚动到底部</button></d…

GitHub Copilot 与 ChatGPT:哪种工具更适合软件开发?

GitHub Copilot 与 ChatGPT&#xff1a;哪种工具更适合软件开发&#xff1f; 比较 ChatGPT 与 GitHub Copilot编程语言功能性定制化训练数据上下文准确性 ChatGPT 与 GitHub Copilot&#xff1a;哪个更适合软件开发&#xff1f;常见问题解答&#xff1a; 不断发展的编程世界正在…

基数排序算法

1. 排序算法分类 十种常见排序算法可以分为两大类&#xff1a; 比较类排序&#xff1a; 通过比较来决定元素间的相对次序&#xff0c;由于其时间复杂度不能突破O(nlogn)&#xff0c;因此也称为非线性时间比较类排序。比较类排序算法包括&#xff1a;插入排序、希尔排序、选择…

matlab绘图杂谈-stem函数和plot函数

出发点 今天在论文中看到一副这样的图&#xff0c;它既有曲线&#xff0c;又有点&#xff0c;并且对两者都添加了图例。三条曲线应该是用plot函数绘制的&#xff0c;而target哪个绿色的圆圈&#xff0c;我的理解是用stem函数绘制的。它只是1个点&#xff0c;并且没有竖线&…

Ps:可选颜色

可选颜色 Selective Color命令可以按指定的颜色&#xff08;范围&#xff09;进行单独的调整&#xff0c;且不会影响图像中的其他颜色。 Ps菜单&#xff1a;图像/调整/可选颜色 Adjustments/Selective Color Ps菜单&#xff1a;图层/新建调整图层/可选颜色 New Adjustment Laye…

Qt 基于海康相机 的视频标绘

需求&#xff1a; 基于 视频 进行 标注&#xff0c;从而进行测量。 曾经搞在线教育时&#xff0c;尝试在视频上进行文字或者图形的绘制&#xff0c;但是发现利用Qt widget 传sdk 句柄的方式&#xff0c;只能使用窗口叠加的方式&#xff08;Qt 基于海康相机的视频绘图_海康相…

【WPF.NET开发】WPF 中的 Layout

本文内容 元素边界框布局系统测量和排列子元素面板元素和自定义布局行为布局性能注意事项子像素渲染和布局舍入 本主题介绍 Windows Presentation Foundation (WPF) 布局系统。 了解布局计算发生的方式和时间对于在 WPF 中创建用户界面非常重要。 1、元素边界框 在 WPF 中构…

React中使用LazyBuilder实现页面懒加载方法一

前言&#xff1a; 在一个表格中&#xff0c;需要展示100条数据&#xff0c;当每条数据里面需要承载的内容很多&#xff0c;需要渲染的元素也很多的时候&#xff0c;容易造成页面加载的速度很慢&#xff0c;不能给用户提供很好的体验时&#xff0c;懒加载是优化页面加载速度的方…

算法基础之树状数组

文章目录 树状数组 树状数组 树状数组能解决的最关键的问题就是能够 O ( log ⁡ n ) O(\log n) O(logn)内&#xff0c;给某个位置上的数&#xff0c;加上一个数&#xff0c;或者求前缀和 他和前缀和数组的区别就是&#xff0c;树状数组支持修改原数组的内容&#xff0c;而前缀…

前端学习之——react篇(渲染列表)

你将依赖 JavaScript 的特性&#xff0c;例如 for 循环 和 array 的 map() 函数 来渲染组件列表。 假设你有一个产品数组&#xff1a; const products [{ title: Cabbage, id: 1 },{ title: Garlic, id: 2 },{ title: Apple, id: 3 }, ]; 在你的组件中&#xff0c;使用 map…

视频尺寸魔方:分层遮掩3D扩散模型在视频尺寸延展的应用

▐ 摘要 视频延展(Video Outpainting)是对视频的边界进行扩展的任务。与图像延展不同&#xff0c;视频延展需要考虑到填充区域的时序一致性&#xff0c;这使得问题更具挑战性。在本文中&#xff0c;我们介绍了一个新颖的基于扩散模型的视频尺寸延展方法——分层遮掩3D扩散模型(…

linux conda 配置 stable video diffusion

安装教程 1 下载仓库源码 git clone https://github.com/Stability-AI/generative-models.git2 创建conda环境 conda create -n svd python3.10 conda activate svd3 安装pytorch gpu cuda和cudnn请参考其他链接配置&#xff0c;使用 conda 或者 pip 安装 pytorch # 使用c…

Linux 驱动开发基础知识——编写LED驱动程序(三)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…

Vue开发之proxy代理的配置(附带uniapp代理配置)

vue 1.在vue.config.js中添加 devServer 属性中配置 proxy 属性 module.exports {productionSourceMap: false,publicPath: /,devServer: {port: 8085,proxy: {/api/admin: {target: http://10.58.104.70:6111,changeOrigin: true,pathRewrite: {/api/: /}},/api: {target: …

NIO-Channel详解

NIO-Channel详解 1.Channel概述 Channel即通道&#xff0c;表示打开IO设备的连接&#xff0c;⽐如打开到⽂件、Socket套接字的连接。在使⽤NIO时&#xff0c;必须要获取⽤于连接IO设备的通道以及⽤于容纳数据的缓冲区。通过操作缓冲区&#xff0c;实现对数据的处理。也就是说…