kapieciiのブログ

日々学んだことを残しておくためのブログです。このブログはGoogle Analyticsを利用しています。

Google Cloud Speech-to-Textを使ってVUIアプリを作ってみた

f:id:kapiecii:20191116032147p:plain

”のぼゆ”さんの記事を参考にさせていただきながら、VUI操作できるアプリケーションを作ってみました。

www.noboyu.com  目次

作ったもの

Googleの「Cloud Speech-to-Text API」と「AIY VOICE KIT」を使い、魔法を体験できるようなVUIアプリを作ってみました。

  1. ボタンを押すと、「呪文を唱えてね」と音声で案内する
  2. 利用者が魔法の呪文を唱える
  3. 魔法の呪文を「Cloud Speech-to-Text API」に送信し、唱えた呪文をテキストデータとして受け取る
  4. 魔法の呪文に合わせた演出をする

使ったもの

AIY VOICE KIT(Raspberry Pi Zero WH, Voice Bonnet, スピーカー, ボタン)

【国内代理店版】Google AIY Voice Kit V2

【国内代理店版】Google AIY Voice Kit V2

  • メディア: Personal Computers

Google Cloud Speech APIについて

cloud.google.com

Google Cloud Speech-to-Text では、使いやすい API で高度なニューラル ネットワーク モデルを利用し、音声をテキストに変換できます。API は 120 の言語と方言を認識し、グローバルなユーザーベースをサポートします。音声コマンド コントロールの有効化や、コールセンター音声の文字変換などが実現可能です。
(公式サイトからの抜粋)

料金表

cloud.google.com

f:id:kapiecii:20191116020526p:plain

2019年11月時点では、毎月60分までは無料で利用できます。60 分の無料枠を超えると、15 秒ごとに音声処理の料金が発生します。
プレミアムモデルは、2019年11月現在では英語(米国)でのみ使えるらしいので、日本で使う場合は標準モデルになります。
データロギングについて、公式ドキュメントを引用します。

データロギングを有効にすると、Cloud Speech-to-Text に送信された音声データを Google が記録するのを許可したことになります。このデータは、Google が音声文字変換に使用する機械学習モデルの改善のために利用されます。そのため、データロギングを有効にした場合の Cloud Speech-to-Text の料金は割安に設定されています。

今回は、機密情報を扱うわけではないので、データロギングを有効にして進めます。

Open JTalkで喋らせる

ボタンを押した際に、音声で案内をします。

AIY KITはsay()というコマンドがあり、任意のセリフを喋らせることができるらしいのですが、英語にしか対応していないようです。
今回は、のぼゆさんと同じくOpen JTalkというソフトを使って任意の日本語を喋れるようにしました。

Open JTailのセットアップ

こちらを参照させていただきながら進めます。

qiita.com

AIY KITのOSイメージをインストール済みのラズパイでOpen JTalkのセットアップを進めていきます。
AIY KITのOSイメージインストールはこちらの記事をご参照ください。

kapiecii.hatenablog.com

インストール

