Software del reconocimiento de gestos mediante Kinect
Como ya hemos mencionado,
el código de este proyecto en C# no ha sido escrito por nosotros,
sino que lo hemos adaptado a nuestro sistema. Inicialmente estaba adaptado a la
Beta
2 del SDK y nosotros lo
hemos adaptado a la versión 1.
Por este motivo, en
lugar de centrarnos en los detalles del código, explicaremos de forma
cualitativa la estructura del software y las modificaciones que hemos tenido
que incluir en el código original.
En el siguiente
diagrama vemos las 4 clases en las que se basa el software:
1. MainWindow.xaml
En esta clase es donde
definimos toda la inteligencia asociada a la interfaz gráfica. En ella hemos tenido que añadir algunos métodos
para poder establecer la conexión
con el robot Kigo y el envío de comandos.
Además, hemos añadido
algunas funcionalidades a la interfaz gráfica que teníamos inicialmente y hemos
modificado algunos métodos para mejorar el sistema de reconocimiento de gestos.
Los métodos más importantes
que encontramos en ella son:
- private void WindowLoaded(object sender, RoutedEventArgs e): Una vez que se ha abierto la interfaz gráfica, se llama a este método para inicializar el Kinect.
- private static void SkeletonExtractSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e): Cuando estamos intentando reconocer un gesto y Kinect tiene un frame del esqueleto listo, este método es llamado. Él se encarga de pasar todos los datos del esqueleto al algoritmo DTW para compararlo con los patrones.
- private void NuiDepthFrameReady(object sender, DepthImageFrameReadyEventArgs e): Es llamado cada vez que un frame de profundidad está listo. Nos permite mostrar en la interfaz las medidas del sensor de profundidad.
- private Point getDisplayPosition(DepthImageFrame depthFrame, Joint joint): Obtiene la posición donde se muestra una articulación en la representación cualitativa del esqueleto que aparece en la interfaz.
- private Polyline GetBodySegment(JointCollection joints, Brush brush, params JointType[] ids): Calcula cómo dibujar las líneas entre los puntos que representan las articulaciones en la interfaz gráfica.
- private void NuiSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e): Cada vez que un esqueleto está listo para ser mostrado, se llama a este método para actualizar el canvas con las líneas que representan los huesos y los puntos que representan las articulaciones.
- private void NuiColorFrameReady(object sender, ColorImageFrameReadyEventArgs e): Llamado cada vez que un frame de vídeo está listo.
- private void DepthImageFrameReady(object sender, DepthImageFrameReadyEventArgs e): Llamado cada vez que un frame de profundidad está listo.
- private void Connect(): Realiza la conexión con el robot Kigo a través de Bluetooth.
- private void DtwReadClick(object sender, RoutedEventArgs e): Al hacer click en “Capturar reposo” se llama a este método, que activa el estado de reposo poniendo a “true” la variable “capturandoreposo”.
- private void DtwCaptureClick(object sender, RoutedEventArgs e): Al hacer click en “Capturar gesto”, lanza una cuenta atrás antes de iniciar la grabación de un nuevo gesto patrón.
- private void CaptureCountdown(object sender, EventArgs e): Es llamado por el método anterior para mostrar por pantalla la cuenta atrás.
- private void StartCapture(): Inicializa el modo de captura de un nuevo gesto patrón, poniendo a “true” la variable “capturing”.
- private void DtwSaveToFile(object sender, RoutedEventArgs e): Guarda los gestos patrón que se han gabrado hasta el momento en un fichero de texto.
- private void DtwLoadFile(object sender, RoutedEventArgs e): Carga un fichero con gestos patrón que hemos grabado anteriormente.
- private void NuiSkeleton2DdataCoordReady(object sender, Skeleton2DdataCoordEventArgs a): Es el método que contiene toda la inteligencia del reconocimiento de gestos, al cual se le llama cada vez que Kinect tiene un esqueleto preparado.Si nos encontramos en el estado de “Capturar reposo” (capturandoreposo=true), este método almacena 6 esqueletos de la posición de reposo.Si nos encontramos en el estado de “Capturar gesto” (capturing=true), empieza a guardar automáticamente todos los esqueletos que le llegan. Después eliminará todos aquellos que sean muy similares a la posición de reposo, pues no contendrán información sobre el movimiento.El resto del tiempo estará comparando los esqueletos que le llegan con los patrones y, si detecta algún gesto conocido, enviará a Kigo el comando asociado a él por Bluetooth.
- private void elevationButton_Click(object sender, RoutedEventArgs e): Cambia la inclinación del Kinect en función de lo que indique el usuario en un slider de la interfaz gráfica.
- private void stopButton_Click(object sender, RoutedEventArgs e): Manda al robot un comando de parada.
- private void WindowClosed(object sender, EventArgs e): Cuando se cierra la ventana de la aplicación, se llama a este método para que se detenga el Kinect y se apague el robot.
2. DTWGestureRecognizer
Es la clase donde se
encuentra implementado el algoritmo de alineamiento
temporal dinámico (DTW) para la comparación de un gesto realizado y los
gestos patrón.
La principal diferencia
respecto al algoritmo que implementamos en la clase DTW.java para el reconocimiento
de gestos con el acelerómetro del móvil es que ahora comparamos vectores de 12 elementos: las coordenadas (x,y) de 6 partes distintas de nuestro esqueleto.
Otra diferencia es que
ahora tenemos una variable llamada “globalThreshold”, un umbral que nos determina la máxima distancia DTW que puede haber
entre dos gestos para que puedan ser considerados iguales. Si la distancia de un gesto realizado a todos los patrones
está por encima de este umbral, el gesto no será reconocido. Podremos
determinar el valor de esta variable desde la interfaz gráfica.
3. Skeleton2DDataExtract
Se encarga de obtener las coordenadas de la posición
de 6 partes distintas de nuestro
cuerpo, gracias a la funcionalidad de “skeletal tracking” del SDK de Kinect, y almacenarla en un
array “p” de objetos de la clase “Point”.
Las partes del
esqueleto con las que trabajaremos y el orden en el que las tendremos
almacenadas se pueden ver en la siguiente tabla:
Posición
del array
|
Parte
del cuerpo
|
p[0]
|
Mano izquierda
|
p[1]
|
Muñeca izquierda
|
p[2]
|
Codo izquierdo
|
p[3]
|
Codo derecho
|
p[4]
|
Muñeca derecha
|
p[5]
|
Mano derecha
|
Además, obtiene las
posiciones de ambos hombros y las almacena en las variables “shoulderRight”
y “shoulderLeft”.
Estas coordenadas serán utilizadas para establecer el origen de nuestro sistema de referencia en el punto medio del segmento que une ambos
hombros.
Por último, para
completar la definición del sistema de coordenadas, normaliza la distancia de cada punto al nuevo origen de coordenadas
dividiendo entre la distancia que separa a los hombros.
4. Skeleton2DdataCoordEventArgs
Coge todas las
coordenadas que habíamos obtenido y expresado según el nuevo sistema de
referencia en la clase Skeleton2DDataExtract y las prepara
para poder aplicar el algoritmo DTW
sobre ellas.
Para ello, crea un
nuevo vector de 12 doubles en el que va almacenando las coordenadas x en las posiciones pares (consideramos que el 0 es par) y las coordenadas y en las posiciones impares.
Este vector será el que
pasemos como parámetro a la clase DTWGestureRecognizer para calcular
distancias.
Adaptación a la versión 1 del SDK
Como todo el código
estaba adaptado a la Beta 2 del SDK, hemos tenido que realizar algunas modificaciones para
conseguir que funcionase con la versión 1, que es con la que
nosotros trabajamos:
- El primer cambio es el nombre de
la librería. Antes había que
importarla con:
usingMicrosoft.Research.Kinect.Nui;
Ahora hay que importarla con:
usingMicrosoft.Kinect; - Algunas clases cambian de nombre al pasar de una versión a la otra. Las dos que nos afectan a nosotros son:
Antiguo nombre
|
Nuevo nombre
|
JointID
|
JointType
|
Runtime
|
KinectSensor
|
- Ahora la obtención de frames de las cámaras de vídeo y profundidad es más compleja, pues ImageFrameReadyEventArgs se divide en ColorImageFrameReadyEventArgs para la cámara de video y DepthImageFrameReadyEventArgs para la cámara de profundidad.
- La forma de sacar los datos de los “ReadyEventArgs” también cambia considerablemente:
Antes: SkeletonFrameskeletonFrame = e.SkeletonFrame;
Ahora : SkeletonFrameskeletonFrame = e.OpenSkeletonFrame(); - También ha cambiado la
forma de recorrer los esqueletos:Antes: foreach (SkeletonData data inskeletonFrame.Skeletons) {}
Ahora: Skeleton[] skeletons = newSkeleton[skeletonFrame.SkeletonArrayLength];skeletonFrame.CopySkeletonDataTo(skeletons);foreach (Skeleton data in skeletons) {} - Antes era necesario realizar una conversión manual en los bytes de la cámara de profundidad, pero ahora no.
- Por último, la forma de inicializar el Kinect ha cambiado.
Muy bueno!! se ve interesante de hecho quiero hacer algo parecido, como hago para obtener el código competo de la aplicación. como te contacto. mi correo es : jramirez1009@hotmail.com
ResponderEliminar