Whisper_mic に faster_whisper を組込む 【Python】

スポンサーリンク
スポンサーリンク

はじめに

前回は、Whisper_mic の使用方法について説明しました。

今回は、Whisper の高速版である faster_wisper を、Whisper_mic に組込む方法について説明していきます。

前提条件

前提条件は以下の通りです。

  • Python == 3.10.6
  • Windows11

faster_whisper のインストール

faster_whisper は以下のコマンドでインストールできます。

python -m pip install faster-whisper
python -m pip install SpeechRecognition

今回は faster_whisper == 0.8.0, SpeechRecognition == 3.10.0 を使用します。

Whisper_mic の準備

前回と同じように準備します。

git clone https://github.com/mallorbc/whisper_mic.git
cd whisper_mic/whisper_mic

プログラムの変更

faster_whisper に対応できるようにプログラムを変更していきます。

cli.py main関数

def main(model: str, english: bool, verbose: bool, energy:  int, pause: float, dynamic_energy: bool, save_file: bool, device: str, loop: bool, dictate: bool,mic_index:Optional[int],list_devices:bool) -> None:
    if list_devices:
        print("Possible devices: ",sr.Microphone.list_microphone_names())
        return
    mic = WhisperMic(model=model, english=english, verbose=verbose, energy=energy, pause=pause, dynamic_energy=dynamic_energy, save_file=save_file, device=device,mic_index=mic_index)
    if not loop:
        result = mic.listen()
        for r in result:
            print(r.text)
        # print("You said: " + result)
    else:
        mic.listen_loop(dictate=dictate)

if __name__ == "__main__":
    main()

faster_whisper は結果をジェネレータで返すので、 for 文で取得しています。

whisper_mic.py

import torch
from faster_whisper import WhisperModel
# import whisper
import queue
import speech_recognition as sr
import threading
# import io
import numpy as np
# from pydub import AudioSegment
# import os
import tempfile
import time
import platform
import pynput.keyboard

# from whisper_mic.utils import get_logger
from utils import get_logger


class WhisperMic:
    def __init__(self,model="small",device=("cuda" if torch.cuda.is_available() else "cpu"),english=False,verbose=False,energy=300,pause=0.8,dynamic_energy=False,save_file=False, model_root="~/.cache/whisper",mic_index=None):
        self.logger = get_logger("whisper_mic", "info")
        self.energy = energy
        self.pause = pause
        self.dynamic_energy = dynamic_energy
        self.save_file = save_file
        self.verbose = verbose
        self.english = english
        self.keyboard = pynput.keyboard.Controller()

        self.platform = platform.system()

        if self.platform == "darwin":
            if device == "mps":
                self.logger.warning("Using MPS for Mac, this does not work but may in the future")
                device = "mps"
                device = torch.device(device)

        if (model != "large" and model != "large-v2") and self.english:
            model = model + ".en"
        
        # self.audio_model = whisper.load_model(model).to(device)
        model_size = "medium"
        self.audio_model = WhisperModel(model_size, device="cuda", compute_type="int8_float16")
        self.temp_dir = tempfile.mkdtemp() if save_file else None

        self.audio_queue = queue.Queue()
        self.result_queue: "queue.Queue[str]" = queue.Queue()

        self.break_threads = False
        self.mic_active = False

        self.banned_results = [""," ","\n",None]

        self.setup_mic(mic_index)


    def setup_mic(self, mic_index):
        if mic_index is None:
            self.logger.info("No mic index provided, using default")
        self.source = sr.Microphone(sample_rate=16000, device_index=mic_index)

        self.recorder = sr.Recognizer()
        self.recorder.energy_threshold = self.energy
        self.recorder.pause_threshold = self.pause
        self.recorder.dynamic_energy_threshold = self.dynamic_energy

        with self.source:
            self.recorder.adjust_for_ambient_noise(self.source)

        self.recorder.listen_in_background(self.source, self.record_callback, phrase_time_limit=2)
        self.logger.info("Mic setup complete, you can now talk")


    def preprocess(self, data):
        return torch.from_numpy(np.frombuffer(data, np.int16).flatten().astype(np.float32) / 32768.0)

    def get_all_audio(self, min_time: float = -1.):
        audio = bytes()
        got_audio = False
        time_start = time.time()
        while not got_audio or time.time() - time_start < min_time:
            while not self.audio_queue.empty():
                audio += self.audio_queue.get()
                got_audio = True

        data = sr.AudioData(audio,16000,2)
        data = data.get_raw_data()
        return data


    def record_callback(self,_, audio: sr.AudioData) -> None:
        data = audio.get_raw_data()
        self.audio_queue.put_nowait(data)


    def transcribe_forever(self) -> None:
        while True:
            # if self.break_threads:
            #     break
            self.transcribe()


    def transcribe(self,data=None, realtime: bool = False) -> None:
        if data is None:
            audio_data = self.get_all_audio()
        else:
            audio_data = data
        audio_data = self.preprocess(audio_data)
        if self.english:
            result = self.audio_model.transcribe(audio_data,language='english')
        else:
            if isinstance(audio_data, torch.Tensor):
                audio_data = audio_data.detach().numpy()
            result = self.audio_model.transcribe(audio=audio_data, language="ja", without_timestamps=True)

        predicted_text = result[0]
        if predicted_text not in self.banned_results:
            self.result_queue.put_nowait(predicted_text)

        # predicted_text = result["text"]
        # if not self.verbose:
        #     if predicted_text not in self.banned_results:
        #         self.result_queue.put_nowait(predicted_text)
        # else:
        #     if predicted_text not in self.banned_results:
        #         self.result_queue.put_nowait(result)

        # if self.save_file:
        #     os.remove(audio_data)


    def listen_loop(self, dictate: bool = False) -> None:
        # threading.Thread(target=self.transcribe_forever).start()
        threading.Thread(target=self.transcribe_forever, daemon=True).start()
        while True:
            result = self.result_queue.get()
            if dictate:
                self.keyboard.type(result)
            else:
                for r in result:
                    print(r.text)
                    # print(type(r.text))
                    if r.text == "終わり" or r.text == "おわり":
                        print("プログラムを終了します")
                        import sys
                        sys.exit()
                        
                # print(result)

    def listen(self, timeout: int = 3):
        audio_data = self.get_all_audio(timeout)
        self.transcribe(data=audio_data)
        while True:
            if not self.result_queue.empty():
                return self.result_queue.get()

    def toggle_microphone(self) -> None:
        #TO DO: make this work
        self.mic_active = not self.mic_active
        if self.mic_active:
            print("Mic on")
        else:
            print("turning off mic")
            self.mic_thread.join()
            print("Mic off")

