KOSAKA LABORATORY->Tips

Kinect SDK XNA 取得した骨格情報に追従してカメラ角度を変更

kinect.jpg
inectはカメラ角度を-27~+27度まで変更できる機能があります。
ユーザはさまざまな動作をします。ユーザの動きに合わせてカメラ角度を自動調整させます。

今回は、分かりやすいように、左手に追従して上下のカメラ角度を変更します。




ソース
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の読み込み
using System.Threading;

namespace WindowsGame6
{
  /// <summary>
  /// 基底 Game クラスから派生した、ゲームのメイン クラスです。
  /// </summary>
  public class Game1 : Microsoft.Xna.Framework.Game
  {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    Runtime nui;  //Kinectのセンサクラス

    int Angle=0;    //首角度
    int Back_Angel =0;  //首角度 一つ前の

    Vector3 HandLeft_P = new Vector3();  //左手の座標
    Thread  Thread_Angle;        //スレッド
    
    Texture2D texture_depth = null;    //奥行テクスチャ
    private  Color[]  depthColor;  //色情報の格納

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

    protected override void Initialize(){

      base.Initialize();

      nui = new Runtime();  //Kinectセンサクラスの初期化

      try{
        //奥行の取得、トラッキング、実画像
        nui.Initialize(  RuntimeOptions.UseDepthAndPlayerIndex |
                RuntimeOptions.UseSkeletalTracking |
                RuntimeOptions.UseColor);
  
      }
      catch (InvalidOperationException){
        Console.WriteLine("Runtime initialization failed.");
        return;
      }


      try{
        //デプスストリームを開く
        nui.DepthStream.Open(  ImageStreamType.Depth,
                    2, 
                    ImageResolution.Resolution320x240, 
                    ImageType.DepthAndPlayerIndex);

      }catch (InvalidOperationException){
        Console.WriteLine("Failed to open stream. ");
        return;
      }

      //ビデオ更新毎にnui_DepthFrameReadyを呼び出す
      nui.DepthFrameReady +=
          new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);

      //フレーム更新毎にnui_SkeletonFrameReadyを呼び出す
        nui.SkeletonFrameReady += 
          new EventHandler<SkeletonFrameReadyEventArgs>(nui_SkeletonFrameReady);
    
      this.Thread_Angle = new Thread(Change);  //スレッドの初期化


    }

    protected override void LoadContent(){
      spriteBatch = new SpriteBatch(GraphicsDevice);
    }

    protected override void UnloadContent(){
    }

    protected override void Update(GameTime gameTime){
      if(GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();

    
      try{
        if(HandLeft_P.Y !=0){
          this.Angle = nui.NuiCamera.ElevationAngle;
          int l=  (int)Math.Abs(this.HandLeft_P.Y -120)/12;

          if(l>=2){
            if(this.HandLeft_P.Y >120){
              this.Angle-=l*2;
            }else{
              this.Angle+=l*2;
            }
          }
        }
        //正規化 -27 ~ 27 に収まるように  
        if(this.Angle >=  27){   this.Angle= 27;  }
        if(this.Angle <= -27){   this.Angle=-27;  }
        if(this.Back_Angel != this.Angle){
          //既にスレッドが動いていないときに実行
          if(this.Thread_Angle.IsAlive !=true){
            this.Thread_Angle = new Thread(Change);
            //スレッドスタート
            this.Thread_Angle.Start();
            this.Back_Angel=this.Angle;
          }
        }
      }catch( InvalidOperationException e){
      }

  


      base.Update(gameTime);
    }

    #region 首振りヘッドスレッド
    public void Change(){
      try{
        nui.NuiCamera.ElevationAngle=this.Angle;  //首を振る
        Thread.Sleep(500);        //スリープ
      }catch{}
    }
    #endregion

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

      this.spriteBatch.Begin();
      
      //奥行画像の描写
      if(this.texture_depth !=null){
        //奥行きTextureの描写
        this.spriteBatch.Draw(this.texture_depth,new Vector2(0,0), null, 
          Color.White,0.0f,Vector2.Zero,1.0f,SpriteEffects.None,0.0f); 
      }
      this.spriteBatch.End();
      

      base.Draw(gameTime);
    }


      #region 奥行き画像
    void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e){

      lock(this){
        PlanarImage Image = e.ImageFrame.Image;
        //  byte[] convertedDepthFrame = convertDepthFrame(Image.Bits);

        int no=0;

        this.depthColor = new Color[Image.Height * Image.Width];
        this.texture_depth = new Texture2D(graphics.GraphicsDevice ,
                         Image.Width, Image.Height);  //テクスチャの作成
        //画像取得
        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*  Image.Width +x ] = 
                      new Color(   intensity,intensity,intensity );
          }
        }

        //左手の位置に赤ドットを打つ
        int px = (int)this.HandLeft_P.X;
        int py = (int)this.HandLeft_P.Y;
        //px,pyがdepthColorの範囲外だとエラーが発生するので注意
        depthColor[ py * Image.Width + px ] = Color.Red;

        this.texture_depth.SetData(this.depthColor);  //texture_imageにデータを書き込む
      }

    }
    #endregion

    #region スケルトン
    void nui_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e){
      SkeletonFrame skeletonFrame = e.SkeletonFrame;

       foreach (SkeletonData data in skeletonFrame.Skeletons){
        if (SkeletonTrackingState.Tracked == data.TrackingState){
          foreach (Joint joint in data.Joints){

            //左手の座標取得
            if(joint.ID == JointID.HandLeft){
              nui.SkeletonEngine.SkeletonToDepthImage(
                joint.Position, out this.HandLeft_P.X, out this.HandLeft_P.Y);
              //座標変換
              //convert to 320, 240 space
              HandLeft_P.X = Math.Max(0, Math.Min(HandLeft_P.X * 320, 320));
              HandLeft_P.Y = Math.Max(0, Math.Min(HandLeft_P.Y * 240, 240));

            }

          }
        }
      }
    }
    #endregion

  }
}
実行結果
 見にくいですが、骨格情報を取得すると、左手に赤い点が出てきます。左手を画面上部に上げると、カメラ角度が上に追従します。反対に画面下に下げるとカメラ角度が下に追従します。
  ElevationAngleの処理は、重いので、スレッド化してあります。高速化できる方法がありましたらどなたかお教えください。
  今回は、左手でしたが、これを体の中心座標や、頭の座標に設定すれば、激しい動きにも追従することができます。