体験的マイコン学習(Arduino編)


第9回 ダンボードで人工会話ロボットを作ってみた  目次


今回はダンボードというキャラクターのフィギュアを加工して、ロボットにしてみました


9-1-3 Raspberry Piの準備作業


ここではRaspberry Piに必要な準備作業について記載します


USBマイクロホンの認識確認

(1) Raspberry piに、USBマイクロホンとスピーカーを接続します

USBマイクロホン接続

(2) USBデバイス検索コマンドの入力

pi@raspberry:~ $ lsusb

USBデバイス検索コマンド

ページトップ

音声処理ライブラリsoxのインストール

音声データの操作には、soxライブラリを使用します

(1) soxライブラリをインストールします

pi@raspberry:~ $ sudo apt-get install alsa-utils sox libsox-fmt-all

ページトップ

音声入力、音声再生の確認・設定

(1) 音声入力のカード番号の確認

pi@raspberry:~ $ arecord -l

音声入力のカード番号の確認コマンド


(2) 再生のカード番号の確認

pi@raspberry:~ $ aplay -l

再生のカード番号の確認コマンド


(3) 音声入力音量の設定

pi@raspberry:~ $ amixer -c1 sset Mic,0 16

音声入力音量の設定コマンド


(4) 再生音量の設定

pi@raspberry:~ $ amixer -c0 sset PCM,0 400

再生音量の設定コマンド


(5) 音声出力先の設定

pi@raspberry:~ $ amixer cset numid=3 1

音声出力先の設定コマンド

※ 再生音声を3.5mmミニジャックから出力させる為の設定です


(6) 再生音声の確認

flacファイルを使用して再生できるか確認します

「音楽 ダウンロード 無料 flac」などで検索すると無料で入手もできます

pi@raspberry:~ $ play danboard/sample.flac

再生音声の確認コマンド


ページトップ

Google APIキーの取得

Googleが提供している音声認識のクラウドサービスを使用します

音声をテキストに変換(SpeechToText)してくれる部分です


(1) Googleアカウントを作成する

 https://accounts.google.com/SignUp?hl=ja


Goodleアカウント作成画像



(2) Chrome-devグループへの加入

 「google chrome dev group」で検索します

 投稿するにはグループに参加してくださいをクリックして、Chrome-devグループに加入する

Chrome-devグループ加入画像



(3) Google Developer Consoleサイトへのログイン

 自分のGoogle IDでログインします

 タイトルバーにある、プロジェクトの選択をクリックします

Google_Developer_Consoleサイトへのログイン画像



(4) 右側にある+ボタンをクリックします

プロジェクト選択画像



(5) デフォルトのプロジェクト名は、"My Project"になっているので、好きな名前に変更してもOKです

 入力ができたら、作成ボタンをクリックします

 画面が「API Manager」の「ライブラリ」へ移動します

プロジェクト作成画像



(6) 検索フォームに「speech」と入力すると、「Speech API」が現れるので選択します

 ※ Google-devグループに加入していないと、表示されないので注意が必要です

検索フォーム入力画像



(7) 「有効にする」をクリックして、Google Speech APIを有効化する

SpeechAPI有効化画像



(8) 有効化ができたら、次に認証情報をクリックします

認証情報クリック画像



(9) 認証情報を作成で、APIキーを選択します

認証情報画像



(10) APIキーを作成しましたと表示が出るので、このAPIキーは大切に保管しておいてください

 ※ 音声認識の実行で、APIキーが必要になってきます

APIキー作成完了画像



ページトップ

Docomo APIキーの取得

docomoが提供している雑談会話のクラウドサービスを使用します

リクエストがあったテキストの内容に対して、適切な返信をしてくれる部分です


(1) docomo Developer supportサイトにアクセスする

 https://dev.smt.docomo.ne.jp/


 ログイン/新規登録をクリックする

docomo_Developer_supportサイトアクセス画像



(2) メールアドレスで新規登録、またはSNSアカウントでログイン/新規登録を選択してください

 ※ 登録手続きの内容については省略します

アカウント新規登録画像



(3) 登録が完了したら、マイページのAPI利用申請・管理をクリックします

登録完了後のマイページ画像



(4) 新規API利用申請へをクリックします

新規API利用申請画像



(5) アプリケーションの登録します。入力項目に従って、入力して下さい

アプリケーションの登録画像



(6) API機能選択で雑談会話にチェックしてください

