RaspberryPiのマイクで録音した音声をテキスト化する【ヒミツのクマちゃん その3】

RaspberryPiのマイクで録音した音声をテキスト化する

ヒミツのくまちゃんと話すための企画その2になります。

前回のエントリーではマイクで録音するところまで来ました。

uepon.hatenadiary.com

後は録音した音声をテキスト化することができれば、拡張性(雑談APIやNobyに渡すことができるので会話っぽい表現を実装可)が増えそうです。

音声のテキスト化(音声認識)のAPI

あたりがいいかなと思いました。(docomoさんのは内部的にはAmiVoiceっぽい) ということでdocomoさんのAPIを使用します。(後日、GoogleさんのAPIも触る予定ですが)

基本的な情報は以下のURLにあります。

dev.smt.docomo.ne.jp

基本動作は音声データを送信すると音声認識したデータをテキスト化してJSON化して返信するものです。つまり、データの送り方(REST)とデータの形式(ファイルのバイナリフォーマット)が分かれば概ね処理ができます。

f:id:ueponx:20161217091728j:plain

今回は特別なSDKなどを使用せず、RESTで通信を行いたかったのでdocomoさんのAPIの中の音声認識API【Powered by アドバンスト・メディア】を使用します。

以下は上記サイトの引用です。

音声認識API【Powered by アドバンスト・メディア】とは
音声データをREST形式で送信するだけで音声認識をすることができます。

主な特徴
* HTTPで音声データをPOSTするだけなので、AndroidiOSに限らず様々なプラットフォームでのご利用が可能です。
* クライアントアプリケーションに特殊なライブラリを組み込む必要がないため自由度の高い実装が可能です。
* クライアントアプリケーションにライブラリを組み込んでリアルタイム認識を行うタイプの音声認識と比較して、応答速度が遅い為、レスポンスが求められる用途には向いていません。

ドキュメントを眺めてみる

以下のURLがドキュメントページになります。

https://dev.smt.docomo.ne.jp/?p=docs.api.page&api_name=speech_recognition&p_name=api_amivoice_1#tag01

サービスのエンドポイント

アクセスを行うエンドポイントは以下となります。

https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize

amiVoiceって書いてあるw

リクエストクエリパラメータ

キー 必須 説明
APIKEY APIにアクセスするアプリの認証に利用する

リクエストヘッダのMIME-TYPE

HTTPで送信するデータ形式MIMEタイプはこのようになっている?

キー 必須 説明
Content-Type MIMEタイプmultipart/form-data; boundary=<バウンダリ文字列>

あれRESTアクセスだし、MIMEタイプは単純なOctet-Streamなんじゃねーの?んが。

やられました

とはいってもマルチパートでってことはファイル+制御用のJSONを送ればいいのかなと予想がつきます。

リクエストパラメータ(マルチパート化されたデータの中身)

キー 必須 説明
a 音声のバイナリデータ。10秒を超える音声データは途中で打ち切られます。音声データのフォーマット:PCM(MSB)16khz/16bit
v - 発話区間検出処理。"on"を指定すると、音声データの無音部分を無視して音声認識処理を行います。

このあたりをマルチパート化して送信するようです。

音声ファイルのフォーマットはサンプリングレート16kHz、16bitの10秒以内の音声でいいようなので、PyAudioへは

FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000

のようにパラメータを指定すれば問題なさそうです。

また、vのキーに関しては制度を気にしなければ問題はなさそうなのですが、無音部分は無視してもらわないとかなり精度がわるそうな気がするので、指定をすることにします。

ドキュメントを見てもわかりますが以下のように通信データが送信されます。

POST https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: multipart/form-data; boundary=<バウンダリ文字列>
--<バウンダリ文字列>
Content-Disposition: form-data; name="v"

on
--<バウンダリ文字列>
Content-Disposition: form-data; name="a"; filename="sample.adc"
Content-Type: application/octet-stream

音声データ(binary)
--<バウンダリ文字列>--

データの格納さえうまく調整できれば、Arduinoとかmbedからでも通信できそうな印象です。

ここまでわかれば、あとはpythonのrequestsモジュールでマルチパート化してPOSTする処理が わかれば、使用できそうです。

pythonのrequestsモジュールでマルチパート化処理を行う

こちらを参考にしました。

クイックスタート — requests-docs-ja 1.0.4 documentation

stackoverflow.com

ふむふむ、複数のデータはマルチパート化してPOSTするときには、以下のどちらかの形式でタプル化してpostの引数に与えれば良いようです。

  • (キー, データ)
  • (キー, データ, Content-Type)
  • (キー, データ, Content-Type, ヘッダー)

例えば、StackOverflowのサンプルをみてみれば、

  • キー=>foo、データ=>bar
  • キー=>spam、データ=>eggs

というデータセットをマルチパート化する場合には以下のような処理になります。

requests.post('http://…', files=(('foo', 'bar'), ('spam', 'eggs')))
# または
files = {'foo':'bar', 'spam':'eggs'}
requests.post('http://…', files=files)

