Para cerrar nuestro blog, os presentamos el vídeo con un resumen y el resultado final de nuestro proyecto. Aunque hemos tenido problemas por la poca precisión del sensor de ultrasonidos, lo cual hace que el sistema autónomo de detección de contenedores no funcione a la perfección, sí que estamos muy contentos por el resultado obtenido con los sistemas de control por reconocimiento de gestos a través del acelerómetro del móvil y de Kinect.
No puedo acabar esta última entrada sin mostrar mi agradecimiento a todas las personas que de alguna manera han contribuido a que hoy estéis viendo este vídeo. Si estáis leyendo esto, es muy probable que seáis una de ellas. Gracias por creer en nuestro proyecto y devolverme la ilusión cuando he atravesado momentos en los que yo misma no era capaz de creer en él.
Kigo: el robot clasificador de residuos
sábado, 26 de mayo de 2012
miércoles, 23 de mayo de 2012
Integración de Kinect (Parte III)
Por último, en esta entrada hablaremos de la interfaz gráfica de la aplicación que utilizamos para el control del robot Kigo a través de Kinect.
El funcionamiento de esta
interfaz gráfica es
muy sencillo, aunque requiere que se maneje entre 2 personas: la persona “modelo” (que se coloca delante del
sensor de Kinect) y la que controla
la interfaz.
En la siguiente imagen
vemos cuál es su aspecto:
Los pasos que se han de
realizar para poder controlar el robot de manera satisfactoria son los
siguientes:
- La persona modelo ha de colocarse frente al sensor y quedarse en posición de reposo. En ese momento, hay que hacer click en el botón de “Capturar reposo”. Esto nos permitirá distinguir cuándo se está realizando un movimiento, pues estableceremos unos umbrales gracias a la posición de reposo.
- Ya se puede empezar el entrenamiento de los gestos patrón. Para ello, se selecciona
en la lista desplegable a qué comando
queremos asociar un gesto.
- Hacemos click en el botón de “Capturar
gesto” y, cuando se indique en pantalla, se puede empezar a realizar
ese gesto. Automáticamente detectará cuándo termina el gesto.
- Con algún gesto ya grabado, cuando lo
realicemos y sea detectado aparecerá
un mensaje por pantalla. De esta manera, podemos probar que la detección
funciona correctamente antes de realizar la conexión con el robot.
- Cuando estemos preparados para enviar comandos al robot, haremos click
en el botón de “Conectar” para que se establezca la conexión Bluetooth con él.
- Ya podemos realizar los gestos que queramos y, si están asociados a algún comando, se le enviará al robot por Bluetooth y él realizará la acción correspondiente.
Otras opciones que
encontramos en la interfaz para mejorar la experiencia del usuario son:
- “Guardar gestos en un archivo”: Nos permite almacenar las coordenadas de los gestos patrón que hemos grabado en un fichero de texto; de esta manera, la próxima vez que abramos la aplicación podremos volver a utilizar los mismos patrones y no perderemos tiempo en grabar unos nuevos.
- “Cargar archivo con gestos”: Podemos establecer como patrones gestos que hayamos grabado y almacenado en un fichero de texto anteriormente.
- “Mostrar archivo con gestos”: Nos abre un fichero de texto donde tenemos almacenadas coordenadas de algún gesto patrón.
- “Ajustar Kinect”: Nos permite inclinar el Kinect tantos grados como hayamos indicado en la barra de selección superior. Hay que tener en cuenta que la inclinación máxima es de ±27º.
- “Umbral”: Podemos determinar el umbral de similitud que establece si dos gestos son iguales o no. Umbrales elevados implican tener un sistema de poca sensibilidad, que establece que dos gestos son iguales a pesar de que haya grandes diferencias entre ellos. Umbrales muy bajos nos obligan a realizar los gestos con mucha precisión para que puedan ser detectados. Nosotros solemos trabajar con un umbral de 2.
- “Parada de emergencia”: Si le enviamos al robot un comando sin querer, necesitaremos pararlo haciendo click en este botón.
Además, podremos ver en
todo momento el esqueleto que Kinect
está detectando y una representación de las medidas del sensor de profundidad.
Con esto, ya conocéis los rasgos más importantes de todo nuestro proyecto. Próximamente publicaremos un vídeo en el que se podrá ver el resultado final de nuestro trabajo.
Integración de Kinect (Parte II)
En esta entrada hablaremos del software que hay detrás de la implementación de Kinect en nuestro proyecto y cómo hemos tenido que adaptar el código del que hemos partido.
Software del reconocimiento de gestos mediante Kinect
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.
martes, 22 de mayo de 2012
Integración de Kinect (Parte I)
Como última mejora de
nuestro sistema, hemos integrado en él el control del robot Kigo a través de Kinect.
Nosotros hemos trabajado con la versión 1.0 del SDK para Kinect. Algunas de las posibilidades que nos ofrece son:
Para ello, hemos
partido de un proyecto open source para el reconocimiento
de gestos a través del Software Development Kit (SDK) de
Kinect que nos ha facilitado nuestro tutor Juan Manuel Montero.
El código de este
proyecto está escrito en C#, a diferencia del código con el
que hemos estado trabajando en el resto de las fases de desarrollo de nuestro
sistema, que estaba escrito en Java.
Al igual que ocurría en
el reconocimiento de gestos a través del acelerómetro de un dispositivo
Android, el algoritmo usado para calcular distancias es el alineamiento temporal dinámico (DTW).
La principal diferencia
que encontramos entre lo explicado para el acelerómetro del móvil y Kinect es
que, en esta ocasión, en lugar de trabajar con 3 coordenadas de la aceleración
sufrida por el móvil, trabajamos con posiciones
de nuestro esqueleto. Estudiaremos la posición de 6 partes de nuestro cuerpo: mano
izquierda y derecha, muñeca
izquierda y derecha y codo izquierdo
y derecho. Nuestro nuevo sistema de coordenadas será de 2 dimensiones, por lo que sólo tendremos coordenadas (x,y).
Además, el origen de coordenadas lo
tendremos en el punto medio del
segmento que une ambos hombros.
En primer lugar,
hablaremos en esta entrada brevemente del hardware
del que dispone Kinect y las
funcionalidades que nos proporciona Microsoft
mediante el SDK para Kinect; posteriormente, en la siguiente entrada,
nos centraremos en todo el software
relacionado con el sistema de reconocimiento de gestos que hemos integrado y en
la interfaz gráfica que usamos.
Breve descripción del hardware de Kinect
Kinect
es un controlador de videojuegos
desarrollado por Microsoft para la videoconsola XBOX 360. Este controlador
permite al usuario interactuar con la consola sin necesidad de tener contacto
físico, mediante el reconocimiento de gestos, de voz o de imágenes.
El hardware de Kinect
está contenido en una barra horizontal de unos 23 cm de largo, conectado a una
base circular con un eje de articulación. Dispone de un mecanismo de inclinación motorizado que le
permite inclinarse ±27º.
Los sensores que dotan a Kinect de su
funcionalidad son principalmente:
- Sensor de profundidad: Mediante un proyector de infrarrojos y un sensor CMOS monocromático, Kinect puede capturar datos de vídeo en 3D sean cuales sean las condiciones de luz ambiental. Los datos de vídeo tienen una resolución VGA (640x480 píxels) a 11 bits, proporcionando así 2.048 niveles de sensibilidad. Se capturan 30 frames por segundo. Su campo de visión es de unos 57º horizontalmente y 43º verticalmente. Además, puede detectar un rango de 1,2-3,5 metros.
- Cámara de color: Utiliza una resolución VGA (640x480 píxels) a 8 bits
- Un array de 4 micrófonos, con audio representado por 16 bits y enviado a 16 KHz.
Software Development Kit para Kinect
En febrero del 2011, Microsoft lanzó el SDK (Software Development Kit)
oficial para Kinect, gracias al cual se
pueden desarrollar aplicaciones basadas en Kinect
en C++, C# o Visual Basic, utilizando Microsoft
Visual Studio 2010.

