nikkie-ftnextの日記

イベントレポートや読書メモを発信

声をPythonに聴かせて(後編:対処し、マイクの音声でも「変じゃないよ」)

はじめに

私の熱い『アイの歌声を聴かせて』ファン活動、アイカツ、はじまります!

この記事は、アイの歌声を聴かせてに関する一技術者なりのファン活動の一幕です。

"ポンコツAI"シオンの「音声を認識してテキストに変換する」機能の実装アウトプットの後編です。
「シオンが変だ!」(=マイクから入力した音声の書き起こしがイマイチ)を解決します。

espnet_model_zoospeech_recognition.Microphone、動作環境については前編を参照ください。

目次

TL; DR(ただし、Workaround)

import tempfile

import numpy as np
import soundfile as sf
import speech_recognition as sr
from espnet2.bin.asr_inference import Speech2Text
from scipy.io import wavfile

speech2text = Speech2Text.from_pretrained(
    "kan-bayashi/csj_asr_train_asr_transformer_raw_char_sp_valid.acc.ave"
)

r = sr.Recognizer()
with sr.Microphone(sample_rate=16_000) as source:
    print("なにか話してください")
    audio_data = r.listen(source)

frame_bytes = audio_data.get_raw_data()
speech_array = np.frombuffer(frame_bytes, dtype=np.int16)  # (1)
with tempfile.NamedTemporaryFile() as tempf:
    wavfile.write(  # (2)
        tempf.name,
        audio_data.sample_rate,
        speech_array,
    )
    audio_array, sampling_rate = sf.read(tempf.name)  # (3)

nbests = speech2text(audio_array)
text, tokens, *_ = nbests[0]
print(text)
  • マイクから入力した音声データ(bytes)をnp.int16のarrayに変換:(1)
  • scipy.io.wavfileを使い、np.int16のarrayを一時ファイルとして保存:(2)
  • soundfile.readで、一時ファイルをnp.floatのarrayとして読み込み:(3)

今回の学習済みモデルにはnp.floatのarrayを入力する必要がある

うまくいくケース

  • wavファイルをsoundfile.readで読み込み
  • arrayのdtypeはnp.float64

うまくいかないケース

  • マイクで受け取った音声を表すbytesをnp.frombufferでarrayに変換
  • arrayのdtypeはnp.int16

再現実験

2つの「こんにちは」のファイルを用意(man sayを参照しました)

say こんにちは -o hello.wav --data-format=LEF32@16000
say こんにちは -o i16_hello.wav --data-format=LEI16@16000

scipy.io.wavfileで読み込むとdtypeが異なる

>>> from scipy.io import wavfile
>>> sample_rate, frame_data = wavfile.read("i16_hello.wav")
>>> sample_rate2, frame_data2 = wavfile.read("hello.wav")
>>> frame_data.dtype, frame_data2.dtype
(dtype('int16'), dtype('float32'))

np.int16のarrayはうまく書き起こせない

>>> from espnet2.bin.asr_inference import Speech2Text
>>> speech2text = Speech2Text.from_pretrained("kan-bayashi/csj_asr_train_asr_transformer_raw_char_sp_valid.acc.ave")
>>> text, tokens, *_ = speech2text(frame_data2)[0]  # np.float32の方
>>> text
'今日は'

>>> text2, tokens2, *_ = speech2text(frame_data)[0]  # np.int16の方
>>> text2
'で'

soundfileで読み込むとdtypeがfloatに揃う(ので、どちらの音声も書き起こせる)

>>> import soundfile
>>> speech_array, sampling_rate = soundfile.read("i16_hello.wav")
>>> speech_array2, sampling_rate2 = soundfile.read("hello.wav")
>>> speech_array.dtype, speech_array2.dtype
(dtype('float64'), dtype('float64'))

解決するために、bytesをnp.floatのarrayに変換したい

ここがよく分からず、tempfileを使う方法が動作したのでそれで実装しました。
音声まわりの伸びしろがあるので、『Pythonで学ぶ音声認識』などで知識を補ってから、簡潔に書けるか考えてみます。

  • Microphonelistenして得られたAudioData
  • 音声を表す部分はAudioData.get_raw_data()で取得されるbytes
  • np.frombufferはdtypeのデフォルトがfloat
    • floatのarrayに変換してもテキストの書き起こしはうまくいかない
    • floatのarrayはsounddeviceplay1しても音が聞こえない(個々の要素の値がうまく変換できていないということ?)
  • soundfileのドキュメントにio.BytesIOを使う方法2もあったが、エラーが送出され未解決

動いた方法(=先のWorkaround)

  • AudioData.get_raw_data()で得られるbytesはnp.int16のarrayに変える(再生して音が聞こえる変換)
  • np.int16のarrayはscipy.io.wavfileで保存
  • soundfileで読み込み、モデルが要請するnp.floatを満たす

終わりに

シオン v0.0.1の音声認識機能は、(動かすのを優先した実装ではありますが、)スタンドアローンでも動作するようになりました!

公開されている機械学習モデル、すごくないですか!?
ブラックボックスなんですが、読み上げ・音声認識と実装できましたよ!

音声の扱いは広大な伸びしろが広がっているので、習熟してシオンを改良していきたいですね。

v0.0.1として残るのは、会話のエンジンですね(現在はオウム返し)。
これは「我に策あり」です!

ちょっとしたところだと、スクリプト実行したあと入力を受け付けるようになったら、シオンっぽく知らせたいなあ(「おはよう!」とか)

変更履歴

  • 2022/07/11: 「TL; DR(ただし、Workaround)」にてバグ修正(nbests = speech2text(speech_array)nbests = speech2text(audio_array)
    • dtypeをfloat64に変換する前のarrayを渡すコードになっていた