今回のAPIではavというキーにデータを入れることになるので

files = {"a": 【音声ファイルのデータ】, "v":"on"}
r = requests.post(url, files=files)

こんな感じになるでしょう。あとはファイルの読み込みですがopen()で読みすればよさそうです。以下は処理の抜粋になります。

path = 'voice.wav'

files = {"a": open(path, 'rb'), "v":"on"}
r = requests.post(url, files=files)

これで一応大体の構想ができました。

API利用登録

では、登録を行っていきます。 下記のdocomoDeveloperSupportのページからログインを行ってください。SNS系のログインでも問題ありません。

dev.smt.docomo.ne.jp

次に音声認識のページの中ほどにある【申請する】のボタンをクリックします。

f:id:ueponx:20161219232846j:plain

すると、【API使用申請】のページに遷移します。

f:id:ueponx:20161219232944j:plain

ここで使用するアプリケーションの情報を登録することになります。 登録時点で決まっていなくても後で修正ができるので問題ありません。

入力が終わったら【API機能選択へ】のボタンをクリックします。

f:id:ueponx:20161219233148j:plain

API機能選択】の一覧画面に遷移するので、その中から

を選択します。他のAPIを含めても問題ありませんが、今回は1つだけにします。選択が終了したら下の方にある【利用するAPI利用規約に同意して次へ】をクリックします。

f:id:ueponx:20161219233928j:plain

【確認画面】に遷移するので、内容確認後【利用申請する】ボタンをクリックします。

f:id:ueponx:20161219234115j:plain

これで申請が完了しました。

f:id:ueponx:20161219234202j:plain

詳細は【マイページ】にある【API利用申請・管理】画面の【登録アプリケーション一覧】に情報が表示されます。API使用のキーに関してもここで確認ができます。

f:id:ueponx:20161219234228j:plain

これで音声認識APIが使用できるようになりました。

音声認識APIにデータを送信する

音声認識APIにデータを送信して、認識結果のテキスト情報を確認してみます。まずは録音した音声データがあればそれを使用します。 音声のデータフォーマットが10秒以内、サンプリングレート16kHz、サンプル16bitになっていない場合にはエラーとなるので注意が必要です。 今回はvoice.wavというファイルを送信することにします。

前述の通りpythonでデータを送信するコードを作成すると以下のようになります。認識された結果はJSON形式で返されるのですが、その中でtextのデータが認識された文書全体となります。

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

import requests
path = 'voice.wav'
APIKEY = '【発行されたAPIのキー】'
url = "https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY={}".format(APIKEY)
files = {"a": open(path, 'rb'), "v":"on"}
r = requests.post(url, files=files)
print r.json()['text']

このプログラムを実行すると以下のようになります。

$ python sample.py
、おまえ滑舌悪いんじゃないの。

無事に認識できたようです。(実験中に嫁に言われました…涙)

録音機能と連携させる

先程は事前に収録したものでテストしましたが、今度はPyAudioと連携し、録音後API側に対して要求を出すものにします。とは言っても、データは一度ファイルとして蓄積するので先程の処理とは大きく変わらないと思います。

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

#マイクからの入力を5秒間録音し、ファイル名:voice.wavで保存する。

import requests
import pyaudio
import sys
import time
import wave

chunk = 1024*2
FORMAT = pyaudio.paInt16
CHANNELS = 1
#サンプリングレート、マイク性能に依存
RATE = 16000
#録音時間
RECORD_SECONDS = 5 #input('収録時間をしていしてください(秒数) >>>')

print 'マイクに5秒間話しかけてください >>>'

#pyaudio
p = pyaudio.PyAudio()

#マイク0番を設定
input_device_index = 0
#マイクからデータ取得
stream = p.open(format = FORMAT,
all = []
for i in range(0, RATE / chunk * RECORD_SECONDS):
        data = stream.read(chunk)
        all.append(data)

stream.close()
data = ''.join(all)
out = wave.open('voice.wav','w')
out.setnchannels(1) #mono
out.setsampwidth(2) #16bit
out.setframerate(RATE)
out.writeframes(data)
out.close()

p.terminate()

print '<<< 録音完了'

path = 'voice.wav'
APIKEY = '【発行されたAPIのキー】'
url = "https://api.apigw.smt.docomo.ne.jp/amiVoice/v1/recognize?APIKEY={}".format(APIKEY)
files = {"a": open(path, 'rb'), "v":"on"}
r = requests.post(url, files=files)
print r.json()['text']

実行結果

$ python pyaudio_amivoice.py
マイクに5秒間話しかけてください >>>
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
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
<<< 録音完了
おはようございます。。

いろいろWarningは出ていますが、うまく動きました。

まとめ

一応録音した音声をテキスト化することができました。更にこれを雑談APIやNobyAPIなどに送信すれば会話を行うことができるようになります。 もう少しでクマちゃんとの会話ができるようになるのではないかと思います。

次回は会話っぽいやり取りまで進めたいと思います。

/* -----codeの行番号----- */