1 経緯
前回は、Web Speech API による音声の認識と合成について、基本的なことを確認し、人間的なインターフェーズという意味で、一般的に利用可能な、音声 <=> テキスト間の変換技術の現状を把握した。
今回は、それを使って、人間とロボットのインタラクティブなやり取りをどのように実装できるかを調べてみよう。
2 Ajax の初歩
インタラクティブな処理を実装するには、Ajax が適している。その初歩を確認するため、最小例を掲げよう。ここでは、簡潔に書けるよう、クライアント側では JQuery を、サーバ側では Bottle を使う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# -*- coding: utf-8 -*- """ Bottle + JQuery による Ajax 最小テスト """ import sys from bottle import get, post, request, run @get('/test') def test(): return ''' <h1>Ajax Test</h1> 質問:<input type="text" id="query-id"> <input type="button" value="Send" onclick="send_by_ajax()" /><br> 応答:<input type="text" id="reply-id"><br> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> <script> var ajax_success_func = function(reply) { document.getElementById('reply-id').value = reply; }; var send_by_ajax = function() { var msg = document.getElementById('query-id').value; $.ajax({ type: "POST", url: "/test", data: "message=" + msg, success: ajax_success_func }); }; </script> ''' @post('/test') def test_post(): if sys.version_info > (3,): msg = request.params.decode().get( 'message' ) else: msg = request.params.get( 'message' ) return '『%s』を受信しました' % msg run(host='0.0.0.0', port=8080) |
このスクリプトを次のように起動し、
1 2 3 4 |
$ python ajax-1.py Bottle v0.12.8 server starting up (using WSGIRefServer())... Listening on http://0.0.0.0:8080/ Hit Ctrl-C to quit. |
http://localhost:8080/test にアクセスすれば、次の画面で、クライアント・サーバ間のやり取りが確認できる。
3 Web Speech API と Ajax を組み合わせる
前回の内容 ― すなわち、音声の認識と合成 ― と上記の内容を組み合わせると、音声を使ったインタラクティブな交信を実装できる。
以下に、実行時の画面イメージとスクリプトの構成および各ソースコードを掲げる。
3.1 起動と操作
後掲のスクリプトを次のように起動し、
1 2 3 4 |
$ python ajax-2.py Bottle v0.12.8 server starting up (using WSGIRefServer())... Listening on http://0.0.0.0:8080/ Hit Ctrl-C to quit. |
ブラウザで http://localhost:8080/test にアクセスすれば、次の画面が表示される。

