OpenCV
Contents
OpenCV
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 imprescindible 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 exhaustivo 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 imprescindibles 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.
- 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. Valores diferentes de cero hacen que el sistema espere esa cantidad de tiempo especificada antes de continuar con la siguiente instrucción.
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); return 0;
}</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 varios 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.
Eventos de teclado y ratón
Las interacciones del usuario con la aplicación que se jecuta son algo imprescindible en un programa. En este subapartado se mostrará la manera de realizar cambios en el comportamiento del programa a través de la pulsación de teclas o de los botones del ratón. También se verá cómo mostrar información relacionada con el programa sobreimpresa en las ventanas de vídeo. El ejemplo que se muestra a continuación realiza todas las acciones mencionadas.
<geshi lang=C lines=0>#include "cv.h"
- include "highgui.h"
- include "stdio.h"
- include "string.h"
CvFont fuente; CvCapture* flujo_video; IplImage* fotograma; char ventana[] = "prueba"; int coord_x, coord_y; char boton[100] = "Pulsado Boton ---", tmp[256];
void mouseHandler(int event, int x, int y, int flags, void *param) { switch(event) { /* boton izquierdo pulsado */ case CV_EVENT_LBUTTONDOWN: strcpy(boton, "Pulsado Boton Izquierdo"); break; /* boton derecho pulsado */ case CV_EVENT_RBUTTONDOWN: strcpy(boton, "Pulsado Boton Derecho"); break; /* movimiento del puntero del raton */ case CV_EVENT_MOUSEMOVE: coord_x = x; coord_y = y; break; } }
int main() { char pos_x[256], pos_y[256]; flujo_video = cvCaptureFromCAM(CV_CAP_ANY); int color_b = 255, offset = 15, tecla;
if( !flujo_video ) { fprintf(stderr, "ERROR: fallo en la captura\n"); getchar();
return -1; }
cvNamedWindow(ventana, CV_WINDOW_AUTOSIZE); cvInitFont(&fuente, CV_FONT_HERSHEY_SIMPLEX, 1.0, 1.0, 0, 1, CV_AA);
printf( "Teclas rapidas: \n"
"\tESC - sale del programa\n" "\tc - cambia color de cursor de azul a negro\n" "\tt - cambia tamaño de cursor de 15 a 30\n");
while(1) { strcpy(pos_x, "X = "); strcpy(pos_y, "Y = "); fotograma = cvQueryFrame(flujo_video);
if(!fotograma) { fprintf(stderr, "ERROR: no hay fotogramas...\n"); getchar(); break; }
cvSetMouseCallback( ventana, mouseHandler, NULL ); cvPutText(fotograma, boton, cvPoint(10, 80), &fuente, cvScalar(0, 255, 0, 0));
sprintf(tmp, "%d", coord_x); cvPutText(fotograma, strcat(pos_x, tmp), cvPoint(10, 400), &fuente, cvScalar(0, 0, 255, 0));
sprintf(tmp, "%d", coord_y); cvPutText(fotograma, strcat(pos_y, tmp), cvPoint(10, 440), &fuente, cvScalar(0, 0, 255, 0));
cvRectangle(fotograma, cvPoint(coord_x - offset, coord_y - offset), cvPoint(coord_x + offset, coord_y + offset), cvScalar(color_b, 0, 0, 0), 2, 5, 0);
tecla = cvWaitKey(10); if(tecla == 27) break; switch( tecla )
{ case 't':
if (offset == 15) offset = 30; else offset = 15;
break; case 'c':
if (color_b == 255) color_b = 0; else color_b = 255;
break; default: break;
} cvShowImage(ventana, fotograma); } cvReleaseCapture(&flujo_video); cvDestroyWindow(ventana); return 0; }</geshi>
Las partes más interesantes de este ejemplo corresponden al código de la función mouseHandler y al interior del bucle while. Este ejemplo muestra en pantalla la captura de flujo de vídeo realizada a través de la cámara por defecto del sistema y sobreimpresiona las coordenadas en las que se encuentra el cursor del ratón en la esquina inferior izquierda. En la esquina superior izquierda se imprimirá un mensaje indicando el botón que se ha pulsado del ratón y el puntero del mismo se mostrará como un cuadrado de color azul que cambiará de tamaño si se pulsa la tecla t o de color si se pulsa la tecla c. La pulsación de ESC nos permitirá abandonar el programa.
En la función mouseHandler, invocada mediante la retrollamada cvSetMouseCallback, se realiza la comprobación de la pulsación de los botones de ratón a través de la variable event que recibe como argumento. El fichero de cabecera highgui.h contiene la lista de todos los eventos de ratón que pueden ser capturados mediante la retrollamada, por lo que no se describirán en este trabajo.
Otra de las partes de que consta la función de retrollamada es la correspondiente a los valores de las coordenadas de la ventana activa en las que tiene lugar el evento. Estos valores se recogen y devuelven en las variables argumento x e y de la función. Con ellos se puede actualizar la posición del puntero del ratón en todo momento mientras se desplaza por la ventana. El argumento correspondiente al valor flags permite gestionar la pulsación simultánea de otras teclas del teclado como pueden ser SHIFT, CONTROL, etc..., y el valor param sirve para pasar argumentos extra a la función que no estén comprendidos en los parámetros anteriores. En este trabajo no se necesitará por lo que no se explicará su uso.
Dentro del bucle while tienen lugar una serie de operaciones que merece la pena comentar. La primera está relaccionada con el texto que se sobreimpresiona en la pantalla activa. La función cvPutText se encarga de ello. Esta función recibe como argumentos la imagen en la que se va a imprimir el texto, que puede ser estática como la de un fichero JPG o puede cambiar de forma contínua como ocurre con las imágenes que provienen del flujo de vídeo en el ejemplo. El segundo argumento corresponde a una cadena de caracteres que es el texto que se desea mostrar en pantalla. El texto a mostrar se puede manipular previamente mediante el uso de las funciones de cadena contenidas en la biblioteca de funciones estandar de C, como es nuestro caso. El tercer argumento que recibe la función es la posición en la que va a ser mostrado el texto. El punto tiene que ser del tipo cvPoint --no vale simplemente con introducir las coordenadas--. El origen de coordenadas para los puntos es el (0, 0) que corresponde a la esquina superior izquierda de la ventana activa.
El tipo de fuente que se va a usar en el texto se especifica con el valor fuente, que tiene que ser del tipo CvFont. Este argumento necesita ser inicializado primero antes de ser usado y la forma de realizar esto es mediante la función cvInitFont. Esta función utiliza como primer argumento el tipo de fuente. El listado de todos los tipos de fuente figura en el fichero de cabecera highgui.h. Los argumentos 2 y 3 corresponden a la escala horizontal y vertical de la fuente. Estos argumentos sólo pueden tomar dos valores, 1.0 ó 0.5. El cuerto argumento permite escribir el texto en itálica para valores mayores de cero y menores de uno --el máximo valor 1 correspondería a una inclinación del texto de 45 grados--. Los dos últimos argumentos ofrecen la posibilidad de especificar el grosor del texto --negrita-- y el tipo de la línea. La cabecera cxcore.h contiene más información relacionada con las funciones de texto. El color de la fuente se especifica en el último parámetro de la función cvPutText, concretamente con los tres primeros valores que siguen a cvScalar, que corresponden a los valores BGR del color.
Para dibujar el puntero del ratón se utiliza la función cvRectangle. Esta función recibe como argumentos la imagen sobre la que se va a dibujar el cuadrado, las posiciones de las esquinas superior izquierda e inferior derecha del cuadrado --que gracias al uso de la variable entera offset podemos cambiar su tamaño desde el teclado--, el color de los lados del cuadrado --especificados por los valores que encierra cvScalar entre paréntesis-- y el grosor de los lados del cuadrado. Si este valor es cero o negativo, el cuadrado (o rectángulo) dibujado aparecerá relleno. El valor 5 del ejemplo, que figura a continuación del 2 que define el grosor de las líneas, corresponde al tipo de línea a utilizar en el rectángulo. En el fichero de cabecera cxcore.h se puede encontrar más información relacionada con esta función.
Para finalizar este apartado se mencionará que la gestión de las pulsaciones de teclas se realiza mediante la función de OpenCV cvWaitKey ya vista anteriormente, por lo que no se realizará ningún comentario adicional de la misma. El código dentro de la sentencia condicional swtich es muy claro y no debería necesitar explicación alguna.