XNAによる深度画像取得
XNAでの深度画像取得方法です。
(作成日:2012/03/01)
1.プロジェクトを作る
前回と同じように作成してください。
2.参照設定を追加
[.NET]のタブを選択し、[Microsoft.Kinect]を追加します。
さらに[PresentationCore]も追加してください。
追加されるとこのように表示されます。
3.プログラミング
以下のコードのように入力してください。
Game1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
// kinectのセンサクラス
// Microsoft.kinectを参照設定に追加
using Microsoft.Kinect;
//////////////////////////////////////////////////
// 参照設定にPresentationCoreを設定しておくこと //
// (深度画像処理に使うため) //
//////////////////////////////////////////////////
namespace getdepthimage
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
/// <summary>
/// 深度画像テクスチャ
/// 640x480
/// </summary>
private Texture2D depthTexture;
/// <summary>
/// kinectから取得した深度データ
/// (short型配列)
/// </summary>
private short[] depthData;
/// <summary>
/// 深度データから色情報に変換されたデータ
/// (byte型配列)
/// </summary>
private byte[] depthImageData;
/// <summary>
/// ユーザーごとにつける色
/// </summary>
private static readonly int[] IntensityShiftByPlayerR = { 1, 2, 0, 2, 0, 0, 2, 0 };
private static readonly int[] IntensityShiftByPlayerG = { 1, 2, 2, 0, 2, 0, 0, 1 };
private static readonly int[] IntensityShiftByPlayerB = { 1, 0, 2, 2, 0, 2, 0, 2 };
/// <summary>
/// ビットシフトに用いる
/// </summary>
private static readonly int Bgr32BytesPerPixel = (System.Windows.Media.PixelFormats.Bgr32.BitsPerPixel + 7) / 8;
/// <summary>
/// 深度データを配列に挿入する順番
/// </summary>
private const int RedIndex = 0;
private const int GreenIndex = 1;
private const int BlueIndex = 2;
private const int AlphaIndex = 3;
/// <summary>
/// KINECTのセンサクラス
/// </summary>
private KinectSensor kinect;
/// <summary>
/// キーボードの状態
/// </summary>
private KeyboardState key;
/// <summary>
/// 1frame前のキーボードの状態
/// </summary>
private KeyboardState oldkey;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
// ウィンドウサイズの指定
graphics.PreferredBackBufferHeight = 480;
graphics.PreferredBackBufferWidth = 640;
}
protected override void Initialize()
{
// kinectの初期化
kinect = KinectSensor.KinectSensors[0];
// 深度画像の取得を開始する
// 私の手法では640x480で処理を行うとすごく重くなった
kinect.DepthFrameReady += new EventHandler<DepthImageFrameReadyEventArgs>(DepthFrameReady);
kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
// スケルトントラッキングを開始する
kinect.SkeletonStream.Enable();
// Kinectを起動する
kinect.Start();
base.Initialize();
}
protected override void LoadContent()
{
// 新規の SpriteBatch を作成します。これはテクスチャーの描画に使用できます。
spriteBatch = new SpriteBatch(GraphicsDevice);
}
protected override void UnloadContent() { }
protected override void Update(GameTime gameTime)
{
// キーボードの状態を取得
key = Keyboard.GetState();
// Escapeキーが押されたらプログラムを終了する
if (key.IsKeyDown(Keys.Escape))
Exit();
// F1が押されたら[ウィンドウモード⇔フルスクリーンモード]の切り替え
if (key.IsKeyDown(Keys.F1) && oldkey.IsKeyUp(Keys.F1))
graphics.ToggleFullScreen();
// このフレームのキーボードの状態を記憶しておく
oldkey = key;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
// スプライトバッチの使用開始
spriteBatch.Begin();
// 深度画像の描写
if (depthTexture != null)
spriteBatch.Draw(depthTexture, new Vector2(0, 0), null, Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
// スプライトバッチの使用終了
spriteBatch.End();
base.Draw(gameTime);
}
/// <summary>
/// 深度画像の取得
/// </summary>
void DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
{
// kinectから深度情報を取得
DepthImageFrame depthImage = e.OpenDepthImageFrame();
if (depthImage != null)
{
// depthDataの初期化
depthData = new short[depthImage.PixelDataLength];
depthImageData = new byte[depthImage.Width * depthImage.Height * Bgr32BytesPerPixel];
// depthImageのピクセルデータをdepthDataへコピーする
depthImage.CopyPixelDataTo(depthData);
// 深度ごとに色づけを行う
ConvertDepthFrame(depthData, ((KinectSensor)sender).DepthStream);
// depthTextureの初期化
depthTexture = new Texture2D(GraphicsDevice, depthImage.Width, depthImage.Height);
// depthTextureにimageDataを反映する
depthTexture.SetData(depthImageData);
}
}
/// <summary>
/// 深度ごとに色づけ&認識ユーザーの色づけ
/// ほぼサンプルコピペ(余分な部分は削除してある)
/// </summary>
private void ConvertDepthFrame(short[] depthFrame, DepthImageStream depthStream)
{
int tooNearDepth = depthStream.TooNearDepth;
int tooFarDepth = depthStream.TooFarDepth;
int unknownDepth = depthStream.UnknownDepth;
for (int i16 = 0, i32 = 0; i16 < depthFrame.Length && i32 < this.depthImageData.Length; i16++, i32 += 4)
{
int player = depthFrame[i16] & DepthImageFrame.PlayerIndexBitmask;
int realDepth = depthFrame[i16] >> DepthImageFrame.PlayerIndexBitmaskWidth;
// 13ビットの深度情報を表示に適した8ビットへ変換する(最上位ビットを無視する)
byte intensity = (byte)(~(realDepth >> 4));
if (player == 0 && realDepth == 0)
{
// 白色
// 40~80cmの間?(近すぎ?)
depthImageData[i32 + RedIndex] = 255;
depthImageData[i32 + GreenIndex] = 255;
depthImageData[i32 + BlueIndex] = 255;
depthImageData[i32 + AlphaIndex] = 255;
}
else if (player == 0 && realDepth == tooFarDepth)
{
// 紫色
// 5m以上を意味する?(遠すぎる?)
depthImageData[i32 + RedIndex] = 66;
depthImageData[i32 + GreenIndex] = 0;
depthImageData[i32 + BlueIndex] = 66;
depthImageData[i32 + AlphaIndex] = 255;
}
else if (player == 0 && realDepth == unknownDepth)
{
// 茶色
// 深度画像が取得できていない?
depthImageData[i32 + RedIndex] = 66;
depthImageData[i32 + GreenIndex] = 66;
depthImageData[i32 + BlueIndex] = 33;
depthImageData[i32 + AlphaIndex] = 255;
}
else
{
// ユーザーごとに色づけ
depthImageData[i32 + RedIndex] = (byte)(intensity >> IntensityShiftByPlayerR[player]);
depthImageData[i32 + GreenIndex] = (byte)(intensity >> IntensityShiftByPlayerG[player]);
depthImageData[i32 + BlueIndex] = (byte)(intensity >> IntensityShiftByPlayerB[player]);
depthImageData[i32 + AlphaIndex] = 255;
}
}
}
}
}
実行結果
このような感じで表示されるはずです。
ソースコード内の「 kinect.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30); 」を変更することで、取得するサイズ・FPSなど変更できます。
取得できる種類
Resolution320x240Fps30
Resolution640x480Fps30(デフォルト)
Resolution80x60Fps30