RAPIRO(ラピロ)の音声制御(参考:日経Linux 2014 6月号)

日経Linux 2014年 06月号 』に記載されていた、ラピロを音声制御する記事を実践してみました。実施例を増やすことが目的なので、ここでは実現に必要な最低限のことしか書いていません。周辺知識とか、しっかりした情報が必要な人は、上記書籍を購入してください(開発者かつ著者の石渡さんへの敬意として)。

ちなみに、自分のやり方がどこか間違っていたのかもしれませんが、掲載記事のままだとうまくいかないところがあったので、そのあたりは補完しています。

 

さて、記事ではラピロにマイクを装着するために『響音4』というUSBオーディオデバイスを使っていますが、持ち合わせていなかったので、手元にあったUSBマイクを使用することにしました。ELECOMの『HS-MC02UBK 』というマイクです。音声出力には、Raspberry Pi基盤上のステレオミニジャックにPC用のスピーカーを繋ぐ形でテストします。

まず、マイクが利用可能かの確認です。マイクを接続する前に、以下のコマンドを実行。

$ lsusb
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 006: ID 2019:1201 PLANEX
Bus 001 Device 007: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 004: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 005: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
$ arecord -l
**** List of CAPTURE Hardware Devices ****

たくさん認識されているのは、自分がセルフパワーのUSBハブを繋いでいるからですね。次に、マイクを接続して同じ事をします。

$ lsusb
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 006: ID 2019:1201 PLANEX
Bus 001 Device 007: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 004: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 008: ID 0d8c:0139 C-Media Electronics, Inc.
Bus 001 Device 005: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

ちゃんと認識されているようです。

次に、録音のテスト。

record -D hw:1,0 -f S16_LE -r 16000 test.wav
Recording WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 16000 Hz, Mono
Warning: rate is not accurate (requested = 16000Hz, got = 44100Hz)
         please, try the plug plugin

記事と同じで、これではうまくいかない。なので、以下のコマンドを実行。

$ arecord -D plughw:1,0 -f S16_LE -r 16000 test.wav
Recording WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 16000 Hz, Mono

この状態で喋って、Ctrl+Cで録音終了。次に、録音できるかのテストです。

$ aplay test.wav

実行すると、「ブッ」と音をたてて、Raspberry Piがフリーズしてしまいました。起動してからステレオミニジャックにPC用スピーカーを繋げたのがマズかったのかしら。

再起動して同じコマンドを実行すると、今度はきちんと再生されました。

$ aplay test.wav
Playing WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 16000 Hz, Mono

 

さて、これでハードウェア関係は準備できたので、次に音声認識エンジン「Julius」のダウンロードです。現時点(5/18)での最新版は、記事と同じ4.3.1のようなので、記事に記載されているコマンドをそのまま実行します。

$ wget http://jaist.dl.sourceforge.jp/julius/60273/julius-4.3.1.tar.gz
$ wget http://jaist.dl.sourceforge.jp/julius/60416/dictation-kit-v4.3.1-linux.tgz

次に、必要なパッケージをインストール。

$ sudo apt-get install libasound2-dev libesd0-dev libsndfile1-dev

次に、juliusのビルド&インストール。

$ tar xzvf julius-4.3.1.tar.gz
$ cd julius-4.3.1/
$ ./configure -with-mictype=alsa
$ make
$ sudo make install

次に、ディクテーションキットを解凍して、出来たフォルダに移動。

$ tar xzvf dictation-kit-v4.3.1-linux.tgz
$ cd dictation-kit-v4.3.1-linux/

juliusが使用するオーディオデバイスを指定して、記事に書いてあるテストプログラムを実行。

$ export ALSADEV=plughw:1,0
$ julius -C main.jconf -C am-gmm.jconf -demo

音声認識する前に毎回環境変数ALSADEVをexportするのが面倒であれば、Raspberry Piのホームディレクトリにある”.profile”ファイルの末尾に以下を記述しておく。

export ALSADEV=plughw:1,0

これで、Raspberry Piにログインするたびに自動的に環境変数ALSADEVがexportされる。.profileへの追記を即有効にする場合は、

$ source .profile

を実行。このあとprintenvをすれば、環境変数ALSADEVが有効になっていることが確認できます。

さて、<<< please speak >>>が表示されてから喋ると、認識された文字列が表示されるそうなのですが、全然認識されません。

「辞書ファイルがでか過ぎるんじゃなかろうか」ということで、もう少し絞り込んだ辞書ファイルを作成。

$ sudo vim rapiro.dic

