Integración de medios digitales Práctica de ejemplo de integración de medios: Introducción al empleo de técnicas de VxC mediante OpenCV Manuel Agustí y Vicente Atienza Curso 2009/2010 Esta práctica constituye una introducción al tema de procesado de secuencias de imágenes utilizando el API de OpenCV. En la presente práctica, se obtendrá una serie de resultados, código y ficheros de imágenes de resultados. Conteste en su espacio de PoliformaT a la tarea Práctica de VxC, incluyendo directamente los nombres de los que realizan la tarea y, bajo el nombre de cada actividad, el código de la operación involucrada en cada una. Utilice la orden make y el nombre del fichero que se indica como respuesta a cada actividad para generar el ZIP correspondiente. En el Anexo I se introduce de forma breve el entorno de trabajo visual multiplataforma que está disponible en el laboratorio, pero en este boletín no se asume uno en concreto y se indica cómo compilar asumiendo que se trabaja en línea de órdenes sobre GNU/Linux. 1. Introducción y objetivos La presente práctica está encaminada a ofrecer una perspectiva final de proyecto de integración. Para ello, se va a participar en la implementación de un prototipo de aplicación que utiliza las imágenes como medio principal de entrada y de salida de información. Los objetivos de esta práctica son: • Dar a conocer al alumno, de un modo participativo, las técnicas básicas de procesado de imágenes en mapa de bits. • Exponer un ejemplo de aplicación donde el uso de la información visual es la base para construir una aplicación multimedia interactiva. • Utilizar una plataforma de experimentación abierta y multiplataforma que permita al alumno proseguir su autoaprendizaje de forma independiente. A partir de la exploración de ejemplos de uso que se expone en el segundo punto, se introduce en el tercero las técnicas de procesado de imágenes aplicables individualmente a los puntos de una imagen; este apartado concluye con un ejemplo de aplicación que se propone como ejercicio: la determinación de la posición de la luz que emite un puntero láser (bajo ciertas restricciones) para realizar su seguimiento en pantalla. En el cuarto punto, se expondrá y se propondrá al alumno que lleve a cabo una operación de procesado en la que hay que utilizar la información de los puntos vecinos para extraer los bordes contenidos en una imagen. 1.1 Acerca de OpenCV Example applications of the OpenCV library are Human-Computer Interaction (HCI); Object Identification, Segmentation and Recognition; Face Recognition; Gesture Recognition; Motion Tracking, Ego Motion, Motion Understanding; Structure From Motion (SFM); Stereo and Multi-Camera Calibration and Depth Computation; Mobile Robotics. OpenCV es una biblioteca de código abierto para tareas de visión por computador originalmente desarrollada por Intel y publicada bajo licencia BSD, que permite que sea usada libremente para propósitos comerciales y de investigación. La biblioteca es multiplataforma, puede ser usada en Mac OS X, MS/Windows y GNU/Linux. En la web, se puede encontrar para descargar la última versión en el sitio web de Sourceforge [1], ejemplos y un rápido repaso en la “introduction to OpenCV” [9] y documentación en el OpenCV Wiki [2] y en el libro “oficial” de OpenCV: “Learning OpenCV: Computer Vision with the OpenCV Library” [3]. También se pueden encontrar fuentes de inspiración (ejemplos y explicaciones) en documentos como el Introduction to OpenCV [9], o páginas personales como la de “Blog de Damiles” [10] de D. Millán y en trabajos anteriores como el de “Trabajando con OpenCV” [11] de A. Ivars que muestra pequeños ejemplos de OpenCV, así como de OpenAL. Figura 1. Logotipos de OpenCV: el inicial de Intel (izquierda) y el actual del OpenCVWiki (derecha). 2. Programas de ejemplo Se desarrollarán pequeños programas para la tarea que se aborde en cada punto, En primer lugar se proponen una serie de ejemplos para familiarizarse con las funciones y el modo de trabajo de OpenCV. La librería se puede descargar del sitio web en [1]. En este apartado el trabajo a realizar es comprobar la utilización de las funciones de OpenCV: cómo se estructura y compila un programa que las utiliza, cómo se llaman algunas funciones típicas vistas en la clase de teoría, qué parámetros pueden recibir y qué resultados devuelven. Es aconsejable tener la ayuda siempre es bueno que esté a mano, a poder ser en local1, otras referencias útiles están disponibles en [2], [3] y [4]. Los siguientes ejemplos los encontrará en los enlaces junto al de este boletín. Descárguelos en local para proceder a su examen. Junto a cada ejemplo se le pedirá en un ejercicio que anote la descripción de las estructuras datos utilizadas y/o de las funciones de OpenCV, guárdelas en un fichero de texto plano con el nombre de los que realizan la práctica para entregarlo al final de esta. 2.1 Un primer programa En este ejemplo se describirá cómo se crea una imagen de grises y una de color, cómo se accede a los píxeles de ambas, cómo se averiguan las propiedades de una imagen (en este caso nos preocupamos sólo de las que afectan a la resolución de la misma), por último cómo se muestran en pantalla y cómo se liberan los recursos al acabar. Figura 2.- Captura de pantalla de la ejecución del ejemplo opencv_1.c En GNU/Linux es habitual que se encuentre en /usr/share/doc/opencv-doc/ref/opencvref_cv.htm. 1 El ejemplo corresponde con el fichero opencv_1.c, ábralo y familiarícese con su código. Su salida se puede ver en la Figura 2. En el laboratorio se compila con: $ gcc -I/usr/include/opencv -lcxcore -lcv -lhighgui -lcvaux -lml opencv_1.c -o opencv_1 y se ejecuta con la orden $ ./opencv_1 La ejecución termina al pulsar cualquier tecla, observe que no basta con cerrar las ventanas de resultados. Como se habrá observado, todo gira en torno a la representación en mapa de bits de las imágenes en la estructura IplImage, que no se muestra en su totalidad: typedef struct _IplImage { int nSize; /* sizeof(IplImage) */ nChannels; /* Most of OpenCV functions support 1,2,3 or 4 channels */ depth; /* pixel depth in bits */ int width; /* image width in pixels */ int height; /* image height in pixels */ ... int ... int ... ... char *imageData; /* pointer to aligned image data */ int /* size of aligned image row in bytes */ widthStep; … } IplImage; que son inicializadas como estructuras en memoria con la función cvCreateImage() y que se asignan valores a los píxeles de la imagen con la función cvSet2D accediendo a los elementos de la imagen por su número de columna (x) y su número de fila (y). Atención al orden en que se deben escribir las coordenadas: primero la y, luego la x. El tipo de cada componente es char (8 bits, sin signo) por lo que pueden tomar valores en el rango [0,255]. La implementación utilizada en el ejemplo se basa en las definiciones de variables del tipo de datos IplImage y las funciones .cvCreateImage, cvNamedWindow, cvShowImage, cvSize, cvWaitKey, cvDestroyAllWindows, cvReleaseImag, cvGet2D y cvSet2D. El esquema de trabajo utilizado en el ejemplo se basa en: ● Declarar las variables ● Inicializar la imagen con imgRGB = cvCreateImage(cvSize(AMPLE, ALT),IPL_DEPTH_8U, 1); ● Bucle ● Asignar un nivel de gris a cada punto de la imagen con cvSet2D. ● Liberar recursos y terminar De forma análoga, es posible escribir sobre las componentes de color (por ejemplo una que tiene los tres planos RGB) de un píxel de la imagen asignándole la memoria correspondiente y asignando valores a cada componente y utilizando la misma función. Así mismo, se pueden leer los valores de cada píxel de una imagen con la función cvGet2D e interpretando el contenido de color conforme indique la propiedad de número de componentes de esa imagen. Ejercicio 1. Modifique el código fuente de ejemplo opencv_1.c para que se cree también una imagen en color (RGB) y en la que aparezca una franja blanca horizontal de 10 píxeles en el centro de la misma. 2.2 Interacción con las ventanas En este ejemplo se exploran algunas funciones para la entrada de datos de los programas mediante las funciones de OpenCV. Este ejemplo muestra un imagen de color y la componente escogida con las teclas 'R', 'G' ó 'B' (para rojo, verde y azul respectivamente) como imágenes de gris. Además, para terminar el programa se puede utilizar la tecla ESC, 'q', 'Q' para salir. El ejemplo se llama opencv_entradaDatos.c. La salida del mismo se puede ver en la Figura 3. Compílelo y ejecútelo de manera análoga al ejemplo anterior. Figura 3: Captura de pantalla después de pulsar la tecla B. El esquema de trabajo utilizado en el ejemplo se basa en: ● Inicializar las imágenes. ● Bucle ● Esperar un tiempo máximo una entrada de teclado: cvWaitKey() ; ● Procesar de acuerdo a la opción del usuario. ● Mostrar la imagen procesada ● Liberar recursos y terminar De una forma más visual los datos que proporciona el usuario se pueden obtener mediante el uso de una (o varias) barra de desplazamiento asociada a la imagen. El ejemplo opencv_barraDespl muestra el uso de una barra para asignar un valor a todos los puntos de la imagen. El esquema de trabajo utilizado en el ejemplo se basa en el uso de las funciones cvWaitKey, cvCreateTrackbar, cvSet, cvAddS y cvZero El esquema seguido es: ● Incializar: declarar la variable de tipo imagen, inicializarla y mostrar la magen ● Crear una barra de desplazamiento con cvCreateTrackbar() ● Establece el rango entre [0, valorMax], el valor actual y la función encargada de actualizar el proceso a cada movimiento de la barra de desplaza-iento. ● Establecer un manejador para las acciones de ratón: cvSetMouseCallback() ● Bucle ● Donde siempre se cambian los valores de la imagen al valor activo. ● Liberar recursos y terminar Este ejemplo permite seleccionar el valor de gris de los píxeles de la imagen a partir del valor que escoge el usuario en la barra de desplazamiento. Figura 4: Captura de pantalla de opencv_barraDesplz en el momento de escoger el valor 223. Ejercicio 2. Modifique el ejemplo opencv_barraDesplz.c para que el píxel sobre el que se hace click con el boton derecho del ratón cambie su nivel de gris al más alto posible. Haga que este comportamiento se mantenga mientras el ratón esté pulsado y se mueva sobre la imagen, hasta que se suelte. 2.3 Primitivas de dibujo Este apartado muestra que se pueden crear contenidos visuales a partir de primitivas vectoriales que son llevadas a cabo sobre las imágenes en formato de mapa de bits dentro de OpenCV. Estas operaciones sirven desde para inicializar el contenido de la imagen, hasta para mostrar resultados al mismo (desde mostrar texto hasta remarcar una zona de la imagen). Hay que tener en cuenta que las coordenadas de dibujo tienen como origen de coordenadas (0,0) en la parte superior izquierda y toman valores crecientes en el sentido de los ejes, hasta alcanzar el máximo (ancho-1,alto-1) en la esquina inferior derecha. En la Figura 5 se puede ver, a la izquierda, el resultado de ejecutar el ejemplo opencv_graficsText.c. Figura 5: Primitivas básicas de dibujo. A la derecha de la Figura 5, una versión modificada del mismo va pintando aleatoriamente diferentes primitivas con diferentes parámetros y trozos de un mapa de bits que ha cargado previamente. Esto se realiza a base a ejecutar las diferentes instrucciones sobre una misma imagen, el mismo mapa de bits (la misma imagen) se visualiza de manera periódica para que se puedan ver las acciones realizadas sobre ella. El esquema de trabajo utilizado en opencv_graficsText.c se basa en el uso de las funciones.cvLine, cvRectangle, cvCircle, cvEllipse, cvFillConvexPoly, cvPolyLine y cvPutText., siguiendo el esquema: ● Inicializar una imagen. ● Pintar sobre ella: ● rectángulos: cvRectangle ● círculos: cvCircle y cvEllipse ● polilíneas: cvPolyLine ● una cruz: cvLine ● Mostrar la imagen y esperar una tecla ● Liberar recursos y terminar Ejercicio 3. Modifique el ejemplo opencv_graficsText.c para incluya una función void pintaCruz(imagen, fila, col, ancho, colorRGB) que pinte con cvLine una cruz centrada en las coordenadas indicadas, así como con el ancho y color recibidos. 2.4 Cargar y guardar imágenes estáticas en fichero Veamos ahora cómo manipular imágenes en mapas de bits, en los formatos conocidos por OpenCV, desde y hacia fichero utilizando las funciones CvLoad y cvSave. El esquema de trabajo del ejemplo actual (opencv_esFichero.c), mostrado en un momento de la ejecución en Figura 6, basado en el uso de las funciones cvLoadImage, cvSaveImage, cvSplit, cvCloneImage y cvCopyImage es: ● Inicializar las imágenes ● Cargar un fichero de mapa de bits variableImagen = cvLoadImage(nombreFichero, CV_LOAD_IMAGE_UNCHANGED); ● Procesar la imagen En este caso a partir de la tecla escogida obtener una de las componentes de color de la imagen original. ● Liberar recursos y terminar Donde nombreFichero es la ruta (relativa o absoluta) del fichero a procesar, incluida la extensión de la misma. Tenga en cuenta que si el fichero no existe o no se tienen derechos de acceso, la operación cvLoadImage devuelve NULL y no hay datos que procesar. Análogamente a la función de cargar un fichero de disco, se puede guardar el contenido de una variable de tipo imagen en disco con: cvSaveImage( nombreFichero, variableImagen ); Ejercicio 4. Incorpore el código necesario al ejemplo anterior para guardar la imagen generada en él y así realizar este proceso. ¿Qué sucede si no existe la ruta indicada o si ya existe el fichero? Justifique si es posible sustituir las funciones cvLoadImage, cvSaveImage, cvCloneImage y cvCopyImage por cvLoad, cvSave, cvClone y cvCopy (respectivamente), Figura 6: Ejemplo de captura de pantalla de la aplicación "opencv_esFichero". 2.5 Cargar y visualizar un vídeo Este apartado se centra en la manipulación de secuencias de imágenes en mapas de bits, en los formatos y CODEC conocidos por OpenCV, desde y hacia fichero. El fichero que se va a utilizar se llama opencv_player.c y se construye el ejecutable de manera análoga a los apartados anteriores. Por ejemplo, se pueden abrir ficheros sin comprimir en AVI o MPEG, permitiendo parar y continuar la reproducción de la secuencia (tecla 'r') o reiniciar la misma (tecla 'i'). Cuando llega al final se para y espera ESC, pudiendo ser reiniciada. El fichero de vídeo a utilizar se recibe como primer parámetro de la línea de órdenes. El esquema del proceso que utiliza opencv_player.c es el siguiente: El esquema de trabajo del ejemplo actual (opencv_player.c) es: ● Inicializar la fuente de vídeo: CvCapture, cvCreateFileCapture ● Bucle ● Cargar un cuadro de la secuencia de vídeo: cvQueryFrame ● Procesar la imagen: procesar( imgOrg, imgDst ) ● Liberar recursos y terminar. ● Observar que no se libera la imagen obtenida con cvQueryFrame por su naturaleza interna a OpenCV Ejercicio 5. Modifique el ejemplo opencv_player.c, para que obtenga una versión en grises del contenido del vídeo y muestre tanto la versión en color como la de niveles de gris en pantalla. Para ello habrá de utilizar uno de los modos de la función cvCvtColor, dependiendo del orden de los campos en la imagen: cvCvtColor( imgOrg, imgDst, CV_BGR2GRAY); ó cvCvtColor( imgOrg, imgDst, CV_RGB2GRAY); 2.6 Captura y procesado de imágenes estáticas a través de la cámara Veamos ahora cómo manipular imágenes en mapas de bits, obtenidas a través de una cámara con OpenCV, utilizando una cámara web como ejemplo más popular y usual de este tipo de periféricos. El fichero que se va a utilizar se llama opencv_camara.c y se construye el ejecutable de manera análoga a los apartados anteriores. El esquema del proceso del ejemplo actual (opencv_camara.c) es: ● Inicializar la fuente de vídeo: CvCapture, cvCreateCameraCapture. ● Bucle ● Cargar un cuadro de la secuencia de vídeo: cvQueryFrame. ● Quizás procesarla. ● Mostrar la imagen. ● Liberar recursos y terminar: cvReleaseCapture. Figura 7. Ejemplo de salida del código de "opencv_camara". Ejercicio 6. Incorpore el código necesario al ejemplo anterior para guardar la imagen generada, el resultado de aplicar un determinado proceso a la imagen capturada, en él y así realizar este proceso. 2.7 Procesar y guardar un vídeo En el anterior apartado se procede a partir de la carga de una imagen estática desde fichero o de la cámara, entonces se dispone de la información y se puede aplicar un procesado a la imagen en cuestión. Si se trata de una secuencia de imágenes (un vídeo), el procesamiento se aplica sobre cada uno de sus cuadros de forma repetida. Para ello deberá observase que en la secuencia de captura de imágenes desde la cámara o desde la lectura de un fichero, se ha de esperar a disponer de una imagen para entonces aplicar el proceso correspondiente. Este puede ser desde simplemente mostrarla hasta modificarla en función de lo que haya sucedido en relación con cuadros anteriores y mostrar un resultado distinto del contenido visual de entrada. El esquema del proceso del ejemplo actual (opencv_grab.c) guarda en un AVI (con el CODEC DIVX el resultado de reproducir un fichero de vídeo que recibe como parámetro. La secuencia de llamadas de funciones de OpenCV es: ● Inicializar la fuente de vídeo: CvCapture, cvCreateFileCapture / cvCreateCameraCapture y el destino de vídeo CvVideoWriter, cvCreateVideoWriter. ● Bucle ● Cargar un cuadro de la secuencia de vídeo: cvQueryFrame ● Quizás procesarla. ● Mostrar la imagen: cvWriteFrame. ● Liberar recursos y terminar: cvReleaseVideoWriter. Ejercicio 7. Modifique el ejemplo para que guarde como vídeo en MPEG el resultado, utilizando un número de cuadros por segundo de 30 y el CODEC: CV_FOURCC('P','I','M','1') 2.8 Histograma de una imagen. Un operador interesante en el caso de las imágenes es el histograma de la imagen: la función de distribución de los valores de los píxeles de la imagen. En este apartado nos centramos en cómo se declaran y manipulan. El ejemplo de uso mostrarHistograma.c muestra cómo calcular (con las funciones propias de OpenCV) este y cómo aplicarlo al caso de una imagen de niveles de gris, así como a una de color en base a obtener el histograma para cada componente por separado. Un ejemplo de la salida del mismo para una misma imagen en color y en niveles de gris se puede ver en la Figura 8. El código se encarga de diferenciar un caso o el otro examinando el número de componentes de la imagen que recibe como parámetro. Figura 8: Ejemplo de histograma para una misma imagen, en grises a la derecha y arriba y en color a la izquierda y abajo. El ejemplo indicado tiene la estructura siguiente (mostrarHistograma.c) es: ● Obtener una imagen ● Para cada plano o componente: ● Inicializar la estructura de histograma correspondiente a la imagen: CvHistogram, cvCreateHist(). ● Calcular el histograma: cvCalcHist() ● Mostrar el histograma, pintando una polilínea a partir de los valores del histograma: cvQueryHistValue_1D() ● Liberar recursos y terminar: cvReleaseHist(). La representación gráfica muestra para cada valor entre 0 y el máximo nivel para una componente (L), en el eje horizontal, el número de veces que aparece en la imagen reescalada con el valor máximo en la vertical para adaptarla al tamaño de la ventana donde se representa el histograma. Se puede obtener la versión en gris de una imagen en color RGB de diferentes formas, por ejemplo, si ● Recargamos la imagen desde fichero forzando la conversión imgGris = cvLoadImage(filename, CV_LOAD_IMAGE_GRAYSCALE); ● Cambiando de espacio de representación de color cvCvtColor( imgRGB, imgGris, CV_RGB2GRAY ); Ejercicio 8. . Partiendo del ejemplo mostrarHistograma.c, modifíque el programa principal para que el segundo parámetro de la línea de órdenes identifique una conversión de espacio color a aplicar sobre la imagen original. Teniendo en cuenta que: i) Se han de contemplar las siguientes conversiones: 0 no hacer conversión, 1 RGB a Gris, 2 RGB a YCrCb, 3 RGB a HSV y 4 RGB a HLS ii) Se ha de mostrar el histograma tanto sobre la imagen de partida como de la convertida, excepto en el caso de 0. Aunque no hay una operación análoga para modificar un valor del histograma, se puede cambiar un valor del vector de datos con: float *f; f = cvGetHistValue_1D(hist, i); *f = cvQueryHistValue_1D(hist,i) / maxHistograma; Actividad 0. Como resumen de este apartado, junte las respuestas de los ejercicios realizados en esta sección y los resultados. Comprimalo en un sólo fichero comprimido (en ZIP), con el nombre act0.zip. Entréguelo como respuesta a la “Actividad 0”. Utilice la orden $make act0.zip. 3. Procesos puntuales Son aquellos que se pueden realizar individualmente con la información de cada punto de la imagen. Nos servirán para entender algunas de las estructuras de datos que hay detrás de OpenCV para representar a las imágenes. 3.1 Añadir un nuevo procesamiento puntual: negativo digital Vamos a añadir un nuevo procesamiento puntual que realice el negativo de una imagen. Para lo cual se partirá del ejemplo opencv_esFichero.c copiándolo sobre un nuevo fichero de nombre negativo.c El negativo se consigue convirtiendo el valor de cada componente de color de la imagen original de acuerdo a la expresión valor_destino= L – valor_original siendo L el máximo valor que puede representar un punto de una imagen en la implementación. Actividad 1. Localice la función procesar y, utilizándola como base, sustituya esta por la función: void Op_NEGATIVO( IplImage *Fuente, IplImage *Destino) Una vez completada, construya el nuevo ejecutable y compruebe el correcto funcionamiento de la opción negativo digital. Si funciona correctamente, el negativo del negativo deberá devolver la imagen original. Responda a esta actividad generando un fichero comprimido (act1.zip), bajo el cual se incluya el código fuente realizado, así como el resultado de aplicarlo a la imagen de “rgb.jpg”, tanto en niveles de gris, como en RGB. Utilice la orden $make act1.zip. 3.2 Generación de imágenes binarias mediante umbralización Las imágenes binarias son aquéllas cuyos píxeles sólo toman dos valores “0” y “1” o cualquiera otros dos se se especifiquen, por ejemplo por facilidad de visualización en pantalla 0 y 255, para imagenes en niveles de gris. Un procedimiento habitual en el ámbito de la visión por computador para generar imágenes binarias a partir de imágenes de niveles de gris es el proceso denominado umbralización. Consiste en establecer un valor de gris denominado umbral. Todos los píxeles de la imagen original que superen el valor de umbral, se convierten en píxeles a “0” en la imagen binaria destino y el resto se convierten a “1” (o al contrario). Habitualmente la umbralización se emplea con propósitos de segmentación de la imagen, esto es, determinar o etiquetar partes diferenciadas en la imagen de acuerdo a un criterio (por ejemplo, un mismo color). Umbralizar es el caso más simple de segmentación, de forma que se consiga que todos los píxeles de la imagen con un determinado valor binario (una etiqueta para saber que pertenecen a la clase objeto) correspondan a partes del objeto de interés de la escena (o varias instancias de una clase de objeto) y el resto corresponden al “fondo” de la imagen. Vamos a añadir un nuevo procesamiento puntual que realice la conversión de la imagen en una imagen binaria (también denominada blanco/negro o B/N) donde los píxeles de la imagen se han reetiquetado con dos valores para indicar cuándo cumplen o no un cierto criterio. Por ejemplo puede tomar la salida mostra en la Figura 9. Figura 9: Ejemplo de salida del programa de binarización por umbralización. Actividad 2. Partiendo del ejemplo opencv_barraDesplz.c y copiándolo sobre un nuevo fichero de nombre umbraliza.c. a) Localice la función procesar y, en su lugar utilice la función void Op_UMBRALIZAR( IplImage *Fuente, IplImage *Destino) que habrá de editar para que tenga el comportamiento esperado. Asegúrese que la imagen fuente es una imagen de niveles de gris, antes de realizar las operaciones propias que permitan obtener la imagen binaria (B/N) que resulte de aplicar el criterio de si los píxeles de la imagen superan o no el valor:definido por la variable que guarde el valor umbral. b) Abra la imagen “gafas_1.bmp”. Conviértala a B/N. Observe el histograma de la imagen e intente deducir un valor de umbral adecuado para que en la imagen binaria aparezca bien definida la forma del objeto. Umbralice la imagen con diferentes valores de umbral y observe los resultados. c) Busque en la ayuda y anote la definición de la función .cvThreshold y los posibles valores del quinto parámetro de esta función.Realice una versión de este ejemplo que permita seleccionar tanto el valor de gris para aplicar el criterio, como el valor que se asigna a los puntos que cumplen el criterio de selección. d) Realice una versión de este ejemplo que permita seleccionar los píxels de nivel de gris exactamente con el valor que escoge el usuario en la barra de desplazamiento. Responda a esta actividad incluyendo el código fuente realizado y adjuntando los resultados sobre la imagen “gafas1.bmp” con el nombre act2.zip. Utilice la orden $make act2.zip. 3.3 Obtención de medidas en imágenes binarias En este apartado se implementará una función que permitirá obtener el centro de gravedad de un objeto que aparece en una imagen binaria. Como requisitos, supondremos que la imagen contiene un único objeto y que éste corresponde a los píxeles a negro (fondo blanco). El algoritmo que permite hallar las coordenadas (cdg_y, cdg_x) del centro de gravedad es el siguiente: suma_x = 0; suma_y = 0; n = 0; Para todo píxel f(x,y) de la imagen fuente si f(x,y) = ”0” entonces suma_x ← suma_x + x suma_y ← suma_y + y n←n+1 si n <> 0 entonces cdg_x = suma_x / n; cdg_y = suma_y / n; sino cdg_x = 0; cdg_y = 0; Actividad 3. Añada en el ejemplo umbraliza.c, realizado en el apartado anterior, la función Op_CENTRO_DE_GRAVEDAD( IplImage *Fuente, IplImage *Destino) e incorpore el código necesario para que calcule el centro de gravedad de una imagen binaria y lo represente. Para señalar la posición del centro de gravedad sobre la imagen Destino con una cruz. Para lo cual habrá de reexaminar del ejemplo opencv_graficsText.c. cómo dibujar líneas y trazar dos centradas en la posicición cdg_y, cdg_x. Localice con ayuda de este ejemplo el centro de gravedad de las gafas en las imágenes “gafas_1.bmp”, “gafas_2.bmp”, “gafas_3.bmp” y “gafas_4.bmp”. Responda a esta actividad incluyendo el código fuente realizado y adjuntando el vídeo resultante con el nombre act3.zip. Utilice la orden $make act3.zip. 3.4 Ejemplo de aplicación: ejercicio de localización de un puntero láser Abra la secuencia de trabajo “laser.avi” y reprodúzcala. En este punto se propone aplicar las técnicas que se han puesto en práctica anteriormente para resolver un problema práctico. Se trata de localizar la posición en la imagen del punto de luz proyectado por un puntero láser en movimiento sobre las superficies de una estancia cerrada. El problema puede resultar de interés para el desarrollo de aplicaciones de realidad virtual o aumentada, pizarras electrónicas, etc. A continuación desarrollaremos un proceso que localice la posición del punto láser aplicando técnicas de umbralización y cálculo del centro de gravedad. Como resultado, la imagen destino será una copia de la imagen fuente sobre la que se habrá marcado con una pequeña cruz la posición del centro de gravedad. Actividad 4. A partir del código desarrollado previamente en el apartado “Procesar y guardar un vídeo”, cree un fichero buscarLaser.c que contenga la función: void Op_BUSCAR_LASER( IplImage *Fuente, IplImage *Destino) Deberá insertarse el código correspondiente como cuerpo de la función indicada. Previamente, el programa principal puede incluir una etapa previa que se indica a continuación. Una vez completado, este proceso podrá ejecutarse y obtenerse el resultado en un fichero de vídeo (por ejmplo act4_secLaser.avi) que se recibe como segundo parámetro, así una posible llamada que ha de implementarse es: $ ./buscarLaser laser.avi act4_secLaser.avi. El primer paso consistirá en determinar el valor de umbral más adecuado para conseguir una imagen binaria en la que sólo aparezca el punto láser. Determine cuál de los tres canales de color ofrece una mayor diferenciación del punto láser respecto al resto de la imagen. Para ello visualice las tres secuencias a partir de la original mediante extracción de cada una de las tres componentes de color para determinar el canal más significativo. Visualice el histograma del primer cuadro de la secuencia correspondiente al canal elegido y a partir de él deduzca un valor de umbral adecuado para segmentar el punto láser por umbralización. Umbralice esta misma secuencia con el valor seleccionado y reproduzca la secuencia binaria generada. Compruebe que la segmentación del punto láser es correcta a lo largo de toda la secuencia binaria. Una vez determinados el canal y valor de umbral más adecuados, complete la función Op_BUSCAR_LASER. Básicamente deberá completar los siguientes pasos: 1. Umbralizar la imagen fuente sobre la imagen destino. 2. Calcular el centroide de la imagen destino binaria. 3. Copiar la imagen fuente sobre la imagen destino. 4. Señalar la posición del centroide con una cruz sobre la imagen destino. Responda a esta actividad incluyendo el código realizado y adjuntando el vídeo resultante con el nombre act4.zip. Utilice la orden $make act4.zip. 4. Procesos de vecindad En este último apartado se propone la implementación de un procedimiento de detección (realce) de bordes mediante un filtro Sobel. Los filtros son procesos de vecindad: para decidir el valor de un determinado píxel, se considera no sólo el valor que tenía originalmente ese píxel, sino también el valor de sus píxeles vecinos. El filtro Sobel considera, en principio, una vecindad de 3x3 píxeles. Como ayuda a su implementación, partiremos del estudio del código de opencv_convol.c la función donde existe una función: void Op_FILTRO3X3( IplImage *Fuente, IplImage *Destino) que implementa un filtro genérico, cuya máscara se define mediante la matriz float m[3][3]= {...}; Los filtros se pueden emplear para realizar efectos en las imágenes, pero en este caso los utilizamos para obtener información del contenido de la imagen, en particular los bordes: la detección de bordes o transiciones entre zonas de un mismo color en la imagen para posteriormente aislar lo que está dentro de ellos (los posibles objetos de interés en la escena) del fondo (la parte de la misma que “no tiene objetos de interés”). El ejemplo opencv_convol.c muestra cómo se aplica una mo filtro y la función, op_Filtro3x3, tiene la estructura siguiente: • Obtener una imagen de partida de niveles de gris: cvCreateImage() y cvCvtColor(). • Inicializar el filtro, tanto la estructura, como asignar la máscara : cvCreateMat() y cvSetData(). • Aplicar el filtro obtenido: cvFilter2Dt(). • Liberar recursos cvReleaseMat(). Para realizar otras operaciones no es suficiente con aplicar el filtro, en ocasiones hay que tener en cuenta posibles desbordamientos a realizar las operaciones, o normalizaciones de los valores resultantes, por lo que es posible que, en determinados filtros sea necesario ajustar también un factor de normalización (o divisor). En otros casos, es necesario combinar el resultado de más de un paso de convolución (con sus correspondientes filtros). El ejemplo opencv_sobel.c se obtiene una detección de bordes exhaustiva, como resultado de la función void Op_SOBEL( IplImage *Fuente, IplImage *Destino) que combina la detección de bordes verticales con los horizontales. siguiendo la estructura siguiente de la función op_Sobel: • Asegurarse que la la imagen de partida es de niveles de gris. • Inicializar variables auxiliares: cvCreateImage(). • Obtener la derivada en X: cvSobel(). • Obtener la derivada en Y: cvSobel(). • Combiar los resultados parciales: cvConvertScaleAbs(). • Liberar recursos auxiliares cvReleaseImage(). Actividad 5. A partir de estás ideas sobre el empleo de máscaras o filtros, realice: a) Sobre el ejemplo opencv_convol.c Cree tres versiones de esta operación void Op_detecPuntos( IplImage *Fuente, IplImage *Destino) void Op_detecLinHor( IplImage *Fuente, IplImage *Destino) void Op_detecLinVer( IplImage *Fuente, IplImage *Destino) tal que se pueda observar el resultado de aplicar filtros diferentes sobre la misma imagen original que se le pase como parámetro en la línea de órdenes. Modifique, en cada versión, los valores de los elementos de la matriz m para implementar los filtros estudiados de detección de puntos, de líneas horizontales y de líneas verticales, respectivamente. b) Realice un detector de esquinas a partir de combinar la información obtenida con el detector de líneas horizontales y el de verticales. A partir del código de opencv_sobel.c que implementa la función void Op_SOBEL( IplImage *Fuente, IplImage *Destino) c) Modifique la misma para que se visualice el efecto de cada máscara (bordes verticales y horizontales visualizados) por separado y la composición de bordes que se obtienen al aplicar las dos máscaras o filtros estudiados en clase de teoría y acumular correctamente los resultados parciales. Utilice el resultado devuelto por la función para ver un nuevo cuarto resultado que sea el AND lógico de la imagen de bordes final con la secuencia de imágenes RGB que se pueda obtener del fichero que se especifique como parámetro o de una cámara en su defecto. d) Incluya un control que permita variar, en tiempo de ejecución, el último parámetro de la función cvSobel, justificando los valores mínimo y máximo escogidos. Responda a esta actividad creando un fichero comprimido (act5..zip) que contenga: el código fuente realizado y las imágenes de los resultados parciales (detección de puntos act5p.jpg, de líneas horizontales act5lh.jpg, de líneas verticales act5lv.jpg, bordes verticales como act5v.jpg y los horizontales como act5h.jpg) y los nuevos resultados de detección esquinas (act5e.jpg ) y el final act5.jpg, la combinación de Sobel y la imagen original. Utilice la orden $make act5.zip. 5. Conclusión En esta práctica, el alumno habrá utilizado las imágenes, ya disponibles en fichero, para realizar procesos sencillos utilizando la librería OpenCV en, al menos, uno de los dos sistemas operativos disponibles en el laboratorio. Se han realizado una serie de actividades para evaluar la capacidad de aplicar los resultados parciales que se han ido mostrando y se ha trabajado en la obtención de resultados en fichero que demuestren lo realizado en las mismas. También habrá experimentado con las operaciones que permiten adquirir información visual de una cámara con la que tomar escenas del mundo real para utilizarlas de partida en la realización de una aplicación multimedia. Así mismo se habrán realizado operaciones donde la decisión u operación es función de los valores de los puntos vecinos a uno dado y que hay que ponderar en la medida que la acción lo requiera mediante la definición de un filtro. 6.Bibliografía y enlaces [1] “Open Computer Vision Library” <http://sourceforge.net/projects/opencvlibrary/>. [2] “OpenCVWiki” <http://opencv.w.illowgarage.com/wiki/Welcome>. [3] Gary Bradski y Adrian Kaehler, “Learning OpenCV: Computer Vision with the OpenCV”, O'Reilly Press, Octubre 2008. [4] OpenCV 1.0 API <http://www.cs.indiana.edu/cgi-pub/oleykin/website/OpenCVHelp/>. [5] “Computational Perception” <http://www.hci.iastate.edu/575x/doku.php?id=>. [6] R. C. Gonzalez y R. C. Woods, “Digital Image Processing”, Addison-Wesley, 1993. [7] A. de la Escalera, “Visión por computador. Fundamentos y métodos”, Prentice Hall, 2001. [8] “Annotated Computer Vision Bibliography”, <http://iris.usc.edu/Vision-Notes/bibliography/contents.html>. [9] Vadim Pisarevsky, “Introduction to OpenCV”, Intel Corporation, Software and Solutions Group. [10] D. Millán, “Blog de Damiles <http://blog.damiles.com/?cat=12> sobre OpenCV, entre otras cosas. [11] A. Ivars, “Trabajando con OpenCV”, trabajo de la asignatura IMD, curso 2k8/2k9 <http://websisop.disca.upv.es/~imd/2k8-2k9/Treballs/Treballs/trabajandoConOpenCV>. 7. Anexo I. El entorno de desarrollo Code::Blocks Code::Blocks <http://www.codeblocks,org> Code::Blocks is a full-featured IDE (Integrated Development Environment) aiming to make the individual developer (and the development team) work in a nice programming environment offering everything he/they would ever need from a program of that kind. Its pluggable architecture allows you, the developer, to add any kind of functionality to the core program, through the use of plugins... En el Wiki de OpenCV (<http://opencv.willowgarage.com/wiki/CodeBlocks>) se puede ver un resumen similar para trabajar con Code:.Blocks en MS/Windows, se observará que es análogo a cómo crear un proyecto en este entorno de desarrollo en la plataforma GNU/Linux del laboratorio que se muestra a continuación. Se puede empezar creando un proyecto desde cero con la opción de menú File | New | Project, con lo que aparece la caja de diálogo New from template, donde se habrá de escoger el tipo de proyecto (console application) y aceptar con el botón “Go”. A partir de aquí el asistente nos guiará por las opciones de cada uno, en nuestro caso escogeremos lenguaje de programación, directorio donde se ubicarán los ficheros, configuraciones para las versiones de desarrollo y de entrega, como se muestra en la siguiente secuencia de imágenes. Una vez abierto, el entorno ofrecerá una apariencia similar a la mostrada en la Figura 10.que muestra un momento de la etapa de depuración de uno de los ejemplos de la práctica. panel de edición panel de proyecto panel de salida Figura 10: Captura de pantalla de Code::Blocks durante la depuración del primer ejemplo de la práctica. Siempre se puede encontrar esta información para actualizarla en la sección de Management (el panel de proyecto) dentro de la pestaña Projects, el área de trabajo (Workspace) del proyecto en que estamos interesados y al pulsar con el botón secundario en el nombre del proyecto. Es interesante establecer algunas opciones más como que se genere información para poder depurar el proyecto, para ello hay que activar la opción correspondiente en el menú contextual del proyecto: apartado Build Options... Para poder utilizar las librerías de OpenCV se habrá de indicar en el menú contextual del proyecto: en su apartado Properties... > Libraries hay que escoger Known libraries “opencv” y utilizando el botón “<”, que está en el centro de las cajas de texto, aparecerá en Libraries used in project. Estas etapas se muestran en las cuatro siguientes imágenes. 7.1 Organización en paneles Como se mostraba en la Figura 10 el entorno se divide en tres paneles: Management, edición y Logs & others. Que se describen a continuación. El panel de proyecto (Management) permite seleccionar tres vistas diferentes del proyecto: 1. Projects Permite acceder a los archivos que componen el proyecto. 2. Resources Permite modificar los recursos de la aplicación (menús, iconos, cuadros de diálogo, etc.) 3. Symbols. Muestra la jerarquía de clases, así como los métodos y atributos asociados a cada una. En el panel de edición se realiza la edición del código fuente, los ficheros de cabeceras y de los recursos del proyecto. En el panel de salida se obtienen los informes generados por el proceso de compilación y depuración y los resultados de las búsquedas realizadas. 7.2 El Menú de compilación (Build) y depuración (Debug) Las opciones más interesantes para el desarrollo de esta práctica se encuentran centradas en las opciones de menú Build y Debug, estas se muestras desplegadas para familizarizarse con los atajos de teclado (Figura 11) y con los iconos de las respectivas barras de herramientas (Figura 12). De ellas destacamos unas pocas en cada grupo Figura 11: Opciones de menú desplegadas para compilación (izquierda) y depuración (derecha). Figura 12: Barras de herramientas de compilación (izquierda) y depuración (derecha). En el menú de compilación: • Build. Es la opción que se seleccionará para volver a crear el nuevo programa ejecutable cada vez que se realice un cambio en alguno de los archivos del proyecto. Recompila sólo los archivos que han sido modificados desde la última construcción del ejecutable. • Rebuild. Recompila el proyecto completo. • Select target > Set Active Configuration. Permite seleccionar la configuración activa: depuración (Debug) o distribución (Release). Para que pueda depurarse el programa es preciso haber seleccionado la configuración Debug. En el menú de depuración: • Start . Ejecuta el programa en modo de depuración. • Continue. Ejecuta de forma ininterrumpida. • Step into. Ejecuta paso a paso. Si se trata de una llamada a función, el proceso paso a paso continúa dentro de la función llamada. • Step out. Ejecuta el resto del código perteneciente a la función actual, hasta el retorno al punto del programa en que fue llamada. • Run to cursor. Ejecuta hasta la posición del cursor en el archivo actualmente en edición.