久しぶりに動かそう

今度は、ちゃんとしたコントをやらせようと思う。
今まで作ったシスデムでかなりこなせるはずだ。
多分、naoqiなど開発ツールは相当更新されていると思うが、頭が戻るまでは、今までのシステムをそのまま使う。
まず、自分が何を作ったのかを思い出そう。このサイトにほとんどが書いてあるので、それを追うしかない。
まず、ロボットの充電から始めるか。
潰してしまった32bitLinuxを作り直そうか。

Two javascript functions for displaying video image with robot

A French student asked me a question about how to display the video. He used the system to send message on the Aldebaran community site. I write this article for answering his question. The followings are two javascript functions "GetVideoImages()" and "DrawImage()" for displaying the video images that the robot get with his camera.  "Ibot" is an object on my javascript file. If you have another question, please use the aldebaran message system.

 1     ///////////////////////////////////////////////////
 2     // Display Video Images
 3     ////////////////////////////////////////////////////
 4     Ibot.session = null;
 5     Ibot.displayVideo = false;
 6     Ibot.savedSubscriberID = null;
 7
 8     Ibot.getVideoImages = function(ipaddress){
 9         $("#subMenu").fadeOut("fast");
10         Ibot.newConnect(ipaddress, function (ret,session) {
11             Ibot.session = session;
12             if (ret === "success") {
13                 Ibot.session.service("ALVideoDevice").done(function (vdev) {
14                     var fps = 1; // frame number
15                     var subID = "subscriberID";
16                     vdev.subscribeCamera(subID, 0, 1, 11, fps).done(function (subscriberID) {
17                         $("#videoStop").unbind();
18                         $("#videoStop").click(function(){
19                             Ibot.displayVideo = false;
20                             vdev.unsubscribe(subscriberID);
21                         });
22                         $("#dialogOK").unbind();
23                         $("#dialogOK").click(function () {
24                             Ibot.displayVideo = false;
25                             vdev.unsubscribe(subscriberID);
26                             $("#mdialog").fadeOut("fast");
27                             $("#glayLayer").fadeOut("fast");
28                         });
29                         var delay = 1000/fps;// interval for timer
30                         $( 'input[name="fpsValue"]:radio' ).change( function() {
31                             Ibot.showConfirm("Network is too slow. It is possible for the robot to stop.",function(ret){
32                                 if(ret === "OK"){
33                                     var newFps = $(this).val(); // value
34                                     Ibot.showAlert("Change frame rate. newrate = " + newFps);
35                                     fps = parseInt(newFps);
36                                     vdev.setFrameRate(subscriberID, fps);
37                                     delay = 1000 / fps;
38                                 }else{
39                                     $('input[name="fpsValue"]').val(["1"]);
40                                     fps = 1;
41                                     vdev.setFrameRate(subscriberID, fps);
42                                     delay = 1000 / fps;
43                                 }
44                             });
45                         });
46                         var canvas = $("#videocanvas");
47                         var context = canvas[0].getContext('2d');
48                         var img = context.createImageData(320, 240);
49                         var timer; //timer
50                         Ibot.displayVideo = true;
51                         var loop = function () {
52                             Ibot.drawImage(subscriberID, vdev,context,img);
53                             clearTimeout(timer);
54                             if(Ibot.displayVideo){
55                                 timer = setTimeout(loop, delay);
56                             }else{
57                             }
58                         };
59                         // loop start
60                         loop();
61                     }).fail(function(){
62                         Ibot.showAlert("Fail to subsctibe");
63                     });
64                 }).fail(function(){
65                     Ibot.showAlert("Fail to start video device.");
66                 });
67             } else {
68                 Ibot.showAlert("Fail to connect to the robot.");
69             }
70         });
71     };
72
73     Ibot.drawImage = function(subscriberID,vdev,context,img){
74         vdev.getImageRemote(subscriberID).done(function (video) {
75             var arrayBuf = Ibot.Base64_To_ArrayBuffer(video[6]);
76             var tpx = 320 * 240;
77             var uint8array = new Uint8Array(arrayBuf);
78             for (var j = 0; j < tpx; j++) {
79                 img.data[j * 4] = uint8array[j * 3];
80                 img.data[j * 4 + 1] = uint8array[j * 3 + 1];
81                 img.data[j * 4 + 2] = uint8array[j * 3 + 2];
82                 img.data[j * 4 + 3] = 200;
83             }
84             context.putImageData(img, 0, 0);
85             vdev.releaseImage(subscriberID);
86         }).fail(function (error) {
87             Ibot.displayVideo = false;
88             vdev.unsubscribe(subscriberID);
89             Ibot.showAlert("Fail to get video image. error = " + error);
90         });
91     };
92
93

