Funny Snowman

FunnySnowman > Geek<ギーク>なコトをやってみよっと

インターネットサービス「RECAIUS」を借りてパワーアップする(音声認識とテキスト表示)

ゴール設定(ラピロにRECAIUSを実装する)

ラピロ(Rapiro)には色々な事をやらせたいので、インターネットで公開されているサービスと繋いで機能を拡張させてみる。例えば、日本語の音声から英語に翻訳してくれるロボットが出来たら、それはまるでドラえもんの秘密道具「翻訳こんにゃく」をついに実現できるかもしれないのだ。そして「RECAIUS(リカイアス)」というインターネットサービスが翻訳のAPI接続を体験させてくれる。今回のゴールは、ラピロが音声認識して、結果をテキスト表示するところまでを「RECAIUS」のインターネットAPI接続で実現する

日本語を認識する音声認識ロボット(Rapiro)

事前準備(PythonでRECAIUSのAPIを使う)

RECAIUS開発者サイトで試用登録

どうしても翻訳を試したかったので、<こちらのRECAIUSサイト>でデベロッパー登録を個人のメールアドレスで申し込んでみた。自動発信の仮登録メールが届き、クリックすると本登録が完了してサービスAPI毎のユーザIDが届きました(無料なのでユーザIDは翌月末までの期限付きです)。登録申請画面のインプット内容がちょっとだけ分かり難かったのですが、必須入力のユーザIDとパスワードは、初めての登録時は自分で自由に決めて良いみたいです。そして最大2ヶ月後の更新時は、メールで届く企業IDをユーザID欄にインプットしたら更新できました。私は個人用途なのですが、利用規約に「自己のサービスのプロトタイプ試作および内部検討の範囲に限り、利用することができるものとします」とあったし、今回はラピロでの翻訳プロトタイプ試作であるから大丈夫かなと自己判断。なお、<公式サイト>の<pythonサンプル>の動作環境は「python 3」ですが、私のラズパイは「python 2.7」であり、公式サイトのサンプルのままでは動作しなかった。以下、「Python 2.7」での動作確認です。

音声認識APIを使えるように…なる…のかな…

音声認識juliusを使えるようにするには、julius本体や辞書など沢山のモジュールをラズパイにインストールした。RECAIUSはPOST/GETするだけの凄く簡単なAPIだと勝手に想像していたが、わりと苦労したのでフローチャートを書き起こした。

RECAIUSで音声認識して文字列データを取得するフローチャート

Pythonプログラム…ん?スクリプト?…プログラムとスクリプトの違いを意識しよう

ここまでプログラムとスクリプトとごちゃまぜに書いてしまったが、再びwiki等で確認すると、『コンパイルを必要とするものがプログラムで、コンパイルを必要としないのがスクリプト。』との事だが、実行時コンパイルがあったりするから、単純なプログラムを記述するための簡易的なプログラミング言語全体をスクリプト言語というらしい。別の定義では『コンパイル作業を必要としないスクリプト言語で書かれたプログラムがスクリプト』ともいうらしい。言い換えると、python言語で書かれたプログラムがスクリプト。つまりソースの事か。以下、見出しを改めます。

Pythonスクリプト(REST-APIの実験)

まずはREST-APIの動作確認を行う。ちゃんとインターネット接続できていること。RECAIUS-APIと正しく通信できること。RECAIUSのID、パスワードが有効であること。などを検査できます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# recaius0.py

import json
import requests

# ===================================================
# ■定義(RECAIUS開発者サイトで取得したID,パスワード)
# ===================================================
glbRecaiusApiUrl = "https://api.recaius.jp"
glbRecaiusPassword = "自分のパスワードを書いてください"
glbRecaiusListenJP = "自分のIDを書いてください"

# ===================================================
# ▼スタート
# ------------------------------------
#  ユーザIDとパスワードを送信して、トークンを取得するだけの実験。
# ===================================================
if __name__ == '__main__':
    global glbToken

    # HTTPリクエスト(POST)
    headers = { "Content-Type": "application/json" }
    body = {
        "speech_recog_jaJP":{
            'service_id': glbRecaiusListenJP,
            'password'  : glbRecaiusPassword
        },
        "expiry_sec": 3600
    }

    # HTTPリクエスト実行
    req = requests.post(
        glbRecaiusApiUrl + "/auth/v2/tokens",
        data = json.dumps( body ), 
        headers = headers
    )

    # HTTPリクエスト結果
    if (req.status_code == 201):            # 201=正常, 4xx/5xx=エラー
        objJson = json.loads(req.content)
        glbToken = objJson['token']
        print( "[RECAIUS] トークン取得に成功「" + str(glbToken) + "」" )
    else:
        print "ERROR: RECAIUS認証失敗(StatusCode=" + str(req.status_code) + ")"
        
  

実行してみる。成功すると下記画面のようにトークンの文字列が表示されます。

pi@raspberrypi:~$ python recaius0.py
[RECAIUS] トークン取得に成功「a24xxxxxxxxxxxxxxxxxxxxxc75ea」
  