pi@raspberrypi:~ $ sudo apt-get install open-jtalk 
pi@raspberrypi:~ $ mkdir OpenJTalk
pi@raspberrypi:~ $ cd OpenJTalk/
pi@raspberrypi:~/OpenJTalk $ wget https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/MMDAgent_Example-1.6/MMDAgent_Example-1.6.zip/download -O MMDAgent_Example-1.6.zip
pi@raspberrypi:~/OpenJTalk $ unzip MMDAgent_Example-1.6.zip MMDAgent_Example-1.6/Voice/*

取り出したファイルを hts-voice-nitechと同じ場所にコピーしておきます。

pi@raspberrypi:~/OpenJTalk $ ls MMDAgent_Example-1.6/Voice/mei/
COPYRIGHT.txt  README.txt  mei_angry.htsvoice  mei_bashful.htsvoice  mei_happy.htsvoice  mei_normal.htsvoice  mei_sad.htsvoice
pi@raspberrypi:~/OpenJTalk $ ls /usr/share/hts-voice
nitech-jp-atr503-m001
pi@raspberrypi:~/OpenJTalk $ sudo cp -r MMDAgent_Example-1.6/Voice/mei/ /usr/share/hts-voice

動作確認

Open JTalkの動作確認をします。
作業用ディレクトリとして、下記を作成します。
「/home/pi/AIY-voice-kit-python/src/examples/」はAIY KITのOSイメージに入っています。
今回は魔法の呪文を唱えたいので「magic」ディレクトリを作成しました。

$ cd /home/pi/AIY-voice-kit-python/src/examples/
$ cp -r voice/ magic/

以降は、作業用ディレクトリで作業します。
下記のコードを「jtalk.py」として保存します。

#coding: utf-8
import subprocess
from datetime import datetime

def jtalk(t):
    open_jtalk=['open_jtalk']
    mech=['-x','/var/lib/mecab/dic/open-jtalk/naist-jdic']
    htsvoice=['-m','/usr/share/hts-voice/mei/mei_normal.htsvoice']
    speed=['-r','1.0']
    outwav=['-ow','open_jtalk.wav']
    cmd=open_jtalk+mech+htsvoice+speed+outwav
    c = subprocess.Popen(cmd,stdin=subprocess.PIPE)
    c.stdin.write(t.encode())
    c.stdin.close()
    c.wait()
    aplay = ['aplay','-q','open_jtalk.wav']
    wr = subprocess.Popen(aplay)

def say_datetime():
    d = datetime.now()
    text = '%s月%s日、%s時%s分%s秒' % (d.month, d.day, d.hour, d.minute, d.second)
    jtalk(text)

if __name__ == '__main__':
    say_datetime()

実行します。
現在の日時を日本語で喋ることが確認できました。 結構大きな音がでるので、実行する環境には注意が必要です。

$ python3 jtalk.py

任意の日本語を喋らせる

時刻を喋る機能を削った版のjtalk.pyをwgetしておきます。

$ wget https://gist.githubusercontent.com/mechanoboyu/e7df8980efa4ca6b8d69c415886e9693/raw/6c292c390e5e31ebc7327bc76c8259a04fef0c58/jtalk.py

同じディレクトリに下記のコードを保存し、実行することで任意の日本語を喋ることを確認します。

#coding: utf-8
import jtalk

jtalk.jtalk('日本語を喋らせます'.encode('utf-8'))

Cloud Speech-to-Text APIの設定

Cloud Speech-to-Text APIを使うための設定をします。

請求先アカウントの設定

Cloud Speech-to-Text APIを利用するためには、請求先情報を設定しておく必要があるようです。

「メニュー-->お支払い」から、請求先のアカウントが登録されていることを確認します。

f:id:kapiecii:20191116021745p:plain

https://console.cloud.google.com/billing

にアクセスして「マイプロジェクト」をクリックします。
プロジェクトに請求先アカウントが紐付けられているか確認します。
紐付いていない場合は、請求先アカウントの設定をします。

f:id:kapiecii:20191116021943p:plain

Cloud Speech-to-Text APIの有効化

APIとサービス-->ライブラリ」に進みます。

f:id:kapiecii:20191116022144p:plain

「Cloud Speech-to-Text API」を選択し、有効にします。

f:id:kapiecii:20191116022324p:plain

f:id:kapiecii:20191116022818p:plain

認証情報を作成します。

f:id:kapiecii:20191116023425p:plain

「サービスアカウント」を選択します。

f:id:kapiecii:20191116023540p:plain

アカウント情報を入力します。

f:id:kapiecii:20191116023747p:plain

「役割-->Project-->閲覧者」を選択します。

f:id:kapiecii:20191116023936p:plain

「キーを作成-->json」を選択し「作成」すると、認証用のjsonファイルがダウンロードされます。

f:id:kapiecii:20191116024142p:plain

f:id:kapiecii:20191116024329p:plain

APIとサービス--> cloud speech to txt-->認証情報-->サービスアカウント」に、先ほど作成したアカウントが追加されていることを確認します。

f:id:kapiecii:20191116024547p:plain

ダウンロードしたファイルをリネームし、Raspberry Piに配置します。

$ cp xxxxx.json cloud_speech.json 
$ sftp pi@xxx.xxx.xxx.xxx
sftp> put cloud_speech.json

動作確認

「Cloud Speech-to-Text API」の動作確認をします。

pi@raspberrypi:~ $ cd ~/AIY-projects-python/src/examples/magic/
pi@raspberrypi:~/AIY-projects-python/src/examples/magic $ ./cloudspeech_demo.py

APIの認証処理が成功すると、「INFO:aiy.cloudspeech:Start listening.」と表示されるので、マイクに向かって話しかけます。 話しかけた内容は「Cloud Speech-to-Text API」に送信され、テキストデータとして返されます。

「大丈夫」と喋ったときの様子。

INFO:aiy.cloudspeech:Start listening.
INFO:aiy.cloudspeech:Stop listening.
INFO:root:You said: "大丈夫"

ボタンを押したときの処理

「~/AIY-projects-python/src/examples/magic/」の「assistant_grpc_demo.py」にボタンを押した際の処理があるので流用します。

「aiy.board」をimportします。

from aiy.board import Board

下記のようにすることで、ボタン入力の待受けと、ボタンを押されたときの処理を指定することができます。

    with Board() as board:
        while True:
            logging.info('Press button to start conversation...')
            board.button.wait_for_press()
            logging.info('Conversation started!'

ここまでの機能をつなぎ合わせる

ここまでに準備した

  • 日本語での発話
  • 聞き取った日本語をCloud Speech-to-Text APIで解析
  • ボタンを押されたことを検知

をつなぎ合わせます。

今回は魔法の呪文を唱えます。
雰囲気は大切なので、使い魔を想定した黒猫のぬいぐるみにAIY KITを仕込みます。利用者は黒猫のぬいぐるみと対話することになるので、猫っぽい言葉づかいにしています。
「kick_server()」では、魔法が発動した時の効果音や、LEDによる光の演出を制御するRaspberry Pi にリクエストを飛ばしています。
「ファイア」と「ファイヤ」で判定が難しくなることが想定されたので、「ファイ」で判別するようにしました。

#!/usr/bin/env python3
#coding: utf-8
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
<200b>
"""A demo of the Google CloudSpeech recognizer."""
import argparse
import locale
import logging
import requests
<200b>
from aiy.board import Board, Led
from aiy.cloudspeech import CloudSpeechClient
<200b>
import jtalk
<200b>
def get_hints(language_code):
    if language_code.startswith('en_'):
        return ('turn on the light',
                'turn off the light',
                'blink the light',
                'goodbye')
    return None
<200b>
def locale_language():
    language, _ = locale.getdefaultlocale()
    return language
<200b>
def kick_server(mode):
    url = 'http://xxx.xxx.xxx.xxx:xxxx/?mode='
    requests.get(url + mode)
<200b>
def main():
    logging.basicConfig(level=logging.DEBUG)
<200b>
    parser = argparse.ArgumentParser(description='Assistant service example.')
    parser.add_argument('--language', default=locale_language())
    args = parser.parse_args()
<200b>
    logging.info('Initializing for language %s...', args.language)
    hints = get_hints(args.language)
    client = CloudSpeechClient()
    
    with Board() as board:
        while True:
            if hints:
                logging.info('Say something, e.g. %s.' % ', '.join(hints))
            else:
                logging.info('Say something.')
<200b>
            jtalk.jtalk('僕と契約して魔法を使おう ボタンを押して、呪文を唱えてね'.encode('utf-8'))
            kickServer('start')
            logging.info('Press button to start conversation...')
            board.button.wait_for_press()
            logging.info('Conversation started!')
<200b>
            text = client.recognize(language_code=args.language,
                                    hint_phrases=hints)
            #text = client.recognize(language_code=args.language,
            #                        hint_phrases='hints. hoho')
<200b>
            if text is None:
                logging.info('You said nothing.')
                continue
<200b>
            logging.info('You said: "%s"' % text)
            text = text.lower()
            if 'ファイ' in text:
                print('ファイ受け付けました')
                jtalk.jtalk('ファイアー'.encode('utf-8'))
                kick_server('fire')
            elif 'アイスストーム' in text:
                print('アイスストーム受け付けました')
                jtalk.jtalk('アイスストーム'.encode('utf-8'))
                kick_server('ice')
            elif 'サンダ' in text:
                print('サンダーを受け付けました')
                jtalk.jtalk('サンダー'.encode('utf-8'))
                kick_server('thunder')
            elif 'おしまい' in text:
                break
            else:
                print('よくきこえませんでした')
                jtalk.jtalk('よく聞こえなかったから もう一度呪文を唱えてにゃ'.encode('utf-8'))
                kick_server('miss')
<200b>
if __name__ == '__main__':
    main()

サンプルコードをつなぎあわせただけではありますが、音声で操作するアプリケーションを作ることができました。

反省点や気付き

VUIアプリと利用環境

自宅で音楽を流し、ある程度騒々しい環境を再現して動作テストをしました。
多少の騒音であれば、正常に音声を認識していました。
しかし、イベント会場で動かしてみたところ、周囲の喧騒が想定以上で、全く音声を認識することができませんでした。
音声で操作する場合、利用環境の影響はかなり大きく、利用環境に合わせた対策を検討する必要があるという学びがありました。

スマートフォンのマイク性能

前述の通り、AIY KITに付属していたマイクでは全く音声を拾うことができませんでした。
しかし、同じ環境でもスマートフォン上のGoogle Asisstantはしっかりと話した内容を聞き取っていました。
スマートフォンのマイクの性能はすごいですね。
次に音声で操作するアプリケーションを作る場合は、スマートフォンのマイクを利用しても良いかもしれません。

音と光と非同期処理

魔法を唱えたあとの効果音やLEDによる演出は、別のRaspberry Piで制御していました。
flaskでWebサーバを立て、HTTPリクエストを受け取って効果音やLEDの演出を制御をしていたのですが、非同期で実行する作りにしていませんでした。
そのため、効果音とLEDの演出のタイミングが微妙に合わなかったので、次回は非同期実行することで、音と光のタイミングを合わせていきたいと思います。

最後に

「イベントに出したい!」ということで、急遽準備をしたのですが、サンプルコードを組み合わせることで簡単にVUIアプリケーションを実装することができました。
AIY KITのライブラリを良く読んで、コードを綺麗にしてからブログにまとめようかと思いましたが、記憶が揮発してしまう前に一旦記事にしておきます。

実際に作ってみたことで、音声で操作するアプリケーション開発について全体の雰囲気を知ることができました。
「今回学んだことを使って、音声操作でログを表示させると近未来感があってかっこいいかも?」なんて妄想をしています。

(鳴り響くアラート音)
SoC担当者 「ちっ。何事だ。」(インカムについているボタンを押す)
SoC担当者 「Apacheのログを出せ。」
VUIアプリ 「かしこまりました。直近24時間のログはこちらです。」
SoC担当者 「ん?なんだかおかしなログがあるぞ?」

つづく

kapieciiのブログについてお問い合わせがある場合、下記のフォームからご連絡をお願い致します。
お問い合わせはこちら