[Kinect SDK][C#][XNA]人が居る・居ないを判断して画面表示を変える
Kinectをデジタルサイネージに利用するハックの第一弾です。
Visual C# 2010 Express と XNA Game Studio 4.0、そしてKinect for Windows SDK Beta2を使用して制作します。
今回は第一弾という事で、非常にシンプルではありますが、
「人が居る・居ないを判断して画面表示を変える」プログラムを作ってみたいと思います。
Kinectで人が居るか居ないかを判断するには、骨格トラッキングの他に深度カメラのプレイヤーインデックスを利用する方法がありますが、骨格トラッキングは人がKinectの前に入ってから骨格の認識をするまでに時間がかかる上に同時に2人までしか認識できないので、今回は人の認識が高速で同時に6人まで認識できる深度カメラのプレイヤーインデックスを利用します。
例えば、デジタルサイネージを見ている人が居ない時にはCM動画等をループ再生させておき、人が近くに来たら操作画面に切り替える。といったシチュエーションで役に立つのではないかと思います。
■処理の流れ
Kinectの深度カメラの映像を取得
↓
その映像に含まれるユーザーインデックスの種類を合計してユーザ数を求める
↓
2枚の画像を用意し、誰も人がいない時には1枚目の画像を表示し、一人以上人がいる場合は2枚目の画像を表示させる
■プロジェクト作成
初めにVisual C# 2011 Expressでプロジェクトを作成します。
[ファイル(F)]→[新しいプロジェクト(P)…]を選択して、XNA Game Studio 4.0の「Windows Game (4.0)」テンプレートを使用してプロジェクトを作成してください。
ここでは適当に「Kinect_UserCount」という名前を付けました。
■Kinect SDKへの参照の追加
Kinectの機能を使うために参照を追加します。
ソリューション エクスプローラーの参照設定で右クリックし、[参照の追加(F)…]をクリック。
Kinect SDKのインストールが正しく行われていれば、.NETタブに[Microsoft.Research.Kinect]が存在するので選択してして[OK]
これでVisual C#でKinectの機能を利用できるようになります。
■画像コンテンツの追加
人が居ない時に表示する画像と、人が居る時に表示する画像の2枚をプロジェクトに追加します。
[ZERO.png]と[ONE.png]という2枚の画像を用意したのでそれらをソリューションエクスプローラーの「Kinect_UserCount(Content)」となっている部分にドラッグアンドドロップします。
ソリューションエクスプローラーが表示されていない場合は[表示(V)]→[その他のウィンドウ(E)]→[ソリューション エクスプローラー(P)]から表示できます。
画像を登録すると、プロジェクトのContentフォルダの中にも複製され、以下のようにファイル名で画像を使用できるようになります。
this.Content.Load<Texture2D>("ZERO");
■ソースコード
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 Nuiの読み込み
namespace Kinect_UserCount
{
/// <summary>
/// 基底 Game クラスから派生した、ゲームのメイン クラスです。
/// </summary>
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
Runtime Kinect; //Kinectのセンサクラス
SpriteBatch spriteBatch;
Texture2D texture_depth = null; //奥行テクスチャ
Texture2D texture_ZERO = null; //人が居ない時の画像
Texture2D texture_ONE = null; //人が居る時の画像
private Color[] depthColor; //色情報の格納
private bool[] user_index = new bool[7]; //ユーザインデックス
private int user_count = 0; //ユーザの数
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.texture_ZERO = this.Content.Load<Texture2D>("ZERO");
this.texture_ONE = this.Content.Load<Texture2D>("ONE");
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 UnloadContent
protected override void UnloadContent()
{
}
#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();
//人が居る時はONE.pngを表示する
if (user_count > 0)
{
this.spriteBatch.Draw(texture_ONE, new Vector2(0, 0), null,
Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
}
else //人が居ない時はZERO.jpgを表示する
{
this.spriteBatch.Draw(texture_ZERO, new Vector2(0, 0), null,
Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
}
//奥行画像Textureの描写
if (this.texture_depth != null)
{
this.spriteBatch.Draw(this.texture_depth, new Vector2(0, 360), null,
Color.White, 0.0f, Vector2.Zero, 0.5f, SpriteEffects.None, 0.0f);
}
this.spriteBatch.End();
base.Draw(gameTime);
}
#endregion
#region 奥行き画像
void Kinect_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
{
lock (this)
{
PlanarImage Image = e.ImageFrame.Image;
int no = 0;
//奥行き画像初期化
this.depthColor = new Color[Image.Height * 2 * Image.Width * 2];
this.texture_depth = new Texture2D(graphics.GraphicsDevice, Image.Width * 2, Image.Height * 2);
//-- ユーザ数カウントサンプル --//
for (int i = 0; i < 7; i++) { user_index[i] = false; } //user_index値リセット
user_count = 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);
int player = Image.Bits[(y * 320 + x) * 2] & 0x07;//プレイヤーインデックス
for (int j = 1; j < 7; j++)
{
if (player == j) {
if ( !user_index[j-1] ){
user_index[j-1] = true;
user_count++; //ユーザ数に加算
}
}
}
}
}
this.texture_depth.SetData(this.depthColor); //texture_depthにデータを書き込む
}
}
#endregion
}
}
■実行結果
・Kinectの前に人が居ない時の画面表示
ちゃんと画像が切り替わっていますね。
また人が居なくなるとZERO.pngに切り替わります。
■解説
今回は画面を切り替える為の最低限の機能以外は省いたので、実画像の取得はせずに深度カメラ映像のみ取得しています。
深度カメラの映像には、画像のすべてのピクセルに深度情報(距離)に加えて、プレイヤーインデックスの情報が保存されています。
プレイヤーインデックスは 0~6 の数値で割り振られていて、そのピクセルに人が居なければ「0」、人が居れば「1~6」の値が割り当てられます。
そこでplayer_index[0~5]に画面内にプレイヤーが居ればtrue、居なければfalseを保存していき、人が見つかった数をカウントしてindex_countに保存します。
for (int j = 1; j < 7; j++)
{
if (player == j) {
if ( !user_index[j-1] ){
user_index[j-1] = true;
user_count++; //ユーザ数に加算
}
}
}
そしてdraw()の中で、カウントが0の時にはZERO.pngを表示し、カウントが1以上の時はONE.pngを表示するように切り替えています。
//人が居る時はONE.pngを表示する
if (user_count > 0)
{
this.spriteBatch.Draw(texture_ONE, new Vector2(0, 0), null,
Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
}
else //人が居ない時はZERO.jpgを表示する
{
this.spriteBatch.Draw(texture_ZERO, new Vector2(0, 0), null,
Color.White, 0.0f, Vector2.Zero, 1.0f, SpriteEffects.None, 0.0f);
}