簡単にできる!音声認識と音声合成を使ってRaspberrypiと会話


いきさつ

少し前にタ○ラト○ーのおもちゃハッカソンに申し込んだのですが、抽選に受からず。。。
「ならば自力でOH○NASの対抗馬を作ってやる!」と思いたったのがきっかけで、簡易会話ロボをRasタソを作ってみました。笑
(*実際の人物や団体等とは一切関係ありません)

やりたい事

・マイクからの音声入力に対して、合成音声で返答をする O○aN○Sもどきの作成

スペック

・Raspberry Pi B+
・raspbian 8 Jessie
SANWA SUPPLY MM-MCUSB16 USBマイクロホン
・イヤフォンはiphoneのやつ

役者

・requests (PythonのHTTPライブラリ)
・pyaudio (pythonでマイク入力)
・AquesTalkPi (合成音声)
・docomo雑談API (会話するためのAPI)
・docomo音声認識API (音声を認識してTEXTに変換するAPI)
※ はじめはJuliusを使おうかと思っていたのですが、軽く触った印象で処理速度と正確さがdocomo音声認識APIの方が良かったのでdocomo音声認識APIを使用する事にしました。

マイクの設定

まずはRaspberryPiでマイクを使用する為の準備を行います。(こちらを参考にしました)
マイクをさした状態でRaspberryPiを起動して、ちゃんと認識されているかを確認します。

$ lsusb
Bus 001 Device 005: ID 056e:4008 Elecom Co., Ltd
Bus 001 Device 004: ID 0d8c:0134 C-Media Electronics, Inc. 
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

良くわからないけど、今回の例だと Bus 001 Device 004: ID 0d8c:0134 C-Media Electronics, Inc.の部分がマイクの事らしいです。

USBオーディオアダプタの優先度を確認します。

$ cat /proc/asound/modules 
  0 snd_bcm2835
  1 snd_usb_audio

このsnd_usb_audioがマイクの事っぽいので、これを最優先にしないといけません。
ちなみにRaspberryPiはALSAを使用しているので、設定を変えるにはalsa-base.confを書き換えます。

ふむふむ、ではalsa-base.confを書き換えますか。。。ん?!
そして事件がおきました!!
なんと/etc/modprobe.d/alsa-base.confを開こうとすると、ファイルが存在していない!
これ結構ハマりましたorz
Jessie(OS)はalsa-base.confの代わりに/usr/share/alsa/alsa.confで修正するんやで、みたいな記事を見つけて、やってみるものの全くうまく行かず、調べても全然わからずにかなりハマりましたorz

結局、「無いのなら作ればよろし、confファイル(字余り)」という事で、alsa-base.confを作成したら解決できました。笑

$ sudo cat /etc/modprobe.d/alsa-base.conf
options snd slots=snd_usb_audio,snd_bcm2835
options snd_usb_audio index=0
options snd_bcm2835 index=1

