カメラ画像から顔が検出されたらループを抜け、ユーザに回答を促すための発話を行います。選択肢としてメニューのリストの内容も読み上げ、リストの最後の要素とその前の要素の間では英語っぽく or を挟むようにしています。
self.status_ui.status('listening')
aiy.audio.say('Hi! What would you like to drink?')
for i, menu inenumerate(self.menu_list):
if i == len(self.menu_list) - 1:
aiy.audio.say('or')
aiy.audio.say(menu)
text = self.recognizer.recognize()
ifnot text:
aiy.audio.say('Sorry, I did not hear you.')
elif text notin self.menu_list:
print('You said: %s' % text)
aiy.audio.say('Sorry, we can not provide it.')
else:
aiy.audio.say("Sure. We'll brought you a tasty %s. Please wait a sec!" % text)
cv2.waitKey(3000)
cap.release()
前回は Voice Kit + Web カメラで画像を表示しつつ、音声で写真を撮るということをやってみました。今回は Web カメラで撮っている画像の中に人がいるかどうかを顔認識によって判定してみたいと思います。
実装内容
今回の実装内容としては、スクリプトを実行すると Web カメラ画像の表示を開始し、 "OK Google, is anyone there?"(そこに誰かいる?)と発話すると、画像内に人の顔があるかを検出し、顔があった場合は "Yes, someone is there." と言って画像内で検出された顔を枠で囲み、緑色のLEDを点灯させます。顔がない場合は "No one is there." と言って黄色のLEDを点灯させます。
pi@raspberrypi:~ $ ls-l opencv/data/haarcascades
合計 9568
-rw-r--r-- 1 pi pi 3414062月 607:59 haarcascade_eye.xml
-rw-r--r-- 1 pi pi 6016612月 607:59 haarcascade_eye_tree_eyeglasses.xml
-rwxr-xr-x 1 pi pi 4113882月 607:59 haarcascade_frontalcatface.xml
-rwxr-xr-x 1 pi pi 3829182月 607:59 haarcascade_frontalcatface_extended.xml
-rw-r--r-- 1 pi pi 6767092月 607:59 haarcascade_frontalface_alt.xml
-rw-r--r-- 1 pi pi 5406162月 607:59 haarcascade_frontalface_alt2.xml
-rw-r--r-- 1 pi pi 26890402月 607:59 haarcascade_frontalface_alt_tree.xml
-rw-r--r-- 1 pi pi 9301272月 607:59 haarcascade_frontalface_default.xml
-rw-r--r-- 1 pi pi 4768252月 607:59 haarcascade_fullbody.xml
-rw-r--r-- 1 pi pi 1953692月 607:59 haarcascade_lefteye_2splits.xml
-rw-r--r-- 1 pi pi 477752月 607:59 haarcascade_licence_plate_rus_16stages.xml
-rw-r--r-- 1 pi pi 3953202月 607:59 haarcascade_lowerbody.xml
-rw-r--r-- 1 pi pi 8285142月 607:59 haarcascade_profileface.xml
-rw-r--r-- 1 pi pi 1961702月 607:59 haarcascade_righteye_2splits.xml
-rw-r--r-- 1 pi pi 754822月 607:59 haarcascade_russian_plate_number.xml
-rw-r--r-- 1 pi pi 1885062月 607:59 haarcascade_smile.xml
-rw-r--r-- 1 pi pi 7858172月 607:59 haarcascade_upperbody.xml
上記チュートリアルの内容を前回のコードをベースに組み込んで行きます。実装する処理としては、スクリプトを起動したら Web カメラの映像をデスクトップに表示開始し、 "OK Google. Take a picture." と発話したらカウントダウンして写真を撮影し、その画像を数秒表示したらまた Web カメラの映像を開始するというものです。
def_run_event(self):
with Assistant(self.credentials) as assistant:
self._assistant = assistant
for event in assistant.start():
self._process_event(event)
発話内容が "Take a picture." だった場合の処理からは画像のキャプチャ用処理は削除し、フラグの変更だけを行なっています。
前回は Voice Kit にブレッドボードとLEDをつないで音声で操作してみました。今回は手持ちの Web カメラを繋げて音声で操作して写真を撮ってみたいと思います。
Web カメラの接続
今回はUSB接続の Web カメラを使用します。まず接続前の USB デバイスの認識状況です。
pi@raspberrypi:~ $ lsusb
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. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
pi@raspberrypi:~ $
pi@raspberrypi:~ $ ls /dev/video*
ls: '/dev/video*' にアクセスできません: そのようなファイルやディレクトリはありません
そして Raspberry Pi の USB ポートに Web カメラを接続した後は下記のようになります。
pi@raspberrypi:~ $ lsusb
Bus 001 Device 004: ID 056e:700f Elecom Co., Ltd
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. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
pi@raspberrypi:~ $
pi@raspberrypi:~ $ ls /dev/video*
/dev/video0
Device 004 として Elecom の Web カメラが認識され、デバイスファイルも作成されました。とりあえずのカメラの動作テスト用に、 guvcview をインストールします。
[Desktop Entry]Encoding=UTF-8Type=Application
Name=Start dev terminal
Commment=Start a terminal with the environment for the AIY projects kit.
Exec=/home/pi/bin/AIY-projects-shell.sh
Terminal=trueIcon=utilities-terminal
#!/bin/bash --rcfilesource /etc/bash.bashrc
source ~/.bashrc
cat /etc/aiyprojects.info
cd ~/AIY-projects-python
source env/bin/activate
echo"Dev terminal is ready! See the demos in 'src/examples' to get started."
(env) pi@raspberrypi:~ $ python
Python 3.5.3 (default, Jan 192017, 14:11:04)[GCC 6.3.0 20170124] on linux
Type "help", "copyright", "credits" or "license"for more information.
>>>>>> import cv2
>>>>>> c =cv2.VideoCapture(0)>>>>>>r, img =c.read()>>>>>> cv2.imwrite('capture.jpg', img)
True
>>>
(env) pi@raspberrypi:~/AIY-projects-python $ src/examples/voice/assistant_library_demo.py
ALSA lib pcm.c:8403:(snd_pcm_set_params) Rate doesn't match (requested 16000Hz, get 48000Hz)[1013:1028:ERROR:audio_input_processor.cc(755)] Input error/home/pi/AIY-projects-python/src/aiy/_drivers/_led.py:51: RuntimeWarning: This channel is already in use, continuing anyway. Use GPIO.setwarnings(False) to disable warnings. GPIO.setup(channel, GPIO.OUT)Say "OK, Google" then speak, or press Ctrl+C to quit...[1013:1031:ERROR:audio_input_processor.cc(755)] Input errorSegmentation fault
動作確認が取れたのでスクリプトを実装していきます。前回の LED のスクリプトの発話内容による分岐に下記の処理を追加します。「Take a picture.」と発話するとカウントダウンして写真を撮影します。また、カウントダウンに合わせて LED も使用してみました。
elif text == 'take a picture':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
now = datetime.now()
filename = 'capture_%s.jpg' % now.strftime('%Y%m%d_%H%M%S')
aiy.audio.say('OK, 3')
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
time.sleep(1)
aiy.audio.say('2')
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.HIGH)
time.sleep(1)
aiy.audio.say('1')
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
time.sleep(1)
c = cv2.VideoCapture(0)
r, img = c.read()
if cv2.imwrite(filename, img):
self.print_and_say('I took nice one.')
else:
self.print_and_say("I couldn't take a picture.")
c.release()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
print('Captured Image: %s' % filename)
スクリプト全体は下記のようになります。
#!/usr/bin/env python3import sys
import time
import cv2
from datetime import datetime
import aiy.assistant.auth_helpers
import aiy.audio
import aiy.voicehat
from google.assistant.library import Assistant
from google.assistant.library.event import EventType
import RPi.GPIO as GPIO
classMyAssistant:
GPIO_LED_GREEN = 2
GPIO_LED_YELLOW = 3def__init__(self):
self.print_and_say('Initializing MyAssistant.')
self.credentials = aiy.assistant.auth_helpers.get_assistant_credentials()
self.status_ui = aiy.voicehat.get_status_ui()
GPIO.setmode(GPIO.BCM)
GPIO.setup(MyAssistant.GPIO_LED_GREEN, GPIO.OUT)
GPIO.setup(MyAssistant.GPIO_LED_YELLOW, GPIO.OUT)
defprint_and_say(self, text):
print(text)
aiy.audio.say(text)
defprocess_event(self, assistant, event):
print('Processing event. The event is %s.' % event.type)
if event.type == EventType.ON_START_FINISHED:
self.status_ui.status('ready')
if sys.stdout.isatty():
print('Say "OK, Google" then speak.')
elif event.type == EventType.ON_CONVERSATION_TURN_STARTED:
self.status_ui.status('listening')
elif event.type == EventType.ON_RECOGNIZING_SPEECH_FINISHED and event.args:
print('You said: %s' % event.args['text'])
text = event.args['text'].lower()
if text == 'turn on green led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
self.print_and_say('Turned on green LED.')
elif text == 'turn off green led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
self.print_and_say('Turned off green LED.')
elif text == 'turn on yellow led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.HIGH)
self.print_and_say('Turned on yellow LED.')
elif text == 'turn off yellow led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
self.print_and_say('Turned off yellow LED.')
elif text == 'turn on all led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.HIGH)
self.print_and_say('Turned on all LED.')
elif text == 'turn off all led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
self.print_and_say('Turned off all LED.')
elif text == 'take a picture':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
now = datetime.now()
filename = 'capture_%s.jpg' % now.strftime('%Y%m%d_%H%M%S')
aiy.audio.say('OK, 3')
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
time.sleep(1)
aiy.audio.say('2')
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.HIGH)
time.sleep(1)
aiy.audio.say('1')
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
time.sleep(1)
c = cv2.VideoCapture(0)
r, img = c.read()
if cv2.imwrite(filename, img):
self.print_and_say('I took nice one.')
else:
self.print_and_say("I couldn't take a picture.")
c.release()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
print('Captured Image: %s' % filename)
elif text == 'goodbye':
self.status_ui.status('stopping')
assistant.stop_conversation()
aiy.audio.say('Goodbye. See you again.')
print('Stopping...')
sys.exit()
elif event.type == EventType.ON_END_OF_UTTERANCE:
self.status_ui.status('thinking')
elif event.type == EventType.ON_CONVERSATION_TURN_FINISHED:
self.status_ui.status('ready')
elif event.type == EventType.ON_ASSISTANT_ERROR and event.args and event.args['is_fatal']:
sys.exit(1)
defmain(self):
self.status_ui.status('starting')
self.print_and_say('Starting main method.')
with Assistant(self.credentials) as assistant:
for event in assistant.start():
self.process_event(assistant, event)
if __name__ == '__main__':
sample = MyAssistant()
sample.main()
動作確認
スクリプトを実行して動作を確認してみます。下記の動画のような動作になりました。
撮影された写真は下記のようになります。
まとめ
USBカメラの接続も特に苦労することなく、 OpenCV でのキャプチャーもすんなり行えました。この辺りの自由度が Google Home と違う面白さかと思います。今回の内容だと撮影してみないとどんな画像かがわからないので、映像を確認しながら撮影できるようにしたり、撮影後にデスクトップに画像を表示するなどの対応を試してみたいと思います。
前回 Google AIY Voice Kit を購入してマニュアル通りに組み立ててサンプルプログラムを動かすところまでやってみましたが、やはり Voice Kit が Google Echo と違って面白いのは Raspberry Pi ベースであるからこその自由度ということで、今回はとりあえずブレッドボードで LED を接続して、音声で LED を操作してみました。
GPIO ピンヘッダの取り付け
AIY Voice Kit では Raspberry Pi に Voice HAT Accessory Board をマウントしているので、 Raspberry Pi の GPIO は全て覆われてしまっています。なので GPIO で接続する場合には Voice HAT の方の GPIO を使用することになります。 Voice HAT の GPIO Pinout はドキュメントでも図で説明されています。
#!/usr/bin/env python3import sys
import aiy.assistant.auth_helpers
import aiy.audio
import aiy.voicehat
from google.assistant.library import Assistant
from google.assistant.library.event import EventType
import RPi.GPIO as GPIO
classMyAssistant:
GPIO_LED_GREEN = 2
GPIO_LED_YELLOW = 3def__init__(self):
self.print_and_say('Initializing MyAssistant.')
self.credentials = aiy.assistant.auth_helpers.get_assistant_credentials()
self.status_ui = aiy.voicehat.get_status_ui()
GPIO.setmode(GPIO.BCM)
GPIO.setup(MyAssistant.GPIO_LED_GREEN, GPIO.OUT)
GPIO.setup(MyAssistant.GPIO_LED_YELLOW, GPIO.OUT)
defprint_and_say(self, text):
print(text)
aiy.audio.say(text)
defprocess_event(self, assistant, event):
print('Processing event. The event is %s.' % event.type)
if event.type == EventType.ON_START_FINISHED:
self.status_ui.status('ready')
if sys.stdout.isatty():
print('Say "OK, Google" then speak.')
elif event.type == EventType.ON_CONVERSATION_TURN_STARTED:
self.status_ui.status('listening')
elif event.type == EventType.ON_RECOGNIZING_SPEECH_FINISHED and event.args:
print('You said: %s' % event.args['text'])
text = event.args['text'].lower()
if text == 'turn on green led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
self.print_and_say('Turned on green LED.')
elif text == 'turn off green led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
self.print_and_say('Turned off green LED.')
elif text == 'turn on yellow led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.HIGH)
self.print_and_say('Turned on yellow LED.')
elif text == 'turn off yellow led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
self.print_and_say('Turned off yellow LED.')
elif text == 'turn on all led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.HIGH)
self.print_and_say('Turned on all LED.')
elif text == 'turn off all led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.LOW)
GPIO.output(MyAssistant.GPIO_LED_YELLOW, GPIO.LOW)
self.print_and_say('Turned off all LED.')
elif text == 'goodbye':
self.status_ui.status('stopping')
assistant.stop_conversation()
aiy.audio.say('Goodbye. See you again.')
print('Stopping...')
sys.exit()
elif event.type == EventType.ON_END_OF_UTTERANCE:
self.status_ui.status('thinking')
elif event.type == EventType.ON_CONVERSATION_TURN_FINISHED:
self.status_ui.status('ready')
elif event.type == EventType.ON_ASSISTANT_ERROR and event.args and event.args['is_fatal']:
sys.exit(1)
defmain(self):
self.status_ui.status('starting')
self.print_and_say('Starting main method.')
with Assistant(self.credentials) as assistant:
for event in assistant.start():
self.process_event(assistant, event)
if __name__ == '__main__':
sample = MyAssistant()
sample.main()
ユーザが hotword の後に発話した内容は ON_RECOGNIZING_SPEECH_FINISHED イベントのパラメータとして渡されますので、その内容によって処理を切り分け、 GPIO の操作をして LED をコントロールしています。ポイントは assistant.stop_conversation() を実行しているところで、これを行うことによって Google Assistant による処理を中断し、ローカルで行わせたい処理だけ行なっています。 stop_conversation() を実行しないと発話内容に対して Google Assistant でも処理を行おうとするので、そのコマンドには対応できない的な返答が返されてしまいます。
if text == 'turn on green led':
assistant.stop_conversation()
GPIO.output(MyAssistant.GPIO_LED_GREEN, GPIO.HIGH)
self.print_and_say('Turned on green LED.')
動作確認
上記のスクリプトを実行して hotword に続いて発話すると、下記の動画のように LED を操作することができます。
まとめ
Voice HAT へのピンヘッダの半田付けは少々手間ですが、それ以外の点ではすんなり GPIO を使うことができました。今回は LED の出力のコントロールだけでしたが、情報を入力するセンサーデバイスも使ってもう少し遊んでみたいと思います。
別途 Raspberry Pi や micro SD カード等も必要なので、単純にスマートスピーカーとして使うだけなら Google Home mini を買った方が安いしお手軽なのですが、 Raspberry Pi 上でスクリプトを作成したり、 GPIO 等でセンサーデバイス等と連携させることもできるのが面白そうだったので購入してみました。今回はひとまず組み立てと動作確認を行ってみます。
実行すると下記のようにスクリプトが実行され、テスト用の音声が再生されますので、スピーカーから聞こえていれば y を入力します。続いてマイクのテストも行われますので、 Enter を押して「Testing, 1 2 3」と喋ります。正しくマイクが機能していれば今喋った内容が再生されますので、 y を入力してテスト終了です。
Enabling audio driver for VoiceKit.
Playing a test sound...
Did you hear the test sound? (y/n) y
When you're ready, press enter and say 'Testing, 1 2 3'...
Recording...
Playing back recorded audio...
Did you hear your own voice? (y/n) y
The audio seems to be working.
Press Enter to close...
次に Wi-Fi の接続テストです。デスクトップの下記スクリプトをダブルクリックで実行します。
実行すると下記のように Wi-Fi の接続確認が行われます。
Checking the WiFi connection...
Trying to contact Google's servers...
The WiFi connection seems to be working.
Press Enter to close...
サンプルプログラムの実行
公式の OS イメージでは Google Assistant SDK のサンプルプログラムが提供されているのでそれを実行してみます。まずは Google Assistant API を有効にする必要がありますので、Google Cloud Platform(GCP) のコンソールにアクセスして、新しいプロジェクトを作成します。
プロジェクト名を入力して 作成 ボタンをクリックします。
プロジェクトが作成されたら「API とサービス」メニューから API とサービスの有効化 をクリックします。
Google Assistant API を検索してクリックします。
API の詳細画面で 有効にする をクリックします。
次に認証情報を作成する必要があるので、「認証情報」メニューの画面で 認証情報を作成 プルダウンから OAuth クライアント ID を選択します。
サンプルプログラム実行の準備ができましたので、デスクトップから Start dev terminal を実行します。
ターミナルが起動しますので、下記のようにサンプルプログラムを実行します。初回起動時は OAuth での認証が必要ですのでブラウザ上で認証します。認証が完了するとプログラムが実行されますので、 Wake Word「OK, Google」を使って話しかけます。ちなみに現状では英語のみの対応となっています。
$ src/examples/voice/assistant_library_demo.py
Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=965148223698-v9m9nb4bgdp58qk159cghm1ketnvin5t.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2F&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fassistant-sdk-prototype&state=tA7PMNhUtTMQdAdj2vRu7IuMAYcUe3&access_type=offline
既存のブラウザ セッションに新しいウィンドウが作成されました。
[2018-01-1314:57:30,435] INFO:google_auth_oauthlib.flow:"GET /?state=tA7PMNhUtTMQdAdj2vRu7IuMAYcUe3&code=4/ESIoWeN3UiJqensJetZpHruY2DCoPh6KWWigZzEuqSE HTTP/1.1"20065[2018-01-1314:57:30,812] INFO:root:OAuth credentials initialized: /home/pi/.cache/voice-recognizer/assistant_credentials.json
Say "OK, Google"then speak, or press Ctrl+C to quit...
続いて、 Google Assistant の gRPC のサンプルプログラムも提供されているので実行してみます。こちらは Wake Word には対応していないので、プログラムを実行した上でスピーカー上部のボタンを押してから話しかけます。こちらもデフォルトでは英語のみの対応となっています。例えば "What's time is it now?" と話しかけると下記のように認識されていることがわかります。
$ src/examples/voice/assistant_grpc_demo.py
/home/pi/AIY-projects-python/src/aiy/_drivers/_led.py:51: RuntimeWarning: This channel is already in use, continuing anyway. Use GPIO.setwarnings(False) to disable warnings.
GPIO.setup(channel, GPIO.OUT)
Press the button and speak
[2018-01-1315:07:12,945] INFO:recorder:started recording
Listening...
[2018-01-1315:07:46,627] INFO:speech:event_type: 1[2018-01-1315:07:46,666] INFO:speech:transcript: what's time is it nowYou said " what's time is it now "Press the button and speak
$ cd ~/デスクトップ/alexa-avs-sample-app/
$ vi automated_install.sh
$ head -20 automated_install.sh
#!/bin/bash#-------------------------------------------------------# Paste from developer.amazon.com below#-------------------------------------------------------# This is the name given to your device or mobile app in the Amazon developer portal. To look this up, navigate to https://developer.amazon.com/edw/home.html. It may be labeled Device Type ID.ProductID=my_device
# Retrieve your client ID from the web settings tab within the developer console: https://developer.amazon.com/edw/home.htmlClientID=amzn1.application-oa2-client.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Retrieve your client secret from the web settings tab within the developer console: https://developer.amazon.com/edw/home.htmlClientSecret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#-------------------------------------------------------# No need to change anything below this...#-------------------------------------------------------#-------------------------------------------------------
Wake Word Engine:"Alexa" というフレーズでサンプルアプリとのインタラクションを開始するためのエンジン
上記をそれぞれ別ターミナルで動作させます。インストールスクリプトの実行までは ssh 等での CLI からの実行でも問題ないかと思いますが、サンプルアプリでは GUI やブラウザを使用しますので、直接 Raspberry Pi にログインするか、 VNC Viewer などで Raspberry Pi の GUI デスクトップにアクセスして実行します。まずは Companion Service を起動します。 Companion Service は node.js のサーバアプリになっています。
$ cd ~/デスクトップ/alexa-avs-sample-app/samples
$ cd companionService && npm start> alexa-voice-service-sample-companion-service@1.0.0 start /home/pi/デスクトップ/alexa-avs-sample-app/samples/companionService
> node ./bin/www
This node service needs to be running to store token information memory and vend them for the AVS app.
Listening on port 3000
これでポート 3000 で接続を待ち受けます。続いてサンプルアプリ本体を起動します。Companion Service とは別のターミナルで下記コマンドを実行します。サンプルアプリ本体は Java アプリになっています。
$ cd ~/デスクトップ/alexa-avs-sample-app/samples
$ cd javaclient && mvn exec:exec[INFO] Scanning for projects...
[INFO][INFO]------------------------------------------------------------------------[INFO] Building Alexa Voice Service Sample Java Client 20160207.7
[INFO]------------------------------------------------------------------------[INFO][INFO]---exec-maven-plugin:1.2.1:exec(default-cli) @ sample-java-client ---
ここまでで GUI のマイクマークをクリックすることで Alexa とのインタラクションをスタートすることはできるようになっていますが、 Wake Word を使用するために Wake Word Engine を起動します。このサンプルアプリのプロジェクトではサードパーティの Wake Word Engine として Sensory の TrulyHandsFree と KITT.AI の Snowboy がサポートされています。今回は下記のように Sensory の TrulyHandsFree を使用してみます。
$ cd ~/デスクトップ/alexa-avs-sample-app/samples
$ cd wakeWordAgent/src && ./wakeWordAgent -e sensory
INFO:main: Starting Wake Word Agent
INFO:WakeWordAgent: State set to IDLE(2)
INFO:Initializing Sensory library | library name: TrulyHandsfree | library version: 5.0.0-beta.10.2 | model file: ../ext/resources/spot-alexa-rpi.snsr
WARNING:Library expires on: License expires on 28 Mar 201800:00:00 GMT
INFO:SensoryWakeWordEngine: mainLoop thread started
INFO:WakeWordIPCSocket::mainLoop thread started
INFO:WakeWordIPCSocket: init socket on port:5123
INFO:WakeWordAgent: thread started
INFO:===> Connected to AVS client <===
動作確認
それではサンプルアプリの動作を確認してみます。 GUI のマイクマークをクリックするか、 "Alexa" と話しかけるとインタラクションが開始します。「今何時?』「今日の天気は?」などと質問すると Amazon Echo と同じように Alexa が応答してくれます。 Wake Word Engine を動かしているターミナルには下記のような出力があります。
INFO:===> WakeWordAgent: wake word detected <===
INFO:WakeWordAgent: State set to WAKE_WORD_DETECTED(3)
INFO:WakeWordAgent: State set to SENT_WAKE_WORD_DETECTED(4)
INFO:WakeWordAgent: IPC Command received:3
INFO:WakeWordAgent: State set to WAKE_WORD_PAUSE_REQUESTED(5)
INFO:SensoryWakeWordEngine: handling pause
INFO: *** THREAD JOINING: Sensory ***
INFO:SensoryWakeWordEngine: mainLoop thread ended
INFO:WakeWordAgent: State set to WAKE_WORD_PAUSED(6)
INFO:WakeWordAgent: IPC Command received:4
INFO:WakeWordAgent: State set to WAKE_WORD_RESUME_REQUESTED(7)
INFO:SensoryWakeWordEngine: handling resume
INFO:SensoryWakeWordEngine: mainLoop thread started
INFO:WakeWordAgent: State set to IDLE(2)
ちなみに Wake Word Engine については、 Amazon Echo Dot ではカタカナ発音の「アレクサ」でも十分検知してくれましたが、今回のケースでは英語っぽく "Alexa" と発音しないと検知してくれませんでした。
まとめ
今回はチュートリアルの内容をトレースすることで Raspberry Pi と Alexa を統合することができましたが、ここまでの内容だけでは Amazon Echo をそのまま使うのと変わらないので、 Raspberry Pi の自由度を活かして色々なデバイスと連携させたりしてみたいと思っています。ただ、サンプルアプリで Java アプリが公開されているものの、実装方法の詳細についての説明は見つからず、 C++ の SDK である AVS Device SDK も見てみましたが詳細な部分はよくわからなかったので、今後わかりそうであれば Raspberry Pi 上で動かす独自アプリを実装してみたいと思います。