API機能選択画像



(7) アプリケーションの登録で入力した情報に間違いがないかを確認します

アプリケーションの登録情報の確認画像



(8) 完了画面が表示されたら、登録アプリケーション一覧へをクリックします

完了画面画像



(9) 登録アプリケーション一覧では、アプリケーション情報が表示されます

 ※ 雑談会話では、このAPIキーが必要になってきます

登録アプリケーション一覧画像



ページトップ

音声合成ソフトウェアのインストール

音声合成については、Open JTalkを使用します


(1) Open JTalkのインストール

pi@raspberry:~ $ sudo apt-get install open-jtalk

(2) 辞書ファイルのインストール

pi@raspberry:~ $ sudo apt-get install open-jtalk-mecab-naist-jdic

ページトップ

人工会話ロボットのプログラムを作成

danboard.py

#!/usr/bin/env python
# coding=utf-8
import http.client
import requests
import json
import os
import time
import sys
import subprocess
import serial
import random

# '===シリアル通信設定=========='

sr = serial.Serial('/dev/ttyACM0', 9600)

# '===APIキーの設定=========='

# GoogleAPIキー
GOOGLE_APIKEY = '********************'

# DOCOMOAPIキー
DOCOMO_APIKEY = '******************************'

# '===URLの設定=========='

# Google_speechのURL

GOOGLE_SPEECH_URL = 'https://www.google.com/speech-api/v2/recognize?xjerr=1&client=chromium&'\ 'lang=ja-JP&maxresults=10&pfilter=0&xjerr=1&key='

# DOCOMOのURL

DOCOMO_URL = 'https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue?'\ 'APIKEY={}'.format(DOCOMO_APIKEY)

# '===音声発話時間設定=========='

FIRST_LISTEN_SECONDS = 1
LISTEN_SECONDS = 5

# '===オーディオ設定=========='

AUDIO_HDS = {'Content-type': 'audio/x-flac; rate=11025'}

# '===音声入力のファイルパス=========='

VOICE_IN_PATH = '/home/pi/danboard/message.flac'

# '===音声出力のファイルパス=========='

VOICE_OUT_PATH = '/home/pi/danboard/voice.wav'

# '===変数設定=========='

action_status = 0

# '===定数設定=========='

CODE_PROMPT = 1
CODE_GOOGLE = 2
CODE_BEFORE = 1
CODE_AFTER = 2
CODE_STATUS = 0
CODE_START_STATUS = 1
CODE_AFTER_STATUS = 2

# '===メッセージの格納変数=========='

google_msg = 'GoogleAPIで変換したテキスト' cmd_msg = 'コマンドプロンプトからの入力あり'

# '===エラーメッセージの格納変数=========='

usb_err_msg = 'USBマイクロホンを確認してください'
inet_err_msg = 'イーターネット接続を確認してください'
voice_err_msg = '音声認識サービスを確認してください'
docomo_err_msg = '雑談会話サービスを確認してください'

# '===発話メッセージの格納変数設定=========='

speak_msg1 = '何か私とお話しましょう'
speak_msg2 = 'こんにちは。ダンボードです。'
speak_msg3 = 'あなたとお話がしたいです'
speak_msg4 = '少しおしゃべりしませんか'
speak_msg5 = 'とても退屈です'

# '===コマンドプロンプトからの入力(テスト用)=========='

argv = sys.argv
argc = len(argv)
if argc == 2:
    SYSTEM_MSG = sys.argv[1]
else:
    SYSTEM_MSG = ''

# '===音声入力(録音)=========='

def listen(seconds, listen_num, status_code):
    if listen_num == 1:
        print('録音テスト...')
    # 音声入力の録音
    else:
        print('録音開始...')
        if status_code == 2:
            # 目を緑色(シリアル通信)
            sr.write(b'G')

    cmdline = 'AUDIODEV=hw:1 rec -c 1 -r 11025 ' + VOICE_IN_PATH + \
        ' trim 0 ' + str(seconds)
    os.system(cmdline)
    return os.path.getsize(VOICE_IN_PATH)

# '===GoogleAPI(SpeechToText)での音声認識=========='

