Visión artificial

Tekst
Loe katkendit
Märgi loetuks
Kuidas lugeda raamatut pärast ostmist
Šrift:Väiksem АаSuurem Aa

Unidad 4
FUNCIONES DE INTERFAZ GRÁFICA DE USUARIO

En este capítulo conocerá las funciones gráficas básicas de creación de interfaces de usuario proporcionadas por OpenCV. Las utilizará para dibujar líneas, delimitar áreas o superponer textos informativos que muestren visualmente los resultados del procesamiento y el análisis de imágenes realizados.

La primera de estas funciones tiene que ver con el manejo de ventanas. Veamos qué es lo que ofrece.

4.1 VENTANAS

Hasta ahora, las ventanas en las que ha mostrado las imágenes se han creado automáticamente con la función imshow(). Sin embargo, OpenCV permite más posibilidades de creación y configuración. Veamos cuáles son las principales.

Para crear una ventana, OpenCV proporciona la función:

namedWindow(ventana)

El argumento de esta función es el nombre de la ventana, que es el que aparece en su barra de título. Además, dicho nombre será el utilizado en la lógica del programa para identificarla y, de esta forma, poder asociarla a imágenes, eventos de teclado o ratón, barras de desplazamiento, etc.

Opcionalmente, tiene un segundo argumento que puede tomar los siguientes valores:

• WINDOW_NORMAL. Permite cambiar el tamaño de la ventana sin restricciones.

• WINDOW_AUTOSIZE. El tamaño de la ventana se ajusta automáticamente al de la imagen mostrada, no puede ser modificada de forma manual.

• WINDOW_OPENGL. La ventana se creará con soporte OpenGL.


OpenGL (Open Graphics Library) es un estándar que define una API multilenguaje y multiplataforma para el desarrollo de gráficos 2D y 3D.

Si lo que desea es mover una ventana a una posición concreta del escritorio, use la función:

moveWindow(nombre ventana, x, y )

El primer argumento es el nombre de la ventana que se va a desplazar a las coordenadas x, y fijadas en los otros dos argumentos.

Para redimensionar una ventana, utilice la función:

resizeWindow(ventana, ancho, alto )

En este caso, la ventana indicada en el primer argumento adquiere las dimensiones establecidas en los otros dos argumentos.

Para cerrar una ventana, llame a la función:

destroyWindow(ventana)

Si lo que quiere es cerrar todas las ventanas que se hayan podido abrir en el programa, use esta otra función:

destroyAllWindows()

El siguiente programa muestra la imagen del cuadro de la niña, pero con una serie de particularidades.


Lo primero que se hace en el programa después de importar la librería OpenCV es cargar la imagen con el cuadro de la niña.

img = cv2.imread(‘../imagenes/cuadro.jpg’, 1)

Luego se crea una ventana (llamada “Cuadro”) utilizando la función namedWindow() con el flag WINDOW_NORMAL, por lo que, a diferencia de cuando se creaba con la función imshow(), ahora podrá redimensionarla sin restricciones. Además, al cerrar la ventana, si ejecutara de nuevo el programa, aparecería con las mismas dimensiones que tenía cuando la cerró.

cv2.namedWindow(‘Cuadro’, cv2.WINDOW_NORMAL)

Una vez creada la ventana, se mostrará en la parte superior izquierda del escritorio.

cv2.moveWindow(‘Cuadro’, 0, 0)

Por último, se muestra la imagen en dicha ventana.

cv2.imshow(‘Cuadro’, img)

Ejecute el programa y compruebe que su funcionamiento es el descrito.

4.2 LÍNEAS

La función proporcionada por OpenCV para el dibujo de líneas (en realidad, se trataría de segmentos, ya que tienen un principio y un fin) es:

line(imagen, punto inicial, punto final, color, grosor)

El primer argumento de esta función es una imagen que representa el lienzo sobre el que se va a pintar la línea. El punto inicial y final son dos tuplas con las coordenadas x, y de los extremos del segmento. El color con el que se va a dibujar es otra tupla con los componentes B, G, R. El último argumento establece el grosor (en píxeles) de la línea.

El resultado de dicha función es la imagen pasada como argumento, sobre la que se ha dibujado la línea.

Si quisiera que la línea acabara con una punta de flecha, la función utilizada sería:

arrowedLine(imagen, punto inicial, punto final, color, grosor)

Como puede observar, los argumentos son los mismos de la función anterior.

En el siguiente programa, se utiliza la función line() para dibujar una cuadrícula sobre la imagen del cuadro de la niña, cargada previamente.


Tras importar la librería OpenCV y cargar la imagen almacenada en el fichero “cuadro.jpg” (situado en la carpeta “imagenes”), se obtiene su alto (alto) y el ancho (ancho) utilizando su atributo shape (el número de canales no se va a utilizar).

alto, ancho, _ = img.shape


Cuando un resultado no interesa, por convención se suele asignar a una variable llamada ‘_’.

Después, se declaran las variables que establecen el color (color) y el grosor (grosor) de las líneas. También se fija el tamaño de la cuadrícula en 80 píxeles (cuadricula).


Luego aparecen dos bucles for. El primero pinta las líneas verticales, y el segundo, las horizontales. Para ello, en el primero se recorre el ancho y en el segundo el alto de la pantalla, a intervalos establecidos por el tamaño de la cuadrícula. En cada uno de los bucles, las líneas se dibujan con la función line().

En el caso de las líneas verticales, la coordenada y del primer punto siempre es 0, mientras que la del segundo es el alto de la ventana. La coordenada x de ambos puntos es la misma, y fija la posición en la que se va a dibujar cada línea vertical. Para ello, toma el valor de la variable x que recorre el ancho de la pantalla en el rango de valores establecido en el primer bucle for.


Para dibujar las líneas horizontales, la coordenada x del punto inicial es siempre 0, mientras que la del final es el ancho de la ventana. La coordenada y (que es la misma en ambos puntos) sitúa cada línea horizontal a la altura indicada por la variable y, que recorre el alto de la pantalla según el rango de valores establecidos en el segundo bucle for.



En los bucles for se ha sumado uno al ancho y alto de la imagen para pintar la última línea, ya que de lo contrario quedaría excluida del rango.

La última sentencia muestra la imagen en pantalla.

cv2.imshow(‘cuadro’, img)

El resultado que obtendría si ejecutara este programa lo puede ver en la siguiente imagen.


El siguiente programa dibuja una flecha que apunta a la niña del cuadro.


Dicho código no requiere explicaciones adicionales, a excepción de la sentencia arrowedLine(), utilizada para añadir una línea roja de 4 píxeles de grosor, acabada en punta de flecha, entre los puntos (200, 100) y (300, 150).


Cuando ejecute el programa, obtendrá como resultado la imagen mostrada a continuación.


4.3 RECTÁNGULOS

La siguiente función que aprenderá a utilizar es la que permite el dibujo de rectángulos:

rectangle(imagen, punto inicial, punto final, color, grosor)

Los argumentos de esta función son los mismos de la anterior, solo que ahora los puntos inicial y final representan la esquina superior izquierda e inferior derecha del rectángulo. Además, si el grosor tomara el valor -1, el rectángulo se rellenaría del color del trazo.

¿Cómo se enmarcaría la cara de la niña del cuadro anterior? Con un programa similar al siguiente:

 

Dicho código no requiere de ningún comentario añadido, a excepción de que en la sentencia rectangle() las coordenadas se han elegido para situarlo sobre la cara de la niña. El resultado de su ejecución lo puede ver a continuación.


La función rectangle() se utiliza frecuentemente en combinación con técnicas de análisis de imágenes, como la de reconocimiento facial, que delimita el área donde se localiza la cara de una persona. En ese caso, será el propio algoritmo de análisis de imágenes el que calcule las coordenadas donde deberá situarse el rectángulo.

4.4 CÍRCULOS Y ELIPSES

Para pintar un círculo, la función que tiene que utilizar es:

circle(imagen, centro, radio, color, grosor)

En el caso de una elipse, sería:


En esta última función, los argumentos que requieren de una explicación adicional son los relacionados con la longitud de los ejes y los ángulos. En el primer caso, esta se expresa como una tupla cuyo primer valor corresponde al eje mayor, y el segundo, al menor, dado en píxeles. Respecto al ángulo, hace referencia al arco de la elipse que se va a pintar. Si esta fuera de 360 grados, se dibujaría completa, 180 grados correspondería a la mitad, etc. El ángulo inicial sería por el que empezaría a pintarse dicho arco, y el final, en el que acabaría.


