2010年12月20日月曜日

[Kinectハック]OpenNI+Flashでマルチユーザー・マルチタッチ OpenNI解析編



少し乗り遅れ気味ですが、Kinectネタを。
OpenNIの登場でKinectのユーザーのスケルトン情報が一通り取れるようになったので、
それをFlashへの橋渡しをして、マルチユーザー・マルチタッチな環境を作りました。

これでFlashでもNaturalInterfaceコンテンツを作れるんダゼ!

今回は「OpenNI解析編」ということで、
Kinectを購入してから、OpenNIを解析、Flashへ信号を投げるまでの解説です。


■開発環境
kinect制御端末 : WinXP VisualC++ Express2010
コンテンツ端末:MacOSX FlashCS4
ソケット通信で Win→Macへユーザーの手の位置を送ります。


Kinectハックの手順は先人たちをお手本に。

WindowでKinectを繋ぐまでの手順:
OpenNI: WindowsでKinectを使う

OpenNIをビルドするまでの手順
OpenNIをVisual C++ 2010 Expressでビルド&デバックするまでの手順。


上記サイトを参考にOpenNIを改変できるようにしたら、
OpenNIと一緒にインストールされたサンプルから改変しやすそうなのを探します。
NiUserTracker.exeはスケルトン表示までできているので、
今回はこれにソケット通信を追加しました。

C++は初めてなので、ここから、かなり試行錯誤してます…。
【C++】ソケット通信」を参考にして、SocketServerクラスを作りました。
クラスファイル間のアクセス方法がわからなかったんですが、
ハッシュファイルというのを作るんですね。ちょっと面倒です…。
SocketServerクラスにconnectとsend関数を作り、
send関数に送りたい信号を投げるとソケットで通信するようになりました。 

#include 
#include 

SOCKET s, s1;         //ソケット
//
//connect
int connect(){
  int result;          //戻り値
  //接続を許可するクライアント端末の情報
  struct sockaddr_in source;
  char buffer[1024];  //受信データのバッファ領域
  char ans[] = "送信成功";

  memset(&buffer, '\0', sizeof(buffer));
  //送信元の端末情報を登録する
  memset(&source, 0, sizeof(source));
  source.sin_family = AF_INET;
  //ポート番号はクライアントプログラムと共通
  source.sin_port = htons(3000);
  source.sin_addr.s_addr = htonl(INADDR_ANY);

  //ソケット通信の開始準備
  WSADATA data;
  result = WSAStartup(MAKEWORD(2, 0), &data);
  if (result < 0){
    printf("%d\n", GetLastError());
    printf("ソケット通信準備エラー\n");
  }
  //ソケットの生成
  s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (s < 0){
    printf("%d\n", GetLastError());
    printf("ソケット生成エラー\n");
  }
  //ソケットのバインド
  result = bind(s, (struct sockaddr *)&source, sizeof(source));
  if (result < 0){
    printf("%d\n", GetLastError());
    printf("バインドエラー\n");
  }
  //接続の許可
  result = listen(s, 1);
  if (result < 0){
    printf("接続許可エラー\n");
  }
  printf("接続開始\n");
  s1 = s;
  //クライアントから通信があるまで待機
 s1 = accept(s, NULL, NULL);
  if (s1 < 0){
    printf("待機エラー\n");
  }
  //クライアントから送信されたデータの受信
  result = recv(s1, buffer, 10, 0);
  if (result < 0){
    printf("受信エラー\n");
  }
  printf("%sを受信しました\n", buffer);
  return 0;
}
//
//send
int send(char id[],char part[] ,char px[],char py[],char pz[]){
 int result;          //戻り値
 char str[50]= "";
 //
 sprintf_s(str,"%s,%s,%s,%s,%s",id,part,px,py,pz);
 //
 result = send(s1, str, strlen(str)+1, 0);
 return result;
}


Flashには、ユーザーの手の情報を送りたいので、 スケルトンを描画しているクラスを解析します。
SceneDrawer.cppの中のDrawLimb()がそれです。