例えば中身はこんな感じです。

<sil>   silB
<sil>   silE
<sp>    sp
ラピロ  r a p i r o
はっしん        h a q sh i N
バンザイ        b a N z a i
たたかえ        t a t a k a e

最後の単語が出て来たのは、作業中になぜか『仮面ライダー龍騎』の神崎士郎お兄ちゃんが頭に思い浮かんだからです。

次に、この辞書ファイルを利用するための設定ファイルを作成します。

$ sudo vim rapiro.conf

中身はこんな感じです。

-w rapiro.dic
-v model/lang_m/bccwj.60k.htkdic
-h model/phone_m/jnas-tri-3k16-gid.binhmm
-hlist model/phone_m/logicalTri
-n 5
-output 1
-input mic
-input alsa
-rejectshort 800
-lv 1500
-demo

それでは、この辞書ファイルを使って再チャレンジ。

$ julius -C rapiro.conf
...
STAT: AD-in thread created
pass1_best: ラピロ<input rejected by short input>
pass1_best: はっしん<input rejected by short input>
pass1_best: たたかえ<input rejected by short input>
pass1_best: バンザイ
<<< please speak >>>

今度は無事に認識されました。

 

次に、Juliusを音声認識サーバとして機能させるために、rapiro.confの最後に以下を追記します。

-module

この状態でJuliusを実行して、サーバとして動作していることを確認する。

$ julius -C rapiro.conf
...
tat: server-client: socket ready as server
///////////////////////////////
///  Module mode ready
///  waiting client at 10500
///////////////////////////////

確認できたら、Ctrl+Cで終了。

次に、音声認識結果がラピロの制御コマンドになるよう、rapiro.dicを修正する。こんな感じです。

<sil>   silB
<sil>   silE
<sp>    sp
#M6     r a p i r o
#M1     h a q sh i N
#M5     b a N z a i
#M9     t a t a k a e
#M0     t o m a r e

できたら、後は制御コマンドをJuliusから受け取って、Arudino基盤にシリアル通信するプログラムを作成します。仮に”rapiro_main.py”として、中身はこんな感じ。本に書いてあることそのまま。

!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import serial

host = 'localhost'
port = 10500

clientsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientsock.connect((host, port))
com = serial.Serial('/dev/ttyAMA0', 57600, timeout=10)

while True:
        recv_data = clientsock.recv(512)
        com.write(recv_data)

これで、一応準備完了のはず。

 

ということで、Juliusをモジュールモードで起動してから、さっきの音声/コマンド変換プログラム”rapiro_main.py”を実行する。

$ julius -C rapiro.conf &
$ sudo python rapiro_main.py

雑誌記事によれば、これで完成…のハズなんだけれど、ラピロがうんともすんとも言わない。

“rapiro_main.py”でprint出力してみると、juliusからは認識した単語そのものが送られてくるのではなく、認識結果をXMLで表したものが送られてきている。ということは、パースしてやらなきゃいけないです。これについては雑誌記事ではまったく触れられていないので、ここまでの自分の手順がどこか間違っていたのかもしれませんが、このあたりを見てみると、元々モジュールモードではXMLで送られてくるのが基本のようなので、先ほどの”rapiro_main.py”にXMLパース処理を追加します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket
import serial
import xml.etree.ElementTree as ET

host = 'localhost'
port = 10500

clientsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
clientsock.connect((host, port))
com = serial.Serial('/dev/ttyAMA0', 57600, timeout=10)

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'):
                                        command = word.get('WORD')
                                        print(command)
                                        com.write(command)
                        except:
                                print("Failed to parse")

自分がほとんどpython使ったことがないのと、XMLのパースをほとんどやったことがないというので、ものすごく適当なパース処理になっています。Juliusからはもっとたくさんの情報がXMLで送られてきているので、ちゃんとした音声認識をしたいとか、もっと効率良く処理したいとかありましたら、適宜パース処理をしっかり書いてください。

それでは、あらためて実行です。

$ julius -C rapiro.conf &
$ sudo python rapiro_main.py

これで動きました!(冒頭の動画参照)
よかったよかった。

さて、これでWiiリモコンと音声入力の二つの方法でラピロを操作できるようになりました。要するに何かしらの入力を受けつけて、それをラピロ用のコマンドに変換して、Arduinoにシリアル通信してくれるものを作ればよいわけですね。

次は勉強がてら、スマホで操作できるようにしよう…と思うものの、さすがにちょっと遊び過ぎたので、多分やれるのは7月ぐらいになりそうです。