会話アニメーションを組み込まれるようにした

iBotで、60種類の会話アニメーションを簡単に貼込めるようにした。お辞儀なんかも、自分でつくる必要があるかと思っていたが、きちんと作ってある。自分でコレグラフで作ろうとしたら、バランスが悪くて倒れそうになった。
全部試していないが、いろいろある。
あと、首を左右それぞれに傾ける動作が欲しいが、これくらいは自分で作ろうと思う。

言葉による一連の命令の逐次実行

ブラウザでロボット対話と動作を制御することはできるようになった。次にやるべきことだ。
NAOに、一連の命令をあたえて、それを実行させたいと思った。QiChatのトピックファイルの中にそれを書き込んで、やらせることはもちろんできる。が、その場で臨機応変にさせたい。
「ここから、3メートル歩いて、右に90度回転し、「こんにちわ」とあいさつをしなさい。そこにいる人の顔を覚えなさい。次に左に向いて、1メートル歩いて座りなさい。「たちなさい」と言われたら、たって、会話を始めなさい。・・・・等々」
と、その場で「人の言葉で」一連の命令を与えるとその通り実行を始めるようにしたいのだ。QiChatでは、命令の受け取りの部分だけを書いて、その流れの記憶と実行は、iBotでする。モジュールの中に新たにC++のクラスを加えよう。
「こういわれたらこう答えなさい」と言った感じで、対話そのものもその場で教えるようにすることもできるはずだ。QiChatスクリプトをその場で作るようなものだ。
対話を軸にしたロボットの管理、というiBotの精神にぴったりである。
かなり役に立つはずである。

NAO顔認識システムの精度と可能性

