domingo, 20 de abril de 2014

Kinect: Obtener posición de las partes del cuerpo.

NOTA: los siguientes ejemplos se realizaron con: Microsoft visual studio 2010, KinectDeveloperToolkit-v1.7.0 y KinectSDK-v1.7.

NOTA: este código es parte de un proyecto, pero solo se mostraran los métodos básicos para que quien lo lea pueda implementarlo sin problemas.
 
Una de las tareas que se produce cuando se quiere realizar un proyecto con la kinect es el de reconocer el espacio y la distancia en la que está el cuerpo, para lo cual les presentare este código que me ayuda a saber dónde está el cuerpo.

Cabe mencionar que las distancias se calculan con respecto a la kinect, esto es por la obviedad que las distancias son captadas por los sensores de la kinect.

A continuación mostrare el código C# que genera la distancia en tiempo real y posteriormente explicare lo esencial para entender este código.

-------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Microsoft.Kinect;

using System.Runtime.InteropServices;
using System.Drawing.Imaging;

namespace FullSkeletonSKD1
{
    public partial class Form1 : Form
    {
        KinectSensor sensor;
        public Form1()
        {
            InitializeComponent();
            sensor = KinectSensor.KinectSensors[0];
        }


        private void button1_Click(object sender, EventArgs e)
        {
            sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
            sensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30);
            sensor.SkeletonStream.Enable();

            sensor.AllFramesReady += FramesReady;
            sensor.Start();
        }

        void FramesReady(object sender, AllFramesReadyEventArgs e)
        {
            ColorImageFrame VFrame = e.OpenColorImageFrame();
            if (VFrame == null) return;
            byte[] pixelS = new byte[VFrame.PixelDataLength];
            Bitmap bmap = ImageToBitmap(VFrame);


            SkeletonFrame SFrame = e.OpenSkeletonFrame();
            if (SFrame == null) return;

            Graphics g = Graphics.FromImage(bmap);
            Skeleton[] Skeletons = new Skeleton[SFrame.SkeletonArrayLength];
            SFrame.CopySkeletonDataTo(Skeletons);

            foreach (Skeleton S in Skeletons)
            {
                if (S.TrackingState == SkeletonTrackingState.Tracked)
                {
                    
                    //body
                    DrawBone(JointType.Head, JointType.ShoulderCenter, S, g);
                    DrawBone(JointType.ShoulderCenter, JointType.Spine, S, g);
                    DrawBone(JointType.Spine, JointType.HipCenter, S, g);
                    //left leg
                    DrawBone(JointType.HipCenter, JointType.HipLeft, S, g);
                    DrawBone(JointType.HipLeft, JointType.KneeLeft, S, g);
                    DrawBone(JointType.KneeLeft, JointType.AnkleLeft, S, g);
                    DrawBone(JointType.AnkleLeft, JointType.FootLeft, S, g);
                    //Right Leg
                    DrawBone(JointType.HipCenter, JointType.HipRight, S, g);
                    DrawBone(JointType.HipRight, JointType.KneeRight, S, g);
                    DrawBone(JointType.KneeRight, JointType.AnkleRight, S, g);
                    DrawBone(JointType.AnkleRight, JointType.FootRight, S, g);
                    //Left Arm
                    DrawBone(JointType.ShoulderCenter, JointType.ShoulderLeft, S, g);
                    DrawBone(JointType.ShoulderLeft, JointType.ElbowLeft, S, g);
                    DrawBone(JointType.ElbowLeft, JointType.WristLeft, S, g);
                    DrawBone(JointType.WristLeft, JointType.HandLeft, S, g);
                    //Right Arm
                    DrawBone(JointType.ShoulderCenter, JointType.ShoulderRight, S, g);
                    DrawBone(JointType.ShoulderRight, JointType.ElbowRight, S, g);
                    DrawBone(JointType.ElbowRight, JointType.WristRight, S, g);
                    DrawBone(JointType.WristRight, JointType.HandRight, S, g);

                }



            }
            pictureBox1.Image = bmap;
        }

        void DrawBone(JointType j1, JointType j2, Skeleton S, Graphics g)
        {
            Point p1 = GetJoint(j1, S);
            if(j1 == JointType.HandLeft){
                this.label1.Text = S.Joints[j1].Position.X + " " + S.Joints[j1].Position.Y + " " + S.Joints[j1].Position.Z;
            }
                Point p2 = GetJoint(j2, S);
                if (j2== JointType.HandLeft)
                {
                    this.label1.Text = S.Joints[j2].Position.X + " " + S.Joints[j2].Position.Y + " " + S.Joints[j2].Position.Z;
                }
            g.DrawLine(Pens.Red, p1, p2);
        }