def convert_to_text():
    print('音声をテキストに変換中...')
    f = open(VOICE_IN_PATH, 'rb')
    voice = f.read()
    f.close()

    url = GOOGLE_SPEECH_URL + GOOGLE_APIKEY
    hds = AUDIO_HDS

    try:
        reply = requests.post(url, data=voice, headers=hds).text
    except IOError:
        return '#CONN_ERR'
    except:
        return '#ERROR'
    print('results:', reply)

    objs = reply.split(os.linesep)

    for obj in objs:
        if not obj:
            continue
        alternatives = json.loads(obj)['result']

        if len(alternatives) == 0:
            continue
        return alternatives[0]['alternative'][0]['transcript']
    return ""

# '===Docomo(会話返信)=========='

def return_speech(message, num):
    # コマンドプロンプトから入力ありの場合
    if num == 1:
        print('コマンド入力メッセージ...' + message)
    else:
        print('テキスト変換メッセージ...' + message)
        url = DOCOMO_URL
        payload = {'utt': message}

        try:
            r = requests.post(url, data=json.dumps(payload))
        except:
            return '#ERROR'
        return r.json()['utt']

# '===音声発話(OpenJTalk)=========='

def speak(message):
    print('音声発話メッセージ...' + message)
    JDIC_DIR='/var/lib/mecab/dic/open-jtalk/naist-jdic/'
    VOICE_DATA='/home/pi/ai/mei/mei_happy.htsvoice'

    cmdline = 'echo ' + message + ' | open_jtalk -x ' + JDIC_DIR + \
        ' -m ' + VOICE_DATA + ' -ow ' + VOICE_OUT_PATH + \
        ' -s 17000 -p 100 -a 0.03'
    subprocess.call(cmdline, shell=True)
    os.system('play ' + VOICE_OUT_PATH)

# '===時刻取得関数=========='

def current_milli_time():
    return int(round(time.time() * 1000))

# '===待機時間取得=========='

def get_sleep_time(action_status):
    if action_status == 1:
        sleep_time = 0.01
    else:
        sleep_time = 4
    return sleep_time

# '===ダンボードの発話処理=========='

def danboard_speak_msg(no):
    if no == 1:
        msg = speak_msg1
    elif no == 2:
        msg = speak_msg2
    elif no == 3:
        msg = speak_msg3
    elif no == 4:
        msg = speak_msg4
    elif no == 5:
        msg = speak_msg5
    else:
        msg = ''

    speak(msg)

# '===起動メッセージ受付処理=========='

def voice_recept():
    while True:
        print('起動メッセージ受付...')
        # 音声入力
        # 時刻を取得
        t0 = current_milli_time()
        size = listen(LISTEN_SECONDS, CODE_AFTER, CODE_START_STATUS)
        print('size:' + str(size))
        t = current_milli_time() - t0
        # 音声入力処理時間が2秒より短い場合
        if (t < 2000):
            # 目の光を赤色(シリアル通信)
            sr.write(b'R')
            print(usb_err_msg)
            speak(usb_err_msg)
            time.sleep(10)
            continue
        print('listened:' + str(t) + 'ms')
        print('voice data size=' + str(size))

        # 音声認識
        # 時刻を取得
        t0 = current_milli_time()
        # GoogleAPI(SpeechToText)で音声をテキストに変換
        message = convert_to_text()
        print('message:' + message)
        print('recognized:' + str(current_milli_time() - t0) + 'ms')
        # インターネットへの接続エラーが発生した場合
        if (message == '#CONN_ERR'):
            # 目の光を赤色(シリアル通信)
            sr.write(b'R')
            print(inet_err_msg)
            speak(inet_err_msg)
            time.sleep(10)
            continue
        # 音声認識に対してのエラーが発生した場合
        elif (message == '#ERROR'):
            # 目の光を赤色(シリアル通信)
            sr.write(b'R')
            print(voice_err_msg)
            speak(voice_err_msg)
            time.sleep(10)
            continue

        # 開始メッセージ処理
        if message != "":
            if message in 'スタート':
                print("start")
                no_word = 0
                wifi_err = 0
                danboard_start_up(no_word, wifi_err)

# '===ダンボード処理開始=========='