whisper_mic.py は全文を記載しました。

変更点のみ説明していきます。

2 – 3 行目

from faster_whisper import WhisperModel
# import whisper

whisper ではなく faster_whisper を import します。

42 – 44 行目

# self.audio_model = whisper.load_model(model).to(device)
model_size = "medium"
self.audio_model = WhisperModel(model_size, device="cuda", compute_type="int8_float16")

モデルは medium を使用します。
compute_type は最も高速な int8 を指定します。

97 – 101 行目

def transcribe_forever(self) -> None:
    while True:
        # if self.break_threads:
        #     break
        self.transcribe()

self.break_threads は、スレッドスタート時に daemon = True としたので、不要です。

104 – 130 行目

def transcribe(self,data=None, realtime: bool = False) -> None:
    if data is None:
        audio_data = self.get_all_audio()
    else:
        audio_data = data
    audio_data = self.preprocess(audio_data)
    if self.english:
        result = self.audio_model.transcribe(audio_data,language='english')
    else:
        if isinstance(audio_data, torch.Tensor):
            audio_data = audio_data.detach().numpy()
        result = self.audio_model.transcribe(audio=audio_data, language="ja", without_timestamps=True)

    predicted_text = result[0]
    if predicted_text not in self.banned_results:
        self.result_queue.put_nowait(predicted_text)

    # predicted_text = result["text"]
    # if not self.verbose:
    #     if predicted_text not in self.banned_results:
    #         self.result_queue.put_nowait(predicted_text)
    # else:
    #     if predicted_text not in self.banned_results:
    #         self.result_queue.put_nowait(result)

    # if self.save_file:
    #     os.remove(audio_data)

whisper の入力は torch.Tensor で、faster_whisper の入力は numpy.ndarray なので、.detach().numpy() 形式に入力を変換します。

また、faster_whisper は日本語を指定し、より高速になる without_timestamps を有効にします。

133 – 149 行目

def listen_loop(self, dictate: bool = False) -> None:
    # threading.Thread(target=self.transcribe_forever).start()
    threading.Thread(target=self.transcribe_forever, daemon=True).start()
    while True:
        result = self.result_queue.get()
        if dictate:
            self.keyboard.type(result)
        else:
            for r in result:
                print(r.text)
                # print(type(r.text))
                if r.text == "終わり" or r.text == "おわり":
                    print("プログラムを終了します")
                    import sys
                    sys.exit()
                        
            # print(result)

threading.Thread() に daemon=True 追加しました。
sys.exit() 時にプログラム全体が終了するようになります。

また、result はジェネレータなので、for 文で一つずつ取得します。
話者が “おわり” と発音したらプログラムを終了するようにしました。

faster_whisper の Whisper_mic を動かしてみる

まずは loop 指定せず動かします。

python cli.py

結果は以下の通りです。

[09/17/23 08:51:31] INFO     No mic index provided, using default                                      whisper_mic.py:60
[09/17/23 08:51:33] INFO     Mic setup complete, you can now talk                                      whisper_mic.py:72
おはようございます。

次に loop 指定して動かします。

python cli.py --loop

結果は以下のようになります。

[09/17/23 10:34:38] INFO     No mic index provided, using default                                      whisper_mic.py:60
[09/17/23 10:34:40] INFO     Mic setup complete, you can now talk                                      whisper_mic.py:72
おはようございます。
寝ています
終わり
プログラムを終了します

しっかり検出できていますね!

おわりに

今回は faster_whisper を Whisper_mic に組込む方法について説明しました。

高速で正確に検出できています。
Whisper_mic の音声処理のおかげでノイズ等も除去され、誤検出が減っています。

コメント

タイトルとURLをコピーしました