はじめに
私の熱い『アイの歌声を聴かせて』ファン活動、アイカツ、はじまります!
私のアイカツ(#アイの歌声を聴かせて 技術者なりのファン活動)、シオン v0.0.1に大きく近づきました🙌
— nikkie にっきー / アイうた円盤📀発売アドカレ中 (@ftnext) 2022年2月13日
この嬉しさ、「やったな、サンダー」級!!
プログラムを起動してから音声で入力、
すると音声でオウム返ししてくれます!😆
(デモでお見せしたいのですが、どう収録すればいいのかな?) pic.twitter.com/vew5qjSLv8
この記事は、アイの歌声を聴かせてに関する一技術者なりのファン活動の一幕です。
"ポンコツAI"シオンの「音声を認識してテキストに変換する」機能の実装アウトプットの後編です。
「シオンが変だ!」(=マイクから入力した音声の書き起こしがイマイチ)を解決します。
espnet_model_zoo
やspeech_recognition.Microphone
、動作環境については前編を参照ください。
目次
- はじめに
- 目次
- TL; DR(ただし、Workaround)
- 今回の学習済みモデルにはnp.floatのarrayを入力する必要がある
- 解決するために、bytesをnp.floatのarrayに変換したい
- 終わりに
- 変更履歴
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で学ぶ音声認識』などで知識を補ってから、簡潔に書けるか考えてみます。
Microphone
をlisten
して得られたAudioData
- 音声を表す部分は
AudioData.get_raw_data()
で取得されるbytes np.frombuffer
はdtypeのデフォルトがfloat
- floatのarrayに変換してもテキストの書き起こしはうまくいかない
- floatのarrayは
sounddevice
でplay
1しても音が聞こえない(個々の要素の値がうまく変換できていないということ?)
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プロジェクトとして、Text-To-SpeechやSpeech recognitionのモデルを触ってるんですが、どちらの専門知識も持ち合わせていない私でも、公開いただけているモデルを使うだけでそれなりのものが作れるの、ヤバイですね!
— nikkie にっきー / アイうた円盤📀発売アドカレ中 (@ftnext) 2022年2月12日
ソフトウェアのライブラリみたくモデルが公開されてるのか
音声の扱いは広大な伸びしろが広がっているので、習熟してシオンを改良していきたいですね。
v0.0.1として残るのは、会話のエンジンですね(現在はオウム返し)。
これは「我に策あり」です!
ちょっとしたところだと、スクリプト実行したあと入力を受け付けるようになったら、シオンっぽく知らせたいなあ(「おはよう!」とか)
変更履歴
- 2022/07/11: 「TL; DR(ただし、Workaround)」にてバグ修正(
nbests = speech2text(speech_array)
→nbests = speech2text(audio_array)
)- dtypeをfloat64に変換する前のarrayを渡すコードになっていた