Raspberry Pi で音声カメラ 【Actions on Google/Google Assistant SDK/API.ai】

Google Assistant SDKの「アクション」を使ってRaspberry Piから写真を撮れるようにしてみました。こんな感じです。

[![Raspberry Pi Actions on Google ](http://img.youtube.com/vi/YSzkQmLTU6E/0.jpg)](https://www.youtube.com/watch?v=YSzkQmLTU6E)

[Raspberry Pi Camera Actions on Google - YouTube](https://www.youtube.com/watch?v=YSzkQmLTU6E)

GoogleAssistantから「アクション（ここでは`Samantha`）」を呼び出し、「インテント（意図）」により写真を撮影しています。画像はこんな感じで保存されています。

![sample.png](https://qiita-image-store.s3.amazonaws.com/0/47128/3902a35d-1755-d7a2-9887-04c110f25290.png)

これ自体はどうということもないのですが、顔認識や画像認識と組み合わせることも可能だと思いますのでちょっとまとめてみます。

## 環境

環境は[前回記事](http://qiita.com/PonDad/items/bd54cf02805cddc064b2)と同じです。

## Actions on Google

大まかな流れは[前回記事](http://qiita.com/PonDad/items/bd54cf02805cddc064b2)でまとめました。

Google Assistantのアクション（要は音声アプリですね）はIFTTTを使って簡易的に行うことも出来るのですが、細かい音声コマンドの指定や、APIなどを利用した音声での返答は、Actions on Googleと名付けられた開発者ツールを使用します。

![Actions on Google.png](https://qiita-image-store.s3.amazonaws.com/0/47128/10710097-d982-5394-0f16-810f9c11fbef.png)

管理画面はこんな感じです。シミュレータもここから起動させることが出来ます。

ポイントになるのは、音声認識→音声発話の処理を「Google Cloud Functions」を使っていることです。「Google Cloud Functions」に関しては以下の記事でまとめられています。

[【翻訳記事】Google Cloud Functions 対 AWS Lambda: サーバーレスクラウドを制する闘いが始まる - Qiita](http://qiita.com/bump_of_kiharu/items/355a73412445c5ff59a0)

調べてみると比較的新しいサービスのようです。AWS Lambdaに対抗して作られた、イベントで駆動するサーバレスアーキテクチャーとのこと。今のところライブラリはNode.jsのみ提供されています。

今回は音声コマンドを使って写真を撮り、実行後の戻り値をGoogle Assistantに返答させてみようと考えました。（わざわざ戻り値を返信させるのは、画像認識結果を返信させるテストの為です）

RaspberryPiはIFTTTでやった時と同じようにWebhookを利用すれば動作させられるはずです。調べたらサンプルレポジトリが公開されていましたのでこちらを利用して動作させます。

[actions-on-google/apiai-webhook-template-nodejs - GitHub](https://github.com/actions-on-google/apiai-webhook-template-nodejs)

## やってみる

詳しくは公式ページの「Get started」に沿ってやっていきました。

[Get started　- Actions on Google](https://developers.google.com/actions/get-started/)

会話部ははAPI.aiを利用します。「エージェント」名は某映画の人工知能と同じ「Samantha」とつけてみました。

![API.AI.png](https://qiita-image-store.s3.amazonaws.com/0/47128/b993ee57-3ed9-01c9-221d-d8f2bed42d10.png)

この「エージェント」の「インテント」で「Say Cheese」と話しかけると「Google Cloud Functions」にWebhookを送信し、イベント駆動させます。
![API.AI (1).png](https://qiita-image-store.s3.amazonaws.com/0/47128/baa27e25-2bc5-36c6-2ab4-4e457fa77128.png)

名前が同じでちょっと分かりづらいのですが「インテント」の中で「Action」の項目があるので、そこにした名前をWebhookで受け取ります。（ここでは`talk.camera`）

「Cloud Functions」のアクション名はここでは`yourAction`になります。ちょっと分かりづらいので注意が必要です。

![Cloud Functions   blissful flames.png](https://qiita-image-store.s3.amazonaws.com/0/47128/39819c98-b4ab-3b82-128c-2e41ae7331b6.png)

「Google Cloud Functions」の中身はこんな風に記述してみました。

```js:index.js
'use strict';

process.env.DEBUG = 'actions-on-google:*';
const App = require('actions-on-google').ApiAiApp;

const exec = require('child_process').exec;
const adress = "https://xxxxxxx.ap.ngrok.io/camera"

// [START YourAction]
exports.yourAction = (request, response) => {
  const app = new App({request, response});
  console.log('Request headers: ' + JSON.stringify(request.headers));
  console.log('Request body: ' + JSON.stringify(request.body));

  // Fulfill action business logic
  function responseHandler (app) {
    // Complete your fulfillment logic and send a response
    exec(`curl ${adress}`, (error, stdout, stderr) => {
      if (error) {
        console.error(`exec error: ${error}`);
        return;
      }
      console.log(`${stdout}`);
      app.ask(`<speak>I took your picture. <break time='1' /></speak>` + `${stdout}`);
    });

  }

  const actionMap = new Map();
  actionMap.set('talk.camera', responseHandler);

  app.handleRequest(actionMap);
};
// [END YourAction]

```
API.aiから送付したリクエストを元に処理を行い、GoogleAssistantへレスポンスを返します。

ここではリクエストを受けとった際、Curlコマンドを利用してRaspberryPiに立てたローカルサーバー(ここでは`https://xxxxxxx.ap.ngrok.io/camera`)へ更にWebhookを送信させています。

RaspberryPiのレスポンスは`child_process`の`${stdout}`で受け取ることが出来、戻り値を発話させることが出来ています。

![ファイル_000.jpeg](https://qiita-image-store.s3.amazonaws.com/0/47128/8ed61a99-5c39-0ade-1abd-93a8b41e9a3e.jpeg)

RaspberryPiで立てたローカルサーバーはこんな感じです。

```python:home/pi/env/cv_work/camera.py
from flask import Flask
from flask import request, jsonify
import json
import numpy as np
import cv2
import subprocess
import time

app = Flask(__name__)

@app.route('/camera')
def camera():
    cap = cv2.VideoCapture(0)
    cap.set(3, 760)
    cap.set(4, 480)
    while(True):
        ret, frame = cap.read()
        cv2.imshow('frame', frame)
        subprocess.call( ["aplay", "camera.wav"] )
        time.sleep(0.5)
        cv2.imwrite("sample.png", frame)
        text = "cool"
        break

    cap.release()
    cv2.destroyAllWindows()

    return text

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8080)
```
Google Assistant SDKが仮想環境からでないと起動しなくなったので、同じフォルダ`env`内にOpenCVを入れ直しました。ローカルサーバーはFlaskで立て、エンドポイントにリクエストが来るとカメラで撮影し（ついでにシャッター音を再生させました）保存します。

`return text`で戻り値を返信できるので、Kerasを使って画像認識させ、戻り値を返信させるのも可能だと思います。

※Google Assistantの返信の待ち受け時間は5秒位の様です。画像認識させるにはその間にさせなければなりません。（RaspberryPiだと処理が遅いのでちょっと厳しいかも。ここら辺もクラウドを使った方が良いのでしょうか...。）

ローカルサーバーはngrokを利用してグローバルアドレスで接続出来る様にしておきます。

## まとめ

今度は物体認識とか顔認識を試してみたいと思います。

正直かなり面倒くさいんですけど、GoogleAssistantの切れのある返信は魅力的です（ヘタするとこちらが言い終わると同時に即レスを返信してきます）。ではまた。