SORACOMで早口言葉練習(音声認識)

スマホやパソコンだけでなく、IoTデバイスでもクラウドサービスの音声認識使ってみたいよね、ってことで『早口言葉の練習』をお題にして、Raspberry Piで音声認識をやってみた。
音声認識は、APIで使えるクラウドサービスRECAIUS、通信は、WiFi環境がなくても使えるSORACOMを使った。

# 環境
* Raspberry Pi 2 Model B + LCDディスプレイ( KOOKYE LCD touch screen )
 * Python, Requests, PyAudio, Tkinter
* 3G USBドングル ( AK-020 ) + SORACOM SIM
* USBマイク ( MM-MCUSB16 )
* USBマウス

![環境_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/125329/bfe792a3-e66e-27ff-7777-aaf03a596aef.jpeg)

# 音声認識はRECAIUS
RECAIUS Developersのサイトでユーザ登録するとAPIが翌月末まで使えるIDとパスワードが発行されるので、それを使う(繰り返し登録することで延長可)。
https://developer.recaius.io

# 通信はSORACOM
RECAIUSはクラウドで音声認識するので、Raspberry Piで録音した音声ファイルを送ると、認識した結果がテキストで帰ってくる。
今回は、WiFi環境がなくても使えるように、SORACOMを使った。

