Closed5

「Silero VAD」を試す

kun432kun432

GitHubレポジトリ

https://github.com/snakers4/silero-vad

Silero VAD

Silero VAD - 事前学習済みのエンタープライズ向け音声活動検出器 (Voice Activity Detector)(また、STTモデルも参照してください)。

依存関係

x86-64システムでPythonの例を実行するためのシステム要件:

  • python 3.8+;
  • 1GB以上のRAM;
  • AVX、AVX2、AVX-512、またはAMX命令セットを備えた最新のCPU

依存パッケージ:

  • torch>=1.12.0;
  • torchaudio>=0.12.0(I/O用);
  • onnxruntime>=1.16.1(ONNXモデル使用時)。

Silero VADは、音声I/Oにtorchaudioライブラリを使用しています(torchaudio.infotorchaudio.load、およびtorchaudio.save)。そのため、適切なオーディオバックエンドが必要です:

  • オプション1 - FFmpegバックエンド。conda install -c conda-forge 'ffmpeg<7';
  • オプション2 - sox_ioバックエンド。apt-get install sox、TorchAudioはlibsox 14.4.2でテスト済み;
  • オプション3 - soundfileバックエンド。pip install soundfile

onnx-runtimeのみを使用してVADを実行する予定の場合、onnx-runtimeサポートされている他のシステムアーキテクチャでも動作します。この場合、以下に注意してください:

  • I/Oを実装する必要があります;
  • 既存のラッパー/例/後処理をユースケースに合わせて調整する必要があります。

主な特徴

  • 優れた精度
    Silero VADは、音声検出タスクで優れた結果を示します。
  • 高速
    単一のCPUスレッドで1つの音声チャンク(30ms以上)の処理にかかる時間は1ms未満です。バッチ処理やGPUを使用することで、さらにパフォーマンスを向上させることが可能です。条件によっては、ONNXが最大4〜5倍速く動作する場合もあります。
  • 軽量
    JITモデルのサイズは約2MBです。
  • 汎用性
    Silero VADは、6000以上の言語を含む膨大なデータセットで学習されており、さまざまな背景ノイズや音質の音声に対して良好に動作します。
  • 柔軟なサンプリングレート
    Silero VADは8000Hzおよび16000Hzサンプリングレートをサポートします。
  • 高い移植性
    Silero VADは、PyTorchおよびONNXのエコシステムから得られる利点を活用し、これらのランタイムが利用可能な環境であればどこでも動作します。
  • 制約なし
    許容的なMITライセンスの下で公開されており、Silero VADは制約が一切ありません - テレメトリなし、キーなし、登録不要、組み込みの有効期限なし、キーやベンダーロックもありません。

主な使用例

  • IoT / エッジ / モバイル向けの音声活動検出
  • データクリーニングおよび準備、一般的な音声検出
  • テレフォニーおよびコールセンターの自動化、音声ボット
  • 音声インターフェース
kun432kun432

事前準備

とりあえずローカルのMacでやってみる。

Python仮想環境を作成。自分はmiseを使うが、適宜。

mkdir silero-vad-work && cd silero-vad-work
mise use python@3.12
cat << 'EOS' >> .mise.toml

[env]
_.python.venv = { path = ".venv", create = true }
EOS

パッケージインストール

pip install silero-vad
pip freeze | grep -i silero
出力
silero-vad==5.1.2

あとオーディオバックエンドが必要になる。今回はsoundfileを使う。

pip install soundfile
pip freeze | grep -i soundfile
出力
soundfile==0.13.0

Fast start

適当なWAVファイルを食わせてみる。ただし、サンプリングレートは8000Hz/16000Hzである必要があるみたいなので、事前にあわせておく必要がある。

sample1.py
from silero_vad import load_silero_vad, read_audio, get_speech_timestamps
import json

model = load_silero_vad()
wav = read_audio('sample.wav')
speech_timestamps = get_speech_timestamps(
  wav,
  model,
  return_seconds=True,  # 秒単位でタイムスタンプを返す
)

print(json.dumps(speech_timestamps, indent=2))
python sample1.py

音声が検出された区間がリストで返される。

