2011年6月17日金曜日

Kinectからの画像をXNAで表示する

公開されたばかりのKinectSDKのドキュメントを読みながらDirectXもWPFも使いたくない!
と思ったのでXNAでKinectから取り込んだ画像を表示できるようにしてみました。
ピクセルフォーマットとか直すところでつまづいたけど、VideoStreamとDepthStreamから得られる画像はきちんと表示できるようになりました。

土日のうちにボーンの描画までして何か作りたいですね。



using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
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;
using Microsoft.Research.Kinect.Nui;

namespace XNAFirst
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

//Kinectの変数
Runtime nui;
int totalFrames = 0;
int lastFrames = 0;
DateTime lastTime = DateTime.MaxValue;

//テクスチャ
private Texture2D texDepth = null;
private Texture2D texBorn = null;
private Texture2D texImage = null;

const int ALPHA_IDX = 3;
const int RED_IDX = 0;
const int GREEN_IDX = 1;
const int BLUE_IDX = 2;

byte[] depthFrame32 = new byte[320 * 240 * 4];
byte[] colorFrame32 = new byte[640 * 480 * 4];

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
graphics.PreferredBackBufferWidth = 640;
graphics.PreferredBackBufferHeight = 480;
}

protected override void Initialize()
{
//kinectの初期化
nui = new Runtime();
try {
nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking | RuntimeOptions.UseColor);
} catch (InvalidOperationException) {
System.Windows.MessageBox.Show("Runtime initialization failed. Please make sure Kinect device is plugged in.");
return;
}
//video stream と depth stream のOpenをする
try {
nui.VideoStream.Open(ImageStreamType.Video , 2 , ImageResolution.Resolution640x480 , ImageType.Color);
nui.DepthStream.Open(ImageStreamType.Depth , 2 , ImageResolution.Resolution320x240 , ImageType.DepthAndPlayerIndex);
} catch (InvalidOperationException) {
System.Windows.MessageBox.Show("Failed to open stream. Please make sure to specify a supported image type and resolution.");
return;
}
//FPS制御のための時間取得
lastTime = DateTime.Now;

//イベント駆動以外でframeを取得したいのでここはちょっと保留
nui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);
nui.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);
nui.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_VideoFrameReady);
base.Initialize();
}

void nui_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
//32bit per pixel RGBA image
PlanarImage Image = e.ImageFrame.Image;
texImage = new Texture2D(this.GraphicsDevice, Image.Width, Image.Height , false , SurfaceFormat.Color);
byte[] converted = convertColorFrame(Image.Bits);
texImage.SetData(converted);
}

void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
//とりあえず保留
}

void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
{
PlanarImage Image = e.ImageFrame.Image;
texDepth = new Texture2D(this.GraphicsDevice, Image.Width, Image.Height , false , SurfaceFormat.Color);
//byteデータで取得
//depthバッファを見やすくする
byte[] convertedDepthFrame = convertDepthFrame(Image.Bits);

//byteデータから画像orスプライトを作成する
texDepth.SetData(convertedDepthFrame);

totalFrames++;

//fps測定
DateTime cur = DateTime.Now;
if (cur.Subtract(lastTime) > TimeSpan.FromSeconds(1)) {
int frameDiff = totalFrames - lastFrames;
lastFrames = totalFrames;
lastTime = cur;
}
}

protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);

// TODO: use this.Content to load your game content here
}

protected override void UnloadContent()
{
nui.Uninitialize();
}

protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

// TODO: Add your update logic here

base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.White);

this.spriteBatch.Begin();

this.spriteBatch.Draw(texImage, new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), Color.White);
//this.spriteBatch.Draw(texDepth, new Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), Color.White);

this.spriteBatch.End();

base.Draw(gameTime);
}

//kinectから取り込んだColorFrameをXNAのColorフォーマットに直す
byte[] convertColorFrame(byte[] colorFrame)
{
for (int i = 0 ; i < colorFrame32.Length ; i += 4) {
colorFrame32[i + ALPHA_IDX] = 255;
colorFrame32[i + RED_IDX] = colorFrame[i + 2];
colorFrame32[i + GREEN_IDX] = colorFrame[i + 1];
colorFrame32[i + BLUE_IDX] = colorFrame[i];
}
return colorFrame32;
}

// Converts a 16-bit grayscale depth frame which includes player indexes into a 32-bit frame
// that displays different players in different colors
//XNAのテクスチャーではColorフォーマットを使うことを想定している
byte[] convertDepthFrame(byte[] depthFrame16)
{
for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length && i32 < depthFrame32.Length; i16 += 2, i32 += 4)
{
int player = depthFrame16[i16] & 0x07;
int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);
// transform 13-bit depth information into an 8-bit intensity appropriate
// for display (we disregard information in most significant bit)
byte intensity = (byte)(255 - (255 * realDepth / 0x0fff));

depthFrame32[i32 + ALPHA_IDX] = 255;
depthFrame32[i32 + RED_IDX] = 0;
depthFrame32[i32 + GREEN_IDX] = 0;
depthFrame32[i32 + BLUE_IDX] = 0;

// choose different display colors based on player
switch (player)
{
case 0:
depthFrame32[i32 + RED_IDX] = (byte)(intensity / 2);
depthFrame32[i32 + GREEN_IDX] = (byte)(intensity / 2);
depthFrame32[i32 + BLUE_IDX] = (byte)(intensity / 2);
break;
case 1:
depthFrame32[i32 + RED_IDX] = intensity;
break;
case 2:
depthFrame32[i32 + GREEN_IDX] = intensity;
break;
case 3:
depthFrame32[i32 + RED_IDX] = (byte)(intensity / 4);
depthFrame32[i32 + GREEN_IDX] = (byte)(intensity);
depthFrame32[i32 + BLUE_IDX] = (byte)(intensity);
break;
case 4:
depthFrame32[i32 + RED_IDX] = (byte)(intensity);
depthFrame32[i32 + GREEN_IDX] = (byte)(intensity);
depthFrame32[i32 + BLUE_IDX] = (byte)(intensity / 4);
break;
case 5:
depthFrame32[i32 + RED_IDX] = (byte)(intensity);
depthFrame32[i32 + GREEN_IDX] = (byte)(intensity / 4);
depthFrame32[i32 + BLUE_IDX] = (byte)(intensity);
break;
case 6:
depthFrame32[i32 + RED_IDX] = (byte)(intensity / 2);
depthFrame32[i32 + GREEN_IDX] = (byte)(intensity / 2);
depthFrame32[i32 + BLUE_IDX] = (byte)(intensity);
break;
case 7:
depthFrame32[i32 + RED_IDX] = (byte)(255 - intensity);
depthFrame32[i32 + GREEN_IDX] = (byte)(255 - intensity);
depthFrame32[i32 + BLUE_IDX] = (byte)(255 - intensity);
break;
}
}
return depthFrame32;
}
}
}

0 件のコメント:

コメントを投稿