(You can see a the youtube video at the following address.

Sorry, I say comments in Japanese.)

音声認識エンジンの入れ替え

Naoqiの音声認識エンジンは、優れているのだろうが、気に入らない。最も気に入らないのは、ワイルドカードによる音声認識がほとんどできないことだ。しかしその難しさは理解できる。ので、なんとかこちらでも工夫をしたいと思っていた。
が、この間、電子書籍の検索とダウンロードをロボットとの対話で行おうとすると、なかなかやっかいなのだ。たとえば、「あ」で始まる著者を探すようにという指示をロボットに与える、Dialogを実現しようとしたが、こうした1語の認識すら難しい。事前に「あ い う ・・」などという、1語のconceptを事前に与えておいても、間違う。いろいろな工夫もあるだろうが、実に面倒になってきた。認識エンジンの細かい設定の調整ができればいいのだろうが、また、メソッドがあるので、そうした設定が可能であるかのようだが、なんら解説がない。
あのSiriのようなレベルのものがあれば良いと思う。
それは難しいにしても、なんとかならないかと探していたらjuliusというフリーソフトがある。試してみたら結構いけるし、細かい設定や、ルールの指定など、自由度が高い。
たとえば、なんのルールも設定せずに、
「わしだ」
という、名前の単語を認識できた。単語レベルでは果物の名前も、事前ルール抜きにほとんど認識できる。また、
「わたし名前はわしだです」
は、
「わたしの名前はわし出すです」
と、まちがったが、結構いけてる。多少のルールを追加すれば、この程度の文章は、完全認識に行けるような気がする。
ライブラリにもできるということなので、NaoqiのC++から、このライブラリを呼び出すことを試みようと思う。一つ不安は、ロボットのマイクデバイスをjuliusで呼び出すことができるかどうかだ。それさえ可能であれば、あとはなんとかなる。

ロボットに、epubフォーマット電子書籍を読み上げさせる

業界標準のepubフォーマットで組版された電子書籍を、ロボットNAOに読み上げさせるためのローカルモジュール(wsbook)を有限会社ネット書房と共同で開発した。

特徴は次のような点にある。
(1)ロボットとの対話で音読を制御できる
(2)ルビがふってある場合は、ルビの方を読み上げる
(3)指定されたファイル順に読み上げる
(4)ロボットが読み上げる1行には限界があるが、任意の場所で区切らずに、句読点(、。)などで区切った読み方にして、不自然さを回避する。
(5)読了を中止した場所を記憶して、その場所から再開する
(6)書籍の長さは、記憶領域以外の制限はない(長い本でも最後まで読み上げられる:1巻、2巻と分けれている場合は別だが)
(7)自分の書棚の本のリストを読み上げる
などの機能を組み込んだが、まだ、次のような問題はある。
(1)ルビがふっていない、旧仮名遣いの読み上げを間違える。(かなり高度な処理が求められる)
(2)ルビがふっていない、漢字の読みを間違える(学習させる必要があるが、たとえば、どのような状況で金を「かね」とよみ、また「きん」とよむか。文脈解析、自然言語処理の機能が必要になる)
(3)中止は、言葉によるか、前頭部タッチによるかだが、少しバタバタしている。
今後、
(1)ネット書房が有する青空文庫11,000冊のepub書籍を自由に、ロボットに直接(パソコンやスマホを介さずに)ダウンロードして読むことができるようにする。
(2)Gutenbergの30,000冊の英文書籍を読み上げられるようにする。
(3)ユーザーが有しているepub電子書籍を読めるようにする
などの対応を予定している。

ロボット映画の準備

NAO(ぴっきーとのっきー)を主演にした、ロボット映画を作成したいと思っている。その準備の第1段階として、ビデオカメラを購入した。型落ちだが、人気と性能が高い機種にした。24,000円弱の価格だった。
videocameraいままでは、スマホかタブレットで撮影していたが、ズームも効かないし、移動、固定、手ぶれ問題があった。
まず、台本を作る必要がある。路上撮影は許可がいるので、大学の中だけで終わるものにしたい。(大学内も許可が必要であるが、路上よりも許可を得やすいだろうと思う。)台本が出来たらキャスティングだ。出演料は払えないが。