- Acceder a los datos de todos los sensores de Kinect: el de profundidad, la cámara de color y el micrófono.
- Skeletal tracking: la habilidad de detectar la posición de cada una de las partes del cuerpo de una persona situada en el campo de visión del Kinect.
- Técnicas de procesado de audio, como la cancelación de ecos, supresión de ruido o identificación de la fuente de sonido.
- Documentación y código de prueba.
De todas las
funcionalidades de Kinect, la que nos
interesa especialmente a nosotros para el reconocimiento de gestos es el “skeletal
tracking”. Kinect es capaz de
detectar la posición de hasta 20 partes
de nuestro cuerpo mediante el siguiente algoritmo:
- Con el sensor de profundidad, mide la distancia a la que se encuentra la persona que se coloca delante y dibuja de forma rudimentaria un esqueleto de las zonas donde ha detectado presencia.
- El cerebro de Kinect empieza a averiguar cuál es cada parte del cuerpo, basándose en su experiencia reconociendo otros cuerpos y en modelos que lleva implementados. Asigna una serie de probabilidades a los distintos píxels. Por ejemplo, si está seguro de que una zona es el hombro izquierdo, asignará a esos píxels una probabilidad elevada.
- Con este conjunto de probabilidades, dibuja un esqueleto con las partes del cuerpo que ha reconocido, escogiendo la combinación que tiene una mayor probabilidad.
El SDK nos proporciona
métodos para acceder a la posición de cada parte del cuerpo, obtenida a través
de este algoritmo de “skeletal tracking”.
viernes, 18 de mayo de 2012
Algoritmo DTW (Dynamic Time Warping)
El algoritmo de alineamiento temporal dinámico nos
permite medir la similitud entre
dos secuencias que pueden variar en el tiempo o en el espacio.
Aunque podríamos haber
elegido no utilizar este algoritmo, hay que tener en cuenta que, cuando
realizamos un gesto, no siempre haremos un movimiento de la misma duración. Si
calculásemos distancias euclídeas y
tuviésemos dos vectores idénticos, pero uno desplazado ligeramente en el tiempo
respecto al otro, al hacer la comparación punto
a punto pensaríamos que ambos vectores son distintos. Sin embargo, con el DTW
podremos realizar gestos con mayor o menor velocidad
y darnos cuenta de que son idénticos.
Con esta entrada
aprenderemos cuál es el fundamento matemático de este algoritmo y cómo ha sido
programado en Java.
Sean las secuencias X=(x1,x2,...,xN), de longitud N,
e Y=(y1,y2,...,yM), de longitud M. Estas secuencias pueden ser señales discretas o señales muestreadas
en puntos equidistantes en el tiempo.
Definimos un espacio vectorial F formado por estas muestras, de manera que:
Para
determinar cuál es el camino óptimo p* entre dos secuencias, se podrían probar
todos los caminos de alineamiento entre X e Y; sin embargo, la complejidad de este
problema crecería de forma exponencial con N y M. Para simplificarlo,
trabajaremos con la matriz D de costes
acumulados:
Gracias a él, podremos calcular la distancia entre un gesto que realicemos
y cada uno de los gestos patrón almacenados. El gesto reconocido será aquel que
se encuentre a menor distancia.
Originalmente, el algoritmo DTW se usaba para comparar
distintos patrones acústicos en el reconocimiento
automático del habla. Otras aplicaciones son el reconocimiento de gestos o de
escritura.
Fundamento
matemático
Definimos un espacio vectorial F formado por estas muestras, de manera que:
Si queremos comparar
dos secuencias, necesitamos una medida de costes
o distancias locales; esta función se define como: 