        Point GetJoint(JointType j, Skeleton S)
        {
            SkeletonPoint Sloc = S.Joints[j].Position;
            ColorImagePoint Cloc = sensor.MapSkeletonPointToColor(Sloc, ColorImageFormat.RgbResolution640x480Fps30);

            return new Point(Cloc.X, Cloc.Y);
        }



        Bitmap ImageToBitmap(ColorImageFrame Image)
        {
            byte[] pixelData =new byte[Image.PixelDataLength];
            Image.CopyPixelDataTo(pixelData);
            Bitmap bmap = new Bitmap( Image.Width,Image.Height,PixelFormat.Format32bppRgb);
            BitmapData bmapS = bmap.LockBits(new Rectangle(0, 0,Image.Width, Image.Height),ImageLockMode.WriteOnly,bmap.PixelFormat);
            IntPtr ptr = bmapS.Scan0;
            Marshal.Copy(pixelData, 0, ptr,Image.PixelDataLength);
            bmap.UnlockBits(bmapS);
            return bmap;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            sensor.Stop();
        }





    }
}
----------------------------------------------------------------------------------------------------

Lo que el anterior código hace es mostrar la imagen de video captada por la cámara de la kinect y captar un esqueleto si es que lo hay, después en un label muestra los tres puntos X-Y-Z que expresan la distancia en metros, tomando como punto de referencia el centro de la kinect.

EXPLICACIÓN DE CÓDIGO:

  • KinectSensor sensor;
          sensor = KinectSensor.KinectSensors[0];

          Con esta pequeña parte del código se toma la referencia del sensor kinect para poder utilizarlo.

  • sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);   sensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30); sensor.SkeletonStream.Enable();
          Con esto se le dice al sensor que es lo que se requiere usar, así los tendrá preparados para su uso.
  •  sensor.AllFramesReady += FramesReady;
            sensor.Start();
          Con esto se asigna el evento que se requerirá y se inicia el sensor.
  •  void FramesReady(object sender, AllFramesReadyEventArgs e)                                                        
           Esta es la función que toma el evento que se asignó anteriormente, aquí se hacen todos los pasos gráficos y la representación de cada parte del cuerpo reconocida.
  •  void DrawBone(JointType j1, JointType j2, Skeleton S, Graphics g)
          Esta es la función que dibuja un hueso del cuerpo, se necesita el puto de referencia de inicio, el de fin, el arreglo desde donde se obtiene la posición de cada parte y el grafico para dibujar el hueso (en este caso no se dibuja un uso exactamente, sino una line que lo representa).

  • Point GetJoint(JointType j, Skeleton S)
          Esta función obtiene la ubicación de los puntos del cuerpo con respecto a la resolución de la imagen en la que se quiere dibujar.

  •  Bitmap ImageToBitmap(ColorImageFrame Image)
          Esta función transforma la imagen que se obtiene desde la cámara a una imagen Bitmap para poder utilizarla y representarla en el cuadro de imagen.

OBTENCIÓN DE LA UBICACIÖN DE LAS PARTES DEL CUERPO:

Como se dice en el título de este artículo lo que se quiere probar con este ejemplo es la ubicación en tiempo real de alguna parte del cuerpo, lo que realiza esta tarea se encuentra en la función DrawBone() dentro de esta función se encuentra este código: 

S.Joints[j1].Position.X + " " + S.Joints[j1].Position.Y + " " + S.Joints[j1].Position.Z;

el cual obtiene la posición de una parte del cuerpo, S representa el esqueleto del que se quiere sacar las posiciones, en el caso de que hubiera una sola persona este esqueleto seria el número 0 del arreglos de esqueletos que se obtiene así:

            SkeletonFrame SFrame = e.OpenSkeletonFrame();
            Skeleton[] Skeletons = new Skeleton[SFrame.SkeletonArrayLength];
            SFrame.CopySkeletonDataTo(Skeletons);

            foreach (Skeleton S in Skeletons)
            {
Así se obtendría cada esqueleto dentro del arreglo de esqueletos que crea el sensor de la kinect.

Joints[] es un arreglo en el cual están las partes del cuerpo y Position es donde se guardan las posiciones X-Y_Z. si yo quisiera obtener la posición en el eje x de la  mano izquierda de un esqueleto seria así:

 S.Joints[JointType.HandLeft].Position.X.

Entonces en el código lo que se hace es cada vez que se quiera dibujar un hueso se revisa la condición de que si es una mano izquierda, si lo es lo es se escribe su posición en un label (se puede visualizar esto en el código ejemplo mostrado arriba en la función DrawBone() ).

Para finalizar mostramos una imagen que comprueba que este código funciona correctamente, mi mano izquierda esta levantada a x= -0.29 mts, y=-0.08 mts y z=1.7mts de la kinect.