ラズパイ4 コナン勉強しなさい(JULIUS利用 音声指示)

概要

 Julius で特定フレーズ認識時に、python で各フレーズに対応する処理を行うプログラムを作成しました。独自辞書でフレーズを定義・起動後、python プログラムで音声認識結果に応じた処理をします。
 今回は、モーター軸上のコナンフィギュアを回転させます。

Juliusを使用しコナンに命令している状況(2倍速再生)



 動画では、コナン後ろのウィンドウに音声認識結果が出力され、コナンはこの結果に対応する回転動作を実行しています。

 発声フレーズと回転動作の内容は次表の通りです。

発声フレーズ回転動作内容
みぎむけみぎ右に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 で除去できるので雑音対策になると、都合良く解釈しています。



 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です