En esta función, el grosor es un argumento opcional.

Para practicar con el dibujo de círculos, va a pintar una diana sobre la imagen del cuadro de la niña. Su código es el siguiente:



Este programa no es difícil de entender. Lo único que sería necesario comentar es que todas las circunferencias con las que se dibuje la diana tendrán como centro el de la propia imagen. De ahí que se utilice el atributo shape para obtener el ancho y alto, ya que el centro de la imagen será el punto:

(ancho/2, alto/2)

Puesto que una división devuelve un valor de tipo float y los píxeles de una imagen deben ser números enteros, tendrá que realizarse una operación de casting para obtener las coordenadas x, y del centro de la imagen (centro_x y centro_y).


El bucle for que hay a continuación establece el rango con los valores del radio de la circunferencia que se va a pintar en cada bucle con la función circle().


Observe el resultado obtenido.


4.5 TEXTOS

Para escribir textos sobre una imagen, la función que deberá emplear es:

putText(imagen, texto, posición, fuente, escala, color, grosor)

El primer argumento de esta función es la imagen sobre la que se va a escribir el texto indicado en el segundo argumento. La posición es una tupla que especifica las coordenadas x, y donde empezará a escribirse. La fuente puede ser cualquiera de las identificadas con las siguientes constantes:

• FONT_HERSHEY_SIMPLEX

• FONT_HERSHEY_PLAIN

• FONT_HERSHEY_DUPLEX

• FONT_HERSHEY_COMPLEX

• FONT_HERSHEY_TRIPLEX

• FONT_HERSHEY_COMPLEX_SMALL

• FONT_HERSHEY_SCRIPT_SIMPLEX

• FONT_HERSHEY_SCRIPT_COMPLEX

• FONT_ITALIC

Las seis primeras fuentes son variantes de sans-serif, las dos siguientes simulan una escritura a mano. La última es la cursiva.

Respecto a la escala, este argumento es el factor por el que se multiplicaría el tamaño base de la fuente. Los últimos argumentos son el color y el grosor (opcional) con el que se va a escribir el texto.


Esta función solo admite un pequeño subconjunto de caracteres ASCII, entre los que no se encuentran caracteres especiales como los acentos o la letra ‘ñ’. Para mostrarlos, tendrá que hacer uso de la librería PIL.

En esta última práctica va a mejorar el programa en el que se señalaba a la niña del cuadro con una flecha, para indicar que se trata de Isabel. El código resultante es el siguiente:



De este programa la única sentencia que es necesario explicar es la que escribe el nombre de la niña (“Isabel”) comenzando en la posición (100, 75), con un tipo de letra que simula estar escrita a mano (FONT_HERSHEY_SCRIPT_SIMPLEX). El color y el grosor tanto de la letra como de la flecha son los mismos. El tamaño de letra (escala) es el doble del que tiene originalmente la fuente elegida.

cv2.putText(img, “Isabel”, (100, 75), fuente, escala, color, grosor)

La siguiente imagen muestra el resultado de la ejecución de este programa.


OpenCV no ofrece posibilidades de alineación de texto, pero a veces puede resultar interesante centrarlo en una zona en la que haya algo de interés, por ejemplo, sobre objetos identificados en aplicaciones de realidad aumentada. Para ello, es necesario conocer las dimensiones del texto que se va a mostrar, que podrá obtener con la función:

getTextSize(texto, fuente, escala, grosor)

El resultado devuelto por esta función son dos valores:

• Una tupla con el ancho y alto del texto.

• El número de píxeles que sobresale por debajo de la línea base el punto más bajo de alguna de sus letras.


La línea base de un texto es sobre la que se apoya la mayoría de sus letras. Por debajo se sitúan los trazos descendentes de aquellas como la ‘g’, la ‘p’, etc.


El siguiente programa emplea esta función para centrar, tanto horizontal como verticalmente, el nombre de la niña en la imagen.


