jueves, 13 de mayo de 2010

Último hito

Hola de nuevo, el jueves pasado tuvimos la última revisión del proyecto, antes de tener que examinarlo el miércoles 26, y para ella hemos preparado un pequeño video demostrativo sin audio en el que se puede ver la práctica ya casi totalmente terminada.
Como ya comentamos en entradas anteriores, el proyecto principal consiste en un jueguecillo en RA, en el que el sistema sigue el movimiento de las manos (siempre y cuando lleven guantes rojos), y sobre estas pinta una raqueta a cada jugador y permite jugar al pong.
En el vídeo podemos apreciar primero como el sistema ha de realizar un reconocimiento del usuario como ya se vió en su momento, y una vez que este se ha 'loggeado', se muestra un menú intermedio en el que se le permite al usuario tanto seleccionar si quiere jugar a nuestro pong o a las Damas, desarrollado por nuestros compañeros de la práctica CheckersBot, y una vez seleccionado el juego, lanza este. Finalmente, nosotros elegimos nuestro juego, y vemos como funciona este; además, podemos apreciar que mientras jugamos se nos muestra el marcador, y una vez que un jugador llega a 5 puntos, el juego se termina y se muestra en grande la puntuación final.




Después de este vídeo, hemos seguido retocando alguna cosilla para mejorar la inteactividad con el juego como es añadir un botón de salir al menú, una finalización correcta de los juegos, en la que al terminar se vuelve al menú, y ahora estamos tratando de meterle audio al programa para mejorar su interactividad, y por ahora no va demasiado mal la cosa, aunque hay que terminar de pulir algún punto...

Hasta la próxima! ;)

miércoles, 5 de mayo de 2010

Juego de RA, primeros pasos

Hola, ya que digamos que hemos conseguido hacer funcionar de manera medianamente decente el facedetect para tomarnos las imágenes e incluso los programas de haatraining, createsamples y performance, nos hemos metido en serio con el juego de RA, bueno, al menos con la parte gráfica, ya que el núcleo del juego ya lo empezó hace tiempo mi compañero y empezó ha hablar de el en un post anterior...

Bueno, ahora metiéndonos más en faena, cuando empezamos a mirar como hacer el seguimiento de las manos para poder jugar al pong, primero pensamos en hacer un haartraining para poder localizar la mano, pero vistos todos los problemas que nos ha causado el crear nuestros modelos para el reconocimiento, y visto que obtener las imágenes positivas y negativas se podía complicar hasta la saciedad, decimos desechar esta idea, ya que también, descubrimos un método que nos permitía mostrar en una imagen en blanco y negro, una imagen de la cámara siendo el blanco el color de la piel, lo cual nos resultaría muy útil para localizar la mano, pero ya que 'jugaba' con los colores, pequeñas modificaciones de la luminosidad, que podían incluso venir dadas por el simple hecho de mover la mano, hacía que se perdiera la representación de la mano como blanco provocando graves fallos en el 'tracking' de esta....

Finalmente, navegando por la red, y con las ideas adquiridas de las pruebas anteriores, encontramos este código libre que hemos modificado para que en vez mover el ratón por la pantalla mientras mueves el objeto a seguir muestre en la posición adecuada una raqueta para jugar a nuestro pong, así como para que en vez de necesitar que el usuario seleccione con el ratón el objeto a seguir, que el propio usuario haga que el sistema le siga la mano al 'coger' la raqueta que se encuentra en una posición fija. Para ello, ya que el sistema simplemente se basa en reconocer colores, y define su seguimiento en función de su variación, hemos optado por usar unos guantes rojos para así lograr que el sistema pueda seguirlos con facilidad.

Por otro lado, para mejorar el sistema de seguimiento, decidimos optar por, en la zona de búsqueda, quitar todos los canales de los otros colores dejando solo el rojo, lo cual lo logramos con el siguiente método:

void cogeRojos( IplImage* src, IplImage* dst )
{
CvScalar s;
for( int i = 0; i <>height; i++ )
{
for( int j = 0; j <>width; j++)
{
s = cvGet2D( src, i, j ); // coge el valor del pixel (i,j)
s.val[0] = 0;
s.val[1] = 0;

cvSet2D( dst, i, j, s );

}
}
}

Cabe destacar que este método nos ha ayudado a lograr un mejor tracking, menos dependiente de la iluminación, pero aun así, sigue teniendo fallos, como que por ejemplo, si el fondo es blanco, da un falso positivo, ya que al quitar los canales B y G de un blanco, este se convierte en rojo, aunque ahora estamos buscando una posible solución.

