Boletín de prácticas

Anuncio
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.
Descargar