概要
Julius で特定フレーズ認識時に、python で各フレーズに対応する処理を行うプログラムを作成しました。独自辞書でフレーズを定義・起動後、python プログラムで音声認識結果に応じた処理をします。
今回は、モーター軸上のコナンフィギュアを回転させます。
動画では、コナン後ろのウィンドウに音声認識結果が出力され、コナンはこの結果に対応する回転動作を実行しています。
発声フレーズと回転動作の内容は次表の通りです。
発声フレーズ | 回転動作内容 |
みぎむけみぎ | 右に90度回転 |
ひだりむけひだり | 左に90度回転 |
まわれみぎ | 右に180度回転 |
もとにもどれ | 正面方向に戻る |
べんきょうしなさい | 動揺して、約3回転してから、 正面方向に戻る |
Julius 独自辞書作成と起動
① 独自辞書作成
「~/julius/dic_konan_cmd」ディレクトリを作成します。その直下に先回投稿と同様、4つの「yomi」「phone」「grammer」「voca」を作成後、「dfa」「dict」「term」を変換(?)・形成します。
今回、作成した4つのファイルは次の通りです。
こなん こなん
みぎむけみぎ みぎむけみぎ
ひだりむけひだり ひだりむけひだり
まわれみぎ まわれみぎ
もとにもどれ もとにもどれ
べんきょうしなさい べんきょうしなさい
こなん k o n a N
みぎむけみぎ m i g i m u k e m i g i
ひだりむけひだり h i d a r i m u k e h i d a r i
まわれみぎ m a w a r e m i g i
もとにもどれ m o t o n i m o d o r e
べんきょうしなさい b e N ky o u sh i n a s a i
S:NS_B KONAM NS_E
KONAM : KONAN
KONAM : MIGIMUKEMIGI
KONAM : HIDARIMUKEHIDARI
KONAM : MAWAREMIGI
KONAM : MOTONIMODORE
KONAM : BENKYOUSHINASAI
% KONAN
こなん k o n a N
% MIGIMUKEMIGI
みぎむけみぎ m i g i m u k e m i g i
% HIDARIMUKEHIDARI
ひだりむけひだり h i d a r i m u k e h i d a r i
% MAWAREMIGI
まわれみぎ m a w a r e m i g i
% MOTONIMODORE
もとにもどれ m o t o n i m o d o r e
% BENKYOUSHINASAI
べんきょうしなさい b e N ky o u sh i n a s a i
% NS_B
[s] silB
% NS_E
[/s] silE
② Julius 起動
ターミナルで下記コマンドにより、Julius を起動します・
julius -C ~/julius/julius-kit/dictation-kit-v4.4/am-gmm.jconf -nostrip -gram ~/julius/dic_konan_cmd/KonanCmd -input mic -module
python プログラム
構成は、ステッピングモーター(+L6470)を接続しています。ラズパイとL6470接続は過去投稿で記載していますので省略します。
プログラムは先回投稿のJulius音声認識プログラムをベースに、行番15〜120 あたりに L6470制御に関連する関数、行番187〜220 あたりに音声認識結果に対応する動作ステップ数設定・実行処理を追加しています。
import socket
import time
import sys
import wiringpi as wp
import RPi.GPIO as GPIO
import struct
import time
HOST = 'raspberrypi' # juliusサーバーホスト(IPアドレス)
PORT = 10500 # juliusサーバーの待ち受けポート
DATESIZE = 1024 # 受信データバイト数
s_Word =['WORD' , 'CLASSID' , 'PHONE' , 'CM'] # 検索ワード
SPI_CH = 0 # SPI チャンネル
SPI_HZ = 1000000 # SPI 通信速度
GPIO_Nbr = 25 # GPIO-NO
GPIO.setmode(GPIO.BCM) # GPIO-NO 指定
GPIO.setup(GPIO_Nbr,GPIO.IN) # GPIO INPUT 指定
# L6470初期設定
def INIT_L6470():
spi_send([0x00,0x00,0x00,0xc0]) # Reset Device
spi_send([0x05,0x00,0x0e]) # Acceleration (12)
spi_send([0x06,0x00,0x0e]) # Deceleration (12)
spi_send([0x07,0x00,0xff]) # Maximum speed (10)
spi_send([0x08,0x00,0x01]) # Minimum speed (13)
spi_send([0x15,0x03,0xFF]) # Full-step speed (10)
spi_send([0x16,0x03]) # Micro Step (8)
spi_send([0x09,0x50]) # Holding Kval (8)
spi_send([0x0A,0x50]) # Constant Speed Kval (8)
spi_send([0x0B,0x50]) # Acceleration starting Kval (8)
spi_send([0x0C,0x50]) # Deceleration starting Kbal (8)
# SPI データ送信
def spi_send(spi_dat_ary):
for itm in spi_dat_ary:
tmp=struct.pack("B",itm)
wp.wiringPiSPIDataRW(SPI_CH, tmp)
# JOG (SPEED指定 : 0---30000)
def L6470_run(run_spd):
# 方向検出
if (run_spd > 0):
dir = 0x50
spd = run_spd
else:
dir = 0x51
spd = -1 * run_spd
L6470_SEND_MOVE_CMD( dir , spd )
# 移動量指定移動
def L6470_POSITIONING(MV_DIST):
# 方向検出
if (MV_DIST > 0):
dir = 0x40
else:
dir = 0x41
MV_DIST = -1 * MV_DIST
L6470_SEND_MOVE_CMD( dir , MV_DIST )
# 絶対位置指定移動
def L6470_MOVE_ABS(MV_DIST):
dir = 0x60
if (MV_DIST < 0):
MV_DIST = -1 * MV_DIST
L6470_SEND_MOVE_CMD( dir , MV_DIST )
# データ加工・送信(共通)
def L6470_SEND_MOVE_CMD( cmd , DAT ):
tmp=[]
tmp.append(cmd)
tmp.append((0x0F0000 & DAT) >> 16)
tmp.append((0x00FF00 & DAT) >> 8)
tmp.append((0x00FF & DAT))
spi_send(tmp)
# 停止指令
def L6470_STOP():
spi_send([0xB0]) # SOFT STOP
time.sleep(0.2)
spi_send([0xA8]) # Hard HiZ
time.sleep(0.2)
# 原点設定
def L6470_SET_ORIGIN():
spi_send([0xD8]) # Reset Position
time.sleep(0.5)
# 原点移動
def L6470_MOVE_ORIGIN():
spi_send([0x70]) # Go Home
time.sleep(0.5)
# ドライバーBUSY解除待ち
def wait_until_not_busy():
while True:
time.sleep(0.2)
mtr_sts = GPIO.input(GPIO_Nbr)
#print(mtr_sts)
if GPIO.input(GPIO_Nbr) == GPIO.HIGH :
print("L6470 NOT BUSY")
break
time.sleep(0.2)
# 現在位置から原点位置までの最短ステップ数
def calc_step(pos):
stp_mod = cur_pos % 1600
if stp_mod > 800 :
mv_stp = 1600 - stp_mod
else :
mv_stp = - 1 * stp_mod
return mv_stp
##################################
# メインプログラム
##################################
wp.wiringPiSPISetup(SPI_CH,SPI_HZ) # SPI 接続
INIT_L6470() # L6470初期設定
L6470_SET_ORIGIN() # 原点位置設定(0)
cur_pos = 0 # 現在位置初期化
try:
# socket通信でjuliusサーバー接続
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
gData = "" # サーバーから取得した全データ
# メインループ
while True:
if '</RECOGOUT>\n' in gData : # データ区切(最終)判定 >>> 取得データ解析
gWord = "" # 取得した言葉を格納
g_CM = "" # 単語信頼度
gCM_Ary = [] # 単語信頼度格納配列
g_Score = '' # 対数尤度(音響スコア+言語スコア)
# 取得データを一行づつ分析
for gLine in gData.split('\n') : # 取得データを改行コードで分割
gWrd_Inf = [] # 一時データ格納用配列
if gLine.find('<WHYPO ') != -1: # 分割行単位でデータ評価
for s_wrd in s_Word : # 'WORD','CLASSID','PHONE','CM' のデータ検索(ループ処理)
wTmp=""
index = gLine.find( s_wrd + '="' ) # 対象データ有無確認
if index != -1:
idx_s = index + len(s_wrd + '="' ) # 対象データ先頭位置確認
idx_e = gLine.find('"' , idx_s ) # 対象データ最終位置確認
wTmp = gLine[ idx_s : idx_e ] # 対象データ抽出
gWrd_Inf.append(wTmp) # 一時データ格納用配列データ追加
# 無音モデル非表示(silB:文頭,silE:文末,sp:単語間)
if gWrd_Inf[2] != "silB" and gWrd_Inf[2] != "silE" and gWrd_Inf[2] != "sp" :
gWord = gWord + gWrd_Inf[0]
gCM_Ary.append(float(gWrd_Inf[3]))
g_CM = g_CM + gWrd_Inf[3] + " "
# print(gWrd_Inf[0]+","+gWrd_Inf[1]+","+gWrd_Inf[2]+","+gWrd_Inf[3]+","+gLine) # データ確認時にコメント解除
elif gWrd_Inf[2] == "sp" :
gWord = gWord + " "
elif gLine.find('<SHYPO ') != -1 :
s_tmp = 'SCORE="'
index = gLine.find( s_tmp ) # 対象データ有無確認
if index != -1:
idx_s = index + len(s_tmp) # 対象データ先頭位置確認
idx_e = gLine.find('"' , idx_s ) # 対象データ最終位置確認
gScore = gLine[ idx_s : idx_e ] # 対象データ抽出
# データ分析結果表示(有効データ未確認時、表示しない)
if gWord.strip() != "" and len(gCM_Ary) > 0 and gScore != "" :
gCM_Ave = round(sum(gCM_Ary) / len(gCM_Ary) , 3 ) # 確信度の平均値計算
# 認識結果最終判定
if gCM_Ave == 1 and min(gCM_Ary) == 1 and float(gScore) < - 3000 :
time.sleep(0.02)
# 動作していない時に、命令に対応するステップ数計算
if GPIO.input(GPIO_Nbr) == GPIO.HIGH :
act_f = 2
rst_p = False
mv_step = 0
if gWord == "まわれみぎ" :
mv_step = - 800
elif gWord == "みぎむけみぎ" :
mv_step = - 400
elif gWord == "ひだりむけひだり" :
mv_step = 400
elif gWord == "もとにもどれ" :
rst_p = True
mv_step = calc_step(cur_pos)
elif gWord == "べんきょうしなさい" :
rst_p = True
mv_step = calc_step(cur_pos) + 4800
else :
act_f = 0
if mv_step != 0:
L6470_POSITIONING(mv_step) # 移動命令
cur_pos = cur_pos + mv_step # 現在位置
if act_f > 0 :
print("[data] " + gWord + " <scr>" + gScore + " <ave>" + str(gCM_Ave))
if rst_p :
cur_pos = 0
else :
print("[BUSY]")
gData = "" # 受信データ初期化
else :
gData = gData + sock.recv(DATESIZE).decode('utf-8') # データ区切(最終)検出まで、データ追加受信
# サーバー接続失敗
except socket.error :
print("Error : Server Connection Failed")
sys.exit()
# 終了処理
except KeyboardInterrupt :
print("Program End")
print("")
sock.send("DIE".encode('utf-8'))
sock.close()
まとめ
最初、“コナン” と呼び掛けた後に指示するつもりでしたが、おそらく 「voca」「grammer」の定義が悪いのだと思いますが、呼び掛け・指示 を一連のフレーズと認識してくれませんでした。
結果的には、雑音を “コナン” と誤認識し、python で除去できるので雑音対策になると、都合良く解釈しています。