Isidoro-TFM-Rovio02
thumb|right|200px|Rovio, la cámara web móvil.
El trabajo que aquí se va a presentar trata sobre la aplicación del robot ROVIO de la empresa Wowwee Technologies a tareas de vigilancia. Para ello se utilizará la biblioteca de visión artificial OpenCV, que será la encargada de procesar la información proviniente de la cámara wifi de ROVIO en un ordenador portátil. Como sistema operativo de base para el desarrollo de esta aplicación se utilizará GNU/Linux, en concreto la distribución Ubuntu. La aplicación será desarrollada enteramente en lenguaje C.
Introducción
Pendiente de redacción...
OpenCV
Si se desea profundizar en OpenCV hay varias fuentes de donde obtener información. Una de ellas es el libro Learning OpenCV: Computer Vision with the OpenCV Library escrito por Gary Bradski y Adrian Kaehler y publicado por la editorial O'Really. En la página web de OpenCV, WillowGarage, también existe abundante información para quienes no dispongan del libro en cuestión. Y en la Red hay numerosa información relacionada con el uso de la biblioteca, que si bien puede estar más o menos desperdigada, también supone una buena fuente de consulta. Por último, los ejemplos que se incluyen en la biblioteca también son una fuente de informacación utilísima. En el desarrollo de este trabajo se han utilizado todas estas fuentes en mayor o menor medida.
Instalación de la biblioteca
Para realizar la instalación de cualquier aplicación o biblioteca en GNU/Linux lo más cómodo suele ser utilizar los paquetes correspondientes. Los usuarios de Ubuntu pueden usar Synaptic (Sistema/Administración/Gestor de paquetes Synaptic) que es la interfaz gráfica por defecto en estos momentos para gestionar los paquetes, aunque también se puede instalar desde la línea de comandos de un terminal:
<geshi lang=Bash lines=0>~$ sudo aptitude install libcv4 libhighgui4 libcvaux4 libcv-dev libhighgui-dev libcvaux-dev opencv-doc</geshi>
Los tres primeros paquetes corresponden a la biblioteca propiamente dicha, por tanto son imprescindibles. Los tres siguientes con el sufijo -dev son los ficheros de desarrollo que también los necesitaremos si queremos desarrollar aplicaciones con esta biblioteca. El último paquete contiene programas de ejemplo y documentación sobre OpenCV y aunque no es imprescidible para trabajar, sí que es recomendable como fuente de consulta. Si se quiere contar con la última versión de las bibliotecas se puede realizar una instalación manual de las mismas a partir de sus fuentes siguiendo el proceso descrito para tal fin en WillowGarage.
Operaciones básicas
Una vez que tenemos preparado el sistema con todo el software necesario, es el momento de la primera toma de contacto con OpenCV. Este apartado no pretende ser un tutorial completo y exahustivo sobre cada una de las funciones que posee OpenCV si no más bien un sendero abierto que conduce directamente hacia el objetivo principal de este trabajo. Y lo mejor es comenzar poco a poco de manera que nuestra primera incursión sea lo menos traumática posible. Por tanto, los primeros programas que vamos a desarrollar consistirán en una serie de operaciones básicas que conviene aprender bien al principio pues serán imprescidibles para el desarrollo de la aplicación.
Las primeras operaciones que se realizarán serán mostrar una imagen y un vídeo que están guardados en el disco duro e interceptar el flujo de vídeo de una cámara para mostrarlo en pantalla.
Mostrar una imagen en pantalla
El siguiente código nos muestra en pantalla la imagen que recibe como argumento en la línea de comandos.
<geshi lang=C lines=0>#include "highgui.h"
int main(int argc, char* argv[]) {
IplImage *img = cvLoadImage(argv[1]); // Carga la imagen en memoria.
cvNamedWindow("imagen", CV_WINDOW_AUTOSIZE); // Crea la ventana donde mostrar la imagen cvShowImage("imagen", img); // Muestra la imagen en la ventana.
cvWaitKey(0); // Espera que se pulse una tecla.
cvReleaseImage( &img ); // Retira la imagen de la ventana. cvDestroyWindow("imagen"); // Libera la memoria.
return 0; }</geshi>
Si guardamos este código con el nombre ejemplo_01.c y lo compilamos con g++ de la forma:
<geshi lang=Bash lines=0>~$ g++ -o ejemplo_01 ejemplo_01.c -Wall -I /usr/include/opencv -L /usr/lib -lm -lhighgui</geshi>
deberíamos poder obtener un fichero que ejecutaríamos mediante la orden
<geshi lang=Bash lines=0>~$./ejemplo_01 mi-imagen.xxx</geshi>
siendo mi-imagen.xxx el nombre del fichero con la imagen que queremos mostrar. OpenCV soporta formatos de imagen del tipo JPEG, BMP, PNG, PGM y TIFF entre otros. Si todo ha ido bien, se debería ver la ventana creada con la imagen en su interior. Si pulsamos una tecla, la ventana se cierra y finaliza el programa.
El código del ejemplo está bien comentado y apenas necesita explicación. Cada vez que se necesite mostrar una imagen en pantalla con OpenCV, el procedimiento será siempre el mismo, tal y como se ve en el ejemplo:
- Se creará una variable de tipo IplImage que guardará la imagen en forma de matriz de puntos y se indicará a la biblioteca que la cargue en memoria mediante la orden cvLoadImage.
- Se creará la ventana donde se va a visualizar la imagen. Esto siempre es necesario, siempre hará falta un soporte sobre el que colocar las imágenes. En todo lenguaje de programación que incluya interfaces gráficas es necesario un contenedor donde se ponen o colocan los demás componentes de la interfaz y OpenCV no es una excepción.
- Se pedirá a la librería que muestre la imagen dentro de la ventana correspondiente mediante la orden cvShowImage.
Una vez que ya no se necesite la imagen, será necesario liberar el espacio de memoria que ocupó la imagen para evitar saturarla. Esta operación se realiza mediante las órdenes cvReleaseImage que elimina la imagen de la ventana que la soporta y cvDestroyWindow que elimina de la memoria del equipo la ventana que contenía a la imagen mostrada.
En el ejemplo anterior se podría comprobar que se ha dado el nombre/ruta de una imagen como argumento en la línea de comandos antes de cargar la imagen para no producir un error en tiempo de ejecución. También se podría comprobar que la operación de carga de la imagen ha tenido éxito --o no-- mediante la adición de código extra, pero se ha preferido evitar esto para mantener el ejemplo lo más simple y claro posible para su mejor entendimiento.
Abrir un archivo de vídeo
En el siguiente ejemplo se tratará de abrir un archivo de vídeo almacenado en el disco duro del ordenador para que pueda ser visualizado en pantalla.
<geshi lang=C lines=0>#include "highgui.h"
int main(int argc, char* argv[]) { cvNamedWindow("ventana_video", CV_WINDOW_AUTOSIZE); CvCapture* flujo_video = cvCreateFileCapture(argv[1]); IplImage *fotograma;
while(1){ fotograma = cvQueryFrame(flujo_video); if (!fotograma) break; cvShowImage("ventana_video", fotograma);
char c = cvWaitKey(33); if(c == 27) break; }
cvReleaseCapture( &flujo_video ); cvReleaseImage( &fotograma ); cvDestroyWindow("ventana_video");
return 0; }</geshi>
En este ejemplo las cosas se hacen de forma ligeramente diferente a como se hacen en el anterior, por lo que merece la pena comentar estas diferencias. Una vez se ha creado la ventana contenedor para el vídeo con la orden cvNamedWindow, se crea una estructura que almacenará el fichero mediante la orden cvCreateFileCapture. Puesto que el vídeo es una sucesión de imágenes mostradas de forma contínua, necesitaremos acceder a cada una de esas imágenes para poder ir mostrándolas en pantalla. Para esto necesitamos crear otra variable, que llamaremos fotograma, que será la encargada de ir tomando de una en una las imágenes contenidas en el fichero de vídeo.
En el bucle infinito creado con la orden while(1) se realizan las siguientes operaciones:
- Se toma un fotograma del fichero de vídeo mediante la orden cvQueryFrame, que permite un acceso secuencial a cualquier flujo de vídeo tanto si está almacenado en un fichero como si proviene de una cámara.
- Se comprueba si se ha podido tomar el fotograma, en caso contrario se abandona el bucle --se ha producido un error debido, por ejemplo, a un fichero corrupto--.
- Se muestra el fotograma en la ventana creada al principio del programa de forma similar a como se hizo en el ejemplo anterior.
- Se esperan 33 milisegundos para comprobar la pulsación de una tecla y si esta tecla corresponde a la de ESC se abandona el bucle. En caso contrario se vuelve al comienzo del bucle de nuevo.
- El programa finaliza liberando la memoria ocupada por las variables creadas al principio para la gestión del fichero de vídeo.
Dos cosas hacen diferente a este ejemplo del anterior: el bucle que permite ir tomando una tras otra las imágenes almacenadas en el fichero de vídeo y que es necesario para mostrar todas las imágenes del vídeo, y el valor de 33 milisegundos que recibe la orden cvWaitKey. Si este valor es cero, el programa esperará a que se pulse una tecla para mostrar el siguiente fotograma.
Un apunte más sobre el manejo de ficheros de vídeo con OpenCV. Si bien el formato de vídeo por defecto que maneja OpenCV es el AVI, también se pueden abrir ficheros de otro tipo siempre que el sistema tenga instalados los codec adecuados. En caso contrario, el programa lanzará un error relacionado con el tipo de fichero que intenta abrir.
Capturar vídeo de una cámara
En el ejemplo que sigue se muestra en pantalla el flujo de vídeo proviniente de una cámara de vídeo o de un fichero dependiendo de si en la línea de comandos del terminal se llama al programa con un argumento o no --el argumento será el nombre del fichero de vídeo a mostrar--.
<geshi lang=C lines=0>#include "highgui.h"
int main(int argc, char* argv[]) { CvCapture* flujo_video; cvNamedWindow("ventana_video", CV_WINDOW_AUTOSIZE); IplImage *fotograma;
if(argc == 1){ flujo_video = cvCreateCameraCapture(0); } else { flujo_video = cvCreateFileCapture(argv[1]); } assert(flujo_video != NULL);
while(1){ fotograma = cvQueryFrame(flujo_video); if (!fotograma) break; cvShowImage("ventana_video", fotograma);
char c = cvWaitKey(33); if(c == 27) break; }
cvReleaseCapture( &flujo_video ); cvReleaseImage( &fotograma ); cvDestroyWindow("ventana_video");
return 0; }</geshi>
Como comentarios al ejemplo se mencionará que el código es similar al anterior excepto por la condición en la que se toma como fuente del flujo o bien la salida de la cámara conectada al sistema o bien el fichero de argumento. Esto es muy bueno para el programador porque le da libertad absoluta para decidir cuál será la entrada de vídeo que quiere usar sin complicar el resto del código, ya que es la propia biblioteca la que se encarga de todo el trabajo sucio relacionado con la gestión del fichero o la cámara.
El valor cero que recibe la orden o función cvCrateCameraCapture como argumento corresponde a la fuente u origen del flujo de vídeo. Normalmente cuando sólo se tiene una cámara en el sistema es suficiente con dar el valor cero que corresponde a la constante CV_CAP_ANY de los ficheros de cabecera de OpenCV. De esta manera se consigue que el programa realice una autodetección del driver necesario para la captura. Otros valores interesantes podrían ser CV_CAP_V4L y CAP_VAL_V4L2 que corresponden al controlador de vídeo compatible con sistemas Video for Linux --vídeo nativo de GNU/Linux-- y que equivalen ambas al valor numérico 200, o el valor CV_CAP_FIREWIRE --valor numérico 300-- que correspondería a una cámara de tipo firewire. En el fichero de cabecera highgui.h se relacionan todos los valores de drivers soportados. Según el libro Learning OpenCV si queremos que el programa nos pregunte qué tipo de cámara vamos a utilizar bastará con poner un -1 como argumento de la función de captura, aunque esto sólo estaría disponible para usuarios de Windows ya que no se ha conseguido durante las pruebas del código en Ubuntu.
Como comentario final sólo se añadirá que la línea assert(flujo_video != NULL) es una simple comprobación de que se ha podido o abrir el fichero o enlazar con la cámara. Es una comprobación útil y muy recomendable pues se podría dar el caso de que un determinado driver de cámara o códec de vídeo no estuviesen instalados en el sistema lo que produciría un error en la ejecución de nuestro programa.
Guardar vídeo en un fichero
Si lo que se necesita es guardar el flujo de vídeo procedente de una cámara en el disco duro, lo mejor es utilizar un código similar al que se muestra a continuación.
<geshi lang=C lines=0># include "cv.h"
- include "highgui.h"
- include "stdio.h"
int main(int argc, char* argv[]) {
CvCapture* flujo_video_entrada = cvCreateCameraCapture(0);
if (!flujo_video_entrada) { printf("No puedo acceder a la cámara"); return 0; } IplImage* fotograma = cvQueryFrame(flujo_video_entrada);
CvSize imgSize = cvSize( (int)cvGetCaptureProperty(flujo_video_entrada, CV_CAP_PROP_FRAME_WIDTH), (int)cvGetCaptureProperty(flujo_video_entrada, CV_CAP_PROP_FRAME_HEIGHT) ); // Dimensiones del fotograma
CvVideoWriter *flujo_video_salida = cvCreateVideoWriter( "flujo_salida.avi", CV_FOURCC('M','J','P','G'), 24, imgSize);
for (;;) { //Get a frame from the input video. fotograma = cvQueryFrame(flujo_video_entrada); cvWriteFrame(flujo_video_salida, fotograma); } cvReleaseVideoWriter(&flujo_video_salida); cvReleaseCapture(&flujo_video_entrada);
}</geshi>
Las partes más importantes de este código son la correspondiente a la función cvCreateVideoWriter y el interior del bucle for. La función cvCreateVideoWriter nos permite escribir en el fichero especificado --en este caso flujo_salida.avi-- con la codificación o códec MJPG todas las imágenes recogidas de la cámara detectada en el sistema a una tasa de 24 fotogramas por segundo. Las dimensiones de cada fotograma vienen definidas por el valor de imgSize, que se obtiene mediante los valores CV_CAP_PROP_FRAME_WIDTH y CV_CAP_PROP_FRAME_HEIGHT --en el fichero de cabecera highgui.h se pueden ver el resto de las propiedades disponibles del fotograma--.
Dentro del bucle for se produce la escritura propiamente dicha del fichero de vídeo mediante la función cvWriteFrame. Esta función escribe en el fichero que se le da como primer argumento la imagen o fotograma a la que se acaba de acceder desde la cámara del sistema. La actuación combinada de ambas funciones, cvCreateVideoWriter y cvWriteFrame, permiten en primer lugar, reservar espacio en la memoria del sistema para almacenar el vídeo y en segundo lugar, volcarlo al disco duro.
Si en lugar de utilizar la cámara del sistema como fuente del flujo de vídeo se utilizase otro fichero de vídeo, modificando el código de manera adecuada se podría conseguir un convertidor de formatos de vídeo. Como argumentos de la línea de comandos se podrían utilizar el nombre del fichero fuente y el del fichero destino incluida su extensión. Convendría recordar que el cambio de formato de vídeo depende de los códec que haya instalados en el sistema. Pretender convertir o guardar un fichero en un formato que no tiene los códecs instalados producirá un error de ejecución. Si en el valor del códec se utiliza un -1 --CV_FOURCC(-1)-- el sistema pedirá que se seleccione uno de entre vaios mostrados, pero sólo es sistemas Windows.
Se deja también propuesta la posibilidad de visualizar el vídeo de la cámara mientras se realiza la captura ya que en este ejemplo, como se habrá podido comprobar, se ha omitido esta posibilidad intencionadamente.
Seguimiento de colores
Pendiente de redacción
Anexos
Anexo A: Configuración de ROVIO
Pendiente de redacción
Anexo B: Comandos de control de ROVIO(?)
Pendiente de redacción
Anexo C: Hardware de ROVIO(?)
Pendiente de redacción
Referencias(?)
Pendiente de redacción
Enlaces Externos(?)
Pendiente de redacción