La sentencia clave de este programa es aquella en la que se obtiene el ancho y alto del texto que se quiere centrar (en este caso, “Isabel”). Para ello, se usará la función getTextSize() que acaba de conocer, cuyos argumentos de entrada son dicho texto, su fuente, escala y grosor. De la respuesta devuelta solo interesa la tupla en la que se encuentran el ancho y alto de dicho texto (ancho_texto y alto_texto).

(ancho_texto, alto_texto), _ = cv2.getTextSize(“Isabel”, fuente, escala, grosor)

En la siguiente sentencia se obtienen las dimensiones de la imagen, contenidas en su atributo shape.

alto_imagen, ancho_imagen, canales = img.shape


Recuerde que el primer valor devuelto es el alto, no el ancho.

Conocidas las dimensiones del texto y de la imagen, solo queda calcular las coordenadas x, y en las que habrá que situarlo para que quede centrado. Tenga presente que dichas coordenadas deben ser números enteros, de ahí que se realice la operación de casting con la función int():


Por último, se añade el texto a la imagen en las coordenadas calculadas con las sentencias anteriores y se muestra en pantalla.


Ya solo queda comprobar que el resultado obtenido es el esperado.


4.6 BARRAS DE DESPLAZAMIENTO

Las barras de desplazamiento son uno de los escasos objetos gráficos de control proporcionados por OpenCV. Se utilizan para modificar el valor de alguna de las variables de configuración de un programa y, en consecuencia, su comportamiento.

Para crear una barra de desplazamiento, OpenCV ofrece la función:



Esta función tiene un último argumento opcional, que contendría la información adicional que quisiera pasarse a la función de callback. Su finalidad es la de evitar el uso de variables globales.

El primer parámetro de esta función es el nombre de la barra de desplazamiento. Se mostrará a su lado, en la ventana a la que se haya asociado, establecida por el segundo argumento. El valor inicial es el que se muestre al crearse. El valor final es el valor máximo que podrá seleccionarse. Por último, la función de callback se ejecutará cada vez que se mueva la barra de desplazamiento.


El margen de valores que podrá elegir en una barra de desplazamiento será entre 0 (no se puede modificar) y el valor máximo.

Para practicar con esta función, va a modificar el programa del apartado anterior (en el que se escribía centrado en la ventana el nombre de la niña), para añadir una barra de desplazamiento que permita modificar la escala de dicho texto.

El código del programa es el siguiente:



Tras importar la librería OpenCV, se carga la imagen del cuadro de la niña, pero esta vez se hace además una copia con el método copy(). El motivo es porque, cuando se cambie la escala, antes de volver a escribir el texto con el nuevo tamaño de letra, habrá que borrar el anterior. Eso se consigue, de forma indirecta, volviendo a cargar la imagen original (ya que no tiene ningún texto).


A continuación, se declaran las variables del programa que ya conoce.

 

Ahora salte la declaración de las dos funciones siguientes para ir a las sentencias del final del programa. En primer lugar, se invoca la función auxiliar centrarImagen(), que toma como argumento de entrada la escala del texto y devuelve como salida las coordenadas x, y donde se debe comenzar a escribir para que quede centrado en la ventana.

posicion_x, posicion_y = centrarImagen(escala)

Una vez conocidas dichas coordenadas, las siguientes sentencias escriben el texto sobre la imagen y la muestran en la ventana “Cuadro”.


La última sentencia es la que crea la barra de desplazamiento. Dicha barra (llamada “Escala texto”) quedará asociada a la ventana “Cuadro” creada en la sentencia anterior, que es la que muestra la imagen de la niña con el texto centrado. Su valor inicial es el de la variable escala y su valor final es 5. La función de callback que se ejecutará cuando se modifique este valor es actualizar_imagen().

cv2.createTrackbar(‘Escala texto’, ‘Cuadro’, escala, 5, actualizar_imagen)

Lo primero que se hace en la función actualizar_imagen() es volver a recuperar la imagen inicial. Al tener que poner el nombre de la niña con un tamaño diferente, hay que borrar lo que se hubiera escrito anteriormente. A continuación, se llama a la función centrarImagen(), y se le pasa como argumento el valor de la nueva escala con el fin de obtener las coordenadas x, y en las que se debe situar el texto para que siga centrado. Luego, se escribe sobre la imagen el nombre de la niña con dicha escala en la nueva posición (posicion_x, posicion_y). Por último, se muestra en pantalla.