NAOの顔認識システムは、とても優れていることがわかった。なぜこのようなことを書くかと言うと、Choregrapheで顔認識を使うと、「なんだこれは?」という結果を出す。例えば、数人の顔と名前をface learnで覚えさせて、その顔を覚えているか、問いかけると答えるようなシステムを作っておく。
そして、面と向かって顔を出して、認識させると、別人の答えを堂々と出してくる。どうなっているの?という感じ。face recognitionのphthonスクリプトをいじって、いろいろ調整してみせたが、うまく正確な答えを出さない。匙を投げた状態だ。
しかし、今回、iBotのモジュールの中で、FaceDetectionのAPIをいろいろいじっていると、相当に精密にチェックして、ほぼ正しい答えを出すことがわかってきた。マニュアルには、OKIのシステムを使っていると書いてあるので、日本製なのかもしれない。
NAOは、顔認識が命じられると、秒単位で、認識している顔のデータをはじき出してくる。一つのデータは、次のようなものである。[[1411453501, 326253], [[[0,
0.0465021, -0.0450004, 0.338801, 0.35308], [1, 0.909, "わしだ",
[0.112934, -0.117693, 0.0830395, -0.107309, 0.142828, -0.124617, 0, 0,
0, 0, 0, 0, 0, 0], [-0.0332158, -0.103847, -0.00332159, -0.096924,
-0.06311, -0.100386, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0,
0, 0, 0, 0], [0.0514845, -0.00346157, 0.0830395, -0.00692313, 0.0199295,
0], [0.112934, 0.0380773, -0.00664318, 0.0519235, 0.0531453, 0.0415388,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]], []], [0.00990079, -0.000137116,
0.212517, 0, -0.609572, -0.0138481], [-0.0178356, -0.0142919, 0.54325,
0.00973533, -0.684393, -0.0291758], 0]
これがわずかな時間に、100個、200個と作り出されてくる。データは、[ ]とカンマで、構造化されているが、使用は、ドキュメントに書かれている。
xmlでもなく、jsonでもなく、c++では取り出しにくいのだが、
1, 0.909, "わしだ",
の部分が重要である。これは、ID 1のユーザー(ロボットにか顔を向けている第1番目の人)は、90.9%の確からしさで、「わしだ」さんであると言うことだ。他の人の可能性も調べるが、そのスコアは、0.5よりも遥かに小さい数になる。
だから、スコアの高いデータの名前を取り出せば、ほぼ間違わないようだ。
iBotで顔認識のシステムを使うと、85%以上の確からしさで人を認識すると、その時点で、イベントを発生させ、スクリプトの対応変数の中に、名前を書き込み、ロボットにしゃべらせることができる。あるいは、100回の顔認識が行われると、最大スコアの顔を、それとして出力するようにしている。
しかし、これほどしっかりしたシステムであるならば、さまざまな使い方が考えられる。名前は、ハードディスクに保存されているようなので、ロボットの電源を落としても忘れない。個人のデータを、顔の情報とともに覚えることができる可能性がある。

メモリエラーで倒れる

これは記録に値する問題だ。
ローカルモジュールで、配列の要素数以上にアクセスしたら、メモリのセグメント違反でアプリは止まる。これは一般のC++アプリでもそうであるし、ロボットのnaoqiも同じだ。ただし、ロボットの場合、一時的に気を失い、倒れる。しばらくすると正気に戻る。
ロボットが気絶するとは、すべてのギアがstiffness(堅牢さ)を失うことだ。DCM Cycle time error というものが発生する。よくわかっていない。そもそも、DCMについて、何度マニュアルを読んでも理解できない。
一体どこに問題があるかを調べるときには、ロボットを支えたまま問題探しをしなければならない。が、やはり、こういう場合は、naoqiのC++SDKにあるシミュレーターを使うべきだ。確かにシミュレーターは便利だ。ほとんどのことはシミュレーターでいいのだが、顔認識機能やいくつかの大切な機能が、ロボットにしかない。

モジュールからQiChatイベントを呼び出す

二日間これにかかり切りだった。
つまり、たとえば、ロボットが歩くなどのモーションを行った後に、QiChatスクリプトの中に組み込まれたイベントのルールを呼び出して、「歩き終わりました」などとしゃべらせたいわけである。
その逆は簡単であった。すなわち、QiChatスクリプトで、ロボットが「これから歩きます」などと言って、歩き始めることは、その言葉のあとに $start_walk=1などと、変数変化を組み込み、これをモジュール側でイベントとして受け取って、コールバック関数を起動して、AlMotionのmoveTo関数を起動してロボットを動かせば良いのである。
私は簡単に、動作が終わったあとモジュールの側でスクリプトに組み込み込んだ、イベント起動の変数の値を入力すればうまくいくものだと思っていた。たとえば、スクリプトの中に、
u:(e:finish_walk) 歩き終わりました
というイベントルールを組み込み、モジュールの側から、このfinish_walkという変数に適当に値を入れれば、変数変化のイベントが発生し、上記の「歩き終わりました」という言葉をロボットが発すると思っていた。
そこで、たとえば、memoryをALMemoryのインスタンスとして、C++APIのinsertData関数を使って、
memory->insertData("finish_walk",1);
このように値を入れればイベントが発生するはずだった。ところが、これがそのようには行かないのだ。
確かに、finish_walkに1という値は入力することができる。しかし、イベントルールは発火しない。
不思議なことに、
u:(あたいをいれる) $finish_walk=1
というルールをスクリプトに書いて、ロボットに、「あたいをいれる」と話しかけると、先のルールが発火する。どうなっているの??という感じだ。原因をさぐるためにいろいろやった。何をやったか忘れるくらいの究明作業だった。
結局、先のinsertData関数ではだめだったのだ。AlMemoryのraseEvent関数を使って、
memory->raiseEvent("finish_walk",1);
のようにしなければならないのだ。なぜそんな簡単なことがわからなかったのか。insertData以前にこれはすでに試みていて、失敗していた。しかし、今思えば、raseEvent関数の問題ではなく、他の欠陥だったのだ。詳しく詮索するのはやめよう。
教訓は次のようにまとめる。
「QiChatの中では、変数に値を入力すると、同時にそれはイベントを発火させる。モジュール側からは、raseEventを使って、入力とイベントを同時発生させる。insertDataは、単に値の入力作業しかしない」
何はともあれ解決してよかった。
モジュールとQiChatスクリプトのそれぞれの出口と入り口がつながって、iBotがChoregrapheの対話機能と同等の機能を持たせることが現実的なものとなった。

qimessaging.js 使い方の注意点

iBotは、ブラウザから、ロボットのモジュールにアクセスし対話や動作をコントロールする。その際最も重要な役割を果たすのが、javascriptで書かれたqimessaging.jsである。Aldebaranから提供されていて、ロボット本体が持っているウェッブサーバーのドキュメントフォルダに置かれている。
javascriptはクライアント側でブラウザを操作するものなので、このロボットにあるqimessaging.jsをユーザー側のブラウザに取込まなければならない。その際、通常のjavascriptと同様に、

<script src="htttp://192.168.11.3/libs/qimessaging/1.0/qimessaging.js"></script>

のように、ヘッダー部分に置くことになる。IPアドレスは、ロボットのものでなければならない。
第1の注意点は、このqimessaging.jsをjqueryの$.getScriptコマンドをつかって、実行途中にダイナミックに取込むと、機能しないということだ。理由はあるのだろうがわからなかった。IPアドレスを取得したあと、phpでヘッダー部分に書き込むようにして、ページをリロードするという作業が必要だった。
このjsを取込むと、
var session = new QiSession(robotip);
というかたちで、Qisessionのインスタンスが作成可能になる。robotipはロボットのIPアドレスである。
第2の注意点は、このQisessionは、接続中に2度作成してはならないということである。2度作成すると、qimessaging.jsがエラーを引き起こす。当然のようであり単純なことなのだが、javaスクリプトファイルをいくつも作っているうちに問題を引き起こしがちである。
一旦、上記のsessionを作成すると、serviceメソッドを利用して、ロボットのnaoqiのさまざまなモジュールを利用できるようになる。基本的に、javascript特有の非同期的組み立てが幾重も必要になる。

XercesからTinyxml2へ

iBotシステムの、個々のロボットの設定ファイルは、xmlフォーマットで書かれていて、ロボット内のiBotモジュールから読み込まれ処理される。モジュールはC++で書いているので、C++でxmlファイルをparseしなければならない。
parserをxercesというライブラリを使うと書いたが、これで作成すると、ローカルモジュールだけじゃなく、このライブラリ自身もロボットに送り込まなければならず、これが数十メガバイトもあり重たい。しかも、ロボットは32ビットcpuという制約もある。
そこで、xercesを改め、もっと軽いそれ自体が、簡単な一つのクラスと一つの実行ファイルでできているtinyxml2に変えた。実に軽く簡単にparseできるようになった。そかし、このために、1日使った。

対話の曖昧さ

ほとんどの対話はかなりの曖昧さを持ったまま行われている。肝心なことは、それで用が足りているということだ。確かに次の電車の時間を尋ねるものと答えるものとの間で、伝えようとしたことと聞こうとしたことのポイントがずれることはないかもしれない。しかし、花の美しさを伝えようとするものと、聞こうとするものの間の会話は、曖昧さに支配される。われわれの日常会話の多くが後者のたぐいだ。
私が岩手大学に勤めていた頃だから、もう三十年近く前のことだ。還暦過ぎの母が福井から出て来て、盛岡にあった私の家を訪問した。私たち家族と母は、八幡平の奥にある温泉に出かけた。大浴場に行ったときのことだ。女風呂から先に出てきた妻が私に言った。
「おばあちゃん、地元のおばさんと楽しそうに話していたわ」
しばらくして、母が風呂から出てきた。私は母に聞いた。
「地元の人と何を話していたの?」
母は答えた。
「それが、相手の話が訛っていて、ぜんぜんわからなかったのよ」
母は、妻が外国語と思うほど、福井弁が強烈である。相手は、岩手弁だったのだろう。会話の呈をなしてなかったと思われる。
それでもその会話は母にとって楽しいものだったのに違いない。意味ある会話だったのである。
ロボット相手の会話も、そのような曖昧さがあっていい。

ローカルモジュールの出力を呼び出し側にかえす

ローカルモジュールからNAOqiへのログ出力については既に書いた。コンソール出力は、ただ、stdoutにストリームを出せばよい。ここで問題にするのは(それほど大げさなことではないが)ローカルモジュールのあるメソッドを実行させた場合に、その戻り値を呼出側でとらえる方法だ、難しくはない。
たとえば、ローカルもジュールのバージョンを獲得する場合を考えよう。次のようなrRobotクラスメソッドをモジュールの中に書き込む。
std::string Robot::getVersion()
{
std::string version = "0.0.4";
return version;
}
これをPythonから呼び出す場合、
>>> ws = ALProxy("Robot","127.0.0.1",9559)
>>> rep = ws.getVersion()
>>> print rep
これで、versionが出力される。
ただし、APIマニュアルでは、クラスのコンストラクタで
functionName("getVersion", getName(), "ibotのバージョンを取得する");
setReturn("char*", "return version");
BIND_METHOD(Robot::getVersion);
とsetReturnを入れてバインドするようにかかれているが、必須ではない。
ただ、一つは困ったのは、文字列をchar*で出力すると、モジュールがコアダンプして止まってしまうことだ。理由はわからない。std::stringで出力するようにしたら正常に獲得できた。どこかに考え違いがあるようだ。