lunes, 2 de noviembre de 2015

Primeros pasos con Kinect One

Autores:

  • Antonio José Navarro Céspedes
  • Miguel Ángel Valenzuela Hidalgo

 

 

Descripción del problema que se aborda


El problema abordado en esta práctica es el de reconocer una determinada posición del usuario así como un gesto. Para ello se ha hecho uso de un dispositivo Kinect ONE (2.0) que nos aporta la detección del cuerpo humano así como su profundidad.
En concreto, el problema ha constado de 3 fases:
  1.  Colocar al usuario en la ubicación adecuada para la detección de su cuerpo completo.
  2.  Detectar que el usuario ha realizado la postura que se le indica.
  3.  Comprobar y contabilizar el número de veces que el usuario realiza el gesto indicado.
La postura que se le indicará al usuario será con la mano izquierda a la misma profundidad que su cuerpo, estando situada a la izquierda del tronco; y la mano derecha estará situada por delante del cuerpo de forma erguida, a la misma altura que la cabeza del usuario. 
En cuanto al gesto, se hará con la mano derecha a partir de su posición indicada por la postura, desplazándose hacia la izquierda simulando un gesto de pasar página.


Descripción de la solución 

 

Vamos a explicar cómo hemos desarrollado el proyecto de forma general. Para saber cómo se ha programado todo, lo tenemos perfectamente explicado en el código de GitHub donde se ven mejor los pasos seguidos. Hemos cogido el código del proyecto de ejemplo Body-Basics-WPF en C# debido a su facilidad de uso.

Reconocimiento de la posición adecuada


Primero, declaramos la ubicación exacta donde queremos que se sitúe el usuario, declarando las siguientes variables globales:

private const double FloorCenterX =  0.0;
private const double FloorCenterY = -1.0;
private const double FloorCenterZ =  2.5;

         De esta forma, cuando Kinect detecte un cuerpo se dibujará una elipse indicando que el usuario debe moverse hacia allí, para después comenzar el reconocimiento de la postura.


 

 

Como vemos, siempre se mostrarán por pantalla indicaciones con texto para que el usuario pueda seguirlas, así como imágenes para saber qué etapas del programa ha pasado con éxito. En cuanto al vídeo, se irán mostrando de la misma forma los puntos a los que tiene que acceder el usuario en las distintas fases del programa.
Para dibujar en el vídeo esa elipse a partir del punto declarado anteriormente, hemos creado la siguiente función:

private double DrawFloor(Joint foot_left, Joint foot_right, Joint head, DrawingContext drawingContext)

Con esta función realizamos varias tareas. Primero, dibujamos la elipse de la ubicación donde queremos situar al usuario. Además usa la posición de los pies y la cabeza para comprobar que el usuario aún no está bien situado, con lo que muestra por pantalla las cadenas de texto que hemos visto en las imágenes anteriores. Queda comentar que la función devuelve la altura de la cabeza, para luego adaptar la postura en función de la altura del usuario.
Entendemos que el usuario está bien situado cuando se encuentra en un área cercana en 30 cm como máximo al área indicada, zona que queda reflejada con el área de la elipse. De esa forma, cuando el usuario está finalmente situado correctamente, le indicamos que no se mueva de dicha posición y comenzamos la etapa de reconocimiento de la postura.

Reconocimiento de Postura


Lo primero es indicarle al usuario que ya se ha posicionado correctamente, cambiando la imagen de la X de la posición a un tic de color verde. Esto lo hacemos de la siguiente forma:

System.String imgPath = Path.GetFullPath(@"..\..\..\Images\good.png");
position.Source = new BitmapImage(new Uri(imgPath));

        La imagen del tic verde es “good.png” , la cual cargamos cuando nos posicionamos donde se nos indica. De la misma forma, en MainWindows.xaml cargamos las imágenes que se muestran inicialmente.
 
 

        Vemos cómo aparecen los puntos objetivos que el usuario tiene que alcanzar con las manos para reconocer la postura exitosamente. Cuando coloque una mano en el lugar adecuado, se mostrará por pantalla cómo el punto objetivo cambia del color rojo al color verde:

 

 

        Para que la profundidad de los objetivos con respecto a las manos quede reflejada, al dibujarse los círculos se dibujarán más pequeños cuanto más alejados estén de la cámara. Así, el usuario puede comparar el tamaño de los círculos de su mano con el de los objetivos, sabiendo si tiene que acercar o alejar la mano. Teniendo esto en cuenta, tenemos una función para dibujar los dos objetivos:

private bool DrawGoal(Joint goal, Joint hand, DrawingContext drawingContext)

        Con esta función se dibuja un objetivo para una mano, habiéndose declarado las coordenadas del objetivo con anterioridad. Además, devuelve un valor booleano indicando si esa mano está situada donde el objetivo (momento en el que se dibuja de color verde).
         Cuando ambos objetivos estén alcanzados, se pasa a la última etapa del programa: la detección del gesto.


Detección del Gesto


Lo primero tras detectar que se ha realizado correctamente la postura, es indicárselo al usuario cambiando la imagen correspondiente:

 

Ya vemos cómo aparece un objetivo de otro color, indicando que es ahí donde debemos colocar la mano para comenzar a realizar el gesto. Al colocarse, se indicará con una línea y otro objetivo el movimiento que hay que realizar: 

 

 


      Vemos cómo el programa va indicando al usuario hacia dónde debe mover la mano. Todo esto lo hacemos primero dibujando los objetivos de una forma muy similar a los anteriores, con la siguiente función:

private bool DrawGesturePoint(Joint goal, Joint hand, DrawingContext drawingContext)

        Con esta función además de dibujar los objetivos comprobamos que el usuario no se sale de un rango para realizar el gesto (el rango es 5 cm en todas las dimensiones de los objetivos, con lo que si por ejemplo levanta demasiado la mano durante el gesto deberá comenzarlo de nuevo). Esta función no dibuja la línea que simboliza el movimiento, sólo los círculos objetivos. Para dibujar la línea, utilizamos la función DrawLine entre el punto de la mano derecha y el objetivo final.

   

     Podemos comprobar cómo al realizar un gesto, se contabiliza en el texto de la izquierda y volvemos a situar un objetivo en el inicio del gesto, para que el usuario pueda repetir el gesto de nuevo.


Problemas encontrados y soluciones empleadas



Debido a que el ejemplo a partir del cual hemos realizado esta práctica (Body-Basics-WPF) muestra todos los cuerpos detectados por pantalla, nos hemos encontrado con el problema de que al aparecer varios cuerpos en la pantalla (en caso de haberlos detectado) se superponían los objetivos de todos los cuerpos detectados ya que su altura se calcula a partir de la altura del primer cuerpo detectado. Como queríamos que nuestra aplicación fuera de un solo usuario, hemos introducido una variable booleana. Nuestro programa trabaja con un vector de cuerpos de tamaño 6, y si detecta menos de 6 cuerpos el resto de posiciones son valores nulos. Con nuestra variable booleana controlamos que el bucle “for” que recorre este vector aplicando nuestro código a cada cuerpo detectado sólo lo aplica al primer cuerpo detectado, de forma que las siguientes iteraciones del bucle no realizan nada. Así, nuestro código se ejecutará para el primer cuerpo detectado, y si aparece algún otro cuerpo durante la ejecución no se tendrá en cuenta (a no ser que el primero desaparezca). 

Código del Proyecto

 

https://github.com/ajncespedes/Practica-1-NPI