出力
[
  {
    "start": 0.9,
    "end": 6.2
  },
  {
    "start": 6.9,
    "end": 7.9
  },
  {
    "start": 8.1,
    "end": 9.3
  },
  {
    "start": 9.6,
    "end": 10.9
  },
  {
    "start": 11.5,
    "end": 12.5
  },
  {
    "start": 13.9,
    "end": 23.2
  },
  {
    "start": 23.5,
    "end": 26.1
  },
  {
    "start": 26.5,
    "end": 31.8
  },
  {
    "start": 31.9,
    "end": 36.5
  },
  {
    "start": 36.7,
    "end": 39.2
  },
  {
    "start": 40.1,
    "end": 40.7
  },
  {
    "start": 40.9,
    "end": 42.7
  },
  {
    "start": 44.4,
    "end": 44.9
  },
  {
    "start": 45.5,
    "end": 52.2
  },
  {
    "start": 52.4,
    "end": 53.4
  },
  {
    "start": 53.6,
    "end": 58.4
  },
  {
    "start": 58.6,
    "end": 61.3
  },
  {
    "start": 61.9,
    "end": 66.9
  },
  {
    "start": 67.0,
    "end": 73.3
  },
  {
    "start": 73.5,
    "end": 78.6
  },
  {
    "start": 78.8,
    "end": 81.5
  },
  {
    "start": 81.6,
    "end": 82.7
  },
  {
    "start": 82.8,
    "end": 84.9
  },
  {
    "start": 85.8,
    "end": 89.5
  },
  {
    "start": 89.6,
    "end": 103.1
  },
  {
    "start": 103.3,
    "end": 104.2
  },
  {
    "start": 104.5,
    "end": 107.1
  },
  {
    "start": 107.5,
    "end": 110.6
  },
  {
    "start": 110.7,
    "end": 119.2
  },
  {
    "start": 119.6,
    "end": 126.2
  },
  {
    "start": 126.4,
    "end": 132.3
  },
  {
    "start": 132.4,
    "end": 134.0
  },
  {
    "start": 134.1,
    "end": 135.2
  },
  {
    "start": 135.4,
    "end": 139.1
  },
  {
    "start": 139.4,
    "end": 140.3
  },
  {
    "start": 141.3,
    "end": 144.2
  },
  {
    "start": 144.3,
    "end": 149.2
  },
  {
    "start": 149.3,
    "end": 151.1
  },
  {
    "start": 151.3,
    "end": 155.6
  },
  {
    "start": 155.7,
    "end": 156.4
  },
  {
    "start": 156.7,
    "end": 157.6
  },
  {
    "start": 157.8,
    "end": 163.5
  },
  {
    "start": 163.7,
    "end": 173.7
  },
  {
    "start": 173.9,
    "end": 175.1
  },
  {
    "start": 175.2,
    "end": 177.1
  },
  {
    "start": 177.4,
    "end": 180.1
  },
  {
    "start": 180.3,
    "end": 183.3
  },
  {
    "start": 183.8,
    "end": 186.8
  },
  {
    "start": 188.6,
    "end": 191.3
  },
  {
    "start": 192.6,
    "end": 196.3
  },
  {
    "start": 197.0,
    "end": 202.8
  },
  {
    "start": 202.9,
    "end": 209.2
  },
  {
    "start": 209.9,
    "end": 212.5
  },
  {
    "start": 213.1,
    "end": 222.0
  },
  {
    "start": 222.3,
    "end": 228.9
  },
  {
    "start": 229.1,
    "end": 231.7
  },
  {
    "start": 232.4,
    "end": 234.0
  },
  {
    "start": 234.2,
    "end": 237.9
  },
  {
    "start": 239.0,
    "end": 244.1
  },
  {
    "start": 244.4,
    "end": 246.2
  },
  {
    "start": 247.7,
    "end": 252.6
  },
  {
    "start": 252.7,
    "end": 255.8
  },
  {
    "start": 255.9,
    "end": 266.1
  },
  {
    "start": 266.4,
    "end": 268.3
  }
]

load_silero_vad()で読み込まれるモデルはデフォルトはJITモデルとなっている。

https://github.com/snakers4/silero-vad/blob/v5.1.2/src/silero_vad/model.py#L5-L25

モデルは以下にあった。

ls -l ./.venv/lib/python3.12/site-packages/silero_vad/data

ONNXモデルもある。

出力
total 11496
-rw-r--r--@ 1 kun432  staff        0  1 15 16:57 __init__.py
drwxr-xr-x@ 3 kun432  staff       96  1 15 16:57 __pycache__
-rw-r--r--@ 1 kun432  staff  2269612  1 15 16:57 silero_vad.jit
-rw-r--r--@ 1 kun432  staff  2327524  1 15 16:57 silero_vad.onnx
-rw-r--r--@ 1 kun432  staff  1280395  1 15 16:57 silero_vad_half.onnx

以下にモデルごとのパフォーマンスが記載されている。

https://github.com/snakers4/silero-vad/wiki/Performance-Metrics#silero-vad-performance-metrics