最初にマイクの使用可否を問い合わせてくるので、許可して音声で質問を入力すれば、応答内容がテキストボックスに表示されるとともに音声で通知される。
ボタンの使い方については画面イメージの吹き出しを参照のこと。
3.2 ソースコードと補足
メインスクリプト (ajax-2.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# -*- coding: utf-8 -*- import sys import re from bottle import route, static_file, get, post, request, run from WeatherRepoter import respond_to; @route('/<file>') def callback(file): return static_file( file, root='./' ) @get('/test') def test(): return ''' <h1>Web Speech API Demo</h1> <p> マイクの使用を許可し、音声で、次の例のように、天気をたずねてください。 <ul> <li>東京の天気は? <li>東京の明日の天気は? </ul> なお、音声を認識した場合には、その内容をテキストボックスに表示し、<br> 自動で送信しますので、ボタンを押す必要はありません。 </p> 質問:<input type="text" size=60 id="query-id" bgcolor="red"> <input type="button" value="Send" onclick="send_query_message()" /><br> 応答:<input type="text" size=60 id="reply-id"><br> <input type="button" value="Stop" onclick="recognition.stop()" /> <input type="button" value="Restart" onclick="recognition.start()" /> <script src="ajax-2.js"></script> ''' @post('/test') def test_post(): if sys.version_info > (3,): msg = request.params.decode().get( 'message' ) else: msg = request.params.get( 'message' ) return respond_to( msg ) run(host='0.0.0.0', port=8080, debug=True) |
音声の認識と合成 (ajax-2.js)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
function include(file) { var script = document.createElement('script'); script.src = file; script.type = 'text/javascript'; script.defer = true; document.getElementsByTagName('head').item(0).appendChild(script); } include( 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js' ); var recognition = new webkitSpeechRecognition(); var ajax_success_func = function(reply) { console.log(reply); document.getElementById('reply-id').value = reply; recognizing = false; var synthes = new SpeechSynthesisUtterance( reply ); synthes.lang = "ja-JP" speechSynthesis.speak( synthes ); var timer = setTimeout( function() { document.getElementById('query-id').style.backgroundColor = "lightgreen"; }, 3000); }; var interact_by_ajax = function(msg) { $.ajax({ type: "POST", url: "/test", data: "message=" + msg, success: ajax_success_func }); }; var send_query_message = function() { var msg = document.getElementById('query-id').value; interact_by_ajax( msg ); }; var synthes = new SpeechSynthesisUtterance( "マイクの使用を許可し、音声で、画面の例のように、天気をたずねてください。" ); synthes.lang = "ja-JP" speechSynthesis.speak( synthes ); recognition.continuous = true; recognition.onresult = function(event) { var length = event.results.length; if ( length > 0 ) { msg = event.results[length-1][0].transcript console.log( msg ); query = document.getElementById('query-id'); query.value = msg; interact_by_ajax( msg ); document.getElementById('query-id').style.backgroundColor = "red"; } }; recognition.start(); document.getElementById('query-id').style.backgroundColor = "lightgreen"; |
質問の解釈と応答 (WeatherReporter.py)
このスクリプトは、想定パタンに従って、時点と地名に相当する部分を正規表現を使って抽出しているだけで、常に「晴れ」を応答する。
実用的にするには、ここで Yahoo!天気・災害のRSS配信 などからデータを取得することになろう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# -*- coding: utf-8 -*- import re; # 理解不能返信の判別用 rx01 = re.compile( '理解できない' ) rx02 = re.compile( 'もう一度' ) # 正常な返信の判別用 rx10 = re.compile( 'の天気は(.+?)です' ) rx11 = re.compile( 'どこの天気' ) # 想定パタン rx20 = re.compile( '\s*(.+?)(の|-)((.+?)の)?天気' ) # 時点表現 rx30 = re.compile( '(今日|明日|昨日|あさって)' ) def respond_to( msg ): # 自分自身の返信メッセージならば無言の返信を行う。 m0 = rx01.search( msg ) if m0: return ''; m0 = rx02.search( msg ) if m0: return ''; m1 = rx10.search( msg ) if m1: return ''; m1 = rx11.search( msg ) if m1: return ''; # 想定したメッセージのパタンにマッチするか判定する。 m2 = rx20.search( msg ) rpl = '' if m2: # マッチした場合 p2 = m2.group( 1 ) t2 = m2.group( 4 ) # 時点が先行しているケースの場合 m3 = rx30.search( p2 ) if m3: return 'どこの%sの天気ですか?' % p2 if not t2: t2 = "今日" rpl = p2 + 'の' + t2 + 'の天気は晴れです' else: # マッチしない場合 rpl = '「%s」は理解できないので、もう一度、どこのいつの天気か、はっきりたずねてください。' % msg return rpl |
4 ロボット制御への応用
上記の例は、ロボット制御に応用を視野に入れ、クライアント側をスマホのブラウザ(Chrome for Android など)とし、サーバ側をロボットの Raspbian 上で実行する、というやり方を想定したものになっている。特に、このスクリプト構成では、ロボットに考えさせたり、動作をさせたりする部分は、スクリプトでは WeatherReporter.py に対応している。
5 課題
ここでは、音声の認識と合成の問題を Wep Speech API で実装し、クライアント側に任せる方法をまとめてみた。
今後の課題は、次のとおり。
- ロボットの動作を含めた実験
- ロボットからのウェブサービス(Yahoo!天気・災害のRSS配信 など)の利用
6 参考ページ
Python3 では、
1 |
msg = request.params.get( 'message' ) |
で文字化けが起きたので、「Python3.3.1 in Bottleで...」を参考にさせてもらって次のように修正した。
1 |
msg = request.params.decode().get( 'message' ) |
■