上記のようにalsa-base.confを新規に作成し、RaspberryPiを再起動します。
そして再度、USBオーディオアダプタの優先度を確認します。
すると、ちゃんと優先度が変更されてるよー!(´∀`)

$ cat /proc/asound/modules
  0 snd_usb_audio
  1 snd_bcm2835

解決できたおかげで、モチベーションが一気に復活しました。笑
さて、次に一応マイクの感度を調整しておきます。

$ amixer sset Mic 50
Simple mixer control 'Mic',0
  Capabilities: cvolume cvolume-joined cswitch cswitch-joined
  Capture channels: Mono
  Limits: Capture 0 - 62
  Mono: Capture 50 [81%] [16.59dB] [on]

では実際に録音して、マイクに問題が無いか確認します。
arecordコマンドで録音を開始して 、Ctrl + Cで終了します。

$ arecord -r 16000 -f S16_LE test.wav
録音中 WAVE 'test.wav' : Signed 16 bit Little Endian, レート 16000 Hz, モノラル
^C 
シグナル 割り込み で中断...

lsコマンドなどで、ファイルができている事を確認したら、実際に再生してみます。

再生コマンドでは下記の様にサウンドカードナンバーとデバイスナンバーを指定する必要があります。
aplay -Dhw:[カードナンバー, デバイスナンバー] test.wav

RaspberryPi B+には標準でイヤホンジャックがついているので、イヤホンジャックのサウンドカードナンバーとデバイスナンバーを確認します。

 $ aplay -l
**** ハードウェアデバイス PLAYBACK のリスト ****
カード 1: ALSA [bcm2835 ALSA], デバイス 0: bcm2835 ALSA [bcm2835 ALSA]
  サブデバイス: 8/8
  サブデバイス #0: subdevice #0
  サブデバイス #1: subdevice #1
  サブデバイス #2: subdevice #2
  サブデバイス #3: subdevice #3
  サブデバイス #4: subdevice #4
  サブデバイス #5: subdevice #5
  サブデバイス #6: subdevice #6
  サブデバイス #7: subdevice #7
カード 1: ALSA [bcm2835 ALSA], デバイス 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

上記の内容だと
カード:1 デバイス:0 が 【スピーカ出力のオーディオ】
カード:1 デバイス:1 が 【HDMI出力のオーディオ】
の事らしいです。
なので今回使用するのは カード:1 デバイス:0 のですね。

では再生してみます。

$ aplay -Dhw:1,0 test.wav
再生中 WAVE 'test.wav' : Signed 16 bit Little Endian, レート 16000 Hz, モノラル

再生されました!
これで無事に、録音から再生まで一通り問題無い事が確認できました。

docomoAPI

次にdocomoAPIを使ってみます。
まずはdocomo Developer supportに登録します。
登録するとAPIKEYが発行されるので、今後はそのAPIKEYを使ってAPIを使用します。
では、まずは雑談APIから試してみます。

$ python
>>> import requests
>>> import json

>>> url = "https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?APIKEY={}".format(APIKEY)
>>> payload = {
  "utt": "こんにちは",
  "context": "",
  "nickname": "光",
  "nickname_y": "ヒカリ",
  "sex": "女",
  "bloodtype": "B",
  "birthdateY": "1997",
  "birthdateM": "5",
  "birthdateD": "30",
  "age": "16",
  "constellations": "双子座",
  "place": "東京",
  "mode": "dialog",
}
>>> r = requests.post(url, data=json.dumps(payload))
>>> print r.json()['utt']
こんちは

上記の例は「こんにちは」っていう話しかけに対してAPIが「こんちは」って返してます。
簡単ですね!

次に音声認識APIを試します。
こちらは音声データを渡す必要があるので、もう一度ドキュメントを見てみます。

音声データのフォーマット:PCM(MSB)16khz/16bit

とあります。
PCMというのは非圧縮の音声データで、wikiには下記のようにあります

圧縮しない音声フォーマットはPCMそのものであり、Windowsでは .wavとして、Mac OS では .aiffとして格納される。

ちょうど先ほど作成したtest.wav(16khz/16bit)と条件も一致しているので、test.wavを使って試してみます。
ちなみにtest.wavには「なんでやねん」と言っている音声が入っています。
音声認識APIなので、この音声を認識して「なんでやねん」というテキストが返ってきたら良い訳ですね。

$ python
>>> import requests
>>> path = '/home/pi/test.wav'
>>> url = "https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY={}".format(APIKEY)
>>>files = {"a": open(path, 'rb'), "v":"on"}
>>>r = requests.post(url, files=files)
>>> print r.json()['text']
なんでやねん。

返ってきました!成功です!
これでdocomoAPIの使用は問題無さそうですね。

pyaudioで録音する

さて、さきほどマイク入力のテストをしましたが、やはり制御ができないと厳しいので、Pythonでオーディオ制御ができるpyaudioを使って録音を行ってみます。
pyaudioに関してはこちらを参考にさせて頂きました。
ではまず、pyaudioをインストールします。

$ sudo apt-get install python-pyaudio

参考サイトのソースをほぼそのまんまコピーして、動作確認します。

$ python pyaudio_test.py
Please input recoding time>>> 3
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
ここでマイク入力を行います。

では、録音したらmono.wavファイルができている事を確認します。
ファイルができていれば先ほどと同じコマンドで、下記のように再生してみて録音ができてればOKです。

$ aplay -Dhw:1,0 mono.wav

うむ、ばっちり録音されておるわ!

AquesTalkで合成音声

さてさて、これで 音声入力 からの 音声認識 からの 雑談 まではできる様になりました。
あとは雑談APIにて、返ってきた返答をRaspberryPiことRasタソに発声させます。

まずはAquesTalkを公式サイトからダウンロードし、RaspberryPiにコピーします。

localpc ~ $ scp  ~/Desktop/aquestalkpi-20130827.tgz user@host:/home/pi/
aquestalkpi-20130827.tgz

RaspberryPiにコピーできたら、RaspberryPi内で解凍します。

$ tar xvf aquestalkpi-20130827.tar

解凍ができたら試しに実行してみます。
イヤホンから合成音声が聞こえてきたらOKです。

$ cd aquestalkpi
$ ./AquesTalkPi "オンギャーオンギャー" |  aplay -Dhw:1,0

再生!!
\オンギャーオンギャー/
き。。。聞こえました。。。
Rasタソが初めての自ら発声しました。。。泣
命が吹き込まれた瞬間です。笑

Rasタソと会話するの巻

さて、今のRasタソは脳に直接書き込まれた文章を発声するだけですが、こんなRasタソに知性を与えて「聞く、考える、発声する」という会話能力を与えます。

といっても上でやってきた事をまとめるだけなので、やる事は簡単です。
こんな感じでdialogue_test.pyを作成します。

復習がてらにdialogue_test.pyの内容を説明すると、
 1.pyaudioでマイク入力を受付。
 2.その音声をdocomo音声認識APIで認識してTextにします。
 3.そのTextを元にdocomo雑談APIを叩きます
 4.その結果を合成音声でしゃべらせます。
というものですね。

では実行してみます。

$ python dialogue_test.py
Please input recoding time>>>3
~~snip~~
元気ですか? ←マイクから入力した内容
元気でちゅ ←Rasタソの回答

$ python dialogue_test.py
Please input recoding time>>>3
~~snip~~
何歳ですか。←マイクから入力した内容
ところで、気になるニュースがあるんだが、カルビーと阪急そばがコラボレーションして「関西だししょうゆポテトチップスそば・うどん」を期間限定で販売、関西だしの旨みと斬新な食感が味わえるそうでちゅ←Rasタソの回答

$ python dialogue_test.py
Please input recoding time>>>3
~~snip~~
誕生おめでとう。←マイクから入力した内容
楽しみに待ってるか? ←Rasタソの回答

赤ちゃんなので、ちょっと会話が成り立たない事もありますが、そこは御愛嬌です!
ちゃんとdocomoAPIを使いこなせば、もっと賢くなるそうですが、それは今後の課題にしていきます。

最後に

こんな事まで簡単にできてしまう世の中にビックリですね。
今後はもっと賢くして、Siriみたいにして、顔認識機能とかもつけてとか夢は広がります^^
という事で、今後も時間と根気などがあればRasタソを成長させていきますので楽しみに待っていてください。