[Kinect SDK][C#][XNA]人を色分けしてシルエット表示する
Kinectをデジタルサイネージに利用するハックの第二弾です。
前回、Kinectで撮影している場所に人が居るか居ないかを取得しましたが、今回はその応用で深度カメラから取得した映像をもとにプレイヤーごとに色分けして表示をしてみます。
さらにせっかくユーザ数も取得しているので画面に表示してみましょう。
■フォントの準備
プロジェクトの作成、参照の追加までは前回のTipsと同じです。
今回はそれにプラスして画面にユーザ数の表示もするので、その表示の為のフォントを読み込みます。
ソリューションエクスプローラーのContentを右クリック→[追加(D)]→[新しい項目(W)…]を選択します。
新しい項目を追加するウィンドウが開くので、Sprite Fontを選択し任意の名前をつけてOK
これでXNAで文字を表示する準備が整いました。
■ソースコード
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;
using Microsoft.Research.Kinect.Nui; //Kinect Uniの読み込み
namespace Kinect_UserColors
{
/// <summary>
/// 基底 Game クラスから派生した、ゲームのメイン クラスです。
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
Runtime Kinect; //Kinectのセンサクラス
SpriteBatch spriteBatch;
Texture2D texture_depth = null; //奥行テクスチャ
Texture2D texture_User = null; //ユーザシルエットテクスチャ
private Color[] depthColor; //色情報の格納
private Color[] UserColor; //色情報の格納
private bool[] user_index = new bool[7]; //ユーザインデックス
private int user_count = 0; //ユーザの数
private SpriteFont font = null; //フォント
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//画面サイズを640 X 480
this.graphics.PreferredBackBufferWidth = 640;
this.graphics.PreferredBackBufferHeight = 480;
}
#region Initialize
protected override void Initialize()
{
base.Initialize();
Kinect = Runtime.Kinects[0]; //Kinectセンサクラスの初期化
this.font = this.Content.Load<SpriteFont>("Font1"); //フォントの読み込み
try
{
//奥行の取得、トラッキング、実画像
Kinect.Initialize(RuntimeOptions.UseDepthAndPlayerIndex |
RuntimeOptions.UseSkeletalTracking |
RuntimeOptions.UseColor);
}
catch (InvalidOperationException)
{
Console.WriteLine("Runtime initialization failed.");
return;
}
try
{
//デプスストリームを開く
Kinect.DepthStream.Open(ImageStreamType.Depth,
2,
ImageResolution.Resolution320x240,
ImageType.DepthAndPlayerIndex);
}
catch (InvalidOperationException)
{
Console.WriteLine("Failed to open stream. ");
return;
}
//ビデオ更新毎にKinect_DepthFrameReadyを呼び出す
Kinect.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(Kinect_DepthFrameReady);
}
#endregion
#region LoadContent
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
}
#endregion
#region Update
protected override void Update(GameTime gameTime)
{
KeyboardState keyState = Keyboard.GetState();
if (keyState.IsKeyDown(Keys.Escape)) this.Exit();
base.Update(gameTime);
}
#endregion
#region Draw
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
this.spriteBatch.Begin();
//奥行画像Textureの描写
if (this.texture_depth != null)
{
this.spriteBatch.Draw(this.texture_depth, new Vector2(0, 0), null,
Color.White, 0.0f, Vector2.Zero, 2.0f, SpriteEffects.None, 0.0f);
}
//ユーザシルエットTextureの描写
if (this.texture_User != null)
{
this.spriteBatch.Draw(this.texture_User, new Vector2(0, 0), null,
Color.White, 0.0f, Vector2.Zero, 2.0f, SpriteEffects.None, 0.0f);
}
//画面にユーザ数を表示
this.spriteBatch.DrawString(font, "UserCount:" + user_count, new Vector2(0, 0), Color.White);
this.spriteBatch.End();
base.Draw(gameTime);
}
#endregion
#region 奥行き画像
void Kinect_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
{
lock (this)
{
PlanarImage Image = e.ImageFrame.Image;
//奥行き画像初期化
this.depthColor = new Color[Image.Height * 2 * Image.Width * 2];
this.texture_depth = new Texture2D(graphics.GraphicsDevice, Image.Width * 2, Image.Height * 2);
//ユーザシルエット画像初期化
this.UserColor = new Color[Image.Height * 2 * Image.Width * 2];
this.texture_User = new Texture2D(graphics.GraphicsDevice, Image.Width * 2, Image.Height * 2); //テクスチャの作成
//-- 人数をカウントする --//
//user_index値リセット
for (int i = 0; i < 7; i++) { user_index[i] = false; }
//画像取得
int no = 0;
for (int y = 0; y < Image.Height; ++y)
{ //y軸
for (int x = 0; x < Image.Width; ++x, no += 2)
{ //x軸
int n = (y * Image.Width + x) * 2;
int realDepth = (Image.Bits[n + 1] << 5) | (Image.Bits[n] >> 3);
byte intensity = (byte)((255 - (255 * realDepth / 0x0fff)) / 2);
this.depthColor[y * 640 + x] = new Color(intensity, intensity, intensity);
#region マスク画像生成
Color tmp = new Color();
int player = Image.Bits[(y * 320 + x) * 2] & 0x07;
switch (player)
{
case 0:
tmp = Color.Black; //背景:黒
break;
case 1:
tmp = new Color(intensity, intensity, 255); //Player1:青
user_index[0] = true;
break;
case 2:
tmp = new Color(intensity, 255, intensity); //Player2:緑
user_index[1] = true;
break;
case 3:
tmp = new Color(intensity, 255, 255); //Player3:水
user_index[2] = true;
break;
case 4:
tmp = new Color(255, intensity, intensity); //Player4:赤
user_index[3] = true;
break;
case 5:
tmp = new Color(255, intensity, 255); //Player5:紫
user_index[4] = true;
break;
case 6:
tmp = new Color(255, 255, intensity); //Player6:黄
user_index[5] = true;
break;
default:
tmp = Color.Black; //背景:黒
break;
}
//ユーザが見つかったらピクセルに色を付ける
if (tmp != Color.Black)
{
this.UserColor[y * 640 + x] = tmp;
}
#endregion
}
}
this.texture_depth.SetData(this.depthColor); //texture_depthにデータを書き込む
this.texture_User.SetData(this.UserColor); //texture_Yserにデータを書き込む
//見つかったユーザインデックスをカウントする
user_count = 0;
for (int j = 0; j < 6; j++)
{
user_count += Convert.ToInt32(user_index[j]);
}
}
}
#endregion
}
}
■実行結果
起動。深度カメラの映像と共に左上にユーザカウントの値が表示されています。
そしてKinectの前に人が入ると・・・
こんな感じで表示されます。
赤色で表示されているので、Player4として認識されているようです。
tmp = new Color(255, intensity, intensity); //Player4:赤
そうなんです。一人しか居ないからといって必ずPlayer1として認識される訳ではないんですね。
今は私以外に人が居ないので一色しか出ていませんが、人が増えるごとに新しい色で表示され、最大6人まで色を分けて表示できるので試してみて下さい。