Finalmente, cabe destacar que estamos aunando la parte física con la parte gráfica, por lo que en cuanto lo tengamos, subiremos un vídeo de ejemplo.

jueves, 29 de abril de 2010

Video demostrativo

Hola, ya que el Jueves 29 tuvimos que presentar el estado del proyecto, hemos grabado un vídeo de prueba (sin audio) en el que se ven 3 procesos de identificación frente a los cuales el sistema siempre se va a encontrar.



Al principio del vídeo, salgo yo y, tras tomarme 20 imágenes, el sistema me identifica correctamente en las 20 fotos.
Después, sale un compañero que no aparecía como modelo para las muestras negativas y al que tras las 20 fotos, detecta en 2 que soy yo, y en las otras 18 dice que no soy yo, y ya que el sistema está pensado para que solo de por válida una identificación positiva, si al menos 17 de las 20 muestras tomadas son positivas, el sistema no lo identifica.
Por último, sale mi compañero, Alejandro, al cual, tras hacerle las 20 fotos de muestra, en ninguna de ellas da 'acierto' contra mi modelo, por lo que el sistema también lo descarta de esta identificación.

Pese a estos números tan prometedores, por desgracia el sistema no funciona tan correctamente como esperábamos, por lo que nos hemos visto obligados a repetir mi modelo con distintas muestras negativas, para ver si así podríamos mejorar su fiabilidad, por lo que en los próximos días esperamos tener ya los dos modelos en su versión definitiva y con un alto grado de fiabilidad.

domingo, 18 de abril de 2010

Problemas y Modelos

Hola de nuevo, ahora relataremos los problemas encontrados a la hora de usar los programas "createsamples.cpp", "haartraining.cpp" y "performance.cpp".

Createsamples: Es un programa que te permite generar archivos .vec de las muestras tomadas indicadas anteriormente y estos archivos valen para luego manejar el haatraining, que se explica a continuación.
Para empezar, el primer problema al que nos encontramos era una conjunción de dos problemas que no decubrimos hasta usar el haartraining, uno nuestro y otro propio del OpenCV al cual navegando por la red encontramos solución; primero, nosotros decidimos usar un tamaño de imagen de 220x270, el cual resulto ser demasiado grande y hacía saltar el programa por problemas con la memoria RAM, y no por falta física de esta en el equipo, sino porque el propio Visual Studio no permitía manejar más. Por otro lado, por un error en el programa cvhaartraining.cpp de OpenCv, las muestras tomadas por el createsamples han de ser cuadradas, aunque nosotros, una vez encontrado el código a modificar, pudimos manejarnos con muestras no cuadradas.
Finalmente, una vez descubiertos y corregidos todos estos errorres, llegamos a la conclusión, tras varios intentos de que el tamaño óptimo para las muestras generadas por el createsamples era un 20% de nuestas imágenes, previa conversión de estas a 210x270 píxeles, es decir, nuestras muestras ahora son de 42x57 píxeles, ya que nos permitía mantener la proporción de las imágenes y nos daba un tamaño suficientemente aceptable para trabajar con ellas.
Por otro lado, pese a que nuestros compañeros del año pasado explicaron bastante bien las variables de estos 3 programas, nos gustaría hacer alguna pequeña anotación al margen a estas explicaciones....
-width y height: son las dimensiones de la muestra, es decir, la imagen que le pasemos y que le indiquemos con el infoname adoptará ese tamaño, da igual si la imagen origen fuera de 1024x768 o de otras dimensiones, si los valores son de 42x57, toda la imagen quedará reducida a esas dimensiones, no se harán múltiples partes de 42x57 hasta cubrir toda la imagen, por eso es bueno que las dimensiones sean proporcionales a la imagen origen.

