DirectXの話 第119回
Kinect SDK for Windows β
Microsoft Research から KinectSDK β が公開されて1ヶ月くらいになるでしょうか?
やっぱりモーションキャプチャをやろうと考えてだらだらやってたら時間がかかりすぎました。
以前は OpenNI を使って戦車の移動をやってみましたが、今回は前述の通りモーションキャプチャをやってみました。
とはいえ、今回はボーンの回転だけで重心移動とかはやってません。
さて、このMSのSDKですが、配布までに時間がかかった割に微妙な出来です。
OpenNI と比べていい点もありますが、ダメな点もあります。
まず、OpenNI にあった認識のポーズが不要です。あの手を上に上げるやつ。
ポーズなしで即認識するので、認識が面倒な人にはいいと思います。
また、Kinect に搭載されているアレイマイクを使用することが出来ます。
アレイマイクなので、音を認識した場合にその方向を知ることが可能です。
残念ながら位置までは認識できませんが、方向を認識できるだけでもだいぶ違うでしょう。
ティルトの調整も可能です。上下の首振りですね。
しかし、ボーンの位置は取れても行列までは取れません。
OpenNI もセンサーとしてはボーンの位置しか取れないのではないかと考えています。
ライブラリ内部で回転行列を求めているだけでしょう。
MSのSDKではこれらの計算は自分で行う必要があります。
また、NITE に対応するジェスチャーライブラリ的なものが存在しません。
これもまた自前で実装する必要があります。
人によっては Windows7 専用という部分がネックになる人もいるでしょう。
Mac や Linux で扱いたい人には残念ながら使用できないので、そういう人たちは OpenNI を使いましょう。
しかし、インストールが容易なのは一般人からするといいかもしれませんが、Kinect でなにかアプリを作ろうとしている人なら OpenNI のインストールでつまずくこともないでしょう。
では、初期化とボーンの位置取得方法を簡単に解説します。
KinectSDK のサンプルでは OpenNI のものと違ってスレッドを立てています。
各情報の取得には各イベントを掴まえて処理しなければならないため、どうしてもスレッドを立てる必要があります。
NUIの初期化は簡単です。
HRESULT hr;
hr = NuiInitialize( NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON | NUI_INITIALIZE_FLAG_USES_COLOR );
NuiInitialize() の引数として,使用したい情報のフラグを指定します。
フラグの意味は以下の通りです。
プレイヤーインデックスは深度値に3ビットの情報として入ってきます。
そのため、Kinectが認識する人数の最大数は 7 となります。
使用する機能についてはイベントを作成し、各命令でイベントの登録をする必要があります。
// イベントを作成する
g_hKinectEvents[KINECT_EVENT_DEPTH_STREAM] = CreateEvent( NULL, TRUE, FALSE, NULL );
g_hKinectEvents[KINECT_EVENT_COLOR_STREAM] = CreateEvent( NULL, TRUE, FALSE, NULL );
g_hKinectEvents[KINECT_EVENT_SKELETON] = CreateEvent( NULL, TRUE, FALSE, NULL );
// スケルトンのトラッキングを有効にする
NuiSkeletonTrackingEnable( g_hKinectEvents[KINECT_EVENT_SKELETON], 0 );
// カラーイメージストリームを開く
NuiImageStreamOpen( NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480,
0, 2, g_hKinectEvents[KINECT_EVENT_COLOR_STREAM], &g_pVideoStreamHandle );
// 深度イメージストリームを開く
NuiImageStreamOpen( NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX, NUI_IMAGE_RESOLUTION_320x240,
0, 2, g_hKinectEvents[KINECT_EVENT_DEPTH_STREAM], &g_pDepthStreamHandle );
イメージストリームはそれぞれにハンドルが生成されます。イメージの取得に使います。
KinectSDK は OpenNI と違ってカラーイメージと深度イメージの解像度が違います。
カラーイメージは 640x480 か 1280x1024 のどちらか、深度イメージは 320x240 か 80x60 のどちらかとなります。
ここまで設定したらスレッドを立てます。
スレッド関数の中では各イベントが発行された際にそれぞれのイベントに合わせてイメージの処理かボーン情報の処理をしてやります。
イメージの処理はカラー、深度どちらも同じ手法となります。
// イメージを取得
const NUI_IMAGE_FRAME* pImageFrame = NULL;
NuiImageStreamGetNextFrame( g_pVideoStreamHandle, 0, &pImageFrame );
// 何らかの処理
// イメージの解放
NuiImageStreamReleaseFrame( g_pVideoStreamHandle, pImageFrame );
上述のコードではカラーイメージを取得していますが、深度のハンドルに変更すれば深度イメージが取得できます。
スケルトンの取得はもっと簡単です。
NUI_SKELETON_FRAME frame;
NuiSkeletonGetNextFrame( 0, &frame );
私のサンプルコードでは、この frame をクリティカルセクションを用いてコピーしています。
ボーンの計算はメインスレッドで行います。
トラッキングしているスケルトンの情報は frame から以下のようにして取得します。
NUI_SKELETON_DATA* data = NULL;
for( int i = 0; i < NUI_SKELETON_COUNT; ++i )
{
if( frame.SkeletonData[i].eTrackingState == NUI_SKELETON_TRACKED )
{
data = &frame.SkeletonData[i];
break;
}
}
if( !data )
{
return;
}
eTrackingState が NUI_SKELETON_TRACKED であればトラッキングに成功しています。
ID番号については一度トラッキングを開始するとそうそう変化することはありませんが、最初のトラッキングではどのIDになるか決まってるわけでもないようです。
特に複数人のトラッキングを行う場合、IDを保存しておくといったやり方はあまりよくないでしょう。
ボーンの回転行列計算についてですが、それほど解説することもないのでソースコード参照ってことで。
位置しか取得できませんが、たとえば胴体の回転なら両肩の方向から求められます。
腕に関してはまっすぐ伸ばされていると捻りが計算できません。肘が曲げられていると正確な捻りを求められます。
下半身に関してはかなり怪しいです。認識が怪しいのか計算が怪しいのかちょっと判然としません。
下半身については下手なことするよりIKで処理したほうがいいかもしれませんね。
というわけでサンプルは以下からDLしてください。
カーソルキー上下でKinectのティルトができるようになっていますが、モーターで動くため毎フレーム設定するのはよろしくありません。
サンプルでは1秒に1回設定するようにしています。ちょっと調整が難しいですが。
あとはKinectの前に立って体を動かしてみてください。あまり精度はよくないですが、それなりに追従するはずです。
次回は未定ですが、Kinectを使って簡単なゲームでも作ってみようかと思っています。