En la función centrarImagen() se obtiene el ancho y el alto del texto (ancho_texto, alto_texto) con la función getTextSize(), cuya escala ha sido pasada como argumento (escala). Después, se calcula la posición x, y (posicion_x, posicion_y) en la que debe empezar a escribirse para que siga apareciendo centrado en pantalla. Dichos valores serán los devueltos como resultado de su ejecución.


Ejecute el programa y observe cómo cambia el tamaño del texto según modifica el valor de la barra de desplazamiento. En la siguiente imagen puede ver dos ejemplos en los que el valor de la escala es 1 y 4.


Unidad 5
INTERACCIÓN CON EL RATÓN Y EL TECLADO

La forma habitual de utilizar la interfaz de usuario de una aplicación es a través del ratón y el teclado. En OpenCV, dicha interacción se realiza capturando los eventos que producen estos dispositivos. Cuando se mueve o se pulsa el ratón, o cualquier tecla, dichos eventos provocan la ejecución de una función de callback, que será la encargada de realizar las acciones correspondientes. Veamos cómo funciona.

5.1 GESTIÓN DE EVENTOS DEL RATÓN

Con frecuencia, será necesario interaccionar con la imagen mostrada en pantalla mediante el ratón. Por ese motivo, OpenCV proporciona un mecanismo que permite la captura de los eventos que genera, los cuales provocarán la ejecución de la función de callback encargada de realizar las tareas correspondientes a cada uno de ellos. La forma de establecer este mecanismo de captura de eventos es mediante la función:

setMouseCallback(ventana, función)

El primer argumento es el nombre de la ventana en la que se van a capturar los eventos del ratón, mientras que el segundo corresponde al nombre de la función de callback encargada de manejarlos.

Se podría añadir un tercer argumento con los parámetros adicionales que se pasarían a la función de callback cuando se produjera el evento.

La función de callback se debe declarar con los siguientes argumentos:

función de callback(evento, x, y, flags)

El primer argumento es el evento generado por el ratón, representado por las constantes:

• EVENT_MOUSEMOVE. El puntero del ratón se ha movido.

• EVENT_LBUTTONDOWN. Se ha presionado el botón izquierdo del ratón.

• EVENT_RBUTTONDOWN. Se ha presionado el botón derecho del ratón.

• EVENT_MBUTTONDOWN. Se ha presionado el botón central del ratón (si lo hubiera).

• EVENT_LBUTTONUP. Se deja de presionar el botón izquierdo del ratón.

• EVENT_RBUTTONUP. Se deja de presionar el botón derecho del ratón.

• EVENT_MBUTTONUP. Se deja de presionar el botón central del ratón (si lo hubiera).

• EVENT_LBUTTONDBLCLK. Se hace doble clic con el botón izquierdo del ratón.

• EVENT_RBUTTONDBLCLK. Se hace doble clic con el botón derecho del ratón.

• EVENT_MBUTTONDBLCLK. Se hace doble clic con el botón central del ratón (si lo hubiera).

• EVENT_MOUSEWHEEL. Los valores positivos y negativos indican un desplazamiento hacia delante o atrás de la rueda del ratón, respectivamente.

• EVENT_MOUSEHWHEEL. Los valores positivos y negativos delatan un desplazamiento hacia la derecha o la izquierda de la rueda del ratón, respectivamente.

Los dos argumentos siguientes son las coordenadas x, y del punto en el que se encontraba el ratón cuando se produjo el evento. El argumento flags indica diferentes situaciones especiales, identificadas por las siguientes constantes:

• EVENT_FLAG_LBUTTON. El botón izquierdo del ratón está presionado.

• EVENT_FLAG_RBUTTON. El botón derecho del ratón está presionado.

• EVENT_FLAG_MBUTTON. El botón central del ratón está presionado.

• EVENT_FLAG_CTRLKEY. La tecla CTRL está presionada.

• EVENT_FLAG_SHIFTKEY. La tecla SHIFT está presionada.

• EVENT_FLAG_ALTKEY. La tecla ALT está presionada.

El siguiente programa se utiliza para escribir el tipo de evento de ratón generado, en el punto de la pantalla en el que se produjo. Por simplicidad, solo se detecta si se ha pulsado el botón derecho o el izquierdo del ratón.