Haartraining: Con este programa, fué con el que más problemas hemos tenido, ya que es le que nos mostraba los problemas del createsamples.
El primer problema que hemos tenido, ha sido el ya explicado de que las muestras eran demasiado grandes.
Por otro lado, nos encontramos al usar tamaños de muestras muy pequeñas, de unos 5x5 o 10x10, el modelo entrenado era bastante poco fiable, lo cual nos llevó a la conclusión de que teníamos que hacer las muestras lo más grandes y proporcionales posibles ya que luego las imágenes que le pasaremos al sistema para testearlo serán más grandes, ya que por problemas con OpenCV que aún no hemos sabido como resolver, las imágenes que tenemos que introducirle al testeador, han de ser más grandes.
Por otro lado, nos encontramos con otro problema de origen desconocido que provocaba que al introducir un archivo con extensión .dat para las muestras negativas, en algunos casos, saltaba un error ya que un método del cvhaartraining intentaba coger una parte inexistente de una imagen. Para solucionar dicho error, probamos a usar como imágenes negativas ya el archivo formateado a .vec lo cual nos ha repercutido en la eliminación de dicho error así como en una reducción bastante considerable del tiempo de proceso del entrenador, ya que el cálculo que realiza antes de cada etapa de entrenamiento crecia rápidamente (llegando a superar los miles de segundos) si usábamos las imágenes en .dat, y si las pasábamos como .vec, este se mantiene constante por debajo del segundo.
Además, probamos a usar un sistema con varias divisiones modificando la variable maxtreesplits, pero esta solo aumentaba el tiempo de proceso de este programa y no encontramos grandes mejoras que justificaran ese proceso extra, ya que también, si no ajustabas bastante bien la variable minpos, muchas veces no realizaba todas las divisiones.
Finalmente, cabe reseñar que hemos logrado entender como funciona ya este programa y tenemos un modelo (el mío) medianamente válido operativo y ahora estamos generando dos nuevos modelos, uno para mi compañero Alejandro, y otro para mi con una serie de correcciones en las muestras negativas para tratar de mejorar la fiabilidad del actual, aunque el proceso de generación de modelos nos está provocando una gran carga de trabajo para el procesador que nos hemos visto obligados a solicitar ayuda al profesor que nos dirige la práctica, Fernando, para poder usar un par de ordenadores multinúcleo del laboratorio en los que ahora mismo se están generando ambos modelos con un tiempo estimado de duración de unos 7-8 días completos.

Performance: Este ha sido el programa que menos quebraderos de cabeza nos ha causado ya que es el último de toda la etapa de consecución de imágenes y modelos, aunque nos ha provocado un par de inconvenientes.
El primero ha sido que a la hora de testear nuestros modelos y por consiguiente futuras imágenes, hemos visto que no nos permitía hacerlo con imágenes de las mismas dimensiones que las empleadas para generar los modelos, por lo que nos hemos visto obligados a tocar las variables maxSizeDiff y scale_factor, detalle del que nos dimos cuenta tras varios intentos infructuosos de lograr que el modelo funcionara correctamente.
El segundo quebradero de cabeza ha sido el descubrir como hacer para modificar el umbral de decisión que usa el propio programa, que al final encontramos que modificando la variable scale_factor, se modificaba dicho umbral llegando a conseguir variaciones en la precisión del modelo que provocaban la no detección de ninguna imagen positiva hasta la detección máxima que el modelo es capaz de ofrecernos.

Más adelante, cuando tengamos los modelos definitivos, colgaremos alguna imagen de los 'logs' que generan por consola de comando los programas haartraining y preformance (el createsamples es irrelevante) con las explicaciones oportunas de que significa cada una de sus partes.

Por último, también queríamos reseñar que para agilizar la tarea del haartraining, decidimos activar el HyperTreading en todo el programa, ya que es una opción que permite usar el 100% de todos los nucleos de los que disponga el ordenador en el que se esté ejecutando el programa (siempre y cuando este esté programado para activar el HyperTreading).
Para hacer este 'upgrade', simplemente hay que irse al proyecto de Visual Studio deseado, que en nuestro caso fueron todos, y en Propiedades --> C/C++ --> Lenguaje, activar la opción de Compatibilidad con OpenMP.

Un saludo, y hasta la próxima.

martes, 13 de abril de 2010

Facedetect, los primeros pasos

Hola de nuevo, siento el parón informativo, pero modificando el facedetect.cpp, hemos tenido un par de etapas muy diferenciadas, una de rápidos avances, en la que el tiempo que le podíamos dedicar a la asignatura lo hemos invertido principalmente en avanzar, ya que los logros obtenidos eran bastante prometedores y visuales y nos animaban a seguir probando cosas. La segunda etapa, es la de la ofuscación, ya que tras los logros conseguidos en la primera parte, el avance se ha parado en seco, a la hora de manejar el haartraining, pero esto ya lo explicaremos con un poco más de calma en otro post...

Ahora, explicaremos que es lo que nos ha tenido entretenidos, mientras veíamos sus progresos y los pequeños problemas a los que nos hemos tenido que enfrentar, así como un poco el funcionamiento del facedetect.