おそらく現在のモデルはV5だと思うので、ONNXモデルのほうが処理性能は2倍弱上ということだと思われる。

ONNXモデルを使う場合は以下。

model = load_silero_vad(onnx=True)
kun432kun432

マイクからの入力を受け取って、無音・有音を検出するサンプル。

import numpy as np
import torch
from silero_vad import load_silero_vad, get_speech_timestamps
import pyaudio
import wave

# Silero VAD モデルのロード
print("Loading Silero VAD model...")
model = load_silero_vad()

# マイク入力の設定
CHUNK = 16000  # フレームサイズ (16000Hzのサンプリングレートに対応)
FORMAT = pyaudio.paInt16  # 音声フォーマット
CHANNELS = 1  # モノラル
RATE = 16000  # サンプリングレート

# PyAudio ストリームを初期化
audio = pyaudio.PyAudio()
stream = audio.open(format=FORMAT, channels=CHANNELS,
                    rate=RATE, input=True, frames_per_buffer=CHUNK)

print("Listening for audio...")

try:
    while True:
        # マイクからデータを読み取り
        data = stream.read(CHUNK, exception_on_overflow=False)

        # バイナリデータを数値データに変換
        # PyAudioはバイナリ形式でデータを提供するため、numpyを使ってint16型の数値配列に変換
        audio_data = np.frombuffer(data, dtype=np.int16)

        # 音声データを [-1.0, 1.0] の範囲に正規化
        # Silero VAD は正規化された音声データを期待するため、numpy配列を使って正規化
        audio_data = audio_data.astype(np.float32) / 32768.0

        # Silero VADで音声活動を判定
        speech_timestamps = get_speech_timestamps(
            audio_data, model, sampling_rate=RATE
        )

        # 判定結果を出力
        if speech_timestamps:
            print("O", end="", flush=True)
        else:
            print(".", end="", flush=True)
except KeyboardInterrupt:
    print("\n終了します...")
finally:
    # ストリームとリソースを解放
    stream.stop_stream()
    stream.close()
    audio.terminate()

以下のノートブックも参考になる。

https://github.com/snakers4/silero-vad/tree/master/examples/pyaudio-streaming/pyaudio-streaming-examples.ipynb

kun432kun432

発話の開始〜終了を検出して、発話部分をファイルに保存するサンプル。

import numpy as np
import torch
from silero_vad import load_silero_vad, get_speech_timestamps
import pyaudio
import wave

# Silero VAD モデルのロード
print("Loading Silero VAD model...")
model = load_silero_vad()

# マイク入力の設定
CHUNK = 16000  # フレームサイズ (1秒分のデータ)
FORMAT = pyaudio.paInt16  # 音声フォーマット
CHANNELS = 1  # モノラル
RATE = 16000  # サンプリングレート

# PyAudio ストリームを初期化
audio = pyaudio.PyAudio()
stream = audio.open(format=FORMAT, channels=CHANNELS,
                    rate=RATE, input=True, frames_per_buffer=CHUNK)

# 音声の一時保存用
audio_buffer = []  # 音声データを一時的に保存する
is_recording = False  # 現在有音かどうかを管理
counter = 1

print("Listening for audio...")

try:
    while True:
        # マイクからデータを読み取り
        data = stream.read(CHUNK, exception_on_overflow=False)

        # バイナリデータをnumpy配列に変換
        audio_data = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0

        # Silero VADで音声活動を判定
        speech_timestamps = get_speech_timestamps(audio_data, model, sampling_rate=RATE)

        if speech_timestamps:
            # 音声が検出された場合
            print("発話開始")
            audio_buffer.append(data)  # 音声データをバッファに保存
            is_recording = True
        elif is_recording:
            # 「有音 → 無音」になった場合にファイルに保存
            print("発話終了")
            is_recording = False

            # バッファを結合
            full_audio = b"".join(audio_buffer)
            audio_buffer = []  # バッファをクリア

            # バッファをファイルに保存
            output_file = f"output_{counter}.wav"
            with wave.open(output_file, "wb") as wf:
                wf.setnchannels(CHANNELS)
                wf.setsampwidth(audio.get_sample_size(FORMAT))
                wf.setframerate(RATE)
                wf.writeframes(full_audio)

            print(f"ファイルに保存しました: {output_file}")
            counter += 1
        else:
            # 無音時
            print("...")
except KeyboardInterrupt:
    print("\n終了します...")
finally:
    # ストリームとリソースを解放
    stream.stop_stream()
    stream.close()
    audio.terminate()
このスクラップは4時間前にクローズされました