Bueno, tras los exámenes he retomado el proyecto con como primer objetivo una vez conseguido la percepción de los objetos que se quedan quietos (vamos, conseguir una imagen binaria aislándolos) de detectarlos, esto es a partir de la imagen binaria obtener su posición.
Con esto me he atascado un poco porque se me ocurrían varias formas de hacerlo, pero no acababa de ver cual era la más apropiada para el problema, pero finalmente me decidí por una, aunque no estoy muy seguro de que sea la mejor opción no obstante sigo trabajando en esto pero aún no he obtenido resultados reseñables.
Mientras me pego un poco con las clases de c++ dejo el vídeo de GAVAB para que lo vea quien no lo halla visto (atención a la música, solo faltan Juanjo y Antonio corriendo a cámara lenta a lo David Jaseljof...jejeje).dddd
He comenzado a crear una clase historial para el fondo en la que en principio guardo un buffer de imágenes del fondo donde la ultima es la última tomada al actualizar el fondo y las anteriores corresponden a actualizaciones previas. Además con cada actualización almaceno una imagen obtenida restando la primera y la última del buffer y umbralizada con un valor umbral relativamente alto para evitar que aparezcan los pequeños cambios. Con esto consigo que aparezcan en blanco solamente los objetos que se quedan parados y sigo detectándolos durante n-1 actualizaciones (siendo n el tamaño del buffer).Aquí dejo un vídeo que muestra el resultado:
La imagen de la derecha va mostrando la imagen obtenida como he explicado anteriormente.
Este método me permite cuanto mayor sea el buffer poder distinguir mejor los objetos que se quedan quietos, pero al aumentar el buffer también aumenta la memoria utilizada y se tarda más en detectar los cambios.Tras esto me he propuesto dar más funcionalidad a esta clase haciendo que en cada actualización detecte los objetos que aparezcan y guarde en algún tipo de registro las posiciones de los objetos detectados, teniendo en cuenta que si un objeto no permanece en blanco en la imagen n-1 actualizaciones es que se ha movido (el número de actualizaciones que considerare para este caso probablemente sera menor de n-1 para evitar que algún tipo de interferencia provoque que se deje de detectar un objeto).
Os dejo el enlace de una noticia que he visto en un blog y me ha parecido interesante sobre un nuevo uso que le van a dar en PCs al procesador Cell que usa la PlayStation 3. Además trae un vídeo en el que usan un interfaz tipo "minority report".
Tras estar haber utilizado las funciones para actualización de fondos se me ha ocurrido cambiar la forma en la que obtenía las imágenes en binario con los objetos que se movían en la imagen en blanco y el resto en negro.
En vez de restas sucesivas con la imagen anterior, he aprovechado el echo de estar usando actualización de fondo para ir restando en cada iteración la imagen actual con el fondo. Esto me genera una serie de ventajas respecto a la implementación anterior, principalmente que de esta forma puedo obtener figuras sólidas y no siluetas como anteriormente y además las figuras siguen siendo detectadas cuando se quedan quietas hasta que no se integran con el fondo.
Como única desventaja he podido apreciar que esta técnica introduce algo de ruido, pero las ventajas me hacen pensar que esta forma me será mucho más útil a la hora de hacer el seguimiento y el ruido que surge es casi despreciable.
En el siguiente video se puede apreciar el resultado:
Tras esto me voy a dedicar a crear algún tipo de historial del fondo para detectar cuando se producen cambios, pero antes voy a rehacer mi código ya que debido a que he ido haciendo muchas pruebas con varias cosas al final ha quedado muy desordenado y complicado de depurar.
que a partir del historial creado con las funciones anteriores crea una estructura que almacena una secuencia de zonas de la imagen que se están moviendo en ese momento. Con esto he vuelto a aplicar la función que me da la dirección del movimiento pero en esta ocasión una vez por cada elemento de la secuencia y los he marcado en la imagen con un circulo y una línea indicando la dirección del movimiento como se puede ver en este vídeo:
9 y 10 de diciembre.
Por otra parte he cogido el ejemplo de actualización de fondos que probé anteriormente y lo he aplicado a mi código utilizando las funciones cvCreateFGDStatModel y cvUpdateBGStatModel de las cuales no he encontrado documentación alguna en los manuales ni en la wiki, parece como si no existieran, pero que compilan y funcionan.
Al actualizar el fondo con este método he podido comprobar como la velocidad del programa se hace insostenible, ya que tarda muchísimo en actualizarse el fondo, pero por otra parte los resultados obtenidos me han parecido muy interesantes para mis propósitos por lo que he decidido intentar hacer un "apaño".
Se me ha ocurrido que al actualizar el fondo no es tan vital que se haga continuamente, por lo que he decidido actualizarlo cada cierto número de frames para evitar que se ralentice el proceso en exceso. De esta forma he conseguido unos resultados mejores de los previstos, ya que se ha reducido el ruido que capta con el paso de la gente en el vídeo de la estación. Haciéndolo cada 50 frames si que me aparecían figuras oscuras con el paso de algunas personas, pero haciéndolo cada 100 frames prácticamente solo se aprecian cambios en la imagen cuando alguien se queda un rato en la misma posición y cuando se abandona un objeto.
Este es el resultado:
En el vídeo, aunque se ve un poco pequeño y mal se puede ver como solo cambia la imagen de la derecha(que es el background) cuando se queda quieto el chico de la mochila y cuando deja la mochila. Pero en el caso del chico como no se queda completamente quieto mucho tiempo no deja un rastro completamente sólido, al contrario que ocurre con la mochila.
Tras esto creo que ya tengo todas las herramientas para empezar a implementar un filtro de partículas.
Utilizando las funciones mencionadas anteriormente para convertir imágenes a escala de grises, hacer la resta de dos y pasar a binario, las he combinado en ese orden para obtener imágenes en las que solo aparece lo que va cambiando de las imágenes en blanco con fondo negro para usarlas en el filtro de partículas que debo implementar.
Antes de nada con esto me he dedicado a investigar las funciones de detección de movimiento de que dispone opencv. Con estas funciones he modificado mi código para que detecte la direccionalidad del movimiento en una zona concreta.
Con estas funciones y utilizando región de interés para limitar los resultados a una zona en concreto he calculado la dirección en la que se producen los movimientos en una zona y lo he mostrado mediante una linea como se puede ver en el siguiente video:
En la parte derecha se puede observar el historial de movimiento que se va actualizando con cvUpdateMotionHistory, a la izquierda se puede observar la imagen que va marcando la orientación creada a partir del historial con cvCalcMotinGradient y abajo está la imagen en binario en la que he dibujado una línea indicando la dirección del movimiento dentro del recuadro con el angulo obtenido decvCalcGlobalOrientation.
Una de las mayores dificultades que he tenido ha sido escoger los parámetros para pasarle a las funciones, ya que en los manuales no queda muy claro para que se usan algunos de ellos. Para resolver esto me ha sido de gran ayuda de un ejemplo que viene con opencv llamado motempl.cpp.
El otro gran problema que me ha surgido utilizando estas funciones es que creaba y liberaba en cada iteración las imágenes de historial y de orientación como venia haciendo con las otras imágenes usadas sin darme cuenta de que de esa forma no se actualizaba el historial, con lo que no obtenía resultados.
Una vez resueltos estos temas he utilizando las funciones para dibujar de opencv y repasando un poco de geometría básica he conseguido obtener correctamente la dirección del movimiento en una zona.
Ahora me he dedicado a acceder a los píxeles individuales de una región de las imágenes para por ejemplo calcular el número de pixels blancos dentro de dicha región, cosa que será de utilidad para desarrollar el filtro de partículas entre otras cosas.
Entre las distintas formas disponibles de acceder individualmente a los pixels he escogido la siguiente:
IplImage* img = cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1); int step = img->widthStep/sizeof(uchar); uchar* data = (uchar *)img->imageData; data[i*step+j] = 111;
Por lo que he leido esta es la forma más eficiente y sencilla de hacerlo, aunque también existe otra forma con C++ usando una clase que resulta aún más sencilla.
Además para probar esto sobre el ejemplo anterior le he añadido una "trackbar" a la ventana donde muestro las imágenes umbralizadas para poder variar el factor de umbralizado durante la ejecución y asi poder probar mejor el programa.
En el siguiente vídeo se puede ver el resultado:
El programa cuenta el número de pixels blancos que hay en la zona delimitada por el recuadro dibujado en la imagen y lo muestra por la consola con el número de pixels blancos del total del recuadro. Se puede apreciar como aumenta el número cuando hay más pixels blancos en el recuadro.
Pese a modificar la forma de ir leyendo las imágenes el proceso sigue siendo mucho más lento que con un vídeo, por lo que me he decidido convertir las secuencias de imágenes en vídeos para trabajar con ellos de una forma más cómoda.
En un primer momento cree un programa utilizando la propia libreria OpenCV con este fin, pero los resultados no fueron los esperados, ya que el programa creaba un archivo con extensión avi pero con 0 bytes. El código básicamente hace lo siguiente:
writer=cvCreateVideoWriter( opciones);
for i=1 to numImagenes { img=cvLoadImage(ruta); cvWriteFrame(writer,img); cvReleaseImage(&img); } cvReleaseVideoWriter(&writer);
No se porque no funciona, pero tras horas de intentarlo al final he utilizado un programa llamado VirtualDub que tiene licencia GNU y puede descargarse de www.virtualdub.org y finalmente he convertido las secuencias de imágenes en vídeos con facilidad.
Tras esto he probado los vídeos con mi código y he comprobado que funcionan bien y a una velocidad correcta.
También he aprovechado estos vídeos para probar el programa de ejemplo de actualización de fondos (background) y he obtenido unos resultados bastante interesantes que me pueden servir en el proyecto, pero la velocidad del algoritmo es bastante lenta y los vídeos no se muestran a una velocidad normal. Tengo que probar si esto se debe al resto de cosas que hace el programa o a la actualización de fondo en si.
Siguiendo con el ejemplo anterior he hecho que mi programa de prueba valla obteniendo la resta de cada imagen en escala de grises con la imagen anterior del vídeo con la función cvSub y valla mostrando la imagen resultante, elresultado es el siguiente:
La en la parte de superior se pueden ver las imágenes a color y en escala de grises y en la inferior la imagen resultado de la resta y en binario(de izquierda a derecha). El resultado de la imagen resta no es muy bueno ya que no se ven muy bien los cambios.
Tras esto he modificado mi código para que en vez de leer los frames de un vídeo valla leyendo las imágenes obtenidas de http://www.cvg.rdg.ac.uk/PETS2006/data.html que contienen ejemplos de abandono de equipaje en una estación de tren.
No se si por que las imágenes son más grandes que las de los vídeos que he usado hasta ahora o porque de la forma que lo he implementado el programa va leyendo los nombres de las imágenes de un archivo de texto, pero si mantengo las cuatro ventanas anteriores las dos que quedan más atras van a "tirones", por tanto próximamente cambiare la forma de obtener los nombres de los archivos para ver si es lo que ralentiza el proceso.
Por otro lado me he dado cuenta de que estaba haciendo las restas del revés y al cambiarlo he obtenido una mejora en las imágenes resultado de esta. El resultado es el siguiente:
A la izquierda se puede ver la imagen resultado de la resta y a la derecha en escala de grises.
Una vez convertidas las imágenes a escala de grises las he umbralizado convirtiéndolas a binario usando la función cvThreshold con el flag CV_THRES_BINARY o CV_THRES_BINARY_INV (segun se quiera una cosa o otra). Esta función tiene dos parametros de tipo double que marcan el "valor de Threshold" y el máximo, he ido probando y con valores alrrededor de 100 y 250 respectivamente se obtienen buenos resultados, pero depende mucho del tipo de video y sobre todo del brillo.
Una vez hecho esto he probado la función cvRectangle que dados dos puntos ( creo q el inferior izquierdo y el superior derecho ) y un color en RGB dibuja un rectángulo en una imagen, esto resultara útil a la hora de depurar el programa.