Reto SDK Kinect: Reconocer gestos con Skeletal tracking

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.

Más sobre Skeletal Tracking

Como ya mencionamos en el reto anterior el Skeletal tracking se basa en un algoritmo de reconocimiento. Este algoritmo ha sido entrenado con muchas imágenes para lograr una gran precisión a la hora de identificar esqueletos.

El proceso de identificación del esqueleto tiene varias fases. Lo primero es obtener los datos del mapa de profundidad para separar los distintos jugadores que tiene en el campo de visión basándose en el fondo.

Una vez tenemos los distintos jugadores se clasifican las distintas partes del cuerpo para más tarde obtener los Joints o articulaciones, los distintos puntos que componen el esqueleto.

A partir de la identificación de los Joints ya podemos crear el esqueleto formado por estos puntos.

image

Reconocimiento de gestos

Cuando nos referimos a reconocimiento de gestos, en este caso, se trata de asignar a ciertos movimientos consecutivos de partes del cuerpo una determinada acción (saltar, saludar, girar, etc.).

Al igual que nos pasa con la detección de posturas el reconocimiento de gestos también tiene muchas técnicas diferentes que se pueden aplicar para lograr una mejor identificación o una implementación más sencilla. Una técnica muy utilizada para estos casos en utilizar redes neuronales las cuales se pueden entrenar para ir alcanzando cada vez más precisión y calidad de detección.

También podemos usar técnicas como definir algorítmicamente el gesto, al igual que hicimos con la postura, o comparar con una serie de plantillas ya definidas. La técnica de las plantillas es muy eficiente para gestos que siempre se realizan de la misma forma y que con cualquier otra técnica sería muy difícil de detectar.

Para ilustrar la técnica de comparación de plantillas podemos poner el golpeo de una bola jugando al tenis. Este movimiento es bastante complejo como para definirlo algorítmicamente y es más fácil si tenemos una serie de plantillas (gestos) e ir comparando cada captura con ellas. El movimiento capturado no será igual a la plantilla que tengamos pero se puede permitir un margen de error y si el resto del movimiento se sigue pareciendo al resto de plantillas podemos darlo por valido.

imageimageimageimage

imageimageimageimage

Repaso del proyecto

Para crear nuestro detector de gestos tendremos que crear un proyecto base WPF al igual que hicimos en el primer reto y añadir el código realizado en el reto anterior para la obtención de partes del cuerpo.

Hagamos un pequeño repaso. Se creaba un nuevo proyecto WPF y agregábamos la referencia al SDK de Kinect para poder utilizarlo. Más tarde añadíamos los eventos Loaded y Closed donde íbamos a implementar la inicialización y finalización del uso del sensor.

En evento Loaded teníamos que añadir en el método Initialize la opción de usar el Skeletal Tracking con el sensor y el evento SkeletonFrameReady,de donde obteníamos el esqueleto y la posición de las partes del cuerpo que vamos a utilizar en el reconocimiento de gestos, en este caso la posición de la mano derecha.

Código del Loaded:

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

Código del evento SkeletonFrameReady:

void kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { foreach (SkeletonData skeleton in e.SkeletonFrame.Skeletons) if (skeleton.TrackingState == SkeletonTrackingState.Tracked) { 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; //Código del detector } }

También hay que crear la estructura Vector3 para trabajar con las posiciones más fácilmente añadiendo un campo más para almacenar la fecha.

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

Deslizar

Ya tenemos el proyecto listo para añadir el código necesario para detectar gestos. Pero antes de eso vamos a ver qué gesto queremos detectar.

Vamos a implementar la detección de Swipe o deslizamiento. Este gesto es simple, se trata de mover la mano derecha hacia la derecha estando siempre a la misma altura y con una determinada velocidad.

image

Con esos factores podremos definir el gesto adecuadamente. Tenemos que tener el cuenta que para ello lo que vamos a hacer es ir comparando cada posición que obtengamos del sensor con las sucesivas para saber si el gesto es el correcto o no.

Los aspectos básicos para reconocer el gesto serán que la altura de la mano sea parecida, que el movimiento se realice a la derecha y que la longitud del movimiento sea suficiente para llegar a ser un Swipe.

Implementar el reconocimiento

Para poder identificar gestos vamos a necesitar 2 listas, una de tipo Vector3 para almacenar las posiciones que vamos obteniendo y otra para llevar la cuenta de los gestos que hemos identificado.

List<Vector3> positionList = new List<Vector3>(); List<Gesture> gestureAcceptedList = new List<Gesture>();

Después crearemos las propiedades que usaremos para definir el gesto, para la longitud, ancho del y duración del movimiento. También tendremos otra propiedad para saber cuándo hemos detectado el último gesto y otra por si queremos añadir un tiempo mínimo entre detecciones de gesto. Por último un enumerado para nombrar los gestos.

const float SwipeMinimalLength = 0.4f; const float SwipeMaximalHeight = 0.2f; const int SwipeMininalDuration = 250; const int SwipeMaximalDuration = 1500; DateTime lastGestureDate = DateTime.Now; int MinimalPeriodBetweenGestures = 0;

public enum Gesture { None, Swipe }

 

Lo siguiente es crear la función que se va a encargar de recorrer la lista de posiciones que tenemos e ir comprobando si existe un conjunto de ellas que pueda ser el gesto que queremos detectar.

Entre estas comprobaciones se encuentran las mencionadas anteriormente de conservar la misma altura de la mano y dirección correcta de movimiento.

Si cumple eso hay que comprobar si la longitud del gesto es la adecuada para más tarde calcular los tiempos para saber si la velocidad es la idónea y entra dentro del rango del periodo entre gestos ( en este caso el periodo entre gestos es 0).

Por último mostraremos que hemos detectado el gesto en una etiqueta, añadimos el gesto a la lista de gestos aceptados, asignamos la fecha de ahora a la propiedad correspondiente a la fecha del último gestos detectado y borramos la lista de posiciones que tenemos.

void Swipe() { int start = 0; for (int index = 0; index < positionList.Count-1; index++) { if ((Math.Abs(positionList[0].Y - positionList[index].Y) > SwipeMaximalHeight) || (positionList[index].X - positionList[index + 1].X > -0.01f)) { start = index; }

                if ((Math.Abs(positionList[index].X - positionList[start].X) > SwipeMinimalLength)) { double totalMilliseconds = (positionList[index].date- positionList[start].date).TotalMilliseconds; if (totalMilliseconds >= SwipeMininalDuration && totalMilliseconds <= SwipeMaximalDuration) { if (DateTime.Now.Subtract(lastGestureDate).TotalMilliseconds > MinimalPeriodBetweenGestures) { label1.Content = "Swipe "+ gestureAcceptedList.Count; gestureAcceptedList.Add(Gesture.Swipe); lastGestureDate = DateTime.Now; positionList.Clear(); } } }

            } }

Ahora añadimos la llamada en el evento SkeletonFrameReady en el lugar indicado donde añadiremos las posiciones a la lista de posiciones, llamaremos al detector de gestos y después borraremos las posiciones sobrantes de la lista si hemos alcanzado el tope máximo que lo hemos puesto en 20.

positionList.Add(new Vector3() { X = handRight.X, Y = handRight.Y, Z = handRight.Z, date = DateTime.Now } ); Swipe(); if (positionList.Count() > 20) { positionList.RemoveAt(0); }

Tu turno… ¡el Reto! – Swipe Vertical

Tenemos implementado un Swipe horizontal, de izquierda a derecha. Os proponemos realizar el Swipe de derecha a izquierda y también el de abajo a arriba (vertical).

Pista: ¡Este reto va sin pistas!

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

Solución: https://msdn.microsoft.com/es-es/windows/hh545505

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 tercer Reto SDK de Kinect: Detectar posturas con Skeletal tracking