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電子書籍を読めるようにする
などの対応を予定している。

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

基本的な戦略はこうだ。
まず、ロボットが見ている画像320X240pxから、人を切り出す。人があるかどうかをチェックするためには、Histogram of Oriented Gradients (HOG法)とSupport Vector Machine (SVM)でやる。認識した人の姿から、HOGに似た手法で、特性値を取り出し、ロボットの関節(20数個ある)の角度ベクトルへの写像関数を推計する。二台のロボットのに方向から推計される姿勢のPointの高いものをロボットが認識した人の姿勢にする。ロボットの関節をその姿勢に向けて動かす。ただし、ロボットが倒れない工夫が難しい気がする。
HOGは、C++のプログラム化した。(SVMができていないので、アルゴリズムが正しいかどうかの検証はしていない、1個の学習画像を読み取り特性ベクトルを計算するまでしか作っていない。学習用の画像読み取りにOpenCVを使っているが、実際の認識の場合は、ロボットのビデオデバイスの画像データを直接読み取る。プログラムの中では、コメントアウトしている。g++のある環境で、makeコマンドをinvokeすればコンパイルできるはずです。)
<一旦公開したが、svmにかけたところデータ作成上に誤りがあるようで、公開を停止した。>
一方、SVMはアイデアはわかるが、プログラム化できていない。20数年前に、ニューラルネットワークやパーセプトロンの経済学への応用に没頭した時期があったが、SVMは、それに似ている。不思議な気がする。
SVMについては、時間をかけて、基本的な理論のところから理解し、プログラム化したいものだ。

人の動作を読み取る

こんなことをさせたいということのかなりのものを実現してきたが、次に、ロボットにやらせたいことは、人の動作を読み取って、自分の動作のためのメタ言語に翻訳することだ。
というのも、この間、ロボットに喋らせることを軸に、人の心とコミュニケーションをとらせるための基礎的な仕組みを作ってきたが、やはり、動作がありきたりでは、届けきれないことに気づいた。
動作を工夫することは、通常のプログラミングでもできる。しかし、次々に多様な動作をプログラミングすることは事実上できない。一番良いことは、人の動作を見て、その動作を自分の(ロボット自身の)体で実現してみることだ。それはすなわち、その動作をするメタ命令が自分の中で創作されていることになる。
それは、すでにロボット工学の分野では実現していることなのだが、私のロボットでもそれをやってみたい。
ロボットの映像をプログラマティカルに取り出すことはすでにできている。できれば、2台のロボットからみた画像を合成して、対象としている人の動きを立体的に再現させてみたいものだ。

複数ロボットと複数端末

ピノキオはスマホタブレットの端末で、一台で複数のロボットを管理するためのツールだった。複数のロボット同士はテレパシーでコミュニケーションする。これに対して、あらたに、一台のロボットが多数の端末とコミュニケーションを取る状況が想定されることが分かった。
(1)ロボット自身が授業を進行させる状況下で、児童・生徒が持っているタブレット端末から、ロボットの問いかけに対する回答を集める。それらの回答をもとになんらかのリアクションをとるような場合。
(2)ロボットの次の言動を複数の人々の意見を元に決定していく状況。(たとえば、その場で創造的に作成されるロボット演劇)
そのために、ロボット側が全ての存在端末を認識することが必要である。端末からの情報をすべて集めて、その中から特定のメッセージ、リクエストを選び出し、それ言動として出力する機能が必要となる。

ロボットを倒さない

テレパシーモジュール(ロボットに組み込むローカルライブラリ)に関わって、対話をめぐるシグナルのやりとりは、すでにビデオにアップしているように、問題無く機能する。ただ、対話と動作を組み込んだテレパシーのやりとりに問題が発生した。動作を開始しようとすると突然倒れるのだ。この問題を回避するために、十日以上時間を使ったことになる。
テレパシーモジュールがらみの問題だと思った。この間の経験から、メモリエラーが発生すると、すぐに倒れる。例えば、配列の枠以上のところにアクセスすると、セグメントエラーで、ロボットが麻痺して倒れることはよくあった。
そのために、モジュールの怪しい箇所を徹底的に調べた。結果的に、forkコマンドからpthreadによるスレッド処理にかえたり、自動で体を動かす機能をコントロールしたり、したが結局それらは問題ではなかった。そもそも、テレパシーモジュールの問題ではなかった。
要するに、歩行開始前の状態の問題だった。歩行開始前は、StandInitのポーズにしなければならない。ibotの基本モジュールでは、初期状態をALPostureモジュールで把握して、それがStandInitでなければ、ポーズをStandInitにセットするとしていたのだが、じつは、実際の姿勢がStandInitではなくても、そうであるかのように値を返すことが分かった。
そのあたりの問題があることに気づいたのは、実は、Choregrapheでも、初期状態をちょっと変にしておくと、Poseをとったときに、バーチャルロボットが倒れることがわかったことが大きい。つまり、Choregrapheも状況によって、機能できないと倒れるのだ。ここがヒントになって、上記のチェックを行った結果、どのような状況でも、moveToコマンド前に、StandInit状態にすることで、倒れなくなった。
さらに、moveToの終了後、まず、Standのポーズをとらせてから、体を自動的に動かす機能を復活させるにしたことも大切な対応だった。というのも、ロボットは、こうしたAutolife機能は、Standの状態で行うので、早めにStandを確保しないと、自動の動きを突然やらせると、体が揺れて、そのために倒れることがあるのだ。これも回避することができるようになった。
ロボットにとって、倒れることは深刻な問題だ。Pepperは、プログラマティカルに倒れることはないだろう。しかし、NAOは二足歩行である。倒れやすい。というか、倒れることを前提に、いろいろな対応が取られている。PepperにはPushrecovery機能があるのに、NAOには、なぜかその機能が組み込まれていない。NAOの足の裏には、片足、4個のセンサーが付いていて、圧力をチェックしているので、それを使って、Pushrecovery機能を持たせることができそうだが、なぜか、そのイベントと値を取得できない。
NAOにとって倒れないプログラム作りは決定的に重要なのだ。

虚構と真実について、ぴっきー、のっきーが語り合う

ibotのテレパシー機能を使って、ぴっきーとのっきーに語り合わせる例の2つめ。
truth
ロ­ボットの目から見て、人間が、虚構と真実を適当に妥協させて、生きているようにみえる­という話です。しかし、それが人間の持っている自由の表れであること、プログラム化する­自分たちとは違うことを議論させてみました。