HTMLのパーサーを作成している

必要があって、C++で、HTMLのパーサーを作成することになった。ロボットに組み込む、ローカルモジュールの一部となる。
XMLはtinyxml2で対応していることは既に書いている。原則的に、Htmlも、Xmlのルールに従っているはずなのだから、tinyxml2でパース(構文解析)してやれば良いようなものだが、実はそう簡単にいかない。
なぜなら、Htmlは、多少文法通りに書かれていなくても、パースをやめたりしてはいけない。なんらかのかたちで表出しなければならないのだ。そうしたあたりのいい加減さをxmlパーサーはわかっていない。こちらが、Htmlパーサーに求めているのは、Xmlほどの厳密さや、正確性ではない。必要な、タグ、属性、テキストを切り分けられれば良い。
ネットで探しても適当なものはなかった。あまり大掛かりなものは、ロボットに組み込めない。必要最小限の軽やかなものでよいので、結局自分で作ることにした。以外と手間はかからない。

Dialog トピックファイルから関節を制御する(3)

Choregrapheなどを使わずに、iBotローカルもジュールに仕組みを組み込んで、Dialogのトピックファイルから関節を直接制御することをやっていた。そうすることで、対話と動作が様々な方法で、柔軟に結合できる。ロボット漫才には不可欠の機能である。
以下に一部試みたビデオがある。(まだ、動作はぎこちないが)

スクリプトファイルは以下のようなものである。関節硬直度の調整、現在のすべての関節状態をログに書き出し、一時保存する、保存した関節状態を復元する、関節状態を変更するに当たって必要な自動動作モードをすべてオフにしたりオンにしたりもできるようになっている。動作は、万歳とか拍手の用に、メソッドでまとめるようにした。ただし、コメントアウトしているが、個々の関節をそれぞれまとめてセットすることもできる。
# ここから
concept:(level)[ぜろ れい じゅう じゅ ふたじゅう ふたじゅ にじゅ にじゅう さんじゅう さんじゅ よんじゅう よんじゅ ごじゅう ごじゅ ろくじゅう ろくじゅ しちじゅう しちじゅ ななじゅう ななじゅ はちじゅう はちじゅ きゅうじゅう きゅうじゅ ひゃく]
concept:(switch) [おん おふ]
u:(拍手しなさい) わかりました。拍手します。 $joint_joint_001="tappingHand,5"
u:(自動動作を _~switch にしなさい) わかりました。自動動作を $1 にします。 $joint_joint_002="setAutoMove,$1"
u:(関節硬直度を _~level パーセントにしなさい) わかりました。関節硬直度を $1 パーセントにします。 $joint_joint_003="setStiffness,$1"
u:(右手をあげなさい) わかりました。右手をあげます。 $joint_joint_004="upRightHand"
u:(左手をあげなさい) わかりました。左手をあげます。 $joint_joint_005="upLeftHand"
u:(ばんざいをしなさい) わかりました。ばんざいします。 $joint_joint_006="banzai"
#u:(右手をあげなさい) わかりました。右手をあげます。 #$joint_joint_007="setJoints,RShoulderPitch:-70,RShoulderRoll:-10,wait:2500"
#u:(右手をさげなさい) わかりました。右手をさげます。 #$joint_joint_008="setJoints,RShoulderPitch:90,RShoulderRoll:-10"
u:(関節{状態}を保存しなさい) はい、関節状態を保存します。 $joint_joint_009="getAllJoints"
u:(関節{状態}を復元しなさい) はい、関節状態を復元します。 $joint_joint_010="restoreAllJoints"
# ここまで
最初にconceptとして入れてあるひらがなの読みは、硬直度の人間の数字を聞き取るためのものである。残念ながら、NAOはワイルドカードで人間の言葉を受け取れないのでconceptで、人間がいいそうな言葉を与えておかなければならない。ただし、これだけでは実際の数字にならない。WSIbotモジュールで、そのカナを数字に変換するメソッドを用意している。

Dialog トピックファイルから関節を制御する(2)

