domingo, 2 de enero de 2011

Mezclando dos imágenes en una con OpenCV

No existe forma directa de mostrar dos imágenes dentro de una misma ventana usando las funciones ofrecidas por OpenCV, pero podemos hacer uso de la región de interés (ROI - Region Of Interest) para conseguirlo.

Con las regiones de interés podemos definir a qué parte de una imagen destino queremos "pegar" el contenido de una imagen fuente. De esta manera conseguimos "componer" la imagen destino deseada a partir de todas las imágenes fuentes necesarias.

En el siguiente ejemplo obtendremos un fotograma del dispositivo de captura y lo mostraremos a tamaño completo con una versión en miniatura del mismo fotograma en su interior.

Una cosa muy importante a tener cuando trabajamos con fotogramas capturados usando OpenCV es que no debemos modificar la imagen entregada por cvQueryFrame ni podremos reutilizar la imagen obtenida por sucesivos fotogramas capturados (explicaré mejor esto último en sucesivos ejemplos).

Por este motivo la composición se hará en una imagen clonada a partir del fotograma capturado usando la función cvClone. Tras ello definiremos la región de interés en dicha imagen clonada (el área de destino donde "pegaremos" de nuevo la imagen) y usaremos de nuevo la función cvResize para copiar una versión escalada del fotograma dentro de la región deseada.

Es importante recordar que la función cvClone aloja la memoria necesaria para almacenar tanto la cabecera de la imagen como su contenido, de modo que llamadas sucesivas a dicha función sin liberar los recursos previamente provocará que nuestra aplicación esté continuamente aumentando su consumo de memoria, de modo que no debemos olvidarnos de liberar los recursos reservados mediante una llamada a la función cvReleaseImage.

Código fuente del ejemplo:

Explicación en detalle paso por paso:
  1. Obtenemos un fotograma del dispositivo de captura en "fotograma".
  2. Creamos una copia del fotograma en "copia".
  3. Seleccionamos una zona de la imagen "copia" sobre la que volcaremos una versión escalada (una miniatura) del mismo fotograma.
  4. Realizamos el escalado de imagen sobre esa zona de interés.
  5. Deshacemos la selección de zona para que la función cvShowImage no nos muestre únicamente la región de interés (nos aparecería únicamente la zona recién escalada), si no la composición completa (el fotograma en grande junto con la miniatura.
  6. Tras mostrar la composición realizada en "copia" liberamos la memoria usada.

El resultado obtenido es:
Captura de imagen con OpenCV

Escalando el tamaño de un fotograma capturado usando OpenCV

Debido a que algunos algoritmos pueden consumir una cantidad excesiva de CPU impidiendo su ejecución en tiempo real (o, al menos, con una frecuencia de 10 veces por segundo), tenemos la opción de reducir el tamaño del fotograma a unas dimensiones que nos permita trabajar con un mayor número de fotogramas por segundo.

Para ello usaremos la función cvResize (escalado de imágenes).

Aquí tenemos un ejemplo en el que mostramos la imagen obtenida del dispositivo de captura, pero escalada al tamaño deseado:

Limitaciones en la captura de imágenes con OpenCV

Dependiendo del dispositivo de captura usado, la plataforma sobre la que se compile la aplicación (cada API de cada sistema operativo tiene diferentes limitaciones y ventajas) y el dispositivo de captura usado (no todos los dispositivos admiten cualquier resolución), podremos hacer uso de la configuración del alto y ancho de la imagen (resolución) que deseamos obtener del dispositivo de captura.

Esto nos permitirá, sobre todo, ahorrar ancho de banda del bus al que se encuentre conectado el dispositivo, disminuir el uso de CPU para procesar cierto tipo de imágenes (sobre todo si se trata de una cámara que ofrece unas resoluciones muy elevadas), aumentar el número de imágenes por segundo que podemos obtener del dispositivo, etc.

Para conseguirlo deberemos hacer uso de la función cvSetCaptureProperty para modificar los parámetros CV_CAP_PROP_FRAME_WIDTH (ancho) y CV_CAP_PROP_FRAME_HEIGHT (alto).

Podemos ver cómo hacerlo en el siguiente ejemplo:

Nota: La implementación de V4L2 en OpenCV 2.1 parece estar rota bajo Linux, por lo que falla el cambio de resolución. He encontrado parches para hacerlo funcionar de nuevo, pero confío en que lo arreglarán en breve espacio de tiempo.

viernes, 31 de diciembre de 2010

Usando una fuente de vídeo con OpenCV

Para usar una fuente de vídeo (webcam, cámara DV, capturadora, etc) deberemos hacer uso de las funciones cvCaptureFromCAM (inicialización del dispositivo de captura), cvQueryFrame (obtención de un fotograma) y cvReleaseCapture (liberar el dispositivo de captura).

Para mostrar la imagen de la webcam por pantalla haremos uso de las funciones cvNamedWindow (crear una ventana), cvShowImage (mostrar una imagen en una ventana previamente creada) y cvDestroyWindow (cerrar una ventana).

Por último, usaremos cvWaitKey para obtener la pulsación de una tecla antes del transcurso de un tiempo determinado para determinar el deseo del usuario de finalizar el programa capturando la pulsación de la tecla ESC.

El código es el siguiente:

Introducción al objetivo del proyecto

Una vez fijados los objetivos del proyecto de fin de máster, lo primero que debemos hacer (a parte de instalar toda la plataforma de desarrollo, incluido JDERobot) es comenzar a buscar un método suficientemente robusto para estimar el movimiento del robot usando únicamente un sensor óptico (sentido de la visión).

Debido a la calidad de las cámaras de vídeo (sobre todo en ambientes mal iluminados) deberán aplicarse filtros o usar algoritmos que presenten una buena robustez al ruido y que se adapten lo mejor posible al escenario propuesto (suelos lisos de la planta de un edificio).

martes, 4 de mayo de 2010

Cuatro pionners en el circuito de cheste

Como experimento fin-de-práctica y para comprobar la seguridad dinámica del algoritmo de navegación he probado a meter cuatro robots pionner en el circuito de cheste para ver qué pasaba.

Para empezar todo iba bien exceptuando que algunas veces si ambos robots decidían sortear el obstáculo por el mismo lugar, al ponerse lado con lado, cuando recuperaban el rumbo chocaban debido a que a ciegas (dándose la espalda parcialmente) se habían colocado uno junto al otro.

Para solucionarlo he aumentado ligeramente en 20 mm el margen crítico y en 50 mm el margen de seguridad. De ese modo los robots no llegan a tocarse, pero tienen dificultad para pasar por el pasillo superior izquierdo debido a que es casi tan ancho como el margen de seguridad.

Me gustaría implementar un estimador de movimiento para detectar objetos en movimiento frente al robot y evitarlos con más antelación que un objeto estático. He pensado en hacer un estimador lineal como el que uso para corregir la dirección del siguelíneas o uno diferencial comparando la diferencia con los ángulos vecinos. Si termino por implementarlo expondré los resultados en el blog.

sábado, 1 de mayo de 2010

Detalles de interés en la práctica de navegación VFF

La práctica la entregué el lunes pasado, pero hasta hoy no me había dado cuenta que no tengo nada en el blog acerca de los detalles de la lógica de decisión que actúa sobre el algoritmo de navegación de nuestro Fernando Robonso y su fórmula R.

Toda la lógica de navegación gira en torno a dos cálculos:

  • Campo de fuerzas virtuales (VFF - virtual force field): todo objeto alrededor del robot ejercerá una fuerza contra él que lo repelerá, evitando el obstáculo.
  • Pasillo de seguridad: el robot sólo avanzará si frente a él hay un pasillo lo suficientemente ancho para pasar.

La unión de ambos cálculos conformará la lógica de decisión que decidirá en qué dirección debe girar el robot basándose en las fuerzas repulsivas que recibe y a qué velocidad avanza teniendo en cuenta la longitud del pasillo que tiene frente a él.

La lógica de decisión está implementada en una máquina de estados finita que está compuesta de tres estados:

  • Estado "normal": Es aquél en el que el robot gira dependiendo de hacia dónde apunta el vector que forman las fuerzas de repulsión y la fuerza de atracción que siente del siguiente punto de control.
  • Estado "rechazo": Es el estado en el que entra el robot al encontrarse con fuerzas frontales que le impiden avanzar normalmente hacia el objetivo. El robot procurará "rodear" el obstáculo dejando las fuerzas de repulsión a un lado en vez de detrás ignorando completamente el vector de atracción del punto de control.
  • Estado de "estrechez": Es un estado especial en el que el robot reduce al mínimo su umbral del campo de fuerzas virtual de repulsión para que no oscile dentro de pasillos estrechos ni salte la repulsión dentro de él.

Quizá lo más complicado es ajustar los valores de los parámetros internos para "afinar" la navegación y ajustar los umbrales a los que el robot pasa de un estado a otro.

Durante todas las pruebas que he estado realizando he probado múltiples combinaciones (no todas de ellas se reflejan en el código final) y al final he implementado algunas mejoras secundarias para mejorar la navegación del robot.

Defensa de la calidad de la práctica:

  • No se chocará nunca con un objeto frente al robot: Aunque el robot se dirija a gran velocidad hacia una esquina pequeña, cualquier objeto dentro del pasillo de seguridad hará que el robot reduzca la velocidad, evitando choques accidentales.
  • No se chocará con un objeto lateral: debido al umbral crítico no se permitirá permanecer ningún objeto dentro del área que necesita el robot para maniobrar de modo que si el obstáculo no se mueve el robot no chocará contra él cuando gire en su sentido. En caso de que un objeto penetre el margen de crítico el robot dejará de moverse y entrará en estado de rechazo, evitando acercarse más al objeto debido a que el robot no se moverá hasta que pierda de vista el obstáculo que ha violado el espacio crítico.
  • No chocará contra el fondo de un pasillo sin salida: debido al margen crítico y de seguridad, el robot no avanzará hacia el fondo de un pasillo sin salida e intentará salir de él a través del estado de repulsión a no ser que el camino de salida sea largo.
  • Pasillos estrechos: el estado "estrechez" permite al robot pasar entre dos objetos que le dejen poco espacio para maniobrar, sin embargo como efecto secundario de los márgenes de seguridad y críticos el tamaño del pasillo mínimo es más elevado que en otras implementaciones precisamente por seguridad en la maniobra. Es decir, como el robot necesita un margen lateral más elevado para permitir que el "culo" no choque contra objetos mientras gira ésto limita el ancho del pasillo que considera el robot seguro para pasar.