XNAでKINECT!
KIENCTを解析していて、XNAでも動いたらいいなとおもってやってみた。
今回はボーンの位置情報のみを取得するプログラムのみです。
(画像処理系とか期待してたらスミマセン)
実行環境は
windows 7
Visual Studio 2008
OpenNI 1.0.0.25
Prime Sense 1.3.017
をつかってやってみます。
OpenNI1.0.0.25ではサンプルに、C#によって作られているUserTracker.netというサンプルがあり、こいつを解読してXNAに実装していきます。
(2012/03/08)
KINECT for Windows SDK を用いて、XNA上でKinectを動かす方法を記述しました。
とりあえずUserTracker.netのサンプルを実行してみると
こんな感じに映ります。(右腕のボーンが消えちゃってますね・・・)
動かす事が出来たらさっそくXNAに実装していきましょう。
プロジェクトの設定
説明が解り難いかも知れないので、プロジェクトの作成からはじめて、画像多めで説明していきます。
1.
まずVisual Studioを立ち上げ新規プロジェクトを作成しましょう。
VisualC#からXNA Game Studioを選び、Windowsゲームを選択。プロジェクト名に名前を付けてOKで作成されます。
作られるプロジェクトは、参照されているフォルダ内に生成されます。(デフォルトなら、マイドキュメント/Visual Studioフォルダ/Projects/にあるはずです)
2.
作られたプロジェクトフォルダ内にOpenNI.net.dllをコピーしておきます。
(OpenNI.net.dllは c:Program Files/OpneNI/bin/ 内にあります)
そうしたらプロジェクトの参照設定を右クリックして、参照の追加。参照タブを選択し、OpenNI.net.dllを追加。
(Contentのなかにある参照設定ではないので注意!)
参照されているなら下図のようになります。
プロジェクトのセットはこれで完了です。
3.
ここからプログラミングです。サンプルのソースを参考に行っていきます。
なお参考にしているソースは、Samples/UserTracker.net/MainWindow.csです。
まず、using xn; と宣言しましょう。これでOpenNIの変数を使えるようになります。
次に変数の宣言・初期化を行います。
using xn;
namespace ○○○○○○
{
public class Game1 : Microsoft.Xna.Framework.Game
{
// 宣言
private Context context;
private DepthGenerator depthGenerator; // 深度
private UserGenerator userGenerator; //
private PoseDetectionCapability poseDetectionCapability; // キャリブレーションの認識
private SkeletonCapability skeletonCapability; //
private string calibPose; // キャリブレーションポーズの情報
private Dictionary<uint, Dictionary<SkeletonJoint, SkeletonJointPosition>> joints; // ジョイントの情報
public Game1()
{
// 初期化
context = new Context("SamplesConfig.xml");
depthGenerator = context.FindExistingNode(NodeType.Depth) as DepthGenerator;
userGenerator = new UserGenerator(context);
userGenerator.NewUser += new UserGenerator.NewUserHandler(userGenerator_NewUser);
userGenerator.LostUser += new UserGenerator.LostUserHandler(userGenerator_LostUser);
poseDetectionCapability = new PoseDetectionCapability(userGenerator);
poseDetectionCapability.PoseDetected += new PoseDetectionCapability.PoseDetectedHandler(poseDetectionCapability_PoseDetected);
skeletonCapability = new SkeletonCapability(userGenerator);
skeletonCapability.CalibrationEnd += new SkeletonCapability.CalibrationEndHandler(skeletonCapbility_CalibrationEnd);
skeletonCapability.SetSkeletonProfile(SkeletonProfile.All);
calibPose = skeletonCapability.GetCalibrationPose();
joints = new Dictionary<uint, Dictionary<SkeletonJoint, SkeletonJointPosition>>();
userGenerator.StartGenerating();
・・・
のように宣言しておきます。最低限この変数のみでKINECTの情報は取得できます。
さて、このままの状態だと userGenerator_NewUser 、 userGenerator_LostUse 等がありませんと警告が出ていると思います。
サンプルソースを下へ少しスクロールすると・・・
// キャリブレーションが完了しトラッキングをするか
void skeletonCapbility_CalibrationEnd(ProductionNode node, uint id, bool success)
{
if (success)
{
skeletonCapability.StartTracking(id);
joints.Add(id, new Dictionary<SkeletonJoint, SkeletonJointPosition>());
}
else
{
this.poseDetectionCapability.StartPoseDetection(calibPose, id);
}
}
// キャリブレーションの認識
void poseDetectionCapability_PoseDetected(ProductionNode node, string pose, uint id)
{
poseDetectionCapability.StopPoseDetection(id);
skeletonCapability.RequestCalibration(id, true);
}
// 新しいユーザの検出
void userGenerator_NewUser(ProductionNode node, uint id)
{
poseDetectionCapability.StartPoseDetection(this.calibPose, id);
}
// ユーザの消滅(ロスト)
void userGenerator_LostUser(ProductionNode node, uint id)
{
joints.Remove(id);
}
それらしいものがつらつらと書かれています。これはユーザーが新しく現れたり、キャリブレーションポーズをしたりなどのイベントを認識する、イベントハンドラです。
これで初期化は完了です。
次に、KINECTから情報をもらってきましょう。
KINECTの情報を得るには
try
{
// 深度の更新
context.WaitOneUpdateAll(depthGenerator);
}
catch (Exception)
{
}
// 認識しているユーザ数
uint[] users = userGenerator.GetUsers();
foreach (uint user in users)
{
// キャリブレーションしているか
if (skeletonCapability.IsCalibrated(user))
{
// 各ユーザのジョイント情報を取得
GetJoints(user);
}
}
をUpDate内に記述してあげます。さて、これで情報は取得できたはずです。さっそく実行してみましょう。
エラーが出たのならば、dllの参照やプログラムをもう一度確認しましょう。
動いたならばとりあえずはOKです。しかし、お気づきの方もいるかもしれませんがものすごく処理が重いです。
原因はKINECTとXNAのFPSの違いです。KINECTは30[fps]、XNAは標準で60[fps]でうごいていて、XNAの更新速度にKINECT側が追い付いていけないのが原因かと思われます。(個人的な見解です)
よって、XNA側を30[fps]にするなどして更新速度を下げてあげれば快適に動くはずです。