![音声認識の仕組み.jpg](https://qiita-image-store.s3.amazonaws.com/0/125329/9cbd8238-4fa7-ec2c-8899-696c838ca138.jpeg)

# 実行スクリプト

以下の2つのスクリプトをデスクトップに用意しておき、順に実行する(アイコンを右クリックして"開く")。

* connect_air.sh : SORACOM接続実行スクリプト
* suiko.sh : 早口言葉練習「スイコ」実行スクリプト

![画面_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/125329/0d5daf4c-27eb-7e29-9b65-8eb912a7f80a.jpeg)

```connect_air.sh
#!/bin/sh

sudo /usr/local/sbin/connect_air.sh
```

`/usr/local/sbin/connect_air.sh`は、SORACOM公式のやり方
[各種デバイスで SORACOM Air を使用する](https://dev.soracom.io/jp/start/device_setting/)
でget。

```suiko.sh
#!/bin/sh

cd /home/pi/recaius/python-recaius

./suiko.py
```

`suiko.py`の中身は後述。

# 動作
* `suiko.sh`を実行するとGUIが立ち上がる

![動作1_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/125329/be102d04-5bf6-2ad8-b23c-1f1265a65724.jpeg)

* スタートボタンをクリックすると録音開始し、2秒後に録音停止する
 * この2秒間でお題の早口言葉を言う！ 

![動作2_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/125329/a390f43d-ec04-4246-3c39-50eb1f17ffef.jpeg)

* 録音停止後、RECAIUSにSORACOM経由で音声ファイルを送信する

![動作3_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/125329/b88d2a1f-da99-7935-b2a1-e550b4637835.jpeg)

* 音声認識されたテキストファイルを受信して表示する(赤字のところ)
 * 早口言葉が正しく言えてたら(認識されていたら)お題と同じになる！

![動作4_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/125329/d0b05e54-1370-dbeb-c630-fb8433b234d0.jpeg)

ぜんぜん言えてない・・・

# コード

```suiko.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from recaius.asr import RecaiusASR
from settings import ASR_ID, ASR_PASSWORD

import pyaudio
import wave

import sys
import Tkinter

import threading

def ClickStart(event):
  Button1.configure(state=u'disabled')
  Label4.configure(fg='blue', text=u'録音中...')
  Label4.pack()

  t_recording = threading.Thread(target=Recording)
  t_recording.start()


def Recording():
  CHUNK = 4096
  FORMAT = pyaudio.paInt16 #
  CHANNELS = 1             #
  RATE = 16000             #
  RECORD_SECONDS = 2       #
  WAVE_OUTPUT_FILENAME = "output.wav"

  p = pyaudio.PyAudio()

  stream = p.open(format=FORMAT,
                  channels=CHANNELS,
                  rate=RATE,
                  input=True,
                  frames_per_buffer=CHUNK)

  print("* recording")

  frames = []

  for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
      data = stream.read(CHUNK)
      frames.append(data)

  print("* done recording")

  stream.stop_stream()
  stream.close()
  p.terminate()

  print("* transrating")

  wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
  wf.setnchannels(CHANNELS)
  wf.setsampwidth(p.get_sample_size(FORMAT))
  wf.setframerate(RATE)
  wf.writeframes(b''.join(frames))
  wf.close()

  print("* done transrating")

  Label4.configure(fg='blue', text=u'認識中...辛抱強く待つのじゃ')
  Label4.pack()

  t_recognition = threading.Thread(target=Recognition)
  t_recognition.start()

def Recognition():
  rec = RecaiusASR(ASR_ID, ASR_PASSWORD)
  rec.set_lang('ja_JP')

  uuid = rec.login()

  # speech recognition from wave file
  result = rec.recognize(uuid, 'output.wav')

  rec.logout(uuid)

  print(result)
  if(result == ''):
    Label4.configure(fg='red', text=u'？？？ 認識できませんでした')
    Label4.pack()
  else:
    Label4.configure(fg='red', text=result)
    Label4.pack()

  Button1.configure(state=u'normal')

def ClickNext(event):
  global index
  index += 1
  index = index % 11

  if index == 0:
    Label2.configure(text=u'『 生麦 生米 生卵 』\n（なまむぎ なまごめ なまたまご）\n')
    Label2.pack()
  elif index == 1:
    Label2.configure(text=u'『 東京特許許可局 』\n（とうきょうとっきょ きょかきょく）\n')
    Label2.pack()
  elif index == 2:
    Label2.configure(text=u'『 青巻紙 赤巻紙 黄巻紙 』\n（あおまきがみ　あかまきがみ　きまきがみ）\n')
    Label2.pack()
  elif index == 3:
    Label2.configure(text=u'『 隣の客は よく柿食う 客だ 』\n（となりのきゃくは よくかきくう きゃくだ）\n')
    Label2.pack()
  elif index == 4:
    Label2.configure(text=u'『 庭には 二羽 鶏がいた 』\n（にわには にわ にわとりがいた）\n')
    Label2.pack()
  elif index == 5:
    Label2.configure(text=u'『 ジャズシャンソン歌手 』\n（じゃず しゃんそん かしゅ）\n')
    Label2.pack()
  elif index == 6:
    Label2.configure(text=u'『 新春シャンソンショー 』\n（しんしゅん しゃんそん しょー）\n')
    Label2.pack()
  elif index == 7:
    Label2.configure(text=u'『 除雪車除雪作業中 』\n（じょせつしゃ じょせつ さぎょうちゅう）\n')
    Label2.pack()
  elif index == 8:
    Label2.configure(text=u'『 李も桃も桃のうち 』\n（すももも ももも もものうち）\n')
    Label2.pack()
  elif index == 9:
    Label2.configure(text=u'『 竹藪に 竹立てかけた 』\n（たけやぶに たけ たてかけた）\n')
    Label2.pack()
  elif index == 10:
    Label2.configure(text=u'『 坊主が 屏風に 上手に 坊主の絵を描いた 』\n（ぼうずが びょうぶに じょうずに ぼうずのえをかいた）\n')
    Label2.pack()

  Label4.configure(text=u'')
  Label4.pack()

root = Tkinter.Tk()
root.title(u"SUIKO - スイコ")
root.geometry("{0}x{1}+0+0".format(root.winfo_screenwidth(), root.winfo_screenheight()))
root.configure(bg=u"White")
#root.overrideredirect(True)

Label1 = Tkinter.Label(fg='Blue', font=('FixedSys', '20'), text=u'\n2秒以内で言うのじゃ\n')
Label1.configure(bg=u"White")
Label1.pack()

# お題
index = 0
Label2 = Tkinter.Label(text=u'『 生麦 生米 生卵 』\n（なまむぎ なまごめ なまたまご）\n')
Label2.configure(font=('FixedSys', '14'))
Label2.configure(bg=u'White')
Label2.pack()

Button1 = Tkinter.Button(text=u'スタート')
Button1.bind("<Button-1>",ClickStart) 
Button1.pack()

# 空行
Label3 = Tkinter.Label(text=u'')
Label3.configure(bg=u"White")
Label3.pack()

# 結果
Label4 = Tkinter.Label(text=u'')
Label4.configure(font=('FixedSys', '14'))
Label4.configure(bg=u"White")
Label4.pack()

# 空行
Label5 = Tkinter.Label(text=u'')
Label5.configure(bg=u"White")
Label5.pack()

Button2 = Tkinter.Button(text=u'次のお題')
Button2.bind("<Button-1>",ClickNext) 
Button2.pack()

root.mainloop()
```

* RECAIUS APIのPythonラッパーは、以下を参考にさせていただきました
[Python wrapper for RECAIUS API](https://github.com/aidiary/python-recaius)

* PyAudio(マイク入力から音声ファイルへの変換)は、以下を参考にさせていただきました
[Pythonでマイクからの音を録音(pyaudio使用) – あかさたな](http://www.ningendesu.net/?p=655)
[PyAudioの基本メモ2 音声入出力 - たけし備忘録](http://takeshid.hatenadiary.jp/entry/2016/01/10/153503)

* USBマイクの設定は、以下を参考にさせていただきました
[簡単にできる！音声認識と音声合成を使ってRaspberrypiと会話 - Qiita](http://qiita.com/kinpira/items/75513eaab6eed19da9a3)
[Raspberry Piで音声認識 - Qiita](http://qiita.com/t_oginogin/items/f0ba9d2eb622c05558f4)
* GUI(Tkinter)は、以下を参考にさせていただきました
[PythonのTkinterを使ってみる - Qiita](http://qiita.com/nnahito/items/ad1428a30738b3d93762)

# Maker Faire Tokyo 2016で展示した

[つくるラボ](https://www.facebook.com/tsukurulab.team/)

![MFT_01.JPG](https://qiita-image-store.s3.amazonaws.com/0/125329/284de968-ad11-29a8-37aa-d71823bfab3d.jpeg)

現状では、結果が表示されるまでに、10秒程度かかってしまう。
音声ファイルへの変換、音声ファイルの送信に時間がかかっている。
2秒間の録音後、音声ファイルへの変換を開始、変換完了後に送信を開始しているので、これを数ミリ秒区切りで、マルチスレッドで実行できるようにすれば、もう少し速くなるかもしれない。
ローカルで音声認識するリソースがなかったり、頻繁に音声認識を使わず、結果の表示までの時間をうまく隠せる使い方があれば、今回つくったような音声認識の環境が使えそう。