この問題でかなり手間がかかった。こういう時は、しばしば、AutonomouseLifeの機能に関連している。
やりたいことは、簡単に言えば、トピックファイルの中に、
#---------以下1行
u:(右手をあげなさい) わかりました。右手をあげます。
$joint_joint_001="setJoints,RShoulderPitch:-70,RShoulderRoll:-10"
#---------終わり
などのルールとイベント変数を入れておいて、そこに指定されている関節を指定角度に動かすということだ。そのための、必要なメソッドをWSIbotのローカルモジュール(C++で組んでいる)に組み込んでおく。
なかなか動かなかったが、結局、ALAutonomouseMoveのsetExpressiveListeningEnabledをfalseにして、さらに、ALMotionのsetBreathEnabeldもチェーンを"All"にしてfalseにすると、指定通り動くようになった。
ローカルモジュールの関数してい部分のC++命令は以下のようになる。
amove->callVoid("setExpressiveListeningEnabled",false);
motion->callVoid("setBreathEnabled","All",false);
qi::os::sleep(1.0f);
float fractionMaxSpeed = 0.3f;
motion->callVoid("setAngles", names, angles, fractionMaxSpeed);
qi::os::sleep(1.0f);
motion->callVoid("setBreathEnabled","All",true);
amove->callVoid("setExpressiveListeningEnabled",true);
setBreathEnabeldのマニュアルには、チェーンの指定を“Body”, “Legs”, “Arms”, “LArm”, “RArm” or “Head”でできると書いてあるが、実際は、"Arms", "Legs" or "All"の三つしかできない(と思う)。
これらのfalse指定は、動きが終わったら、trueにして元に戻すのだが、そうすると、もとのアイドル、リスニングモードに戻ってしまう。元に戻したくなければ、それらをtrueにするのをsleepで遅らす必要がある。
また、setAnglesは、non-blockingモードなので、動きを開始後すぐに次の処理に映って、動作の終了を待たない。へたすると、動作の途中でやめてしまう可能性もあるので、その辺りも留意する必要がある。
これで、コツは掴んだので、Choregrapheでやるような、ロボットを動かして、それを関節に組み込めるように処理したり、拍手や、いろいろな「芸人動作」を、トピックファイルから呼び出せるマクロとして作っておいたりしようと思う。

トピックファイル中の命令変数と引数(1)動作編

iBotのシステムは、トピックファイルを中心にしている。対話の中で様々な動作を入れたり、顔を認識させたり、テレパシーを相手に送ったりするなどの機能は、すべてトピックファイルの記述されている。そして、その基本は、変数であり、変数の名前、変数の引数がいろいろな意味を持っている。
今回、関節を独立に自由に動かすための、コマンドをトピックファイルの中に入れるに当たって、引数の与え方を拡張する。それにあたって、いままで、様々な機能を組み込んできて、複雑になっているので、まず、これまでの方法を整理しておこうと考えた。
(動作)
動作については、トピックファイルの中に、$ファイル名_動作タイプ_番号=1
(※ 二つのアンダースコアーで区切っている。したがって、トピックファイル名にアンダースコアを使ってはならない)
という形で入れることを基本にしていた。
QiChatのトピックファイルの変数は、無条件でグローバル変数になるので、複数のトピックファイルを有効化したときに、名前のバッティングが問題になるのでファイルの中での一意性だけを考えれば良いようにしている。
動作タイプは、
walk
navi
sitdown
standup
lyingback
crouch
をさしあたって組み込んでいる。このうち、walk とnaviについては、引数が必要である。
たとえば、walkの変数が、$testfile_walk_001である場合、次のようなコメント文をトピックファイルの中に組み込む。
## <ibot motion walk>testfile_walk_001,0.5,1.2,30</ibot motion>
これは、前方50cm、左方向1.2mに左に30度回転する形で進むということになる。(実際、方向と回転を同時に指定すると動きが複雑になって思うようにいかない場合が多かった。)
対話の本文の中に、
$testfile_walk_001=1
と入れたところで、歩行が実行される。
引数のいらない歩行のポーズコマンドは、そこに値1が代入された時点でポーズの実行動作に入る。たとえば、
$testfile_sitdown_001=1
と会話本文に入っていれば、座る動作をする。
(動作終了イベント)
一般に、動作が終わると、また次の対話、あるいは動作に入るので、iBotの動作変数については、終了後のイベントを自動的に登録している。たとえば、先の$testfile_walk_001=1というイベントが終了するとdid_testfile_walk_001というイベントが自動的に発火する。それを捉えるためには、たとえば、
u:(e:did_testfile_walk_001) 次は何をしましょうか。
と会話文を置いておくと、動作終了時に「次は何をしましょうか」とロボットが喋ることになる。
(次回は、顔認識のためのトピックファイル)