void DrawLimb(XnUserID player, XnSkeletonJoint eJoint1, XnSkeletonJoint eJoint2)
{
 if (!g_UserGenerator.GetSkeletonCap().IsTracking(player))
 {
  printf("not tracked!\n");
  return;
 }

 XnSkeletonJointPosition joint1, joint2;
 g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(player, eJoint1, joint1);
 g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(player, eJoint2, joint2);

 if (joint1.fConfidence < 0.5 || joint2.fConfidence < 0.5)
 {
  return;
 }

 XnPoint3D pt[2];
 pt[0] = joint1.position;
 pt[1] = joint2.position;

 g_DepthGenerator.ConvertRealWorldToProjective(2, pt, pt);
 glVertex3i(pt[0].X, pt[0].Y, 0);
 glVertex3i(pt[1].X, pt[1].Y, 0);
}

このコードからGetSkeletonJointPosition()がユーザーの部位の位置を返す関数だとわかります。
ユーザーIDと体の部位を指定するとx,y,z座標を返してくれます。
DrawLimb()を改変した関数SendPosition()を作り、SocketServerのsendに送ります。

void SendPosition(XnUserID player,int id, XnSkeletonJoint part,char _part[2])
{
 if (!g_UserGenerator.GetSkeletonCap().IsTracking(player))
 {
  printf("not tracked!\n");
  return;
 }
 XnSkeletonJointPosition joint1;
 g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(player, part, joint1);
 XnPoint3D pt[1];
 pt[0] = joint1.position;
 g_DepthGenerator.ConvertRealWorldToProjective(1, pt, pt);
 //
 char _id[2] = "";
 sprintf( _id, "%d", id);
 //
 char _px[10] = "";
 sprintf( _px, "%.3f", pt[0].X);
 //
 char _py[10] = "";
 sprintf( _py, "%.3f", pt[0].Y);
 //
 char _pz[10] = "";
 sprintf( _pz, "%.3f", pt[0].Z);
 //
 send(_id,_part,_px,_py,_pz);
}

以上で、OpenNIにソケット通信を追加できました。
今回は手の位置だけですが、顔や胴、足の位置も同じ仕組みで取得できます。

ここまでのソース: NiUserTracker_sdtech.zip

※VS上のデバックビルドでは動きますが、exeだとソケット通信開始時に落ちてしまいます。
いいところまで出来ているのに、なぜ…。
[追記 2010.12.22]
exeでも動くようになりました。
OpenNI/Data内の「SamplesConfig.xml」を読み込んでいたのですが、
開発環境とexeではパスが変わってしまうので、読めなくなっていたようです。


main.cppの
#define SAMPLE_XML_PATH "../../Data/SamplesConfig.xml"

#define SAMPLE_XML_PATH "SamplesConfig.xml"として、
exeと同じディレクトリにSamplesConfig.xmlをコピーすれば、
落ちなくになります。


 続きはFlash編へ。


Xbox 360 Kinect センサー
定価:¥ 14,800
新品最安価格:¥ 17,300 (2店出品)
売上ランク:62位
レビュー平均:4.54.5点 (127人がレビュー投稿)
by amazon通販最速検索 at 2010/12/21

6 件のコメント:

  1. Multitouch-vista has a tuio imput server on 127.0.0.1 port tcp 3333, if your software output the blobs coordinate in tuio format, we have a windows 7 multitouch with Kinect.

    Please implement tuio output to this sample

    返信削除
  2. Thank you for your comment, and for your interest in our works.
    C++ does not fall within the realm of our specialty, and therefore we are
    not sure that we can do it, and it may take some time. We will,
    however, do our best for you!

    返信削除
  3. where i can download the EXE (binaries)for Windows?

    返信削除
  4. We do not publish compiled EXE files,because OpenNI has the policy of opoen source.
    Please check this post.
    you can download the improved program. but not EXE.
    http://www.sd-tech-blog.com/2010/12/kinect-sdkinectio.html

    返信削除
  5. I tryed to compile your modification to NiUserTracker. When I start it up it sais

    'Open Connection'

    Then when i hit connect in the flash app the NiUserTracker sais

    'test received
    InitFromXml failed: File not found!'

    could you help me out with this?

    返信削除
  6. ok that was just an xml pathing issue

    返信削除