SDK1.5ver - 2.Kinectで骨格の座標を取得してXNAに表示する
About
このコンテンツはKinectプログラミングについて順を追って解説しています。未読の場合は先のコンテンツ「1.XNAでKinectのカメラ画像を表示する」を先に参照してください。ここではKinectを使ってユーザの骨格情報を取得します。取得した骨格情報を基にして、画面上に映し出されたユーザの特定部位にテクスチャを表示することを目標にします。ここでは簡単のためサンプルプログラムを2つ用意しています。1つ目から順に説明している点に注意してください。画像はサンプルプログラム2の実行結果です。 |
- &ref(): File not found: "WindowsGame1_GetUsersPosition.zip" at page "Kinect/KinectForWindowsSDK/Basics/KinectWithXNA/GetUsersPosition/SDKVer1.5";
- VisualStudio2010プロジェクト
- KinectForWindowsSDK 1.5で動作します
補足資料
- 後述しますが、取得できる骨格の座標とその座標系については次のページを参照してください。
- 骨格座標と深度座標のデータ形式
Kinectへのアクセスの確保と骨格情報取得機能の有効化
Kinectのプロジェクトを作成し、インストールしたKinectSDKへの参照を追加しておきます。「Kinectプログラミングのためのプロジェクト作成」に掲載された内容です。読了していない場合は必ず参照してください。
Kinectへのアクセスの確保やカメラ画像の表示に必要なテクスチャの用意などは前回と同様なのでここでは説明を割愛します。今回は取得した骨格情報を保存しておく必要があるので、フィールドに次のような変数を用意しておきます。
「SkeletonPointクラス」はKinectから受信する骨格の座標情報を示すクラスです。ここでは「ユーザの座標」と「ユーザの頭部の座標」を保存してみようと思います。保存した座標情報の表示については、簡単のため先ず文字列として出力しようと思います。画面上へ文字列を表示するための「SpriteFont」もついでに用意しておきます。
//ユーザの位置を示す骨格座標上の点 SkeletonPoint skeletonPoint_UserPosition; //ユーザの頭の位置を示す骨格座標上の点 SkeletonPoint skeletonPoint_HeadPosition; //頭部の座標を表示するためのフォント SpriteFont spriteFont;
Gameクラスのコンストラクタ内でKinectへのアクセスを確保して、そのKinectからのカメラ画像のストリーム(取得)を有効化する所までは、前回と同様の手順です。ここではさらに骨格情報のストリーム(取得)を有効化する設定を追加します。次のソースコードの様に「kinect.SkeletonStream.Enableメソッド」を利用して、Kinectから骨格情報を受け取る設定を有効にします。
Kinectから受け取ったデータを処理する機構は、カメラ画像の場合であっても骨格情報の場合であっても同様で、イベントハンドラによって実装されます。カメラ画像を受信したときは「ColorFreamReadyイベント」が通知されましたが、骨格情報の場合は「SkeletonFrameReadyイベント」が通知されるので、そこにイベントハンドラを追加します。例によってイベントハンドラの中身については後述します。ここまで設定を終えた後、「Kinect.Startメソッド」によってKinectの動作を開始します。
//骨格情報を取得するための設定 kinect.SkeletonStream.Enable(); kinect.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(kinect_SkeletonFrameReady); //Kinectの動作を開始する kinect.Start();
Kinectから受け取った骨格情報の処理
カメラ画像の処理と同様に、Kinectから受信した骨格情報はまずイベントハンドラ内で扱われます。ここでは受け取ったデータの扱いと、骨格情報からその座標を保存する方法とを2つに分けて解説します。
Kinectから受け取ったデータとその扱い方
イベントハンドラ(のメソッド)は次のソースコードの様に実装されます。カメラ画像の時は「ColorImageFrameReadyEventArgsクラス」でしたが、骨格情報の場合は「SkeletonFrameReadyEventArgsクラス」の変数 e から、Kinectから送られたデータを取得することが出来ます。
基本的な扱いはカメラ画像の時と同様です。まず「e.OpenSkeletonFrameメソッド」によってKinectから送られてきた骨格情報「SkeletonFrame」を受け取ります。Kinectが何かしらの影響で骨格情報に関するデータを送信してこない場合があるので null の値を取った時は処理を中断します。骨格情報は「Skeletonクラス」によって管理されます。Kinectは、最大7人の位置座標と2人までの骨格座標を取得できますが、「Skeletonクラス」のインスタンス1つ当たりに、1人のデータが与えられることに注意してください。
次にKinectから受け取った骨格情報「SkeletonFrame」から実際のデータを保存します。「Skeletonクラス」の配列を用意しておき、「SkeletonFrame.CopySkeletonDataToメソッド」によって保存することができます。データの保存に十分な配列の長さは「SkeletonFrame.SkeletonArrayLengthプロパティ」によって取得できます。
void kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { //SkeletonFrameを取得する SkeletonFrame skeletonFrame = e.OpenSkeletonFrame(); //骨格情報が正常に取得できていない時は処理を中断する if (skeletonFrame == null) return; //骨格情報の配列を保存するための変数を用意する Skeleton[] skeletonArray = new Skeleton[skeletonFrame.SkeletonArrayLength]; //骨格情報のデータを全て保存する skeletonFrame.CopySkeletonDataTo(skeletonArray);
Skeletonを保存する場合も、カメラ画像を保存する場合も、殆ど同じような処理手順を踏んでいることに注目してください。
受け取ったデータから座標情報を保存する
Kinectから受け取った「Skeletonクラス」(のインスタンス)の配列は、それぞれ1人当たりの骨格情報を持っていますから、そこから必要な情報を取り出して保存します。次のソースコードでは、foreach を用いた繰り返し構文によって処理を実装していますが、ここではKinectに映し出されているユーザは1人であると想定しています。
「Skeleton.SkeletonTrackingStateプロパティ」は、対象の人物(Skeleton)の骨格取得がどのような状況にあるかを示すプロパティです。骨格(関節)座標まで取得できている場合は「Tracked」、位置座標のみ取得できている場合は「PositionOnly」、取得できていない場合は「NotTracked」が返ります。ここでは骨格(関節)まで取得できた場合を想定して実装を進めます。
//保存した骨格情報に対して繰り返し処理を行う //※ここでは1人のみ映っていることを想定しています foreach (Skeleton skeleton in skeletonArray) { //あるユーザが骨格(関節)までトラッキングできていたら if (skeleton.TrackingState == SkeletonTrackingState.Tracked) { //ユーザの座標を保存しておく this.skeletonPoint_UserPosition = skeleton.Position; //ユーザの頭の座標を保存しておく this.skeletonPoint_HeadPosition = skeleton.Joints[JointType.Head].Position; } } //Kinectから受け取ったデータは使わなくなったら破棄する skeletonFrame.Dispose();
「Skeleton.Positionプロパティ」によって対象の人物(Skeleton)の位置座標を取得することができます。これは「SkeletonTrackingState」が「PositionOnly」であっても取得できる値で、その人物の空間上の凡その位置(腰のあたり)を示します。「SkeletonTrackingState.Tracked」であるユーザの特定の部位は、「Skeleton.Joints[JointType]」によって取得することができます。ここでは「JointType.Head」を指定して頭部の座標を取得していますが、例えば「JointType.HandLeft」を指定すると左手の座標を取得します。
最後にカメラ画像を受け取った時と同様に、不要になった受信データを破棄して処理を終えます。
補足資料
取得できる部位の種類、座標値とその座標系については次のページを参照してください。
「骨格座標と深度座標のデータ形式」
骨格座標の画面出力
座標をテキストとして表示 - 1
サンプルプロジェクト1では保存した骨格の座標情報(SkeletonPoint)をテキストとして画面上に表示します。「Drawメソッド」内でカメラ画像を描画した後に、次のソースコードの様にして座標情報を描画します。「SkeletonPont」の座標(X,Y,Z)は、それぞれ「SkeletonPoint.X」とすることで取得することができます。これらを一度 XNA の「Vector3」にして「ToStringメソッド」によって座標情報をテキスト(string)として出力しています。頭部の座標(SkeletonPoint)についても同様なので、ここではソースコードと解説を割愛します。
//左上の原点から生成したテクスチャを描画する spriteBatch.Draw(this.tex2d_KinectCamera, Vector2.Zero, Color.White); //ユーザの位置の座標をVector3にする Vector3 v3_UserPosition = new Vector3( skeletonPoint_UserPosition.X, skeletonPoint_UserPosition.Y, skeletonPoint_UserPosition.Z); //ユーザの位置の座標を画面に表示する spriteBatch.DrawString( spriteFont, v3_UserPosition.ToString(), Vector2.Zero, Color.GreenYellow);
画面上の対応する位置にテクスチャを表示
サンプルプロジェクト1では取得した座標(SkeletonPoint)を画面上にテキストとして表示していますが、非常に分かり難くゲームなどにも活用できないので、サンプルプロジェクト2では、取得した座標を画面上の座標へ変換し、そこにテクスチャを表示します。サンプルでは座標位置へ表示するテクスチャを「tex2d_PositionMark」として用意しています。
Drawメソッド内で描画を行うのはサンプルプロジェクト1と同様です。まずは変換作業から行います。「KinectSensor.MapSkeletonPointToColorメソッド」は、取得した SkeletonPoint から、カメラ画像上の座標を示す「ColorImagePointクラス」の値を取得するメソッドです。引数には座標系の基となるカメラ画像のフォーマットを指定します(※640x座標かもしれないし、1280x座標かもしれない)。取得した ColorImagePoint の座標値は「ColorImagePoint.X(またはY)」で取得することができます。
ある骨格座標系の点(SkeletonPoint)をカメラ画像上の座標(ColorImagePoint)へ変換出来たので、そこにテクスチャを表示します。テクスチャの中心を丁度 ColorImagePoint と同じ位置に表示したい場合、テクスチャの幅と高さの半分を引いた座標に表示することに注意してください。XNA ではテクスチャの座標は左上を原点としているためです。
//左上の原点から生成したテクスチャを描画する spriteBatch.Draw(this.tex2d_KinectCamera, Vector2.Zero, Color.White); //ユーザの座標をカメラ画像上の座標に変換する ColorImagePoint userPosition = kinect.MapSkeletonPointToColor (skeletonPoint_UserPosition, kinect.ColorStream.Format); //ユーザの位置にテクスチャを描画する spriteBatch.Draw( tex2d_PositionMark, new Vector2(userPosition.X - tex2d_PositionMark.Width / 2, userPosition.Y - tex2d_PositionMark.Height / 2), Color.White );
後は先のカメラ画像のサンプルと同様に、プログラムの終了時にKinectを停止して完了です。