jueves, 17 de mayo de 2012

Software para el reconocimiento de gestos (parte I)

Para profundizar en el funcionamiento de nuestro sistema de reconocimiento de gestos mediante el acelerómetro de un dispositivo Android, en esta entrada presentaremos de forma detallada cada una de las clases e interfaces que hemos desarrollado.

La estructura de este software tiene cierta complejidad y podemos verla resumida en el siguiente diagrama. En él, los cuadros naranjas representan interfaces y, los azules, clases; las flechas con línea continua representan una relación de extensión (AccelerometerData extiende a Data) y las de línea discontinua implementación (AccelerometerGesture implementa a la interfaz Gesture y AndroidDevice a Device).


Aunque al ver la estructura de las interfaces y clases pueda parecer que algunas de las interfaces no serían necesarias en nuestro proyecto, lo hemos diseñado de esta forma pensando en una futura extensión de este sistema de reconocimiento de gestos a más dispositivos.

De todas ellas, la única que no ha sido desarrollada por nosotros es la clase DTW.java, cuyo autor es Cheol-Woo Jung; nosotros simplemente la hemos adaptado a nuestro proyecto.

A continuación iremos describiéndolas una a una, aunque por la complejidad del software, tendremos que dividir su explicación en 2 entradas distintas:


Clase Data.java

Únicamente tiene como variable un array de floats llamado “values”, con su correspondiente getter: el método getValues(). A su constructor tendremos que pasarle como parámetro un array de floats.

Un objeto de la clase Data.java contendrá coordenadas de la aceleración medidas con el acelerómetro. Por lo tanto, normalmente la variable “values” será un array formado por 3 floats:
  • values[0]: coordenada X de la aceleración
  • values[1]: coordenada Y de la aceleración
  • values[2]: coordenada Z de la aceleración

Clase AccelerometerData.java

Como esta clase extiende a Data.java, a su constructor tendremos que pasarle como parámetro un array de floats para poder hacer la llamada correspondiente al constructor de la clase padre.

La función de esta subclase es acceder por separado a cada uno de los elementos que forman el array “values” de un objeto Data, para poder trabajar con cada coordenada de la aceleración de manera independiente.

Para ello, consta de los 3 métodos getXAcceleration(),getYAcceleration() y getZAcceleration(), que acceden respectivamente a la primera, segunda y tercera posición del array “values”.  


Interfaz EventListener.java

En esta interfaz sólo encontramos un método abstracto: public void onEventReceived(float[] values, Device d), que posteriormente utilizarán otras clases en la fase de realización de gestos para determinar qué hacer al detectar un evento del acelerómetro.


Interfaz GestureListener.java

Como ocurría con la interfaz anterior, ésta consta de un único método abstracto: public void onGestureDetected(int idGesture). 

Este método se usará en la fase de comparación con los patrones y especificará qué hacer en caso de detectar una coincidencia entre un gesto realizado y un patrón guardado.


Clase FactorySensorListener.java

Es una clase que implementa la interfaz SensorEventListener (proporcionada por las liberías Android) y que se encarga de crear una estructura de datos en la que se almacena un gesto realizado en la fase de entrenamiento.

La estructura de datos elegida para almacenar los gestos es un array de 512 objetos de la clase AccelerometerData; a su vez, cada uno de ellos, contiene un array de 3 floats (las coordenadas x, y, z de la aceleración). En la siguiente figura se ve de manera sencilla cómo es la estructura:


Esta clase comienza a trabajar cuando nos encontramos en la fase de entrenamiento, indicamos que queremos enseñar un nuevo gesto y el acelerómetro del móvil detecta un evento.

Antes de explicar el funcionamiento interno de la clase, es importante señalar que en todo momento trabajaremos con aceleraciones lineales, aislando el efecto producido por la gravedad. Como ya explicamos en la entrada “El acelerómetro de un dispositivo Android”, este sensor nos proporciona medidas de aceleración total, teniendo en cuenta la gravedad.

Si queremos trabajar únicamente con la aceleración lineal que producen nuestros movimientos, tendremos que implementar un filtro paso bajo que aísle la fuerza de la gravedad y, posteriormente, restar estas aceleraciones filtradas a las medidas por el acelerómetro.

Las librerías de Android nos indican cómo hay que implementar este filtro:



En él, la constante “alpha” que se utiliza se calcula como:


En esta expresión, tau es la constante de tiempo del filtro paso bajo y dT la velocidad a la que los eventos son detectados. Las librerías Android nos aconsejan ponerle un valor de 0,8 a la constante “alpha”; como hemos comprobado que con este valor funcionaba, es el que estamos utilizando.

Conociendo ya este detalle importante, pasaremos a explicar el algoritmo que hay detrás de esta clase:

  1. Antes de empezar a almacenar información, comprueba que el evento detectado por el acelerómetro del móvil ha sido causado realmente por un movimiento hecho a propósito. Para ello, primero calcula el módulo de la aceleración lineal producida (restando el efecto de la gravedad):


    Se compara este valor con un umbral de 0,8 (establecido así de forma empírica) y, si no lo supera, consideraremos que seguimos en estado de reposo.

  2.  Cuando se supera el umbral de movimiento, pasamos al estado de almacenamiento de gestos. En él, se van tomando lecturas del acelerómetro, se les resta el efecto de la gravedad y se almacenan en la estructura que hemos creado. Todo esto se realiza mientras tengamos menos de 512 conjuntos de coordenadas (mientras la variable “index” sea menor que 512).

  3. Cuando la variable “index” toma como valor 512, ya tenemos información suficiente sobre el gesto realizado y se termina el almacenamiento.

  4. Sólo volveremos al estado de reposo del punto 1 si indicamos a través de la aplicación Android que vamos a realizar de nuevo un entrenamiento del mismo gesto o queremos enseñar otro gesto distinto.


Clase GestureFactory.java




Esta clase implementa toda la inteligencia en la que se fundamenta la fase de entrenamiento, ayudándose de la clase FactorySensorListener. Aquí podemos ver el diagrama de estados en el que se ve el funcionamiento conjunto de ambas clases (hacer click sobre la imagen para ver en grande):



Para comprender el diagrama, es necesario conocer los 4 métodos más importantes de esta clase:
  • public void startLearning(int type, int iterations, int idGesture):
    Llamamos a este método cuando queremos empezar el entrenamiento de un gesto determinado. Como parámetro se le pasa un entero asociado al tipo de sensor que vamos a utilizar (en nuestro caso, siempre tomará el valor 0, que identifica al acelerómetro), el número de iteraciones que hay que realizar de un mismo gesto para completar el entrenamiento (nosotros pediremos siempre 3 iteraciones) y un identificador del gesto que se va a enseñar (si será el de avanzar, retroceder, parar…).
    La principal misión de este método es crear la estructura de datos donde se almacenará la información sobre cada gesto: no será más que un nuevo array formado por las 3 estructuras que crea la clase FactorySensorListener y que ya hemos explicado:

     

    Al ser llamado, este método pone la variable “learning” a “true” para indicar que hemos empezado la fase de entrenamiento.

  • public void startIteration():
    Podremos empezar una iteración de un gesto sólo si anteriormente hemos llamado al método startLearning() (se comprueba si “learning=true”) y si hemos realizado menos de 3 iteraciones del gesto que estamos enseñando. Si no se cumplen estas dos condiciones, lanzará una excepción.
    Una vez que se han satisfecho ambas condiciones, se asocia el FactorySensorListener al acelerómetro para que empiece a almacenar los 512 conjuntos de coordenadas del movimiento realizado, como ya hemos visto anteriormente.

  • public void stopIteration()
    Simplemente incrementa el contador de iteraciones realizadas del gesto actual y elimina la asociación entre el FactorySensorListener y el acelerómetro.

  • public Gesture stopLearning()
    Este método lanzará una excepción si es llamado cuando no estamos en fase de entrenamiento (si “learning=false”) o si el número de iteraciones realizadas no es 3.
    Si no se da ninguna de estas condiciones de excepción, pone la variable “learning” a “false” para indicar que salimos de la fase de entrenamiento y devuelve un objeto de la clase AccelerometerGesture. A este objeto se le pasan como parámetro el identificador del gesto que hemos entrenado y el array bidimensional donde tenemos almacenados todos los valores medidos de coordenadas de aceleración en las 3 iteraciones.

En la siguiente entrada completaremos la explicación de las clases e interfaces de las que todavía no hemos hablado.



No hay comentarios:

Publicar un comentario