MSDN España

Noticias, avisos, reflexiones... del equipo que hay detrás de MSDN en España... también tendremos alguna que otra firma invitada :)

Reto SDK de Kinect: Detectar posturas con Skeletal tracking

Reto SDK de Kinect: Detectar posturas con Skeletal tracking

  • Comments 3

Imprescindible

Descargar el SDK de Kinect para Windows y tener alguna versión de Visual Studio 2010. Te puedes bajar la versión gratuita de Visual Studio 2010 Express o de la Ultimate Trial.

Para programar con el SDK utilizaremos la herramienta de desarrollo Visual Studio 2010 y Windows 7. Para ver más información sobre requerimientos previos y qué se puede hacer con la SDK de Kinect visita el post referente al SDK de Kinect en nuestro blog de MSDN.

Estamos atentos a tus comentarios en el twitter de @esmsdn y en el hashtag #retosmsdn.

¿Skeletal tracking?

La funcionalidad estrella del sensor Kinect es el Skeletal tracking. Skeletal tracking significa seguimiento de esqueleto y se basa en un algoritmo que logra identificar partes del cuerpo de las personas que están en el campo de visión del sensor. Por medio de este algoritmo podemos obtener puntos que hacen referencia a las partes del cuerpo de una persona y hacer un seguimiento de éstos identificando gestos y/o posturas.

image

Obtener partes del cuerpo

El SDK de Kinect nos permite obtener los puntos de articulaciones(Joints) del esqueleto y su posición en el espacio de una forma sencilla. Partiremos del proyecto base que explicamos en el primer reto. En él creábamos una aplicación WPF y añadíamos las referencias al SDK de Kinect. Después creábamos los eventos Loaded y Closed de la ventana principal para inicializar y finalizar el uso del sensor.

Para poder usar el Skeletal tracking deberemos añadir la opción UseSkeletalTracking cuando inicializamos el sensor en el evento Loaded y crearemos el evento SkeletonFrameReady que nos permitirá capturar los datos del esqueleto una vez que se obtengan.

kinect = new Runtime();
kinect.Initialize(RuntimeOptions.UseDepthAndPlayerIndex | RuntimeOptions.UseSkeletalTracking);
kinect.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(kinect_SkeletonFrameReady);

En el método kinect_SkeletonFrameReady que se habrá creado será donde implementemos el código para obtener las partes del cuerpo. Para ello tenemos que obtener los datos del Skeleton a partir del SkeletonFrame que se captura. El SkeletonFrame puede tener varios esqueletos pero sólo nos interesa los que tengan datos. Utilizaremos un foreach para realizar la detección de gestos por cada esqueleto correcto que genere el sensor. El siguiente paso es saber si ese esqueleto es correcto o no. Eso se hace mediante la propiedad TrackingState (NotTracked, PositionOnly o Tracked).

void kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
     foreach (SkeletonData skeleton in e.SkeletonFrame.Skeletons)
        if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
        {

El SDK sólo nos da la opción de acceder a los datos de 2 esqueletos a la vez. El resto de posiciones del vector serán esqueletos vacíos (NotTracked) por ello tenemos que ir recorriendo el vector hasta conseguir el correcto.

Ya tenemos el esqueleto, ahora vamos a ver como obtener las posiciones de las partes del cuerpo. Para ello tenemos un enumerado llamado JointID que utilizamos para obtener del vector de Joints el elemento que queremos de una forma más natural. Por ejemplo vamos a coger la posición de la mano derecha.

float HandRightX = skeleton.Joints[JointID.HandRight].Position.X;
float HandRightY = skeleton.Joints[JointID.HandRight].Position.Y;
float HandRightZ = skeleton.Joints[JointID.HandRight].Position.Z;

Con la propiedad position obtenemos la posición con respecto al sensor. Con las posiciones de las distintas partes podremos detectar gestos y posturas usando diferentes técnicas.

Para trabajar mejor con las posiciones vamos a definir una estructura de datos llamada Vector3. Tendrá 3 propiedades, una por cada valor de dimensión facilitando la tarea de almacenar y acceder a los valores de posición de la parte del cuerpo correspondiente.

[Serializable]
public struct Vector3
{
    public float X;
    public float Y;
    public float Z;
}

Cambiamos el código anterior utilizando la nueva estructura de datos para la mano derecha y la cabeza:

Vector3 handRight = new Vector3();
handRight.X = skeleton.Joints[JointID.HandRight].Position.X;
handRight.Y = skeleton.Joints[JointID.HandRight].Position.Y;
handRight.Z = skeleton.Joints[JointID.HandRight].Position.Z;

Vector3 head = new Vector3();
head.X = skeleton.Joints[JointID.Head].Position.X;
head.Y = skeleton.Joints[JointID.Head].Position.Y;
head.Z = skeleton.Joints[JointID.Head].Position.Z;

Detectar posturas

Ahora vamos a definir algorítmicamente la postura que queremos detectar. Empezaremos por algo sencillo como es detectar si la mano derecha está tocando la cabeza o no. Restaremos las 3 dimensiones de mano y cabeza entre si y si la distancia resultante está dentro de un rango de error, que definimos nosotros, daremos por buena la postura.

bool HandOnHead(Vector3 hand, Vector3 head)
        {
            float distance = (hand.X - head.X) + (hand.Y - head.Y) + (hand.Z - head.Z);
            if (Math.Abs(distance) > 0.05f)
                return false;
            else
                return true;
        }

Como veis es un código simple para una postura simple.

Lo siguiente es crear lo que llamaremos el detector de posturas. En él llevaremos la cuenta de posturas en proceso de detección, ya que una postura se tiene que mantener por un periodo de tiempo antes de convertirse en una postura detectada.

Para esto necesitaremos unas propiedades donde definiremos el número de veces que se tiene que identificar una postura antes de considerarse postura detectada, un acumulador para llevar la cuenta de las posturas detectadas, una propiedad para saber qué postura está en proceso de detección y otra para almacenar la última postura detectada. También tendremos un enumerado de posturas para que sea más fácil hacer referencia a ellas.

const int PostureDetectionNumber = 10;
int accumulator = 0;
Posture postureInDetection = Posture.None;
Posture previousPosture = Posture.None;
public enum Posture
{
    None,
    HandOnHead
}

Al detector de posturas se le pasará la postura que acabamos de identificar algorítmicamente. Dentro comprobamos si la postura es la misma que está en proceso de detección. Si no lo es pondremos el acumulador a 0 y pondremos esa postura como postura en proceso de detección.

En caso de ser la misma miramos si hemos alcanzado el número para que sea postura detectada. Si todavía no hemos llegado a ese número aumentaremos el acumulador. En caso contrario si la postura es distinta a la última postura detectada actualizamos la postura anterior con la actual, ponemos el acumulador a 0 y devolvemos verdadero para saber que hemos detectado una nueva postura con éxito, sino sólo inicializamos el acumulador a 0.

bool PostureDetector(Posture posture)
{
    if (postureInDetection != posture)
    {
        accumulator = 0;
        postureInDetection = posture;
        return false;
    }
    if (accumulator < PostureDetectionNumber)
    {
        accumulator++;
        return false;
    }
    if (posture != previousPosture)
    {
        previousPosture =posture;
        accumulator = 0;
        return true;
    }
    else
        accumulator = 0;
    return false;
}

Para acabar llamaremos a la función dentro del bucle anterior, justo después del código de obtención de la posición de mano y cabeza, mostrando el resultado en un Label que hemos añadido previamente a nuestra ventana principal.

  if (HandOnHead(handRight, head))
  {
     if (PostureDetector(Posture.HandOnHead))
         label1.Content = previousPosture.ToString();
  }
  else
     if(PostureDetector(Posture.None))
        label1.Content = previousPosture.ToString();

}

 
Para comprobar que el sensor te detecta bien sería recomendable añadir el código del reto anterior correspondiente a las cámaras de profundidad y detección de jugador. Así sabremos si estamos en la posición correcta para que el sensor nos capture completamente y logre obtener nuestro esqueleto.

Tu turno… ¡el Reto! – La cruz

Detectar si la mano está tocando la cabeza es muy fácil. Os proponemos que implementéis la función cross que detectará si estamos en posición de cruz. Esta posición se caracteriza por tener los brazos rectos perpendiculares al cuerpo y las piernas juntas. Hay varias formas de hacerlo, ¡propón la tuya!

Pista: Comprobar la altura a la que se encuentran los brazos.

Recursos: Post sobre Kinect en el blog de MSDN, documentación sobre el SDK de Kinect y centro de desarrollo de WPF.

Solución: http://msdn.microsoft.com/es-es/windows/hh545503

Participa: twitter de @esmsdn o hashtag #retosmsdn.

¡A Kinectear!

José Perona – @JVPerona - Developer Evangelist Jr.

 

Ver el primer Reto SDK de Kinect: Desarrolla con Kinect

Ver el segundo Reto SDK de Kinect: Usar las cámaras del sensor

Ver el cuarto Reto SDK de Kinect: Reconocer gestos con Skeletan tracking

Leave a Comment
  • Please add 7 and 2 and type the answer here:
  • Post
  • Hola José,

    Un par de preguntas sobre el tema:

    ¿Existe algún factor del entorno que influya en la detección correcta de las coordenadas? He detectado que con la mano en la cabeza la detecta en ciertas partes de la cabeza, e incluso a unos 10 o 15 cm de ella.

    ¿Seria bueno utilizar el TransformSmoothing, modificando sus parametros para obtener mejor resultado?

    Un saludo y enhorabuena por el post!

  • Buenos días Luis,

    Con TransformSmoothing puedes reducir las vibraciones causadas por la actualización del frame así que sería bueno utilizarlo. Puedes usar por ejemplo los siguientes parámetros:

    kinect.SkeletonEngine.TransformSmooth = true;

    var parameters = new TransformSmoothParameters

    {

    Smoothing = 0.75f,

    Correction = 0.0f,

    Prediction = 0.0f,

    JitterRadius = 0.05f,

    MaxDeviationRadius = 0.04f

    };

    kinect.SkeletonEngine.SmoothParameters = parameters;

    Con respecto a tu primera pregunta, es causado por el margen de error que he puesto en la función de detección de la postura. Lo ideal es crear un margen de error para la resta de cada tipo de coordenada X, Y y Z en vez de sumarlos todos y poner sólo un margen.

    Puedes mejorar ese margen todo lo que quieras para obtener unos resultados más óptimos haciendo pruebas.

    Un saludo!

  • como seria con el sdk 1.8 para kinect

Page 1 of 1 (3 items)