def danboard_start_up(no_word, wifi_err):
    try:
        while True:
            print('danboard_start_up...')
            print('音声入力の開始...')
            # マイクを相手に向ける(シリアル通信)
            sr.write(b'S')
            # 音声入力
            # 時刻を取得
            t0 = current_milli_time()
            size = listen(LISTEN_SECONDS, CODE_AFTER, CODE_AFTER_STATUS)
            action_status = 1
            print('size:' + str(size))
            t = current_milli_time() - t0
            # 音声入力処理時間が2秒より短い場合
            if (t < 2000):
                # 目の光を赤色(シリアル通信)
                sr.write(b'R')
                print(usb_err_msg)
                speak(usb_err_msg)
                time.sleep(10)
                continue
            print('listened:' + str(t) + 'ms')
            print('voice data size=' + str(size))

            # 音声認識
            # 時刻を取得
            t0 = current_milli_time()
            # GoogleAPI(SpeechToText)で音声をテキストに変換
            message = convert_to_text()
            print('message:' + message)
            print('recognized:' + str(current_milli_time() - t0) + 'ms')
            # インターネットへの接続エラーが発生した場合
            if (message == '#CONN_ERR'):
                # 目の光を赤色(シリアル通信)
                sr.write(b'R')
                print(inet_err_msg)
                speak(inet_err_msg)
                time.sleep(10)
                continue
            # 音声認識に対してのエラーが発生した場合
            elif (message == '#ERROR'):
                # 目の光を赤色(シリアル通信)
                sr.write(b'R')
                print(voice_err_msg)
                speak(voice_err_msg)
                time.sleep(10)
                continue

            # 連続5回何も話されない場合
            if (len(message) <= 1):
                no_word = no_word + 1
                if (no_word >= 5):
                    # 目の光を赤青緑に点滅(シリアル通信)
                    sr.write(b'A')
                    rand_no = random.randint(1, 5)
                    # ダンボードのメッセージ発話
                    danboard_speak_msg(rand_no)
                    no_word = 0
                continue

            # 終了メッセージの場合
            if message != "":
                if message in 'ストップ':
                    print("stop")
                    # 左腕を初期状態に戻す
                    sr.write(b'E')
                    # 処理待ち
                    time.sleep(3)
                    # 目の光を青色(シリアル通信)
                    sr.write(b'B')
                    voice_recept()

            # 会話返信
            if message != "":
                # 時刻を取得
                t0 = current_milli_time()
                print(google_msg)
                # GoogleAPIで変換したテキストを渡す
                message = return_speech(message, CODE_GOOGLE)
                print('会話返信メッセージ...' + message)
                print('replied:' + str(current_milli_time() - t0) + 'ms')
                # DOCOMO会話返信でのエラーが発生した場合
                if (message == '#ERROR'):
                    print(docomo_err_msg)
                    speak(docomo_err_msg)
                    time.sleep(10)
                    continue

                # 音声発話
                speak(message)
                print('talked:' + str(current_milli_time() - t0) + 'ms')

    except KeyboardInterrupt:
        # 左腕を初期状態に戻す
        sr.write(b'E')
        # 処理待ち
        time.sleep(3)
        # 目の光を青色(シリアル通信)
        sr.write(b'B')
        pass

if __name__ == '__main__':
    print('ダンボード起動')
    # 目の光を青色(シリアル通信)
    sr.write(b'B')
    if SYSTEM_MSG == '':
        # 処理待ち
        time.sleep(2)
        print('音声入力のテスト...')
        listen(FIRST_LISTEN_SECONDS, CODE_BEFORE, CODE_STATUS)
        # 音声受付処理
        voice_recept()
    else:
        # コマンドプロンプトからの入力がある場合
        print('===================================')
        print(cmd_msg)
        print('===================================')
        # コマンドプロンプトからの入力文字を渡す
        message = return_speech(SYSTEM_MSG, CODE_PROMPT)
        print('会話返信メッセージ...' + message)
        # DOCOMO会話返信でのエラーが発生した場合
        if (message == '#ERROR'):
            # 目の光を赤色(シリアル通信)
            sr.write(b'R')
            print(docomo_err_msg)
            speak(docomo_err_msg)
        time.sleep(10)
        # 処理待ち
        time.sleep(2)
        # 目の光を赤青緑に点滅(シリアル通信)
        sr.write(b'A')
        # 音声発話(テスト)
        speak(message)
        # 目の光を青色(シリアル通信)
        sr.write(b'B')
        # 処理終了
        sys.exit()

ページトップ

ファイルの配置

作成したArduinoとRaspberry Piのプログラムを配置していきます

ファイルの配置には、FTPソフト(Cyberduck)を使用します。

インストールされてない場合こちらを参考にしてください → Cyberduckのインストール


(1) 今回は下記のように、ファイルを配置します

ファイルの配置画像



ページトップ