KOSAKA LABORATORY->Tips

Kinect SDK 応用編 KinectTANK(キネクたん) その1

kinect.jpg
 Kinecには、上下方法の首振りはありますが、横方向の首振りはありません。ユーザを認識後にユーザが左右の画面外に出ていったら認識が外れてしまいます。
 もしKinectが「ユーザを追尾して横方向の回転移動もできたならば・・・」、「ユーザが遠ざかっていくと追いかけてきたなら・・・」 ユーザを常に自動追尾してくるかわいいKinect。素敵ではないですか?

 そこで登場するのが、タミヤの「楽しい工作シリーズ」です。
以前、「Wiiでラジコン」「Wiiラジコン その2」で紹介した、アレです。
これを用いれば、Kinectの横方向の回転も簡単にできますね。テレビの回転台をモータで回そうと思ったのですが、とりあえず第一弾としての「KinectTANK(キネクたんの紹介です。


必要なもの
 以下は、最低限必要なものです。

  • Arduinoをはじめようキット ×1個
  • 楽しい工作シリーズ No.100 トラック&ホイールセット (70100) X 1個
  • 楽しい工作シリーズ No.157 ユニバーサルプレート 2枚セット (70157) ×2個
  • 楽しい工作シリーズ No.168 ダブルギヤボックス 左右独立4速タイプ (70168) ×1個
  • モータドライバー TA7291P X2個
 
  Amazonアソシエイトで小銭を稼ごうとは思っていませんが、Amazonさんで大抵のものは揃います。

 
 

これらのほか、USBケーブルや、ネジやネット、スペーサーや、電池ボックス(単三X4個)、工具が別途必要です。動きまわるのでUSBケーブルはKinectのケーブルと同じく長めのほうが良いでしょう。もちろんKinect本体も必要です。

組立
今回は、電池ボックスを中段の
DSC06865.JPG
 中段のユニバーサルプレートのとりつけ
DSC06859.JPG
横からみた図(古い写真なので、乾電池が2つタイプ)
DSC06860.JPG
Arduinoとブレットボードの取り付け
DSC06861.JPG
配線
DSC06863.JPG
ひたすら配線
DSC06862.JPG
上段のユニバーサルプレートの取り付けました。
DSC06864.JPG
Kinectの搭載 重心位置に注意しないと、少し動かしただけで倒れます。結束バンドで取り付けました。
DSC06866.JPG DSC06867.JPG DSC06868.JPG
回路図
zu.jpg
ソースコード(Arduino)
void setup(){
  //L Moter PinSET
  pinMode(6,OUTPUT);  //Pin6 OUTPUT
  pinMode(7,OUTPUT);  //Pin6 OUTPUT

  //R Moter PinSET
  pinMode(8,OUTPUT);  //Pin6 OUTPUT
  pinMode(9,OUTPUT);  //Pin6 OUTPUT
  
  
  Serial.begin(9600);
}

void loop(){
  SerialRead();  
}

void SerialRead() {
  int   i=0;
  char  read_c;           //1文字読み込み
  char  read_datas[15];   //受信文字列

  //データ受信
  if (Serial.available()) {  //受信データがある時
    while (1) {
      if (Serial.available()) {
        read_c         = Serial.read();  //1文字読み込み
        read_datas[i]  = read_c;
        // 文字列の終わりは'z'で判断
        if (read_c  == 'z'){ break; }
        i++;
      }
    }
    read_datas[i] = '\0';    //受信データの最後にEOFを付加
    ///////////////////////////////////////////////////////
    //データ解析
    //////////////////////////////////////////////////////
   
    //FF
    if(read_datas[0]=='F' && read_datas[1]==',' && read_datas[5]=='\0'){
      int val = RetunValue(read_datas);  //文字を数値に変換
       L_Moter_F(val);
       R_Moter_F(val);
    }
  
    //Back
    if(read_datas[0]=='B' && read_datas[1]==',' && read_datas[5]=='\0'){
      int val = RetunValue(read_datas);  //文字を数値に変換
       L_Moter_B(val);
       R_Moter_B(val);
    }

  //L
    if(read_datas[0]=='L' && read_datas[1]==',' && read_datas[5]=='\0'){
      int val = RetunValue(read_datas);  //文字を数値に変換
       L_Moter_B(val);
       R_Moter_F(val);
    }

  //R
    if(read_datas[0]=='R' && read_datas[1]==',' && read_datas[5]=='\0'){
      int val = RetunValue(read_datas);  //文字を数値に変換
       L_Moter_F(val);
       R_Moter_B(val);
    }

  //STOP
    if(read_datas[0]=='S' && read_datas[1]=='T' && 
       read_datas[2]=='O' && read_datas[3]=='P' && read_datas[4]=='\0'){
      Moter_STOP();
    }
  

  }  
}
//*************************************
// RetunValue
//*************************************

int RetunValue(char *data){
     char tmp[5];
     tmp[0] = data[2];
    tmp[1] = data[3];
    tmp[2] = data[4]; 
     tmp[3] = '\0';
     return atoi(tmp);
}



void Moter_STOP(){
  digitalWrite(6,LOW);
  digitalWrite(7,LOW);
  digitalWrite(8,LOW);
  digitalWrite(9,LOW);
}

void L_Moter_F(int Speed){
  analogWrite(3,Speed);  //SpeedSET
  digitalWrite(6,HIGH);
  digitalWrite(7,LOW);
}
void L_Moter_B(int Speed){
  analogWrite(3,Speed);  //SpeedSET
  digitalWrite(6,LOW);
  digitalWrite(7,HIGH);
}
void R_Moter_F(int Speed){
  analogWrite(5,Speed);  //SpeedSET
  digitalWrite(8,LOW);
  digitalWrite(9,HIGH);
}
void R_Moter_B(int Speed){
  analogWrite(5,Speed);  //SpeedSET
  digitalWrite(8,HIGH);
  digitalWrite(9,LOW);
}
ソースコード(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の読み込み
using System.IO.Ports;                    //シリアル

namespace KinectTANK
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

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

        Texture2D texture_image = null; //実画像テクスチャ
        Color[]    imageColor; //色情報の格納

        SpriteFont font = null;      //フォント

        System.IO.Ports.SerialPort Port_Arduino;    //Arduinoデバイス
        String send_MSG="";
        String send_MSG_B="";

        #region コンストラクタ
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            //画面サイズを640 X 480
              this.graphics.PreferredBackBufferWidth = 640; 
              this.graphics.PreferredBackBufferHeight= 480;

        }
        #endregion

        #region Initialize
        protected override void Initialize()
        {
            #region Kinect初期化
            nui = new Runtime();    //Kinectセンサクラスの初期化

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

            try{
                //ビデオストリームを開く
                nui.VideoStream.Open(    ImageStreamType.Video, 
                2, 
                ImageResolution.Resolution640x480,
                ImageType.Color);

                //デプスストリームを開く
                nui.DepthStream.Open(    ImageStreamType.Depth,
                2, 
                ImageResolution.Resolution320x240, 
                ImageType.DepthAndPlayerIndex);
            }catch (InvalidOperationException){
                Console.WriteLine("Failed to open stream. ");
                return;
            }

            
            //フレーム更新毎にnui_ColorFrameReadyを呼び出す
            nui.VideoFrameReady += 
            new EventHandler<ImageFrameReadyEventArgs>(nui_ColorFrameReady);

            #endregion
            #region Arduino初期化
                this.Port_Arduino = new SerialPort();
                this.Port_Arduino.BaudRate =9600;
                this.Port_Arduino.PortName ="COM70";
            #endregion

            base.Initialize();
        }
        #endregion

        #region LoadContent
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
             //フォントの読み込み
           this.font = this.Content.Load<SpriteFont>("SpriteFont1");

            this.imageColor = new Color[640 * 480];
        }
        #endregion

        #region UnloadContent
        protected override void UnloadContent()
        {
        }
        #endregion

        #region Update
        protected override void Update(GameTime gameTime)
        {
            #region キーボード処理
            KeyboardState keyState = Keyboard.GetState();
            keyState = Keyboard.GetState();

            //ESCキーが押されたら終了
            if(keyState.IsKeyDown(Keys.Escape)){
                this.Exit();
            }

            //F5キーが押されたら
            if(keyState.IsKeyDown(Keys.F5)){
                if(this.Port_Arduino.IsOpen!=true){
                            Console.Write("Open\n");
                            this.Port_Arduino.Open();
                }
            }
            //F6キーが押されたら
            if(keyState.IsKeyDown(Keys.F6)){
                if(this.Port_Arduino.IsOpen==true){
                        this.Port_Arduino.WriteLine("STOPz");
                        Console.Write("Close\n");
                        this.Port_Arduino.Close();
                }
            }

            if(this.Port_Arduino.IsOpen==true){
                //上キーが押されたら
                if(keyState.IsKeyDown(Keys.Up)){
                    this.send_MSG ="F,255z";
                }else //下キーが押されたら
                if(keyState.IsKeyDown(Keys.Down)){
                    this.send_MSG ="B,255z";
                }else //左キーが押されたら
                if(keyState.IsKeyDown(Keys.Left)){
                    this.send_MSG ="L,255z";
                }else //右キーが押されたら
                if(keyState.IsKeyDown(Keys.Right)){
                    this.send_MSG ="R,255z";
                }else{    //何も押されなかたら停止
                    this.send_MSG ="STOPz";
                }
                #region シリアル送信処理
                //同じ信号をなんども送るのは無意味なので
                //以前送った信号と異なる時のみ送信
                if(this.send_MSG_B != this.send_MSG){
                    this.Port_Arduino.Write(this.send_MSG);
                    Console.WriteLine(send_MSG);
                }
                #endregion
                this.send_MSG_B = this.send_MSG;
            }

            #endregion

            base.Update(gameTime);
        }
        #endregion

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



            #region spriteBatch
            this.spriteBatch.Begin();

                //実画像Textureの描写
                if(this.texture_image !=null){
                this.spriteBatch.Draw(this.texture_image,new Vector2(0,0), null, 
                Color.White,0.0f,Vector2.Zero,1.00f,SpriteEffects.None,0.0f);
                }

                #region ディスプレイ表示
                    this.spriteBatch.DrawString(this.font, "ESC    :EXIT"  ,
                        new Vector2(0,0),Color.White);
                    this.spriteBatch.DrawString(this.font, "Arduino:"+this.Port_Arduino.PortName
                        ,new Vector2(0,20),Color.White);
                    this.spriteBatch.DrawString(this.font, "   Open:"+this.Port_Arduino.IsOpen
                        ,new Vector2(0,40),Color.White);
                #endregion


            this.spriteBatch.End();
            #endregion

            base.Draw(gameTime);
        }
        #endregion


        #region 実画像処理
        void nui_ColorFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            // 32-bit per pixel, RGBA image
            lock(this){
                PlanarImage Image = e.ImageFrame.Image;
                this.texture_image = new Texture2D(graphics.GraphicsDevice,640,480);//テクスチャの作成

                int no=0;

                //画像取得
                for (int y = 0; y < Image.Height; ++y){ //y軸
                    for (int x = 0; x < Image.Width; ++x, no += 4){ //x軸
                        this.imageColor[ y*  Image.Width +x ] =
                        new Color(  Image.Bits[no+2],Image.Bits[no+1],Image.Bits[no+0] );

                    }
                }

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

        }
        #endregion

    

    }
}
実行結果
F5キーを押すことでCOMポートをOpenします。
F6キーを押すころでCOMポートをCloseします。

 COMポートとの接続している状態で、キーボードの矢印キーを操作することでKinectTANKが動きます。左右のキーを押すことで、戦車特有の超信地旋回も可能です。なにもキーが押されなければ止まります。
 
デジカメの電池が切れたので、携帯カメラでアップしました。後日差し替えます。

 
解説  
 今回は、Kinectらしいことは一切行っていません、画面に画像を出して、キーボードで操作するラジコンを制作しただけです。
 XNAからArduinoに対してシリアル通信を行っています。この通信方法を覚えておけば非常に汎用性が高いです。

 今回は、文字情報で通信を行っています。XNAからArduinoに対して5つのコマンドを制作しました。
コマンド 動作 使用例 意味
F 前進 F,255z 0~255段階中、255のスピードで前進
B 後進 F,255z 0~255段階中、255のスピードで後進
L 左ターン L,255z 0~255段階中、255のスピードで左ターン前進
R 右ターン R,255z 0~255段階中、255のスピードで右ターン前進
STOP 停止 STOP 停止する
 コマンド終端には、終端記号である「z」を負荷しました。(zに深い意味はありません。なんでもOKです。負荷しなくても\0で通常は認識されるのですが、昔、試したときにうまく動作しなかったため、いまだに終端記号を付けています。)

 次回は、検出した人物を追跡するTipsを行います。