Facedetect: es un programa propio de ejemplo de las librerías de OpenCV, y nos está sirviendo de base para la parte del reconocimiento visual. Este código, lo que realiza es la captura de una imagen obtenida mediante una webcam (también permite aplicarlo sobre imágenes almacenadas, u obtenidas por otros medios, pero es una parte que no nos interesa, por lo que no hemos indagado en ella) y detectar las posibles caras que en ella se encuentren, con un alto grado de detección, en detrimento de las falsas alarmas, ya que nos ha detectado en diversas ocasiones zonas arbitrarias como caras...

Recuadrar la zona de interés y guardarla: Para realizar esta parte, primero nos aprovechamos de que el propio programa pintaba un círculo sobre la cara detectada, por lo que nos aprovechamos de las dimensiones de dicho círculo, y su centro para establecer las cuatro esquinas de nuestro recuadro que se focalizará la cara, y nos ayudará a mejorar el sistema de detección de caras que estamos implementando, ya que así reducimos gran cantidad del fondo de la imagen que no va ha hacer más que meter ruido.
También, tuvimos problemas a la hora de salvar las imágenes, ya que los formatos que manejaba el facedetect, el Mat y cv::Mat, no permitían guardar la información como imagen ni convertirla al formato IplImage, que es el que si se puede guardar fácilmente, por lo que tuvimos que analizar el código, y modificarlo, ya que por suerte las capturas desde la webcam las realiza y guarda como variables IplImage.
a continuación, tras intentar manejarnos con punteros y extraer la información de estos, para poder modificar esta, acabamos teniendo que recurrir a la opción de guardar la imagen original y acto seguido cargarla de nuevo, en otra variable, para poder modificar esta sin afectar a la primera.
Además, por necesidades de los programas que nos permiten generar el sistema de reconocimiento, todas las imágenes de cara tenían que tener las mismas dimensiones, y hemos descubierto que es altamente recomendable que estén en escala de grises, lo que nos ha obligado a redimensionar las imágenes obtenidas, así como hacerles una conversión a escala de grises, que incluye un ajuste en el histograma (véase el código ejemplo)

Código:

Size s(210, 270);

IplImage* img = cvLoadImage(NOMBRE_IMAGEN, 1);

IplImage* img2 = cvCreateImage(s, 8, 1);

cvResize( img, img2, CV_INTER_LINEAR );

cvCvtColor( img2, img2, CV_BGR2GRAY ); //Convertimos a escala de grises

cvEqualizeHist( img2, img2 );//equalizamos el histograma a una imagen de un solo canal de 8 bit

cvSaveImage(NOMBRE_NUEVA_IMAGEN, img2, 0);


Por otro lado, también cabe destacar que como acabamos necesitando guardar gran cantidad de imágenes, nos valimos de una serie de comandos que hay en c++ para poder crear un bucle de captura en función de una variable de tipo int, la cual añadíamos como número, que no como caracter, al nombre del archivo a guardar, para generar una colección de imágenes con nombres legibles. Para esto, usamos el siguiente código:

for (int i = 0; i < 3000; i++){
char* nombre = "imagenPruebas";

std::string cont;

std::stringstream out;

out << i;
cont = out.str();

String extension = ".png";

String tot = nombre + cont + extension;

char *total = &tot[0];

}


Finalmente, cabe destacar que para evitar problemas, con los bordes de la pantalla, hemos introducido una serie de condiciones, que impiden guardar la imagen, si esta se encuentra en una posición que haría que el recuadro que seleccionamos, se salga de las dimensiones máximas de nuestra imagen total.

Superponer imagenes: Una vez que conseguímos guardar las imágenes, intentamos superponer unas encima de otras, para así poder realizar más adelante la parte de la realidad aumentada. Para esto, tuvimos que cargar una imagen y, pixel a pixel, pintarla sobre la imagen original, pero ya que las imágenes solo hemos descubierto como cargalas con fondo, hemos tenido también que hacer un filtrado de cada color, para saber cuales pintar y cuales no, aunque este método no funciona totalmente bien, ya que la imagen que pintamos además está redimensionada para ajustarla a la zona sobre la que la estamos pintando, que normalmente coincide con una parte del cuerpo y, en función de la distancia a la que nos encontremos de la cámara, nuestra imagen será más pequeña, y por ello, la imagen superpuesta ha de ajustarse.
Además, como parte de la imagen podía llegar a salirse de los límites de la imagen de 'fondo', nos vimos obligados a ajustar los valores de pintado, para que esta siempre se mostrara correctamente, y las partes que salieran de la pantalla, las mostrara correctamente 'cortadas' por los bordes de la imagen.

