Respeakerで音声認識を行う

ReSpeakerというマイクアレイ+Linux搭載デバイスを使って音声認識を試してみました。

## できたこと

「Brian, play music!」というと、音楽をかけてくれる

![mov.gif](https://qiita-image-store.s3.amazonaws.com/0/42108/43d574f4-f9c0-f15d-bed7-bc12558ba034.gif)


## 試したこと

[Getting Started](https://github.com/respeaker/get_started_with_respeaker)に従って以下を試しました。

- ローカル(ネットワークに繋がない)の音声認識で`Hey Siri`的な認識開始ワードを認識する。
- 認識開始ワードに続く言葉を取得し、Bing APIに投げ、結果を取得する。
- 認識結果を使って音楽を再生する。

## ReSpeakerとは

[SeeedStudio](https://www.seeedstudio.com/about_us.html)という中国のスタートアップが販売しているデバイスです。
[XMOS](http://www.xmos.com/)という会社のマイクロホンアレイ用プロセッサ(複数のマイクを扱い、発話位置の同定や発話部分の音のみを切り出すことができる)と、[Ascip7688](http://www.acsip.com.tw/index.php?action=products-detail&fid1=11&fid2=&fid3=&id=75)というWi−Fi内蔵プロセッサモジュールが搭載されています。デフォルトで[OpenWRT](https://openwrt.org/) Linux上にさまざまな便利ライブラリが搭載されています。

ここから購入できるようです。
- [Linuxが乗ってる基板](https://www.seeedstudio.com/ReSpeaker-Core-Based-On-MT7688-and-OpenWRT-p-2716.html)
- [マイクアレイが乗ってる基板](https://www.seeedstudio.com/ReSpeaker-Mic-Array-Far-field-w%2F-7-PDM-Microphones--p-2719.html)

## ReSpeakerの初期設定を行う

### ReSpeakerをインターネットに繋げる

ReSpeakerに電源を入れると、ReSpeakerはWi-Fiのアクセスポイントとして立ち上がります。ReSpeakerXXXXのような名前のアクセスポイントに対して接続し、 `http://192.168.100.1`に接続すると、家のWi-FiのSSIDとパスワードを入力する画面が表示されます。ここで適切な値を入力すれば、ReSpeakerは家のWi-Fiに接続されます。

<img width="284" alt="スクリーンショット 2017-01-29 12.58.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/42108/cf0f7136-2f44-a8e3-cb70-1b6630469e00.png">

あらためて`http://192.168.100.1`を開くと以下のように接続状態を確認することができます。また、ネットワークやデバイスの各種設定(SSIDの変更、ファームウェアのアップグレード)も行うことができます。

### ReSpeakerにSSHで接続する

PCからReSpeakerをアクセスポイントとして接続している状態でSSH接続することができます。
```
ssh root@192.168.100.1
```
こんな感じのコンソールになります。デフォルトユーザとパスワードがログインメッセージに書かれてるのがなんかすごい。
<img width="386" alt="スクリーンショット 2017-01-29 13.10.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/42108/8bfd3ffb-e5f4-4129-c949-8687c0f1284d.png">

### Bingの音声認識APIを使う準備をする

[Bing Speech API](https://www.microsoft.com/cognitive-services/en-us/speech-api)に無料会員登録を行います。登録が完了するとkey1とkey2が取得できるのですが、今回はkey1のみを使用します。

<img width="658" alt="スクリーンショット 2017-01-29 16.35.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/42108/9bad1329-443b-8a1b-1bf2-64db58f2711f.png">

## サンプルを実行する

### 実行する

- [ReSpeakerチームのが提供するサンプルコード](https://github.com/respeaker/get_started_with_respeaker/blob/master/QuickStart.md#first-impression-with-voice-interaction---respeaker-play-music)を、ReSpeakerの`/root`以下に置きます。
- 先程取得した音声認識用のKey1を`BING_KEY`としてコード内に記載します。
- 実行します。
    - `python sample.py`

## サンプルを読む

### ローカルの音声認識

ローカルの音声認識は[pocketsphinx](https://github.com/cmusphinx/pocketsphinx)を使用しています。

[ReSpeakerに出荷時に配置されている`respeaker`ライブラリ](https://github.com/respeaker/respeaker_python_library/blob/master/respeaker/microphone.py)を見てみます。

- 初期化時に音響モデルとキーワードを読み込み(ここで5秒くらい実行時間がかかっています)

```respeaker/microphone.py
    def create_decoder():
        from pocketsphinx.pocketsphinx import Decoder

        path = os.path.dirname(os.path.realpath(__file__))
        pocketsphinx_data = os.getenv('POCKETSPHINX_DATA', os.path.join(path, 'pocketsphinx-data'))
        hmm = os.getenv('POCKETSPHINX_HMM', os.path.join(pocketsphinx_data, 'hmm'))
        dict = os.getenv('POCKETSPHINX_DIC', os.path.join(pocketsphinx_data, 'dictionary.txt'))
        kws = os.getenv('POCKETSPHINX_KWS', os.path.join(pocketsphinx_data, 'keywords.txt'))
```

- 音声データの取得を開始し、

```respeaker/microphone.py
    def detect(self, keyword=None):
        self.decoder.end_utt()
        self.decoder.start_utt()
        ...
        self.status |= self.detecting_mask
        self.stream.start_stream()```
```

- ストリームを認識器に渡し、認識対象となる単語の有無を判定しています。

```respeaker/microphone.py
    def detect(self, keyword=None):
        ...
            hypothesis = self.decoder.hyp()
            ...
                    if hypothesis.hypstr.find(keyword) >= 0:
                        result = hypothesis.hypstr
```

### サーバでの音声認識

上記と同じリポジトリにある`bing_speech_api.py`ではAPIを叩いて結果をテキストで取得しています。

```respeaker/bing_speech_api.py
    def recognize(self, audio_data, language="en-US", show_all=False):
        self.authenticate()
        if isinstance(audio_data, types.GeneratorType):
            def generate(audio):
                yield self.get_wav_header()
                for a in audio:
                    yield a

            data = generate(audio_data)
        else:
            data = self.to_wav(audio_data)

        params = {
            ...
        }

        headers = {
            "Authorization": "Bearer {0}".format(self.access_token),
            "Content-Type": "audio/wav; samplerate=16000; sourcerate=16000; trustsourcerate=true",
        }

        url = "https://speech.platform.bing.com/recognize/query"
        response = self.session.post(url, params=params, headers=headers, data=data)

        if response.status_code != 200:
            raise RequestError("http request error with status code {}".format(response.status_code))

        result = response.json()
        ...
```

## サンプルをいじってみる

### [トラブルシューティング]Bingサーバにうまく接続できない場合

2017/01/29時点では、pipでインストールされる(または出荷状態でインストールされている)respeakerの音声認識ライブラリが古く、適切にBing APIに接続できない問題がありました。([このコミット](https://github.com/respeaker/respeaker_python_library/commit/488938624ceefa881527ce5095830683df1ad119)がまだ取り込まれていない)

[ライブラリのGithubリポジトリ](https://github.com/respeaker/respeaker_python_library)からコードを取得し、ReSpeakerに`scp`した上で`python setup.py install`することで修正後のコードをインストールすることができました。

### トリガーとなる音声を変えてみる

プリインストールされている音響モデルが英語向けのものなので、別の英単語の認識ならすんなりと行きます。

- 1: RespeakerにSSH等で接続します。
- 2: `/usr/lib/python2.7/site-packages/respeaker/pocketsphinx-data/dictionary.txt`
 - 例: `brian	B R AY AH N`
 - [ここ](https://raw.githubusercontent.com/respeaker/pocketsphinx-data/master/dictionary.txt)の単語から選ぶと良さそう。
- 3: `/usr/lib/python2.7/site-packages/respeaker/pocketsphinx-data/keyword.txt`に認識したい単語とそのthresholdを追記します
    - 例: `brian /1e-6/`
- 4: サンプルアプリを再起動すると`Brian!`と呼びかけることでReSpeakerが反応します。

### 認識中のLEDパターンを変えてみる

linuxが乗っているチップセットとは別のマイコン(arduino/ATmega32U4)に対してSPIでコマンドを送っています。

```respeaker/pixel_ring.py
    def write(self, address, data):
        data = self.to_bytearray(data)
        length = len(data)
        if self.hid:
            packet = bytearray([address & 0xFF, (address >> 8) & 0xFF, length & 0xFF, (length >> 8) & 0xFF]) + data
            self.hid.write(packet)
            print(packet)
        spi.write(address=address, data=data)
```

こんな感じにデータをセットします。
```
pixel_ring.set_color(rgb=0x20FF30)
```

LEDは[WS2812(NeoPixel)](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf)を使ってる模様。

## 感想

- 音声認識のためのライブラリやサンプルコードがすでにインストールされていて、すぐに触り始めることができるので素晴らしいと思いました。
- マイクの基板のみをPCに挿してUSBマイクとしても認識させることができるので、OpenWRTにstickしない形でも遊べそう(未確認)。
- [node-pocketsphinx](https://github.com/cmusphinx/node-pocketsphinx)というのがあるらしいので、nodeで書いてみたい。
- 音声をトリガーにして、amazonで注文したり家電をコントロールしたりjokeを返したりしてみたい(g○○gle H○meとかAmaz○n Ech○みたいに)