LeapMotionの操作用パネルを作成
eSEATがようやくユーザ定義のデータ型の入出力ポートが使えるようになりましたので、SSRの菅さんが開発されたLeapRTCと連携できるような操作パネルを作成してみたいと思います。
LeapRTCを取得してコンパイル
LeapMotionからのデータを取得するためのRTCは、SSRの菅さんの方で既に開発ずみでしたので、これを活用させていただこうと思っています。
LeapRTCのリポジトリは、Githubで既に公開されています。ただし、Windowsでの利用方法に関するドキュメントがないようですので、私が行ったメモ程度を書き留めたいと思います。
最初に、LeapRTCのリポジトリからソースコードをダウンロードして展開しておきます。このソースコードは、OpenRTM-aist-1.0.0で開発されていたようですが、1.1.1 for VC2012 を使ってコンパイルしてみることにしました。
OpenRTM-aist-1.1.1 は、まだ正式リリースではありませんが、私のサイトで公開しているインストーラは、ほぼ最新のリポジトリから作成しておりますので、問題ありません。
そこで、LeapRTCをコンパイルするために下記のツールを使っています。
- OpenRTM-aist-1.1.1 RC for VC2012
- Visual C++ 2012
- LeapSDK 2.0.2 (現在は、2.0.4 ですが問題ないと思います)
- CMake 2.8.12.2
LeapRTCを自分でコンパイルする場合には、上記のツール類はそろえておいてください。
コンパイルする準備が整ったところで、CMakeを使ってVC2012用のソリューションを作成します。これは通常のRTCの開発と同じなので、やり方は省略します。ただ、Configureの後にドキュメントを生成するかどうかのフラグが設定できますので、doxygenがない場合にはフラグを外しておきます。
あとはGenerateでVC2012のソリューションが生成されます。
次にVC2012でコンパイルするのですが、LeapRTCでは独自データ型を使っていますので、omniidlでidlファイルのコンパイルを行うことになります。omnidlを起動するにはpythonが必要になっていますし、VC2012では、pythonがPATHの指定なしで呼び出されますので、環境変数のPATHに python.exeがあるディレクトリを追加しておくことを勧めます。
エラーが出たときに自力で解決できる人は問題ありません。
python.exeにPATHを通した後に、VC2012を起動し先ほど作成したソリューションファイルを開いて、ビルドしてください。エラーがなくビルドできれば、OKです。
このページには、LeapRTCと実行に必要なDLLを含めたものを添付資料としておきます。(LeapMotion.zip)
LeapRTCからの出力を受け取るGUIの作成
LeapRTCの準備ができたら、eSEATでGUIパネルを作成したいと思います。最終的なGUIとしては、コンポーネントの操作も含めた下図のようなGUIパネルにしたいと思います。
GUIパネルの実装
このパネルでは、2行目までがrtc_handleを使ったRTCの操作にかかわるものです。次の2行は、LeapMotionが認識しているハンドの数と指のタッチの状態を含んだ表示です。4行目以降が、LeapRTCから受け取ったデータのジェスチャ認識結果とハンドの位置を表示する部分になっています。
基本的には、4行目以降のGUIアイテムがあれば十分かと思います。このGUIパネルは、label要素とinput要素がほとんどですので、比較的単純なUIだと思います。
このパネルのGUI部分は、下記のようになります。
<state name="Main"> <input id="RtcList" width="20" colspan="4"> --->Click [RTCs]</input> <button label="RTCs" ></button> <brk /> <button label="Connect" ></button> <button label="Disconnect" ></button> <button label="Activate" ></button> <button label="Deactivate" ></button> <brk /> <label text="Num of Hands:" /> <input id="nHandOut" width="10" ></input> <label text="TouchingR" /> <input id="handPosR" width="20" ></input> <brk /> <space length="2" /> <label text="TouchingL" /> <input id="handPosL" width="20" ></input> <brk /> <label text="Hand_R:" /> <input id="Hand1Pos" width="20" colspan="2"></input> <input id="Hand1Ori" width="20" colspan="2"></input> <brk /> <label text="Hand_L:" /> <input id="Hand2Pos" width="20" colspan="2"></input> <input id="Hand2Ori" width="20" colspan="2"></input> <brk /> <label text="Geature:" /> <input id="InfoOut" width="35" colspan="4"></input> <brk /> <label text="Swipe:" /> <input id="SwipeOut" width="35" colspan="4"></input> <brk /> <label text="Circle:" /> <input id="CircleOut" width="35" colspan="4"></input> <brk /> <label text="Screen:" /> <input id="ScreenOut" width="35" colspan="4"></input> <brk /> <label text="Key:" /> <input id="KeyOut" width="35" colspan="4"></input> </state>
独自データ型のデータポートの作成
次に、LeapRTCからの出力データのハンドリング処理を考える前に、独自データ型への対応について説明します。eSEATは、pythonとOpenRTM-aist for pythonの組み合わせで実装さえています。そのためで独自データ型を使ったデータポートを付加するには、OpenRTM-aistの他のRTCの時と同じようにidlファイルをomniidlでコンパイルしてimportしなければいけません。
Pythonに対応したidlファイルのコンパイルは、OpenRTM-aist for VC++ には含まれていませんので(正確には、python用バックエンドがないだけなのですが)OpenRTM-aist for PythonをインストールするかomniORBpyをインストールすればいいのだと思います。
そのため
omniidl -bpython <idlファイル名>でコンパイルできると思います。
idlがコンパイルできれば、あとはadaptor要素を宣言する前にgeneral要素の下でscript要素を使ってimportすればOKです。
LeapRTCのデータ型が定義は、ssrというネーム空間を使っていますので、下記のように定義すればよいと思います。
<general name="LeapMotion"> <script> import ssr </script> <adaptor name="gesture_out" type="rtcout" datatype="TimedString" /> <adaptor name="hands_out" type="rtcout" datatype="TimedFloatSeq" /> <adaptor name="frame" type="rtcin" datatype="ssr.Frame" /> </general>
また、rtc_handleを使う場合には同様にgeneral要素の下にscript要素を定義します。eSEATでは、general要素に関しては記述された順で解釈、実行されます。
LeapRTCからのデータのイベント処理
最後に、LeapRTCからのデータ処理を実装します。LeapRTCからのデータは、ssr.Frameというデータ型で送られてきます。ssr,Frameのデータ型は、idlファイルを見ると下記のように定義されています。
struct Frame { long id; long long timestamp; sequence<Hand> hands; sequence<GestureFrame> gestures; };
OpenRTM-aistでは、通信層のライブラリとしてomniORBを使っています。omniORBpyでは、構造体はクラスにマッピングされますので、Frame型のメンバ変数は、”.変数名”で参照することができます。また、eSEATでは、データポートからのデータ受信イベント発生時の処理は、rule要素にsource属性を指定することで記述することができますので、下記のようなコードを書くと受信したデータをコンソールへ出力することができます。
<rule source="frame"> <script> print rtc_in_data </script> </rule>
この記述からもわかるように、script要素の中では受信されたデータは "rtc_in_data" という変数で参照することができます。またscript要素は、複数記述することができますので、様々な処理をすることができます。
例えば、LeapRTCから得られたデータからハンドに位置を"Hand1Pos"という識別子のinput要素に出力するには、
<rule source="frame"> <script> info = "%f, %f, %f" % ( rtc_in_data.hands[0].position.x, rtc_in_data.hands[0].position.y, rtc_in_data.hands[0].position.z) seat.setEntry("Main:Hand1Pos", info) </script> </rule>
とすればよいと思います。
さらに、この結果を”hands_out”という出力ポートへ出力する場合には、script要素にsendto属性を付加し、rtc_resultという変数にデータを代入すればOKです。
SEATMLでは、このようにPythonスクリプトのコードを直接記述できますが、様々な処理を直接書くと内容が複雑になりすぎますので、外部ファイルに関数を定義してそれを呼び出すようにした方が解かりやすくなる場合があります。
私の場合には、いくつかの関数を定義してgeneral要素の下のscript要素で読み込みを行って実装を行っています。
詳しくは、添付資料にあるSEATMLファイルを読んでください。
下にLeapRTC と上で作成したeSEATのGUIパネルの動作例を画面キャプチャ動画にしました。videoタグをサポートしているブラウザで再生可能です。
動作では、作成したGUIパネルでコンポーネントの接続、有効化等をrtc_handleの機能を使って実現しています。
資料