RAPIRO(ラピロ)の音声制御 Ver. 2.0 その2

前回まででJuliusの動作確認まではしたので、続いてJuliusをサーバモードで起動させます。

といっても、これについては、前回作成した設定ファイル”rapiro.conf”に一行追加するだけです。

-module

これでもう一度実行すると、サーバモードで動作していることが確認できます。

$ julius -C rapiro.conf

...

Stat: server-client: socket ready as server
///////////////////////////////
///  Module mode ready
///  waiting client at 10500
///////////////////////////////

あとは待ち受け用のPythonプログラム作成です。以前作成したときは、この待ち受け用プログラムがそのままラピロ本体のArduino制御プログラムも兼ねていました。具体的には、認識した音声を”#M0″などの文字列と対応付けて、その文字列をシリアル通信でArduinoに送り込む、というものです。

これはこれでシンプルでわかりやすいのですが、いかんせん、あれから自分のラピロのシステムは随分複雑化してきていまして、今はこんな感じになっています。

rapiro_components

。。。我ながら色々やってきたなあと思います。多分幾つかの要素はまとめることができると思うのですが、自分にとっての分かりやすさ優先&スキル不足により、今はこういう構造になっています。

というわけで、今回自分が作成するJulius待受用プログラムは、音声を認識したら、動作制御用WebサーバにHTTPでアクセスする、という形のものになります。あまり汎用的なものではない気がしますが、せっかくなので、ソースコードを載せておきます。

# coding: utf-8
import socket
import serial
import xml.etree.ElementTree as ET
import requests
import time
import threading

host = 'localhost'
port = 10500

COMMAND_READY_DURATION     = 15
COMMAND_EXECUTING_DURATION = 30

STATUS_WAITING   = 0 
STATUS_READY     = 1 
STATUS_EXECUTING = 2 
STATUS_FINISH    = 3 

status = STATUS_WAITING

def status_change(new_status):
  global status
  if status == STATUS_WAITING:
    if new_status == STATUS_READY:
      status = new_status
      print "Status is READY."  
      threading.Timer(COMMAND_READY_DURATION, status_change, args=[STATUS_WAITING]).start()
  elif status == STATUS_READY:
    if new_status == STATUS_WAITING:
      status = new_status
      print "Status is WAITING."  
      res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/init')
      print 'Julius Server Received from Control Server:%d' % res.status_code
    elif new_status == STATUS_EXECUTING:
      status = new_status
      print "Status is EXECUTING."  
      threading.Timer(COMMAND_EXECUTING_DURATION, status_change, args=[STATUS_FINISH]).start()
  elif status == STATUS_EXECUTING:
    if new_status == STATUS_WAITING:
      print "Command is executed now, so I do NOT change status."
    if new_status == STATUS_FINISH:
      status = STATUS_WAITING
      print "Status is WAITING."  
      res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/init')
      print 'Julius Server Received from Control Server:%d' % res.status_code

clientsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientsock.connect((host, port))
print("Connected Julius Server.")

while True:

  recv_data = clientsock.recv(512)
  # juliusの区切り文字で分割
  sp = recv_data.split('.\n')
  #print(sp)
  for elem in sp:
    if(elem != ''):
      try:
        root = ET.fromstring(elem)
        for word in root.iter('WHYPO'):
          keyword = word.get('WORD')
          print(keyword)
          if keyword.startswith(u'ラピロ'):
            if status == STATUS_WAITING:
              print "RAPIRO is ready to your command."
              status_change(STATUS_READY)
              res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/talk?text=' + keyword + '&face=happy&emotion=happy')
              print 'Julius Server received from Control Server:%d' %  res.status_code
          elif keyword in [u'おやすみなさい', u'おやすみ', u'おはよう', u'電気消して', u'電気点けて']:
            if status == STATUS_READY:
              print "RAPIRO got command:" + keyword
              status_change(STATUS_EXECUTING)
              res = requests.get('http://192.168.24.50:10080/v1/robots/rapiro/control/talk?text=' + keyword + '&face=happy&emotion=happy')
              print 'Julius Server Received from Control Server:%d' % res.status_code
      except:
        print("Failed to parse")

 相変わらずJuliusから取得したXMLのパースが適当ですが、それはご勘弁ください。

ラピロの状態遷移は以下のようになっています。

rapiro_voice_state_change_2

開始音声入力受付状態のときに「ラピロ」と呼びかけると、ラピロが命令音声入力受付状態に遷移します。命令音声入力受付状態になってから15秒以内に命令音声を喋ると、対応動作処理中になり、対応動作の実行開始から30秒が経過すると、また開始音声入力受付状態に戻ります。

こういう仕様にしている理由は、ラピロの音声誤認識による勝手な命令実行を抑制するためです。単語辞書を絞った状態で音声認識をオンにしておくと、ラピロが何らかの音を勝手に単語辞書とマッチングさせてしまい、その結果いきなり意図しない動作をしてしまう、ということがよくあるので。

現状の単語数だと、勝手に「ラピロ」と認識した後に勝手に「電気消して」を認識してしまう、というのもそれなりにあったりしますが。。。もう少し単語数が増やしたり、もしくはJuliusの渡してくるパラメータの値をうまく利用してやることができれば、このへんの問題は解決できるのかもしれません。

プログラム中で”http://192.168.24.50:10080/v1/robots/rapiro/control/talk?text=”とか書かれているのが、自分の動作制御用Webサーバにリクエストを発行しているところになります。動作制御用Webサーバは、パラメータ”text”の内容を見て、音声発話用WebサーバとirMagicianと連携することで、喋ったり赤外線信号を発信したりします。

 

で、その実行結果が冒頭の動画になります。 ビームライフル射撃音はオマケでつけてみました。

個人的には、動画だけ見るとそこそこ良い感じかなーと思うのですが、いかんせん、音声の認識率がかなり悪いです。何テイク撮り直したことか。。。

認識率を上げるためには、Juliusのパラメータとかモデルとかをちゃんと理解していじってやる必要がありそうで、ちょっと今はそこに踏み込むのは止めておきます。やることがなくなってきたときにでも、また取り組もうかと思います。