lunes, 5 de abril de 2010

El desarrollo físico del juego

Buenas despues de tanto tiempo sin actualizar, os vamos a contar los avances desarrollados en la práctica durante este último mes, aunque a partir de ahora actualizaremos más a menudo. Mientras Jorge se esta encargando del entrenamiento para la detección de caras, para ir avanzando, iré desarrollando la parte física de lo que será nuestro juego.

El primer problema que se planteó, como en cualquier juego, fue el simple hecho de empezarlo. ¿Cómo hacerlo?¿Por donde empiezo? ¿Como lo vamos a ligar a la otra parte?

Después de mucho pensarlo decidimos desarrollar el juego utilizando una matriz de dos dimensiones de numeros enteros rellenandola con ceros para los puntos en blanco, señalando con 1 los puntos criticos de la pelota, y con un 2 las palas.
Para ello creamos nuestra matriz
int TablaJuego[Pixelesfila][PixelesColumna]
Con esto ya tenemos la tabla ajustada al tamaño de la pantalla.
Además tambien hemos definido un RADIO de la pelota, que realmente será una cruz pero que nos dará los puntos donde la pelota puede chocar contra los bordes o las palas y un tamaño para la pala TAMANOPALA que momentaneamente y como una primera simplificación ocupará una columna.
for (int i=90; i<90+TAMANOPALA; i++){
TablaJuego [i][1]= 2;
TablaJuego [i][PixelesColumna-1] = 2;
}
Con esta inicialización estamos dejando una linea a la "espalda" de nuestro pala que será considerada como tanto del jugador contrario.

A partir de este momento se nos plantea la gran dificultad del juego. El movimiento de la pelota, sobre todo en los momentos críticos. Los golpes con la pala y los rebotes con las paredes. Y aún mas crítico será el momento en el que la pelota rebote y ademas se encuentre con la pala.

Asi pues recorreremos la matriz variando la posición de la pelota acorde a unas sencillas leyes como por ejemplo la que ilustra el siguiente fragmento de código.

ContinuaTrayectoria [0] = Trayectoria [0];
ContinuaTrayectoria [1] = Trayectoria [1];
TablaJuego [i+ContinuaTrayectoria[0]][j+ContinuaTrayectoria[1]] =1;
TablaJuego [i][j]=0;
i= i+ContinuaTrayectoria[0];
j= j+ContinuaTrayectoria[1];

Cuando el programa esté completamente operativo, se podrá observar, para quien quiera leer el código que se están estudiando todos los casos posibles.

Además en la última parte se ha incluido un pequeño marcador que variará cada vez que un jugador anote un gol.

Esperamos volver a escribir pronto.

miércoles, 10 de marzo de 2010

Los inicios

Hola, realmente esta entrada no refleja la actividad de una sola semana, sino la del comienzo de nuestro proyecto desde que elegimos realizar esta práctica, empezando por algo tan simple a primera vista que nos ha traído más de un dolor de cabeza. Para empezar, y tal como reflejamos en la presentación, nos instalamos el Visual Studio Express Editión 2008 y el OpenCV2.0 descargados de los sitios oficiales, y tras varios intentos, entre los que se incluyen reinstalaciones del Visual Studio por la versión completa del 2005, y la completa del 2008, conseguimos descubrir gracias a los profesores de la asignatura, que las librerías que teníamos no nos servían, ya que están pensadas para Linux (extensiones dll.a) y nosotros estamos trabajando en Windows. Para poder solventar este problema, tras probar con las sugerencias de diversas páginas webs, conseguimos que todo funcionara correctamente compilando nosotros mismos las librerías que nos pasó el profesor Juan Manuel Montero, y que podéis descargároslas desde aquí.

Finalmente, ya hemos conseguido que funcione todo y que compile correctamente, sin necesidad de acceder a la configuración del Visual Studio, y en no demasiado empezaremos a contaros nuestros primeros avances modificando el programa facedetect, que es con el que hemos decidido empezar nuestro proyecto, ya que nos facilita mucho el proceso de detección y reconocimiento facial, que es nuestro primer objetivo. Este programa reconoce caras señalándolas con un círculo de color azul. Nuestro próximo objetivo será realizar fotos automáticamente cada vez que detecte una cara, para introducir estas en una base de datos.