このページで示したロボットのハードを使っています。
なお、EEPROMの内容は、このページで作成した内容と同じです。
(別途のRAMにSPI受信処理を埋め込む方式で検討しています)
sudo apt update sudo apt upgrade sudo apt install portaudio19-dev現在はPython 3.9.2 (default, Mar 12 2021, 04:06:34)で動作しています。(Raspberry Pi OS :Bullseye の標準環境)
sudo apt install python3-pip python3-cffi libportaudio2 pip3 install vosk pip install pyaudio以上で、srt-3.5.3 tqdm-4.67.3 vosk-0.3.45 websockets-15.0.1、pyaudio-0.2.14がインストールされました。
wget https://alphacephei.com/vosk/models/vosk-model-small-ja-0.22.zip unzip vosk-model-small-ja-0.22.zip mv vosk-model-small-ja-0.22 model
import wave
import json
from vosk import Model, KaldiRecognizer
# 1. モデルのパスを指定(前回ダウンロードしたフォルダ名)
model_path = "model"
# 2. 読み込むWAVファイル
wav_file = "morter_ctrl.wav"
if not wave.open(wav_file):
print("WAVファイルが開けません")
exit(1)
wf = wave.open(wav_file, "rb")
# フォーマットチェック
if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE":
print("WAVファイルは '16-bit PCM mono' である必要があります。")
exit(1)
model = Model(model_path)
# サンプリングレートをファイルに合わせる
rec = KaldiRecognizer(model, wf.getframerate())
print("認識中...")
while True:
data = wf.readframes(4000)
if len(data) == 0:
break
if rec.AcceptWaveform(data):
# 認識確定時の結果
result = json.loads(rec.Result())
print("確定結果:", result.get("text", ""))
# 最終的な認識結果
final_result = json.loads(rec.FinalResult())
print("最終結果:", final_result.get("text", ""))
このファイル(vosk_wav_test.py)と、認識させるテスト用の音声ファイル(morter_ctrl.wav)を次のように配置させて、
実行させます。
/usr/local/apps
├── vosk_wav_test.py (実行するプログラム)
├── morter_ctrl.wav (音声ファイル)
└── model/
├── am/
├── conf/
├── graph/
├── ivector/
└── rescore/
この実行結果は、次のようになりました。suzuki@raspberrypi:/usr/local/apps $ python vosk_wav_test.py LOG (VoskAPI:ReadDataFiles():model.cc:213) Decoding params beam=13 max-active=7000 lattice-beam=4 LOG (VoskAPI:ReadDataFiles():model.cc:216) Silence phones 1:2:3:4:5:6:7:8:9:10 LOG (VoskAPI:RemoveOrphanNodes():nnet-nnet.cc:948) Removed 0 orphan nodes. LOG (VoskAPI:RemoveOrphanComponents():nnet-nnet.cc:847) Removing 0 orphan components. LOG (VoskAPI:ReadDataFiles():model.cc:248) Loading i-vector extractor from model/ivector/final.ie LOG (VoskAPI:ComputeDerivedVars():ivector-extractor.cc:183) Computing derived variables for iVector extractor LOG (VoskAPI:ComputeDerivedVars():ivector-extractor.cc:204) Done. LOG (VoskAPI:ReadDataFiles():model.cc:282) Loading HCL and G from model/graph/HCLr.fst model/graph/Gr.fst LOG (VoskAPI:ReadDataFiles():model.cc:308) Loading winfo model/graph/phones/word_boundary.int 認識中... 確定結果: 全身 更新 左 回転 右 回転 同率 最終結果: suzuki@raspberrypi:/usr/local/apps $上記実行で使った、morter_ctrl.wav は、705Kbps モノラル 44.1KHz 16ビットサンプル で、 「前進 後進 左回転 右回転 倒立」と発音したの10秒の音声ファイルです。
# 認識させたい言葉のリストをJSON形式の文字列で作成 words = '["前進", "後進", "左", "右","回転", "倒立", "[unk]"]' rec = KaldiRecognizer(model, wf.getframerate(), words)10秒程度のファイルでは長すぎるので、1秒程度ファイルで判定することにした。
| spi.max_speed_hzの設定値 | 左値で設定される分周比 | 実際のSPI周波数 | 換算したサンプリング周波数 |
|---|---|---|---|
| 256000 | 978 | 250MHz/978=約255623Hz | 約255623Hz/16=約15.976KHzで 16KHzの目標より約4%ほど遅い |
| 256148 | 976 | 250MHz/976=約256147Hz | 約256147Hz/16=約16.009KHzで 16KHzの目標よりチョット速い |
| 300000 | 834 | 250MHz/834=約299760Hz | 約299760Hz/16=約18.735KHzで 16KHzの目標から余裕がある速さ |
dummy_data = [0, 1] * 16000 # 一括送信して受信データを得る received_data = spi.xfer2(dummy_data)# この間、ハードウェアが連続してクロックを出し続けるですが、Raspberry PiのデフォルトのSPIバッファサイズは 4,096バイト です。
def recive_loop(spi: spidev.SpiDev): # SPI 送受信スレッド用関数
start_flag = True
block = [0, 1] * 16000 # 約1秒16ビットの音声用ばバッファ
while True:
if start_flag :
received_data0 = spi.xfer3( [0, 1] ) # 先頭データ受信
if received_data0[0] == 0 and received_data0[1] == 0:
start_flag = False
else:
received_data = spi.xfer3( block ) # 約1秒間受信
tart_flag = True
block = [0, 1] * 16000
print(received_data0 + received_data)
#このデータを補正後に.wavファイルを生成し、Voskに音声認識させ、応答でロボットを動かす予定;
break
#
spi = spidev.SpiDev() # SPI操作オブジェクト生成
spi.open(0, 0) # bus=0 device=0 (CE0) でSPIオープン
spi.max_speed_hz = 256148 # 約256147Hz = 16.009KHz*16 SPIクロック
spi.mode = 0 # クロック信号(SCLK)はLow待機、LowからHighに立ち上がる瞬間でデータを読み取り
なお、SPIで一度に送れるサイズは でファイルで、4096バイト までに制限されています。
#!/usr/bin/python3
# 『/usr/local/apps/makewav.py』の内容
# 16bit 16KHz の wavファイルを生成する
import wave
def make_wave_file( int_list, path="test.wav"):
''' int_list の10bitリトルエンディアン符号なし列から、16bit 16KHzのサウンドファイルを作る'''
barray = bytearray()
for i in range(0,len(int_list), 2):
v = int_list[i] + (int_list[i+1] << 8)
signed_16bit = (v << 6) - 32768 # 16bitに拡大して、中央値を引いて符号付きにする
# リトルエンディアン(下位バイト -> 上位バイト)で格納
barray.append(signed_16bit & 0x00FF) # 下位バイト
barray.append((signed_16bit >> 8) & 0x00FF) # 上位バイト
#
wavefile = wave.open(path, 'wb') # waveファイルをバイナリ保存用として開く。
wavefile.setnchannels(1) # モノラル(単一の音信号)
wavefile.setsampwidth(2) # 2byteのデータ群と録音する指定
wavefile.setframerate(16000) # サンプリングレート 16Hkz
wavefile.writeframes(barray) # 上記で設定した属性で、ファイルに出力
wavefile.close()
if __name__ == "__main__":
import matplotlib.pyplot as plt
plot_y_list = [] # グラフ表示用のYデータリスト
plot_x_list = []
int_list=[] # int_listは、ADCからの生データの代わりのデータリスト
# ADCは符号無し10bitでリトルエンディアンで得られるので。その検証用ダミーデータでwavファイル生成を試す
v=0
for x in range(16000):
int_list.append( v & 0x0ff ) # 下位バイト
int_list.append( (v & 0x0ff00) >> 8 ) # 上位バイト
print(v)
plot_y_list.append(v)
plot_x_list.append(x)
v+=8
if v > 0x3ff: v=1
plt.plot(plot_x_list, plot_y_list) # プロット表示
plt.show()
make_wave_file( int_list , "morter_ctrl.wav")