2011年1月6日木曜日

[Kinectハック] SD_KinectIOをFlashで受ける

[Kinectハック] SD_KinectIO とりあえず公開でKinectからFlashへスケルトン情報を送るプログラムを公開していますが、それをFlashで受けるためのサンプルとライブラリも併せて作ってみました。



先のエントリでは人物のスケルトンモデルを表示するだけのデモを動かしていましたが、それと以前のオブジェクト移動のデモを合体させたサンプルを作成しています。

SD_KinectIOからは

"ユーザーID","部位ID","状態","x座標","y座標","z座標" (Null)

(例)
1,1,add,100.223,600.032,1534,302
1,1,update,140.344,632.323,1523,324
1,1,remove,0,0,0

ユーザーID:1~10
部位ID:1~24
状態:add,update,remove


というフォーマットで信号が送られてきます。

今回はスケルトン情報からFlash上にスケルトンモデルを表示させたいのですが、各ユーザーの各パーツがバラバラに送信されてくるので、それをFlashの方でユーザーごとにまとめてやる必要があります。
IDとパーツとの対応づけや、それを各ユーザーに割り振る処理は中々面倒そうだったので、今回もライブラリとしてまとめました。Google Code上のライブラリを更新していて、com.sdtech.kinect以下がそれに該当します。

・kinectパッケージ
・kinect.usersパッケージ

ひとまず、各オブジェクトをコンストラクタでインスタンス化していきます。


public function Main():void{
//
//ポインタ生成
mp=new MultiPointer(-1 , false);
mp.simulate=true;
mp.registerDefaultCursor(HandCursor);
addChild(mp);
//
//ボーン描画用
kmu=new KinectMultiUser();
kmu.mouseEnabled=kmu.mouseChildren=false;
addChild(kmu);
//
//FieldOfView設定
fov.addEventListener(Event.ENTER_FRAME , getFOV);
//
//接続開始
connect_btn.addEventListener(MouseEvent.CLICK , connectStart);
//
}


スケルトンモデルとボーン描画用のクラスのKinectMultiUserをインスタンス化し、以前も使用したPointerパッケージを組み合わせて使うため、同じようにインスタンス化します。また、3DのFieldOfViewを動的に変更出来るようにスライダーに、接続先を動的に変更出来るようにボタンにイベントを貼付けています。


private function connectStart(e:MouseEvent):void{
//
kc=new KinectConnector();
kc.addEventListener(Event.CONNECT , connectComplete);
kc.addEventListener(KinectEvent.RECEIVE , receiveKinectData);
kc.connect(ip.text , int(port.text));
//
}


接続ボタンが押されたら、SD_KinectIOに接続に行きます。受け取るデータは単なるプレーンテキストなのでXMLSocketで受け取っても問題ないのですが、今後特殊な仕様が追加される事を考えて、ここではKinectConnectorを使用しています。イベントリスナも専用の物を使用していますが、結局DataEvent.DATAと同じ処理になっているので、実はDataEvent.DATAを使用しても問題なかったりします。


private function receiveKinectData(e:KinectEvent):void{
//
//受け取ったデータをパースし、ボーンを描画
kmu.parseData(e.data);
//
for(var i:int=0 ; i<kmu.users ; i++){
//
//ユーザーを取得
var ku:KinectUser=kmu.getUserById(i);
if(!ku)continue;
//
//両手を取得
for(var n:int=0 ; n<2 ; n++){
//
//一意のIDを作成
var pointer_id:int=int(String(i+1)+(n+1));
//
var parts:KinectUserParts;
if(n==0){
parts=ku.getPartsById(KinectUserParts.R_HAND);
}else{
parts=ku.getPartsById(KinectUserParts.L_HAND);
}
//
if(!parts){
mp.lost(0,0,pointer_id);
continue;
}
//
//グローバル座標に変換
var p:Point=ku.local3DToGlobal(new Vector3D(parts.x , parts.y , parts.z));
//
//ポインタ認識
mp.detect(p.x,p.y , pointer_id);
//ポインタを動かす
mp.move(p.x,p.y,pointer_id);
if(parts.z>1500){
mp.down(p.x,p.y,pointer_id);
}else{
mp.up(p.x,p.y,pointer_id);
}
//
}
//
}
//
//
}


データを受け取ったらボーンを描画しますが、KinectMultiUserにはサーバーから受け取ったデータを渡すと自動的にボーンを描画してくれるparseDataというメソッドが実装されているので、ボーン描画部分はその一行で済んでいます。
内部的には、受け取ったデータをパースしてユーザーIDを取得し、まだ存在しなかったらユーザー追加、存在したらデータ更新、という何の変哲も無い処理になっています。

肝心のボーン描画部分は一行で済んでしまっていますが(笑)、これでFlash上にスケルトンモデルを描画する事が出来ました。

さて、ここから以前のようにオブジェクトを動かせるようにしていきます。


for(var i:int=0 ; i<kmu.users ; i++){
//
//ユーザーを取得
var ku:KinectUser=kmu.getUserById(i);
if(!ku)continue;
//
//両手を取得
for(var n:int=0 ; n<2 ; n++){
//
//一意のIDを作成
var pointer_id:int=int(String(i+1)+(n+1));
//
var parts:KinectUserParts;
if(n==0){
parts=ku.getPartsById(KinectUserParts.R_HAND);
}else{
parts=ku.getPartsById(KinectUserParts.L_HAND);
}
//
if(!parts){
mp.lost(0,0,pointer_id);
continue;
}
//
//グローバル座標に変換
var p:Point=ku.local3DToGlobal(new Vector3D(parts.x , parts.y , parts.z));
//
//ポインタ認識
mp.detect(p.x,p.y , pointer_id);
//ポインタを動かす
mp.move(p.x,p.y,pointer_id);
if(parts.z<1500){
mp.down(p.x,p.y,pointer_id);
}else{
mp.up(p.x,p.y,pointer_id);
}
//
}
//
}


まず、KinectMultiUserのusersプロパティで現在認識されている最大のユーザー数を取得し、各KinectUserインスタンスへの参照を取得します。各ユーザーの両手を認識したいので、さらにfor文で両手分処理を繰り返します。繰り返し処理の中は以前のエントリとほぼ同じですが、x,y座標は各ユーザーの両手の座標を使用したいので、各パーツ(KinectUserParts)の座標を使用します。また、今回はx,yに加えてz座標も加味する必要があるので、座標変換の時にlocalToGlobalでなくlocal3DToGlobaを使用しています。

これで、スケルトンモデル+オブジェクト操作で、スケルトンがオブジェクトを操作しているようなデモができました。
オブジェクトの操作について、現在は奥行きでDOWN/UPを判断していますが、手のひらの開閉が取れればもっと自然で良いんですけどね。C++は専門外なので、解析するにしても時間がかかりそうです…

ソースをダウンロード

0 件のコメント:

コメントを投稿