はじめに
おはようございます!AnyTechの渡邉です。最近はAIとお話しすることにハマってます。
本記事はおしゃべりAIをオフラインかつローカルで実装するシリーズです。
JARVISって?
映画アイアンマンに登場する、主人公を日常・戦闘・開発のあらゆる状況で助けてくれる架空の人工知能です。
声で話しかけて、それに応じてJARVISが情報を提供したり、アイアンマンを自律制御したりサポートしてくれます。
主人公の良き相棒であり、頼れる存在です。
シリーズ
こちらの手順を踏まえて、作っていきたいと思います。
※ 本シリーズはローカルUbuntuマシンにGPUがある場合を想定しております。
第一回:マイクからリアルタイムで音声認識する
第二回:音声認識した結果から返答生成する
第三回:返答を合成音声で喋らせる
第四回:キャラクターが喋っているような見た目を作る
第五回:今までで作ったものを組み合わせる
第一回:音声認識
日本語に対応していて、オフラインで音声認識する方法はいくつかあります。
- RealSense SDK
- ESPNet2
- Whisper
今回は精度も高く推論も速く、個人的にも長年お世話になっているESPNet2を使用させていただきます。
ESPNetは、End-to-Endの音声処理のツールであり、音声認識、合成音声、音声言語理解など幅広く音声言語系をカバーしています。実装はPyTorchでCNN、RNN、LSTM、Transformerなどのモデルが実装されています。
何を作るか
音声認識を常時起動しておいて、一定の音量を超えると録音して音声認識をしてテキストを標準出力する処理を作ります。
テキストを標準出力した後、再び音声認識を機動するループへ戻ります。
環境準備
必要なライブラリをインストールします。
sudo apt-get install libasound-dev portaudio19-dev libportaudio2 libportaudiocpp0 sudo apt-get install ffmpeg libav-tools pip install PyAudio==0.2.11 pip install SoundFile==0.10.3 pip install espnet==0.10.1 pip espnet-model-zoo==0.1.7
PyTorch系のバージョン:
torch==1.7.1+cu110 torch-complex==0.2.1 torch-optimizer==0.1.0 torch-summary==1.4.5 torchaudio==0.7.2 torchvision==0.8.2+cu110
処理の流れ
- マイクで音声を受け付ける
- 音量が閾値以上になったら録画開始
- 音量が下がったら録画した音声を保存
- 保存した音声を音声認識で日本語に文字起こして、1に戻る
コード
マイクから音声を受け取り、文字起こしするコードです。
tools/voice_recognize.py
:
import os import sys import soundfile import pyaudio import wave import numpy as np from espnet_model_zoo.downloader import ModelDownloader from espnet2.bin.asr_inference import Speech2Text FORMAT = pyaudio.paInt16 # Audio params chunk = 1024 #1つの音声チャンクの長さを1024サンプルとする threshold = 0.50 #マイクの音量がこれを超えると録音を開始する sampling_rate = 16000 #サンプリングレート、マイク性能に依存 record_seconds = 15 #録音時間の上限 record_cache_seconds = 0.9 stop_seconds = 2 #録音中断までの時間 B = int(sampling_rate / chunk * stop_seconds) A = int(sampling_rate) / int(chunk) * int(record_seconds) A_CACHE = int(sampling_rate) / int(chunk) * int(record_cache_seconds) def load_voice_recog_model(): d = ModelDownloader() model = Speech2Text( **d.download_and_unpack("Shinji Watanabe/laborotv_asr_train_asr_conformer2_latest33_raw_char_sp_valid.acc.ave"), device="cuda" ) return model def save_wave(all, audio_path): data = b''.join(all) out = wave.open(audio_path,'w') out.setnchannels(1) #mono out.setsampwidth(2) #16bits out.setframerate(sampling_rate) out.writeframes(data) out.close() def inference(speech2text, audio_path): speech, _ = soundfile.read(audio_path) nbests = speech2text(speech) text, *_ = nbests[0] return text def recognize(speech2text): audio_path = "speech.wav" p = pyaudio.PyAudio() stream = p.open(format=FORMAT, channels=1, rate=sampling_rate, input=True, frames_per_buffer=chunk) all = [] while True: #まずサンプルを取る data = stream.read(chunk, exception_on_overflow=False) x = np.frombuffer(data, dtype="int16") / 32768.0 xmax = x.max() print(xmax) if xmax <= threshold: all = [] for i in range(0, int(A_CACHE)): data_cache = stream.read(chunk, exception_on_overflow=False) all.append(data_cache) else: n = 0 #連続して閾値を下回った回数を記録するための変数 for i in range(0, int(A)): data = stream.read(chunk, exception_on_overflow=False) all.append(data) x = np.frombuffer(data, dtype="int16") / 32768.0 xmax = x.max() if xmax <= threshold: n += 1 #閾値を下回っていたらincrementする else: n = 0 #閾値を上回ったらリセットする # 中断判定 if n == B: break # 音声保存 save_wave(all, audio_path) all = [] # 音声認識 text = inference(sr_model, audio_path) print(f"Recognized text = {text}") if __name__ == '__main__': sr_model = load_voice_recog_model() recognize(sr_model)
実行
python tools/voice_recognize.py
まとめ
手元にGPUマシンがあれば、音声認識できてしまうのはすごいですよね! 次回は音声認識した結果に返答する処理の紹介をする予定です。