Blog dedicado a CREAR conocimiento sobre robótica, electrónica, proporcionando las herramientas necesarias para que puedas "hacer tú mismo" tu propio robot :)
Es obvio que una línea horizontal para estimar la dirección que sigue una línea frente a ti no es buena idea. En el siguiente post explicaré el motivo, pero en este me centraré en comunicar la modificación de uno de los umbrales para que éste no sea lineal.
El resultado es el que se muestra en el siguiente vídeo.
Más adelante explicaré en qué mejora tener uno u otro sistema.
Este artículo lo dejé como borrador hace tiempo y he decidido publicarlo tal cual lo dejé para que no se quede en el olvido. El orden lógico sería tras el artículo Sigue líneas visual con umbrales lineales con fecha 28 de abril de 2010.
Antes de ayer me informaron que el código del guardado de imágenes capturadas por la webcam no compilaba en Ubuntu 12.04.
Como en un principio el mensaje daba a entender que era problema de no tener instaladas las librerías (por el típico error "undefined reference") recomendé instalar todo lo relacionado con opencv-dev que se me vino a la cabeza que debería necesitar:
El problema persistía, por lo que para probar y arreglar el problema (a mí me funcionaba en máquinas Debian 5, 6 y Ubuntu 10.04) me instalé una máquina virtual y comprobé que realmente daba problemas el método de compilación propuesto en mi código:
redstar@redstar-virtual-machine:~$ gcc `pkg-config --libs --cflags opencv` -o webcam_jpeg webcam_jpeg.c
/tmp/cchQFB8g.o: In function `cvRound':
webcam_jpeg.c:(.text+0x19): undefined reference to `lrint'
/tmp/cchQFB8g.o: In function `cvDecRefData':
webcam_jpeg.c:(.text+0xa5a): undefined reference to `cvFree_'
webcam_jpeg.c:(.text+0xacb): undefined reference to `cvFree_'
/tmp/cchQFB8g.o: In function `cvGetRow':
webcam_jpeg.c:(.text+0xbc1): undefined reference to `cvGetRows'
/tmp/cchQFB8g.o: In function `cvGetCol':
webcam_jpeg.c:(.text+0xbec): undefined reference to `cvGetCols'
/tmp/cchQFB8g.o: In function `cvReleaseMatND':
webcam_jpeg.c:(.text+0xbff): undefined reference to `cvReleaseMat'
/tmp/cchQFB8g.o: In function `cvSubS':
webcam_jpeg.c:(.text+0xd1f): undefined reference to `cvAddS'
/tmp/cchQFB8g.o: In function `cvCloneSeq':
webcam_jpeg.c:(.text+0xd6d): undefined reference to `cvSeqSlice'
/tmp/cchQFB8g.o: In function `cvSetNew':
webcam_jpeg.c:(.text+0xdcc): undefined reference to `cvSetAdd'
/tmp/cchQFB8g.o: In function `cvGetSetElem':
webcam_jpeg.c:(.text+0xe5f): undefined reference to `cvGetSeqElem'
/tmp/cchQFB8g.o: In function `cvEllipseBox':
webcam_jpeg.c:(.text+0xf5f): undefined reference to `cvEllipse'
/tmp/cchQFB8g.o: In function `cvFont':
webcam_jpeg.c:(.text+0xfaf): undefined reference to `cvInitFont'
/tmp/cchQFB8g.o: In function `cvReadIntByName':
webcam_jpeg.c:(.text+0x103d): undefined reference to `cvGetFileNodeByName'
/tmp/cchQFB8g.o: In function `cvReadRealByName':
webcam_jpeg.c:(.text+0x10ce): undefined reference to `cvGetFileNodeByName'
/tmp/cchQFB8g.o: In function `cvReadStringByName':
webcam_jpeg.c:(.text+0x1128): undefined reference to `cvGetFileNodeByName'
/tmp/cchQFB8g.o: In function `cvReadByName':
webcam_jpeg.c:(.text+0x1158): undefined reference to `cvGetFileNodeByName'
webcam_jpeg.c:(.text+0x116e): undefined reference to `cvRead'
/tmp/cchQFB8g.o: In function `cvCreateSubdivDelaunay2D':
webcam_jpeg.c:(.text+0x11a1): undefined reference to `cvCreateSubdiv2D'
webcam_jpeg.c:(.text+0x11cb): undefined reference to `cvInitSubdivDelaunay2D'
/tmp/cchQFB8g.o: In function `cvContourPerimeter':
webcam_jpeg.c:(.text+0x12ff): undefined reference to `cvArcLength'
/tmp/cchQFB8g.o: In function `cvCalcHist':
webcam_jpeg.c:(.text+0x1327): undefined reference to `cvCalcArrHist'
/tmp/cchQFB8g.o: In function `main':
webcam_jpeg.c:(.text+0x1558): undefined reference to `cvCreateCameraCapture'
webcam_jpeg.c:(.text+0x15bf): undefined reference to `cvQueryFrame'
webcam_jpeg.c:(.text+0x1608): undefined reference to `cvSaveImage'
webcam_jpeg.c:(.text+0x1614): undefined reference to `cvWaitKey'
webcam_jpeg.c:(.text+0x163e): undefined reference to `cvDestroyWindow'
webcam_jpeg.c:(.text+0x164a): undefined reference to `cvReleaseCapture'
collect2: ld devolvió el estado de salida 1
Para arreglarlo simplemente hay que cambiar el orden de los parámetros y poner como primer parámetro el archivo del programa C que se desea compilar, acto seguido qué archivo ejecutable será generado y por último la referencia a los parámetros necesarios para usar includes y librerías de OpenCV:
Así que he aprovechado y he cambiado TODAS la cabeceras de mis aplicaciones publicadas en este blog para que estén en el orden correcto.
Si alguien conoce el motivo exacto por el que ha dejado de funcionar el método que usaba anteriormente agradecería que me lo hiciera saber, siempre he sido curioso y me gusta saber el porqué de las cosas y no aceptarlas porque sí.
Una buena parte del desarrollo software de mi proyecto correrá sobre una plataforma ARM, cuando lo más normal para los programadores es hacerlo en plataformas x86 o x86_64.
El motivo de este cambio es por la necesidad de desarrollar aplicaciones embebidas en dispositivos Android o Raspberry PI que están basados en procesadores ARM.
La primera toma de contacto con esta plataforma la he tenido al proporcionar soporte a un compañero de trabajo a la hora de capturar imágenes en un archivo guardado en disco.
Para este desarrollo haremos uso de una nueva función C llamada cvSaveImage. Pasaremos los parámetros de formato y calidad de compresión mediante una estructura CvArr.
Después de un año de transición desde el antiguo Máster Oficial en Sistemas Telemáticos e Informáticos, impartido en el campus de Móstoles de la URJC, al nuevo Máster Universitario en Sistemas Telemáticos e Informáticos, impartido en el campus de Fuenlabrada de la Universidad Rey Juan Carlos, retomo el proyecto dándole un pequeño giro a algunas cosas.
En un principio la plataforma JDERobot sobre la que se iba a trabajar era la versión 4.3.0. Actualmente está funcionando la versión 5.0.
La principal diferencia entre una y otra versión es el uso de un middleware para la comunicación entre componentes, permitiendo implementar drivers para todo tipo de sensores, robots, etc de manera más eficiente, abierta y sobre cualquier plataforma.
Puede usarse dicho middleware en lenguajes de programación como C, C++, Java, PHP, Python, etc.
Aprovecharemos la versatilidad del middleware para comunicarnos con el robot y enviar/recibir las imágenes captadas para su análisis y decidir si el flujo del movimiento puede ser estimado con fluidez suficiente en el propio robot (usando un móvil android) o bien el cálculo pesado debe seguir siendo tarea de un PC de sobremesa.
Por lo pronto a partir de ahora me volcaré más en implementar aplicaciones OpenCV basadas en Android.
Por otro lado, y de manera paralela, desarrollaré el control autónomo de un robot basado en un viejo coche teledirigido para las pruebas de campo.
El sujeto del experimento:
En la imagen se observa el coche teledirigido con los tres sensores de proximidad de largo alcance montados sobre él.
Placa de desarrollo IOIO:
En la imagen se observa únicamente uno de los sensores conectados a la placa de desarrollo.
Con muy poquitas modificaciones realizo ahora el seguimiento de dos puntos centrales de la imagen para calcular la rotación (balanceo) de la cámara.
El código de ejemplo es:
Con un par de modificaciones rápidas más (hardcoded) guardo las imágenes obtenidas en un vídeo AVI con FOURCC "XVID", 30 imágenes por segundo y con nombre de archivo "salida.avi". Lo ideal sería calcular la velocidad de cuadro real y/o enviar el mismo fotograma a la salida en caso de ralentización.
Ese tema lo dejaremos para más adelante.
El código de ejemplo modificado es:
En el siguiente vídeo puede observarse el seguimiento del horizonte realizado por el algoritmo.
Hay que recordar que no se hace corrección de perspectiva, por lo que si la webcam no sólo se rota (balancea) si no que se gira o cabecea, el cálculo del balanceo comienza a acumular errores. Por eso en el vídeo reinicio el horizonte en dos ocasiones.
He realizado una pequeña modificación para mostrar el movimiento acumulado (y representado por la flecha roja central) mediante una cruz verde.
Aparentemente se sigue el movimiento del punto de la escena, pero sólo funciona si la escena se mueve al mismo tiempo sin rotación y sin que haya un objeto en el centro que se mueva.
El código de ejemplo es:
El resultado de un movimiento satisfactorio (sin rotación ni objetos en la zona central que interfieran) puede verse en el siguiente vídeo:
Pero si realizamos rotación de la webcam o pasamos un objeto por el centro de la escena (pero no delante del objeto perseguido) entonces el algoritmo falla, ya que la información la está obteniendo del centro de la pantalla y no del objeto rastreado:
La forma de arreglar este problema es sencilla. En vez de dejar fijo el punto del fotograma anterior al que queremos calcular el flujo de movimiento lo vamos actualizando al mismo punto donde se ha calculado el desplazamiento.
El código del ejemplo mejorado es:
En el siguiente vídeo se muestra cómo ahora el algoritmo es más robusto frente a la rotación de la imagen, al movimiento de objetos en la zona central, pero no a los movimientos de objetos frente al objeto rastreado:
Vamos a dar un salto brusco en la línea de aprendizaje para llegar al análisis del flujo de movimiento usando la librería de visión artificial OpenCV.
En este ejemplo (algo más complejo que los anteriores) usaremos tres imágenes temporales, el fotograma actual en color sobre el que dibujaremos líneas que representarán el flujo de movimiento y una copia del fotograma anterior y actual en escala de grises, y dos imágenes de trabajo para las dos pirámides necesarias para la implementación piramidal desarrollada por Jean-Yves Bouguet del algoritmo iterativo de seguimiento de características de imagen de Lucas-Kanade.
Tiene como parámetros de entrada los dos fotogramas sobre los que deseamos estudiar el flujo de movimiento (en escala de grises), las dos imágenes temporales (pirámides), una serie de puntos sobre los que deseamos averiguar el flujo de movimiento, el vector de movimiento resultante y el criterio de parada.
El código de ejemplo es (le faltan unos retoques):
Se puede experimentar con diversos criterios de parada (número máximo de iteraciones y error permitido). Si se alcanza el número máximo de iteraciones sin haber llegado a un resultado que se ajuste al error deseado la característica será marcada como no calculada. Esto suele ocurrir, sobre todo, cuando se trabaja con zonas de color liso o durante movimientos bruscos en los que el emborronado de movimiento de la cámara (o webcam) suaviza los detalles de la imagen haciéndolos irreconocibles desde el fotograma anterior. Por otro lado, os recuerdo que una gran mayoría de webcams entregan los fotogramas en formato JPEG, por lo que para evitar que los artefactos cuadriculados de la compresión JPEG estorben en el análisis de movimientos debe usarse una apertura superior a 16 pixeles.