失敗すると下記画面のメッセージを表示して終了。RECAIUSサービスIDの有効期限が切れると、この画面になります。

pi@raspberrypi:~$ python recaius2.py
ERROR: RECAIUS認証失敗(StatusCode=401)
  

Pythonスクリプト(音声入力とテキスト表示)

今度は、マイクから音声入力してテキストで表示するまでのプログラムを書いて、音声認識の動作確認する。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# recaius1.py

import json
import requests
import pyaudio
import sys
from collections import deque
import time

# ===================================================
# ■定義(プログラム)
# ===================================================
glbFrames = deque()
glbToken = ""
glbUUID = ""
glbWavFile = "output.wav"
glbVoiceID = 1

# ===================================================
# ■定義(RECAIUS開発者サイトで取得したID,パスワード)
# ===================================================
glbRecaiusApiUrl = "https://api.recaius.jp"
glbRecaiusPassword = "自分のパスワードを書いてください"
glbRecaiusListenJP = "自分のIDを書いてください"

# ===================================================
# ■RECAIUS REST-APIその1(トークンの取得)
# ------------------------------------
#  ユーザIDとパスワードをPOSTすると、トークンを取得でき、
#  そのトークンを共通キーにして、(1)音声送信、(2)テキスト受信を行う。
# ===================================================
def recaius_gettoken():
    global glbToken

    # HTTPリクエスト(POST)
    headers = { "Content-Type": "application/json" }
    body = {
        "speech_recog_jaJP":{
            'service_id': glbRecaiusListenJP,
            'password'  : glbRecaiusPassword
        },
        "expiry_sec": 3600
    }

    # HTTPリクエスト実行
    req = requests.post(
        glbRecaiusApiUrl + "/auth/v2/tokens",
        data = json.dumps( body ), 
        headers = headers
    )

    # HTTPリクエスト結果
    if (req.status_code == 201):            # 201=正常, 4xx/5xx=エラー
        objJson = json.loads(req.content)
        glbToken = objJson['token']
        print( "[RECAIUS] トークン取得に成功「" + str(glbToken) + "」" )
    else:
        return "ERROR: RECAIUS認証失敗(StatusCode=" + str(req.status_code) + ")"
        sys.exit()

# ===================================================
# ■RECAIUS REST-APIその2(音声認識セッションを開始)
# ------------------------------------
#  音声認識のセッションIDに使うUUIDを取得する。
# ===================================================
def recaius_callback_getuuid():
    global glbUUID

    # HTTPリクエスト(POST)
    headers = {
        "Content-Type": "application/json",
        "X-Token": glbToken
    }
    body = {
        "model_id": 1,              # 日本語(1) 英語(5) 中国語(7)
        "energy_threshold": 400     # 目安 170:スマートフォン ~ 400:騒音あり
    }

    # HTTPリクエスト実行
    req = requests.post(
        glbRecaiusApiUrl + "/asr/v2/voices",
        data = json.dumps( body ), 
        headers = headers
    )

    # HTTPリクエスト結果
    if (req.status_code == 201):             # 201:成功
        objJson = json.loads(req.content)
        glbUUID = objJson['uuid']
        print( "[RECAIUS音声認識] UUID = " + str(glbUUID) )
    else:
        print( "ERROR: UUID取得が失敗" + str(req.status_code) )
        sys.exit()

# ===================================================
# ■マイクの音声をWAV形式のファイルで録音する。
# ------------------------------------
# RECAIUSの仕様に合わせる(サンプル幅 16bit, サンプリング周波数 16kHz, チャンネル数 1)
# ===================================================
def record():

    # PyAudioを使ってマイクから入力する音声データをどんどんcallback()関数に詰め込む
    p = pyaudio.PyAudio()
    stream = p.open(format = pyaudio.paInt16,   # 16bit
                    channels = 1,               # 1: モノ  2: ステレオ
                    rate = 16000,               # 16kHzがRECAIUS音声認識用。44.1kHzは不可。32kHzは誤認識する。
                    input = True,
                    frames_per_buffer = int( 16000 * 0.512 ), # Chunk
                    stream_callback = callback)

    stream.start_stream()

    # 音声データの取得をcallback()関数内で繰り返す
    while stream.is_active():
        time.sleep(0.1)

# ===================================================
# ■メインループとして、音声データを取り込み、アクションを実行。
# ===================================================
def callback(in_data, frame_count, time_info, status):
    global glbFrames, glbVoiceID

    glbFrames.append(in_data)
    if (len(glbFrames) > 1):

            # WAVファイルをPUTして音声テキストに変換する。
            strTemp = callback_putWAV()

            # 音声テキストがあればアクションする。
            if ( len(strTemp) > 0 ):
                objJson = json.loads(strTemp)
                strParm = objJson[0][0].encode('utf_8') 
                strText = objJson[0][1].encode('utf_8') 
                print( '[' + strParm + ']' + strText )

            glbVoiceID = glbVoiceID + 1

    return (in_data, pyaudio.paContinue)