En primer lugar, se importan las librerías que se van a utilizar en el programa. En este caso, además de OpenCV, se importa NumPy para crear una imagen blanca que se usará como fondo de pantalla.


Dicha imagen se obtiene con la función zeros(), tal como ya conoce.


A continuación, se declaran las variables que establecen el color del texto (rojo), su grosor, la fuente y la escala.


Luego, se muestra la ventana “Eventos raton” con el fondo blanco.

cv2.imshow(‘Eventos raton’, img)

Saltando a la última línea del programa, puede ver que se establece la función eventos_raton() como la encargada de tratar los eventos del ratón producidos en la ventana “Eventos raton”.

cv2.setMouseCallback(‘Eventos raton’, eventos_raton)

Dicha función debe ser declarada antes de esta última sentencia. De los parámetros que tiene, usará únicamente el nombre del evento y las coordenadas en las que se ha producido.


Dentro de dicha función hay una condición if que comprueba si el evento es el de la pulsación del botón derecho (cv2.EVENT_LBUTTONDOWN). De no ser así, se verificaría si corresponde al izquierdo (cv2.EVENT_RBUTTONDOWN). En caso contrario, la función no haría nada. De producirse alguno de los dos eventos anteriores, se escribiría en pantalla de cuál se trata con la función putText().


Como se ha modificado la imagen, dentro de esta función se tiene que volver a llamar a la función imshow() para mostrarla de nuevo con los cambios realizados.

cv2.imshow(‘Eventos raton’, img)

El resultado de la ejecución de este programa puede verlo a continuación, donde se aprecia que se ha pulsado dos veces el botón izquierdo del ratón y otras tantas el derecho en diferentes puntos de la ventana.


A continuación, realizará un programa de carácter más práctico, con el que conseguirá una pizarra electrónica en la que, pulsando el botón izquierdo del ratón, podrá dibujar lo que quiera. Su código es el siguiente:


Este programa es similar al anterior, por lo que solo se explicará la función de tratamiento de eventos de ratón, que ahora se llama pinta(). En dicha función, lo primero que se hace es declarar como globales las variables que fijan la posición en la que estaba situado el ratón cuando se produjo el evento anterior (x_prev, y_prev). La idea es dibujar una línea entre el punto en el que estaba el ratón en ese momento y el actual (cuando se ha disparado el evento de movimiento). La línea dibujada sería diminuta porque (en el caso ideal) uniría dos píxeles adyacentes. Eso es debido a que, cada vez que mueva el ratón lo suficiente como para pasar de uno a otro, se generaría un evento.

Pero, para dibujar en la pizarra, se debe haber presionado el botón izquierdo previamente. Por eso, la condición del primer if de esta función comprueba si se ha producido el evento cv2.EVENT_LBUTTONDOWN, asignando la posición en la que se produjo a las variables x_prev e y_prev. Será a partir de ese punto cuando se empiece a dibujar una línea.


El evento cv2.EVENT_LBUTTONDOWN no se volverá a disparar mientras mantenga el botón izquierdo del ratón pulsado. El que sí se producirá será cv2. EVENT_MOUSEMOVE al mover el ratón. Pero antes de empezar a trazar la línea siguiendo este desplazamiento, será necesario asegurar que esté pulsado el botón izquierdo del ratón, por lo que tendrá que recurrir al argumento flags, cuyo valor cv2.EVENT_FLAG_LBUTTON será el que indique este hecho.


El punto inicial desde el que se trace la línea será en el que estaba el ratón la última vez que se movió (o cuando se pulsó el botón izquierdo). El punto final corresponderá a aquel en que se encuentra actualmente. Si se siguiera moviendo el ratón, este punto sería, a su vez, el de inicio de la siguiente línea. Por eso, la posición actual se asigna posteriormente a las variables x_prev e y_prev.


Una vez realizado el cambio en la imagen (se le ha añadido una nueva línea), se visualiza de nuevo con la función imshow().

cv2.imshow(‘Pizarra’,img)


Mientras tenga el ratón pulsado, las diminutas líneas dibujadas entre píxeles se mostrarán como una sola.

El resultado de la ejecución del programa lo puede ver a continuación, al mismo tiempo que mis escasas dotes artísticas.