Esta función nos devolverá un coste c(x,y)
pequeño si x e y son similares y un coste c(x,y) elevado si son muy distintas.
Si utilizamos esta función para calcular la distancia entre cada par de
elementos de las secuencias X e Y, obtenemos una matriz de costes:
Cada elemento de esta matriz se define como c(n,m)=c(xn,ym). La matriz C nos
permite alinear las secuencias X e Y, buscando un camino de alineación de coste
mínimo a lo largo de ella.
Formalmente, un camino de alineación (warping
path) se define como una secuencia:
Esta secuencia debe satisfacer tres condiciones:
- Condición de frontera:
para que los primeros y los últimos elementos de X e Y estén alineados y así las secuencias completas estén alineadas. Con ella nos aseguramos de que elementos que se sucedan en el tiempo también se sucedan al ser alineados.
- Condición de salto:
Nos indica que no se puede omitir ningún elemento de X e Y y que el camino de alineamiento es continuo.
En la siguiente figura vemos claramente en qué
consiste cada una de estas condiciones para una secuencia X de longitud N=7 y una secuencia Y de longitud M=9. Mientras que en (a) se cumplen las 3, en (b) se viola la condición de frontera,
en (c) la de monotonía y en (d) la de salto.
El coste
total cp(X,Y) de un camino de alineamiento p será la suma de
todos los costes locales:
El camino de alineamiento
óptimo entre X e Y es un camino de alineamiento p* que, entre todos los posibles caminos de alineamiento, tiene el coste total mínimo. Se define la distancia DTW entre X e Y como el coste
total de p*:
La distancia DTW es la que calcularemos para comparar los gestos
que han de ser reconocidos con los patrones.
En esta expresión, aparecen unas secuencias prefijo:
La distancia acumulada
es la distancia entre la celda actual y la mínima de las distancias acumuladas
para los elementos adyacentes:
Por lo tanto, el cálculo de distancia acumulada
se podrá programar de manera recursiva y podremos calcular la distancia DTW en
función de la acumulada:
Clase
DTW.java
Una vez que conocemos el fundamento matemático del
algoritmo DTW, es muy fácil traducirlo al lenguaje de programación y ver los
pasos que sigue la clase DTW.java para el cálculo de
distancias.
El código de esta clase no ha sido escrito por
nosotros, sino que su autor es Cheol-Woo
Jung y nosotros lo hemos adaptado para poder calcular la distancia DTW
entre dos arrays de objetos de la clase Data (donde se almacenan las
coordenadas de la aceleración sufrida por el acelerómetro del móvil al realizar
un gesto).
Las variables que utilizamos son:
- Dos arrays de Data seq1 (coordenadas del gesto a reconocer) y seq2 (coordenadas del patrón).
- Un array bidimensional de enteros warpingPath, que representa un camino de alineamiento.
- 3 enteros n, m y k, que serán usados como índices para recorrer bucles.
- El double warpingDistance, donde quedará almacenada finalmente la distancia DTW que calculemos.
Los métodos
de los que disponemos para este complejo cálculo de distancias son:
- protected double distanceBetween(Data p1, Data
p2): Calcula la distancia euclídea
entre 2 puntos cualesquiera que le pasemos como parámetro. Como en nuestro caso
trabajamos con 3 coordenadas de
aceleración, se calculará esta distancia como:
- protected int getIndexOfMinimum(double[] array): Devuelve el índice del elemento con valor mínimo en un array de doubles.
- public double getDistance(): Simplemente devuelve el valor de la variable “warpingDistance”. Tras realizar el cálculo de la distancia DTW, el valor final estará almacenado en esta variable y lo podremos recuperar con este getter.
- protected void reversePath(int[][] path): Invierte el orden de un camino de alineamiento, es decir, hace que su primer punto se convierta en el último y viceversa. Para ello, parte del array bidimensional “path” que le pasamos como parámetro y crea uno nuevo llamado “newPath” del mismo tamaño y con los elementos en orden decreciente. Finalmente almacena el nuevo camino de alineamiento en la variable “warpingPath”.
- public void compute(): Es el método más importante
de la clase, que utiliza a todos los demás para poder determinar el camino de alineamiento óptimo y la distancia DTW asociada a él. Este
proceso lo realiza a través de la matriz
de distancias acumuladas.
Conociendo las variables y los métodos de los que
disponemos, explicaremos de forma sencilla qué pasos sigue esta clase para obtener
distancias DTW:
- Se llama al constructor de la clase, pasando como parámetro un array de Data “sample” con las coordenadas del gesto a reconocer y otro “template” con las coordenadas del patrón con el que se quiere comparar.
- Se inicializan las variables “seq1” con el array “sample” y “n” con su longitud; de la misma manera, “seq2” es inicializado con el array patrón “template” y “m” con su longitud.
- Se inicializa el array bidimensional “warpingPath” con un tamaño de (N+M)x2 y establecemos que la distancia del camino óptimo “warpingDistance” inicialmente es 0.
- El constructor invoca al método compute() para que calcule la distancia DTW. En primer lugar, éste crea una matriz “d”, que contiene la distancia euclídea entre cada punto de la primera y de la segunda secuencia que se están comparando (por lo tanto, es una matriz NxM). También crea la matriz “D”, que será la matriz de costes acumulados.
- A
partir de la matriz de distancias “d”, se va completando la matriz de
costes acumulados “D”.
- Gracias a la matriz de costes acumulados, se va
construyendo el camino de alineamiento
óptimo rellenando el array bidimensional “warpingPath”, de tamaño
Kx2 (siendo K<N+M). El cálculo de este camino p*=(p1,p2,...,pK) se realiza en orden inverso, empezando por el
punto final pK=(N,M). Cada elemento se obtiene
como:
- Como tendremos el camino de alineamiento óptimo calculado en orden inverso, tendremos que invertirlo llamando al método reversePath().
- La distancia DTW entre las dos secuencias, que almacenaremos en la variable “warpingDistance” y será el resultado final de nuestro cálculo, se obtiene como la distancia acumulada al llegar al último punto de este camino de alineamiento óptimo.
Con esta entrada damos por terminada la
explicación de todo lo relacionado con nuestro sistema de reconocimiento de
gestos mediante el acelerómetro de
un dispositivo Android. En los próximos días os hablaremos de cómo hemos
implementado el control del robot Kigo mediante Kinect.
jueves, 17 de mayo de 2012
Modificaciones en la aplicación Android para el reconocimiento de gestos
Para integrar el sistema
de reconocimiento de gestos en la aplicación Android, hemos tenido que realizar
ciertas modificaciones en su software. En esta entrada explicaremos cuáles son
los cambios que ha sufrido la aplicación.
El cambio más significativo es la creación de
una nueva clase: SettingsActivity.java. Con ella, la estructura del software de
la aplicación queda de la siguiente manera:
Cuando la actividad se inicia, se invoca al
método onCreate(), que crea:
Además, este método
define cómo debe responder la actividad al evento de hacer click sobre uno de los botones:
Inicializa los dos nuevos botones que antes no
teníamos en nuestra aplicación (que se encuentran en la nueva página de “Gestos”)
y define cómo deben responder al evento de que el usuario haga click sobre
ellos:
Actividad
SettingsActivity.java

