Visión artificial y aprendizaje
Practica 2: Sistema Inteligentes
Alvaro Cano Rocamora
Índice
Introducción
---------------------------------------- pag.3
La plantilla Cara
----------------------------------------- pag.3
----------------------------------------- pag.3
ImageFilter ----------------------------------- pag.4 PlantillaPractica2SI ------------------------ pag.4 Creación del algoritmo AdaBoost ------------------ pag.5 Hiperplano ----------------------------------- pag.5 Clasificador Débil --------------------------- pag.6 Clasificador Fuerte ------------------------- pag.8 Adaboost ------------------------------------- pag.9 Ejecución ----------------------------------------------- pag.12 Experimentación -------------------------------------- pag.13 BuscadorCaras --------------------------------------- pag.17 Planteamiento y diseño ------------------- pag.17 Ejecución ------------------------------------ pag.20 Experimentación --------------------------- pag.21 Conclusiones -------------------------------- pag.24 Bibliografía -------------------------------------------- pag.25
Introducción
La práctica consiste en crear un algoritmo de aprendizaje Adaboost para que pueda adivinar si una imagen es una cara o no. Para poder implementar el Adaboost, primero se tendrá que implementar un clasificador fuerte y los clasificadores débiles que lo componen
El proyecto se creara con el IDE Netbeans, a partir de la plantilla que se nos proporciona.
La plantilla La plantilla que se nos proporciona se encarga de gestionar todos los parámetros de entrada y la base de datos. Se compone de la clase Cara, ImageFilter y PlantillaPractica2SI, que viene a ser la plantilla propiamente dicha, ya que apenas tendremos que añadir código a ella.
Cara Esta clase recibe una imagen, que puede no ser realmente una cara, y la transforma en un array de enteros, que representan los pixels de la imagen en escala de grises (0 a 255). La imagen además es tratada con una máscara, para pasarla toda a un solo color. A esta clase se le ha añadido
un atributo llamado peso, necesario para el
funcionamiento del clasificador débil.
ImageFilter Esta clase filtra las imágenes de entrada, en este caso las de la base de datos, controlando que su extensión sea válida. También nos permite identificar el formato de las imágenes que le pasemos al programa.
PlantillaPractica2SI
<> Esta es la clase principal de la primera parte de la práctica, en ella se van a invocar al resto de clases y es donde tenemos la función Main(). Esta clase gestión además los parámetros de entrada, y realiza ajustes en base a estos, además de ser el punto de entrada del programa y recoger la ruta a la base de datos. La base de datos en, llamada facesDB, es un directorio compuesto por otros dos, llamados cara y noCara. Estos contiene respectivamente una colección de caras y otra de fotos de fondos, suelos,.. Y cualquier cosa distinta a una cara. Todas las imágenes tienen un tamaño de 24*24.
A partir de esta base de datos, la clase Practica2SI se encarga de crear dos listas para el Adaboost, una de entrenamiento y otra de testeo.
Creación del algoritmo Adaboost
El algoritmo Adaboost consta de un Clasificador fuerte, que a su vez esta constituido por X clasificadores débiles, cada uno con un hiperplano, por ello lo primero que tenemos que construir es la clase Hiperplano.
Hiperplano
El hiperplano seguirá la ecuación proporcionada en el pdf de la práctica
La clase hiperplano constara de un array de enteros (tipo double)
que
representara una serie de puntos en el espacio. Como las fotos serán de 24*24 nuestro hiperplano constara de un vector de dirección de 576 puntos. Estos puntos se generan aleatoriamente con un Math.random().
Ahora necesitamos obtener el termino independiente C, para esto obtendremos un punto al azar, teniendo en cuenta que al ser una escala de grises su valor tiene que oscilar entre 0 y 255. Este punto nos permitirá despejar la ecuación y obtener C.
Por ultimo necesitamos una función que asigne un punto a uno de los subespacios que crea el hiperplano, de forma que sepamos si el punto está por encima o por debajo. Esta función es la que tendrá que llamar después el clasificador débil.
La clase también incluye los Set y Get que permiten asignarle tantos unos puntos como un valor C arbitrarios. Con la clase hiperplano completa, podemos pasar a crear el clasificador débil
Clasificador débil Esta clase consta de un hiperplano, un valor de confianza y un valor de error. El hiperplano se genera automáticamente al llamar al constructor del clasificador, pero también puede asignase mediante get/set. El cometido principal de esta clase consiste en recibir una instancia de la clase Cara y evaluarla utilizando el hiperplano. Para esto tendremos la función evaluación que utiliza la fórmula:
Para evaluar los pixels de la cara con el hiperplano. Esta ecuación nos devolverá un número positivo si el objeto de la clase Cara es realmente una cara, o un número negativo si no lo es.
Pero dado que el hiperplano se genera al azar y puede ser mejor o peor a la hora de discernir si una imagen es cara o no, primero tendremos que “entrenar” el clasificador débil. Este entrenamiento consiste en ir pasándole la lista de caras dedicadas a entrenamiento e ir comprobando el resultado que nos da el clasificador: Con cada fallo se le asigna un error al clasificador, que consiste en la suma del peso de cada cara que falle. Este error además nos permitirá calcular el valor de confianza: α = ½ Ln (1-є/ є). Este valor es lo que nos indicara como de eficaz es este clasificador concreto, y nos será necesario para la ejecución del clasificador fuerte.
Clasificador fuerte El clasificador fuerte consiste en una batería de clasificadores débiles, todos entrenados con la misma lista de caras, de forma que cada uno tendrá un valor de confianza distinto según lo bueno que sea clasificando. El clasificador funciona evaluando con todos sus
clasificadores cada instancia de Cara que le pasemos y devolviendo su respuesta en función de las respuestas de estos últimos, de forma que si la mayoría de ellos dicen que la instancia de Cara es una cara, esa será la respuesta que se retorna. El valor de confianza entra en juego aquí, dado que cuanto más alto sea este valor, más importancia se le dará a la decisión toma por dicho clasificador débil.
El clasificador fuerte también implementa la función de guardado, que guarda la instancia actual de objeto en un fichero de tipo .obj, con el estado actual del momento del guardado. Este es el motivo de que tanto esta clase como la clase Débil y la clase hiperplano implementen el interfaz Serializable, así podemos guardar el objeto y cargarlo en cualquier otro proyecto siempre que este tenga incluido él .jar de Adaboost.
Con esto hecho podemos pasar a implementar el Adaboost
Adaboost
Adaboost no es una clase en sí misma, es una función contenida dentro de Practica2SI que devolverá una instancia de la clase Fuerte, con la que se trabajara. El algoritmo Adaboost funciona entrenando una serie de clasificadores débiles con la lista de aprendizaje para formar un clasificador fuerte.
Así, el algoritmo genera la cantidad de clasificadores débiles que le pasemos por el parámetro numClasificadores y se seleccionara el mejor de ellos. Este clasificador débil se entrena con la lista de entrenamiento y se le asigna un valor de confianza. Ahora el clasificador débil pasara a formar parte del clasificador fuerte. Una de las claves de Adaboost es que, después de añadir cada clasificador débil al fuerte, actualizara los pesos de las caras de la lista de forma que las caras en las que se ha fallado tengan más relevancia y en las siguientes iteraciones se les dedique más atención. El peso se actualiza utilizando la ecuación (siendo Z la normalización):
Cuando ht(i) es igual a yi el valor es positivo, con lo que el peso de la instancia Cara incrementa, en caso contrario, al coincidir el resultado será negativo y decrementara el peso. También podemos ver con el seudocódigo proporcionado que en caso de que obtengamos un error igual a 0 el bucle termina. Finalmente Adaboost nos devolverá un clasificador fuerte muy entrenado con el que podremos empezar a clasificar las imágenes. La llamada Adaboost la pondremos dentro de Practica2SI.init(), en la línea 81 como nos indica el pdf de la práctica:
Esto nos entregara el clasificador que tendremos que invocar en las líneas 111 y 123, donde se realiza la clasificación, primero de la lista de entrenamiento y después de la lista de test:
Al ejecutarlo, si todo funciona correctamente obtendríamos el resultado por la salida del Shell:
El esquema resumido en UML seria así:
Ejecución La entrada del programa es la clase Practica2SI, que puede recibir varios parámetros de entrada, algunos de ellos obligatorios. -d ruta
Este es el único parámetro obligatorio, tiene que contener la ruta al fichero facesDB.
-t testrate
Este parámetro define el porcentaje de caras que irán a parar a la lista de aprendizaje y a la de test, por defecto está fijado a 20%.
-T maxT
Este
parámetro
es
el
máximo
de
iteraciones que puede realizar Adaboost, por defecto está fijado en 10. -c numClasificadores
Es el número de clasificadores débiles que se añadirán al clasificador fuerte en el Adaboost, por defecto son 5.
-v
Fija el verbose como activado, por defecto
esta
desactivado
pero
se
recomiendo activarlo en cada ejecución
La ejecución más básica del algoritmo será java Practica2SI –d ruta.
Además cabe señalar que el clasificador se guardara automáticamente en la carpeta src del proyecto.
Experimentación y cuestiones -¿Cuál es el número de hiperplanos que se han de generar para que un clasificador débil funcione? En el caso del clasificador débil solo se necesita uno, ya que el C. débil solo contiene un hiperplano. Como el hiperplano es aleatorio, no se puede asegurar la eficacia. En el caso del clasificador fuerte:
Se han utilizado series de 10, 20 y 40 hiperplanos con porcentajes de 50% y 80% Alrededor de 20 clasificadores débiles (20 hiperplanos) los resultados empiezan a ser más positivos.
-¿Cómo afecta el número de hiperplanos generados al tiempo empleado para el proceso de aprendizaje?
Como podemos ver en la gráfica el nuero de hiperplanos aumenta exponencialmente el tiempo que tarda en completarse el algoritmo, añadiendo alrededor de 2,3 segundos por cada 20 hiperplanos mas con los que tiene que trabajar. -¿Qué importancia le darías? Depende del uso que se le quiera dar a la ordenación, en general diría que poca importancia porque por norma se suele generar un clasificador adaboost una vez y utilizarse para varias funciones o algoritmos, como por ejemplo el programa BuscadorCaras que no necesita que se genere un clasificador nuevo cada vez. -¿Se observa sobre entrenamiento? No se llega a observar sobre entrenamiento en los rangos que he estado probando, en la tabla se muestra un ejemplo, todas al 50%. Solo aparece el sobre entrenamiento si se “fuerza” a que aparezca, introduciendo parámetros mucho más elevados de lo necesario.
En conclusión se puede afirmar que si bien el clasificador puede llegar a ser muy eficaz clasificando imágenes, tiene cierta carencias: Si observamos la tasa de fallos, encontramos que dependiendo de los hiperplanos que genere y de los parámetros de entrada, suele rondar el 3% como máximo, pero prácticamente todos los fallos ocurren en imágenes no caras, pero que, entendiendo que el clasificador solo “ve” escalas de grises, todas la imágenes que causan error siguen un patrón de fondo oscuro y un centro de la imagen más claro
Programa BuscadorCaras El objetivo de la segunda parte es utilizar nuestro clasificador fuerte obtenido con adaboost para crear un programa que, al recibir una image, identifique y marque las caras que encuentre. Este proyecto solo consta de dos clases: la clase principal se llama BuscaCaras, y es la entrada del programa, se encargara de recibir por parámetro la ruta donde este almacenado el clasificador a utilizar y la imagen que se quiera escanear. Después tenemos una clase auxiliar arquetípica del estándar de Oracle, mostrarCaras, que nos permitirá mostrar la imagen en una ventana al terminar de funcionar nuestro algoritmo. Además, como el proyecto carga el clasificador usando serializacion, el proyecto Practica2SI está incluido como .jar, de forma que podemos utilizar sus clases, pero no modificarlas.
Planteamiento y diseño El primer paso que debemos realizar es cargar tanto el clasificador como la imagen. El primero no tiene mayor misterio porque podemos utilizar los métodos estándar de carga de objetos por serializacion.
La imagen en cambio tenemos que tratarla primero, asique utilizaremos parte del código de la clase Cara, de la parte anterior de la práctica.
Esto nos proporcionara un array de enteros con los pixels de la imagen, además de las medidas de estay un BufferedImage, que necesitaremos más tarde. El siguiente problema es que nuestro clasificador solo admite imágenes de 24*24, pero las imágenes que aceptara BuscadorCaras pueden ser de cualquier tamaño mayor. Para solucionar esto tendremos que recorrer la imagen seccionando trozos de 24*24 que nuestro clasificador analizara. Esto no podemos hacerlo a partir del array de pixels, pero si con BufferedImage y su clase getRGB(). Además de esto, tenemos que tener en cuenta que las imágenes con las que se ha entrenado al clasificador estaban tratadas con una máscara, por lo que nosotros también tendremos que tratar con la misma marcara a las secciones de 24*24 que le pasemos al clasificador:
Otro factor a tener en cuenta es que si recorremos pixel a pixel la imagen, cuando encontremos cara, al avanzar un solo pixel es muy probable que nuestro clasificador
marque otra vez la misma cara, incluso varias veces según la imagen, Para solucionar esto haremos que nuestro clasificador avance 24 pixels de una vez en el eje horizontal cuando encontremos una cara. Con todo esto, solo tenesmo que crear un doble bucle for que recorra la imagen:
Como podemos ver, se le pasan al clasificador recuadros de 24*24 y tratados con mascara, pero sin tratar con mascara la foto completa, solo una sección dentro de un array auxiliar. Así no estropeamos la foto original y podemos pintar un recuadro transparente si encontramos una cara. También tenemos que tener en cuenta los límites de la foto, que controlamos con un if, dado que la imagen no tiene por qué ser divisible exactamente en secciones de 24*24. En fotos pequeñas o con caras bien separadas podemos activar una función que pintara un recuadro alrededor, pero en fotos de aglomeración no es recomendable porque ensuciaría demasiado la imagen.
Ejecución El programa se ejecuta de manera similar a Practica2SI: Java BuscadorCaras –d rutafoto –t rutaclasificador -d rutafoto
-> La ruta donde tenemos guardad la foto a analizar
-t rutaclasificador -> La ruta donde se tiene almacenado el clasificador O utilizando NetBeans:
Además es necesario incluir como librería el proyecto Practica2SI
Con esto listo, podemos pasar a ejecutar y probar el programa.
Experimentación Vamos a probar a introducir esta imagen
Utilizaremos un clasificador fuerte que en el anterior proyecto nos ha dado un 99,78 de aciertos, configurado con 50 clasificadores débiles a un ratio del 25% y 120 iteraciones.
Como podemos ver, aunque el algoritmo sí reconoce caras y las marca, también comete fallos y marca coas como los edificios del fondo o los globos. En general podemos empezar a ver una pauta, ya que marca secciones de fondo oscuro con un tono más claro que se cruza por el centro.
Probemos ahora con una imagen más sencilla:
Y este será el resultado que obtenemos
Como podemos ver, aunque ha marcado las dos caras también ha marcado parte de las camisetas, justamente cuando tenemos una parte oscura cruzada con una mucho más clara.
Conclusiones:
La conclusión que se puede obtener es que, si bien el programa sí reconoce caras, está claro que no es lo suficientemente completo como para ser realmente eficaz. Algunas mejoras las podemos encontrar en el tema 13 de teoría. -Selección de características En una imagen de 24*24 tenemos más de 180000 posibles y tenemos que seleccionar unas pocas, como por ejemplo el cambio de color del puente de la nariz o los ojos. Esto permite identificar de forma mucho más fiable las caras que si solo nos guiamos por el color -“Cascada de atención” El algoritmo de “Attentional Cascade” consiste, a grandes rasgos, de una criba por niveles de las distintas imágenes, utilizando clasificadores menos precisos pero más rápidos para seleccionar las características más fáciles de reconocer. Aunque este método es difícil de implementar los resultados son prácticamente perfectos.
Además, como nuestro clasificador hace una búsqueda en profundidad por la imagen, el tiempo de ejecución es exponencial respecto al tamaño, tardando una media de 8 segundos en imágenes de 600*400.
Bibliografía
-Wikipedia.org -Apuntes de teoría temas 10 a 13 - enciclopedia_universal.esacademic.com - http://docs.oracle.com/javase/7/docs/api/javax/imageio/ImageIO.html