# ===================================================
# ■RECAIUS REST-APIその3(オンライン音声認識)
# ------------------------------------
#  音声データ(wav形式)を送信し、認識結果の文字列を取得する。
# ===================================================
def callback_putWAV():

    # HTTPリクエスト(PUT)
    headers = {
        "Content-Type": "multipart/form-data",
        "X-Token": glbToken
    }
    body = {
        'voice_id': glbVoiceID
    }
    files = {
        'voice': (glbWavFile, glbFrames.pop(), 'application/octet-stream')
    }

    # HTTPリクエスト実行
    req = requests.put(
        glbRecaiusApiUrl + "/asr/v2/voices/" + glbUUID,
        headers = headers,
        data = body,
        files = files
    )

    # HTTPリクエスト結果
    return( req.text )

# ===================================================
# ▼スタート
# ===================================================
if __name__ == '__main__':

    recaius_gettoken()            # RECAIUS音声認識トークンを取得(ユーザ認証)
    recaius_callback_getuuid()    # RECAIUS音声認識セッションID(=UUID)を取得
    record()                      # RECAIUS音声認識を実行する

  

実験(RECAIUSのAPIで音声認識を実行する)

PythonスクリプトとRECAIUS API接続による音声認識が正しく動作することを実験で確認する。

pi@raspberrypi:~$ python recaius1.py

[RECAIUS] トークン取得に成功「871b1XXXXXXXXXXXXXXXXXXc9ca5」
[RECAIUS音声認識] UUID = f39a0beXXXXXXXXXXXXXXXX20e1183d

ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
jack server is not running or cannot be started
# <ALSAとjack serverの不具合メッセージがずらっと表示されるが無視する;;>

[TMP_RESULT]こんにちは。
[SOS]
[TMP_RESULT]もしもし。
[TMP_RESULT]もしもーしておいてますか。
[SOS]
[TMP_RESULT]もしもし。
[RESULT]もしもし、聞こえてますか。
  

マイク音声入力をテキストに変換することが出来た。変換する文字列と同時に[TMP_RESULT]や[SOS],[RESULT]といった認識結果のパラメータが届く。プログラミングするときは、このパラメータも考慮する必要がある。今度は、長めの文章を読んでみよう。ロボットに話かけた文章は、「どうしてもオンラインの翻訳サービスを試したかったので、こちらのサイトでデベロッパー登録してみました」です。

[SOS]
[TMP_RESULT]どうしても
[TMP_RESULT]どうしても本来の翻訳サービス
[TMP_RESULT]どうしても本来の翻訳サービスを試したかっ
[TMP_RESULT]どうしても本来の翻訳サービスを試したかったのでこちら
[TMP_RESULT]どうしても本来の翻訳サービスを試したかったのでこちらのサイトで
[TMP_RESULT]どうしても本来の翻訳サービスを試したかったのでこちらのサイトでレベルアッパー
[RESULT]どうしてもオンラインの翻訳サービスを試したかったのでこちらのサイトでレベルアッパー登録してみました。
[SOS]
[TMP_RESULT]どうしてもう
[TMP_RESULT]どうしても本来の翻訳さ
[TMP_RESULT]どうしても本来の翻訳サービスを試したかっ
[TMP_RESULT]どうしても本来の翻訳サービスを試したかったのでこちら
[TMP_RESULT]どうしても本来の翻訳サービスを試したかったのでこちらのサイトで
[TMP_RESULT]どうしても本来の翻訳サービスを試したかったのでこちらのサイトでレベルアッパー
[RESULT]どうしてもオンラインの翻訳サービスを試したかったのでこちらのサイトでレベルアッパー登録
[SOS]
[TMP_RESULT]デベロッパー
[SOS]
[TMP_RESULT]デベロッパー登録
[RESULT]デベロッパー登録
  

長い文章もちゃんと認識しましたね。"本来の翻訳"が最終的に"オンラインの翻訳"に修正されたところは凄いですね。"レベルアッパー"は言い直したら認識できたので、私の発音が悪かったのでしょう(;_;)。
プログラミングするときは、[RESULT]のパラメータが出たらキーワードを見つけて、アクションを実行すれば良さそうです。課題は"こんにちは"などの短い単語の場合、次の単語を期待していると思われ[TMP_RESULT]で終わってしまうパラメータの処理です。

まとめ(ラピロにRECAIUSを実装する)

REST-API初心者でも、RECAIUSのAPIをPythonスクリプトで書いて、ラピロ(正しくはロボットに内蔵しているRaspberry Pi3)に実装することが出来ました。最初、音声認識ならjuliusでいいじゃんと思っていたけれど、プログラミングして気が付いたのが下図の通り、システム構成がシンプルなRECAIUSは、音響モジュールや言語辞書などのインストールが不要なので、例えば、パソコンで作ったスクリプトを、他のラズパイにコピーするだけ。私の場合は、箱型ロボットBB-2からラピロへの移植がpythonスクリプトのコピーだけなので、とても簡単でした。並べてみたら、JuliusとRECAIUSって最後の'us'が同じなんだけどひょっとして意図的ですか。

JuliusとRECAIUSの違い(システム構成)

▲上に戻る