La
clase SettingsActivity es una actividad encargada del menú de la aplicación, desde el cual se
lleva a cabo la fase de entrenamiento
para el reconocimiento de gestos.
- Una lista desplegable “sp” de la clase “Spinner”, que contiene todos los posibles comandos a los que se les puede asociar un gesto.
- Cuatro botones “startLeartingBtn”, “startIterationBtn”, “stopIterationBtn” y “stopLearningBtn” para poder realizar el entrenamiento (inicialmente sólo estará habilitado el de “Empezar Entrenamiento”.

- Empezar entrenamiento:
Llama al método public void startLearning(int type, int iterations, int idGesture) de la clase GestureFactory, pasándole como parámetro que el tipo de sensor con el que tiene que trabajar es el acelerómetro (type=0), que se han de realizar 3 iteraciones para completar el entrenamiento (iterations=3) y el número de identificación idGesture asociado al comando que se ha seleccionado en la lista desplegable “sp”.
Además, al hacer click sobre este botón se habilita el de “Empezar gesto” y se inhabilita él mismo, pues ya se entra en la fase de entrenamiento. - Empezar gesto:Simplemente invoca al método public void startIteration() de la clase GestureFactory, para que asocie un listener al acelerómetro y almacene las coordenadas de la aceleración que sufre el dispositivo Android al realizar el gesto.Tras hacer click sobre él, él mismo se inhabilita y sólo queda habilitado el botón de “Finalizar gesto”.
- Finalizar gesto:
Llama al método public void stopIteration() de la clase GestureFactory para eliminar la asociación entre el listener y el acelerómetro.Además de inhabilitarse a sí mismo, si el número de iteraciones realizadas de un mismo gesto ya es 3, habilita el botón “Finalizar entrenamiento”; si es inferior a 3, vuelve a habilitar el de “Empezar gesto”. - Finalizar entrenamiento:
En primer lugar, resetea el contador de iteraciones e inhabilita todos los botones excepto el de “Empezar entrenamiento”.
Invoca al método public Gesture stopLearning() de la clase GestureFactory y añade el objeto de la clase Gesture que este método devuelve a nuestro ArrayList de gestos patrón.
La aplicación queda preparada para comenzar un nuevo entrenamiento o pasar a la fase de realización de gestos y comparación con los patrones.
Cambios
en NXJAndroidActivity.java
Hemos tenido que introducir algunas modificaciones en
la actividad principal NXJAndroidActivity.java para poder
comenzar la detección de movimientos
del usuario y enviar los comandos correspondientes
al robot.
Ahora esta actividad crea un objeto “ad”
de la clase AndroidDevice, que se encargará de eliminar el efecto de la gravedad en las lecturas que tome el
acelerómetro en la fase de realización de gestos.
- Iniciar gesto:
Añade un GestureListener asociado al acelerómetro y entramos en la fase de realización de gestos. A partir de aquí, es la clase GestureManager la que se encarga de comparar los movimientos detectados con sus umbrales de energía y de tiempo para establecer en qué momento se está realizando un gesto, tal y como explicamos en la entrada “Software para el reconocimiento de gestos (parte II)”.
Cuando la clase GestureManager detecta que se ha terminado de realizar el gesto, compara sus coordenadas con las de todos los patrones mediante el algoritmo DTW y decide cuál de ellos se parece más, calculando la distancia mínima; se almacena en una variable el número de identificación del gesto patrón más parecido.
Al finalizar el gesto, se invoca al método onGestureDetected() de la interfaz GestureListener, al que se le pasa como parámetro el número de identificación “idGesture”. Dependiendo de este número, se le enviará por Bluetooth al robot Kigo un comando u otro a través del método public static void send(byte[] bytearray) de la clase Robot. En esta tabla podemos ver la correspondencia establecida: - Detener gesto:
El listener de gestos estará continuamente detectando posibles movimientos del dispositivo Android en busca de nuevos gestos hasta que hagamos click sobre este botón. Lo único que hace al detectar que el usuario ha hecho click sobre él es inhabilitar el AndroidDevice y, por consecuencia, eliminar la asociación entre el SensorEventListener y el acelerómetro.
Con él salimos del sistema de reconocimiento de gestos.
Suscribirse a:
Entradas (Atom)