「PaPeRo iのマイクでRaspberry Pi の Juliusによる音声認識を行う」による音声認識を利用した簡単な例として、認識結果をPaPeRo i にしゃべらせる方法を紹介します。
Raspberry Pi 側の準備
(1) 「PaPeRo iのマイクでRaspberry Pi の Juliusによる音声認識を行う」の手順(Raspberry Pi 側)の(1)~(3)を実施します。
(2) pythonのパッケージws4pyをインストールします。
$ sudo pip3 install ws4py
(3) 「PaPeRo iをRaspberry Pi上のpythonから操作する」の「Raspberry Piへ通信ライブラリをインストール」に従い、ライブラリを配置します。
以下の説明では、ライブラリを ~/papero の下に配置したものとします。
(4) 下記の内容をコピー・ペーストして、julius_sample.py を作成し、~/papero の下に置きます。
import sys
import time
import socket
import select
from enum import Enum
import xml.etree.cElementTree as ET
import pypapero
class State(Enum):
st0 = 10
st1 = 11
st2 = 12
end = 999
class JuliusManager:
def __init__(self, host, port, vraw):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.buf_julius_lines = []
self.buf_recv = b""
self.finish = False
try:
self.sock.connect((host, port))
except:
self.finish = True
self.vraw = vraw
def receive_msg(self):
rtn_msg = ""
self.potential_readers = [self.sock]
self.potential_writers = []
self.potential_errs = []
timeout = 0.1
ready_to_read, ready_to_write, in_error = \
select.select(self.potential_readers, self.potential_writers, self.potential_errs, timeout)
if self.sock in ready_to_read:
err = False
try:
bytes = self.sock.recv(4096)
except:
err = True
if err or (not bytes):
self.finish = True
else:
self.buf_recv += bytes
messages = self.buf_recv.split(b"\n")
n_msg = len(messages)
i_msg = 0
while i_msg < (n_msg - 1):
msgline = messages[i_msg].decode('utf-8')
self.buf_julius_lines.append(msgline)
if self.vraw:
print("Received: " + msgline)
i_msg += 1
self.buf_recv = messages[i_msg]
while True:
idx_separate = (-1)
for i in range(len(self.buf_julius_lines)):
if self.buf_julius_lines[i] == ".":
idx_separate = i
break;
if idx_separate >= 0:
msg_xml = ""
for i in range(idx_separate):
msg_xml += self.buf_julius_lines[0] + "\n"
del self.buf_julius_lines[0]
del self.buf_julius_lines[0]
rtn_msg = self.extract_sentence_from_julius_xml(msg_xml)
if (len(rtn_msg) > 0) or (idx_separate < 0):
break
return rtn_msg
def modify_invalid_char_xml(self, msg_xml):
rtn_xml = ""
instr = False
for i in range(len(msg_xml)):
chr = msg_xml[i]
if instr:
if chr == '<':
chr = '<'
elif chr == '>':
chr = '>'
elif chr == '"':
instr = False
else:
if chr == '"':
instr = True
rtn_xml += chr
return rtn_xml
def extract_sentence_from_julius_xml(self, msg_xml):
msg_xml_modified = self.modify_invalid_char_xml(msg_xml)
rtn_sentence = ""
objs = ET.fromstring("<Root>" + msg_xml_modified + "</Root>")
shypoes = objs.findall(".//SHYPO")
for shypo in shypoes:
for elm in shypo:
if elm.tag == "WHYPO":
word = elm.attrib["WORD"]
rtn_sentence += word
return rtn_sentence
def send_command(self, command):
msgbytes = command.encode('utf-8') + b'\n'
try:
self.sock.sendall(msgbytes)
except:
self.finish = True
def close_connection(self):
self.sock.close()
def main(julius, papero):
state = State.st0
last_time = time.monotonic()
past_time = 0
speech_idx = 0
speaking = False
list_sentence = ["聞き取った言葉を発話します。座布団のボタンで終了します。"]
while state != State.end:
sentence = julius.receive_msg()
if len(sentence) > 0:
print("Recognized: " + sentence)
list_sentence.append(sentence + "って聞こえたよ。")
elif julius.finish:
print("Connection to julius closed.")
state = State.end
messages = papero.papero_robot_message_recv(0.1)
now_time = time.monotonic()
delta_time = now_time - last_time
last_time = now_time
if messages is not None:
msg_dic_rcv = messages[0]
else:
msg_dic_rcv = None
if papero.errOccurred != 0:
print("------Error occured(main()). Detail : " + papero.errDetail)
break
if state == State.st0:
if len(list_sentence) > 0:
if not speaking:
julius.send_command("PAUSE")
papero.send_turn_led_off("ear")
papero.send_turn_led_on("mouth",
["G3G3G3G3G3G3G3G3G3", "2", "NNG3G3G3G3G3NN", "2",
"NNNG3G3G3NNN", "2", "NNG3G3G3G3G3NN", "2"], repeat=True)
speaking = True
papero.send_start_speech(list_sentence[0])
del list_sentence[0]
past_time = 0
state = State.st1
else:
if speaking:
papero.send_turn_led_off("mouth")
papero.send_turn_led_on("ear", ["W3W3", "5"], True)
julius.send_command("RESUME")
speaking = False
elif state == State.st1:
past_time += delta_time
if past_time >= 1.0:
papero.send_get_speech_status()
state = State.st2
elif state == State.st2:
if msg_dic_rcv is not None:
if msg_dic_rcv["Name"] == "getSpeechStatusRes":
if str(msg_dic_rcv["Return"]) == "0":
state = State.st0
else:
past_time = 0
state = State.st1
if msg_dic_rcv is not None:
if msg_dic_rcv is not None:
if msg_dic_rcv["Name"] == "detectButton":
papero.send_turn_led_off("mouth")
papero.send_turn_led_off("ear")
state = State.end
def get_options(argv):
papero_addr = argv[1]
vraw = False
if (len(argv) > 2) and (argv[2] == "-vraw"):
vraw = True
return papero_addr, vraw
if __name__ == "__main__":
if len(sys.argv) > 1:
papero_addr, vraw = get_options(sys.argv)
simulator_id = ""
robot_name = ""
ws_server_addr = "ws://" + papero_addr + ":8088/papero"
julius = JuliusManager("localhost", 10500, vraw)
try:
papero = pypapero.Papero(simulator_id, robot_name, ws_server_addr)
except:
papero = None
if julius.finish:
print("Connection to Julius failed.")
elif papero is None:
print("Connection to PaPeRo i failed.")
else:
main(julius, papero)
if papero is not None:
papero.papero_cleanup()
julius.close_connection()
else:
print("Usage:")
print(sys.argv[0] + " papero_i_ip_addr [-vraw]")
PaPeRo i 側の準備
(1) PaPeRo i 制御用WebSocket通信アドオンシナリオをまだインストールしていない場合は、「PaPeRo iをRaspberry Pi上のpythonから操作する」の「PaPeRo iにアドオンシナリオをインストール」に従ってインストール作業を行います。
(2) 「PaPeRo iのマイクでRaspberry Pi の Juliusによる音声認識を行う」の手順(PaPeRo i 側)の(1)を実施します。
音声認識の実行
(1) Raspberry Pi のターミナルで、入力元として adinnet を指定して、モジュールモードで Julius を起動します。
$ cd ~/julius/dictation-kit-v4.4
$ julius -C main.jconf -C am-gmm.jconf -input adinnet -module
(2) Raspberry Pi のターミナルをもう一つ開き、その中で julius_sample.py を実行します。
$ cd ~/papero
$ python3 julius_sample.py PaPeRoiのIPアドレス
起動が完了すると、PaPeRo i が「聞き取った言葉を発話します。座布団のボタンで終了します」と発話します。
(3) PaPeRo i 側でadintoolを起動し、マイクの音声がJuliusに送られるようにします。
(debian_stretch)# adintool -in mic -out adinnet -server IPアドレス
ここで指定するIPアドレスは、Raspberry PiのIPアドレスです。
これ以降、認識された言葉をPaPeRo i が「~って聞こえたよ」と発話するようになります。
Juliusとクライアントとの間の通信について
Juliusはモジュールモードで起動すると、10500番ポートでクライアントからのTCP接続を待ちます。
クライアント(今回の場合はjulius_sample.py)が接続すると、Juliusから下記のような形でクライアントにXMLのメッセージが送られてきます。
<STARTPROC/>
.
<INPUT STATUS="LISTEN" TIME="1516336015"/>
.
<STOPPROC/>
.
<STARTPROC/>
.
<INPUT STATUS="LISTEN" TIME="1516336023"/>
.
<INPUT STATUS="STARTREC" TIME="1516336026"/>
.
<STARTRECOG/>
.
<INPUT STATUS="ENDREC" TIME="1516336028"/>
.
<ENDRECOG/>
.
<INPUTPARAM FRAMES="118" MSEC="1180"/>
.
<RECOGOUT>
<SHYPO RANK="1" SCORE="-2926.393311">
<WHYPO WORD="" CLASSID="<s>" PHONE="silB" CM="0.633"/>
<WHYPO WORD="こんにちは" CLASSID="こんにちは+感動詞" PHONE="k o N n i ch i w a" CM="0.482"/>
<WHYPO WORD="。" CLASSID="</s>" PHONE="silE" CM="1.000"/>
</SHYPO>
</RECOGOUT>
.
<INPUT STATUS="LISTEN" TIME="1516336029"/>
.
<STOPPROC/>
.
<STARTPROC/>
.
<INPUT STATUS="LISTEN" TIME="1516336033"/>
.
<INPUT STATUS="STARTREC" TIME="1516336035"/>
.
<STARTRECOG/>
.
<INPUT STATUS="ENDREC" TIME="1516336040"/>
.
<ENDRECOG/>
.
<INPUTPARAM FRAMES="191" MSEC="1910"/>
.
<RECOGFAIL/>
.
<INPUT STATUS="LISTEN" TIME="1516336040"/>
.
<INPUT STATUS="STARTREC" TIME="1516336057"/>
.
<STARTRECOG/>
.
<INPUT STATUS="ENDREC" TIME="1516336062"/>
.
<ENDRECOG/>
.
<INPUTPARAM FRAMES="185" MSEC="1850"/>
.
<RECOGOUT>
<SHYPO RANK="1" SCORE="-5087.060059">
<WHYPO WORD="" CLASSID="<s>" PHONE="silB" CM="0.488"/>
<WHYPO WORD="ご飯" CLASSID="ご飯+名詞" PHONE="g o h a N" CM="0.413"/>
<WHYPO WORD="を" CLASSID="を+助詞" PHONE="o" CM="0.018"/>
<WHYPO WORD="食べ" CLASSID="食べ+動詞" PHONE="t a b e" CM="0.149"/>
<WHYPO WORD="に" CLASSID="に+助詞" PHONE="n i" CM="0.221"/>
<WHYPO WORD="いこう" CLASSID="いこう+動詞" PHONE="i k o u" CM="0.037"/>
<WHYPO WORD="よ" CLASSID="よ+助詞" PHONE="y o" CM="0.013"/>
<WHYPO WORD="。" CLASSID="</s>" PHONE="silE" CM="1.000"/>
</SHYPO>
</RECOGOUT>
.
<INPUT STATUS="LISTEN" TIME="1516336063"/>
.
<STOPPROC/>
.
<STARTPROC/>
.
<INPUT STATUS="LISTEN" TIME="1516336068"/>
.
改行コードは “\n”であり、一つのメッセージは”.”のみの行で終端されています。
メッセージが
但し、タグの属性文字列の中に”<“という文字が含まれている事がある為、このままXMLパーサに渡すとエラーとなってしまうようです。そのため、julius_sample.pyでは、””で囲まれた範囲にある”<“、”>”を<、>に置き換える処理を、パース前に行っています。
PaPeRo i がしゃべっている間は、自分の言葉をマイクで拾わないようにする為、PaPeRo i にしゃべらせる前にJuliusに対し”PAUSE”コマンドを送信して音声認識を中断し、しゃべり終わったら”RESUME”コマンドで再開させています。
尚、julius_sample.py の起動の際に、下記のように -vraw オプションを指定する事により、上記のXMLでの受信メッセージを実際に確認する事ができます。
$ python3 julius_sample.py PaPeRoiのIPアドレス -vraw