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回の顔認識が行われると、最大スコアの顔を、それとして出力するようにしている。
しかし、これほどしっかりしたシステムであるならば、さまざまな使い方が考えられる。名前は、ハードディスクに保存されているようなので、ロボットの電源を落としても忘れない。個人のデータを、顔の情報とともに覚えることができる可能性がある。

モジュールから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で出力するようにしたら正常に獲得できた。どこかに考え違いがあるようだ。

スクリプトの暗号化

QiChatはスクリプトであり、ロボットは可読なテキストファイルの状態でそれを入力する。作成したユーザーがそれを一貫して読めることは大事だが、そのスクリプトを他者に使わせるときに読まれることが望ましくない場合がある。
iBotシステムでも、ロボットが使用する瞬間には可読なテキストファイルでなければならないが、ユーザーから他者にわたったときには暗号化されているようにするつもりだ。それができるのは、スクリプトをロボットに読み込ませるのはiBotであって、iBotが暗号化されたスクリプトを複合できれば良いわけだから。
ユーザーは、結局最終的なスクリプトの利用者に複合化キーを渡さなければならないので、完全ではない。
iBotが固有の複合キーを持たせてスクリプトを隠蔽すれば、利用者はほぼ完全にテキストで見れなくなるが、iBotは、C++ で書かれ、コンパイルされ、バイナリファイルになっているので、それをリバースエンジニアリングで解読すればそれも見られてしまう。しかし、それは仕方がないだろう。

QiChatにおける変数の扱い

iBotシステムは、最後の詰めのところで、複雑で手間のかかる作業になっている。
特に変数の取り扱いだ。iBotは、Choregrapheを全く使わなくても、対話、動作、認知の機能を利用できるようにするシステムだ。中心には人との対話があり、それに起動されてさまざまな動作、認識機能が発動される。コンセプトがそうなっている。
その全体を直接コンんとロールするのがQiChatスクリプトである。QiCHatスクリプトにはめ込まれる機能を全て解釈して実行するのは、直接にはロボットなのだが、人とそのロボットの機能を媒介するシステムがiBotである。ほとんどそれはiBotで実現できるようになるのだが、NAOQiには、多様な機能があるので、全ては組み込まない。
その際、変数の扱いが面倒である。というのもNaoqiというロボットOSは、変数のイベント処理が重要な役割を持っている。変数に値が入力されたり変更されたりすると、それだけでイベントが発生し、プログラムの方でそれを獲得できるようになっているのだ。
たとえば、人の顔を学ぶとき、それが誰の顔かを変数で入れ籠まないと行けない。具体的な名前にすると、全く融通が利かないからだ。それをQiChatから、iBotシステム経由で、naoqiに渡す作業が必要になる。あるいは、認識した顔が誰であるかをQiChatのスクリプトの中に戻せなければならない。これら全てが変数によって媒介される。非常によくねられたシステムであることは間違いない。ちゃんと使えるようになっている。
iBotがどのように拡張されても、この変数を合理的に扱えるようなシステムにしたい。

編集機能の充実

iBotシステムのQiChatスクリプト編集機能で、簡単に入力できるように、また、質疑応答の構造かも容易になるような機能を組み込んでいる。当初は、これは後まわしにしてシステムを公開することを考えたが、この編集機能があるかないかで、スクリプトの開発スピードが格段に違うようになりそうなので、その部分も完成した後に公開しようと思う。