Czytaj książkę: «Visión artificial»


Visión Artificial. Aplicaciones prácticas con OpenCV - Python
Primera edición, 2021
© 2021 Tomás Domínguez Mínguez
© 2021 MARCOMBO, S. L.
www.marcombo.com
Diseño de cubierta: ENEDENÚ DISEÑO GRÁFICO
Maquetación: cuantofalta.es
Asesor técnico: Ferran Fàbregas
Correctores: Beatriz Garcia y Manel Fernández
Directora de producción: M.a Rosa Castillo
«Cualquier forma de reproducción, distribución, comunicación publica o transformación de esta obra solo puede ser realizada con la autorización de sus titulares, salvo excepción prevista por la ley. Diríjase a CEDRO (Centro Español de Derechos Reprográficos, www.cedro.org) si necesita fotocopiar o escanear algún fragmento de esta obra».
ISBN: 978-84-267-3347-4
Producción del ePub: booqlab
Todo lo que sucede fue una vez un sueño
TABLA DE CONTENIDO
1. INTRODUCCIÓN
1.1 Visión artificial
1.2 OpenCV
1.3 Python
2. INSTALACIÓN DE OPENCV
3. PRIMEROS PASOS
3.1 Carga y visualización de la imagen almacenada en un archivo
3.2 Obtención de las características de una imagen
3.3 Creación de una imagen a partir de una matriz de píxeles
3.4 Modificación del valor de los píxeles de una imagen
3.5 Almacenamiento de una imagen en un archivo
4. FUNCIONES DE INTERFAZ GRÁFICA DE USUARIO
4.1 Ventanas
4.2 Líneas
4.3 Rectángulos
4.4 Círculos y elipses
4.5 Textos
4.6 Barras de desplazamiento
5. INTERACCIÓN CON EL RATÓN Y EL TECLADO
5.1 Gestión de eventos del ratón
5.2 Gestión de eventos del teclado
6. OPERACIONES BÁSICAS DE MANEJO DE IMÁGENES
6.1 Obtención del color de un píxel
6.2 Recorte de regiones
6.3 Escalado
6.4 Adición
6.5 Sustracción
6.6 Operaciones bit a bit
6.7 Cambio del espacio de color
7. HISTOGRAMAS
8. FILTROS DE PROCESAMIENTO DE IMÁGENES
8.1 Filtros basados en umbral
8.1.1 Filtro de umbral simple
8.1.2 Filtro de umbral Otsu
8.1.3 Filtro de umbral adaptativo
8.1.4 Comparación entre filtros
8.2 Filtros lineales
8.2.1 Filtro paso bajo (suavizado)
8.2.2 Filtro paso alto (de gradiente)
8.3 Filtros morfológicos
8.3.1 Filtro de dilatación
8.3.2 Filtro de erosión
8.3.3 Otros filtros morfológicos
8.4 Filtro Canny
9. CONTORNOS
9.1 Identificación
9.2 Dibujo
9.3 Cálculo del perímetro y el área
9.4 Bounding box
9.4.1 Pasatiempos. Las siete diferencias
9.5 Contornos de aproximación
9.6 Otras funciones
10. BÚSQUEDA DE IMÁGENES
11. RECONOCIMIENTO DE OBJETOS
11.1 Reconocimiento facial
11.2 Reconocimiento de ojos
11.3 Identificación de personas
12. OPERACIONES BÁSICAS DE MANEJO DE VÍDEO
12.1 Visualización de las imágenes capturadas por una cámara
12.2 Almacenamiento de vídeos
12.3 Reproducción de vídeos
13. PROCESAMIENTO Y ANÁLISIS DE VÍDEO
13.1 Contador de monedas
13.2 Clasificación de objetos por tamaño
13.3 Identificación de figuras geométricas
13.4 Reconocimiento facial
14. REALIDAD AUMENTADA
15. SEGUIMIENTO DE OBJETOS EN PANTALLA
15.1 Color tracking
15.1.1 Rastreo de objetos
15.1.2 Control gestual
15.2 Meanshift
16. SUSTRACCIÓN DE UNA IMAGEN DE FONDO
17. ANEXO. FUNDAMENTOS DE PYTHON
17.1 Entorno de desarrollo
17.1.1 Instalación
17.1.2 Descripción general
17.2 Sintaxis básica de Python
17.3 Variables
17.4 Tipos de datos básicos
17.4.1 Números
17.4.2 Cadenas de caracteres
17.4.3 Booleanos
17.4.4 Conversión de tipos
17.5 Operadores
17.6 Estructuras de control
17.6.1 if…else
17.6.2 while
17.6.3 for
17.7 Estructuras de datos
17.7.1 Listas
17.7.2 Tuplas
17.7.3 Conjuntos
17.7.4 Diccionarios
17.8 Entrada de datos de usuario
17.9 Depurador de código de Python
17.10 Funciones
17.11 Alcance de las variables
17.12 Clases y objetos
17.12.1 Herencia
17.13 Módulos
Unidad 1
INTRODUCCIÓN
La visión artificial es una disciplina científica formada por un conjunto de técnicas que permiten la captura, el procesamiento y el análisis de imágenes. El objetivo es que un ordenador sea capaz de extraer información útil para responder a preguntas sobre su contenido, como ¿qué aparece en la imagen?, ¿hay algún objeto en el que esté interesado?, ¿es una persona conocida?, ¿qué está haciendo?
Las técnicas necesarias para conseguir dicho objetivo proceden de diversas áreas como la ingeniería o la informática, cuya naturaleza matemática ha provocado que solo estuvieran al alcance de especialistas en la materia. Sin embargo, la aparición de librerías como OpenCV ha permitido ocultar los complejos algoritmos en los que se basan y ha democratizado su uso. Al igual que para ponerse al volante de un coche no es necesario ser mecánico, sino únicamente saber manejar los mandos que permiten conducirlo, para desarrollar aplicaciones de visión artificial no se tiene por qué haber estudiado una ingeniería, basta con conocer las funciones de aquellas librerías que permitan hacer lo que se pretenda, por ejemplo, identificar un determinado objeto, su color o su tamaño.
En este primer capítulo se darán unas pinceladas de lo que es la visión artificial, así como de la librería OpenCV, que pondrá a su alcance las técnicas necesarias para beneficiarse de todo lo que ofrece esta disciplina.
1.1 VISIÓN ARTIFICIAL
Hasta hace poco tiempo, las tareas que un ordenador era capaz de hacer con las imágenes consistían en almacenarlas, reproducirlas o, incluso, procesarlas para modificar su aspecto. Pero en ningún caso llegaba a obtener información que mostrara una compresión real de su contenido. Estas mismas imágenes que tanta información pueden ofrecer a una persona para un ordenador solo representaban píxeles que almacenan un nivel de luz o color.
El análisis que cualquiera de nosotros realizamos de forma natural cuando vemos algo ha estado vedado a los ordenadores. Afortunadamente, estas capacidades, antes relegadas exclusivamente al ámbito humano, comienzan a ser automatizadas gracias a la aplicación de las técnicas de visión artificial.
El objetivo de la visión artificial es interpretar las imágenes con el fin de extraer información de utilidad, por ejemplo, saber si delante de una cámara hay alguien conocido o un lugar de interés. Dicha información servirá para conseguir un fin, que en los ejemplos anteriores podría ser dar acceso a una aplicación (en vez de usar una clave) o proporcionar información turística relevante del sitio donde se encuentre. Ambos ejemplos son tan solo una muestra de los muchos campos en los que su empleo está ganando terreno. Y es más, con alguno de ellos experimentará a lo largo de las páginas de este libro:
• Seguridad. Como se acaba de comentar, la visión artificial puede utilizarse para desbloquear una aplicación, pero pronto comprobará que también permite el desarrollo de alarmas inteligentes que no se disparen cuando el movimiento detectado sea el de su mascota paseándose por la casa.
• Industria. Al comparar las imágenes de objetos manufacturados con las de otros utilizados como modelo, podrían automatizarse procesos de control de calidad. Aplicando estas mismas técnicas, realizará un programa de carácter más lúdico que podría ayudarle a solucionar pasatiempos, como el de las siete diferencias, que a veces tanto cuesta resolver.
• Comercio. Saber cómo quedaría un mueble en su sala de estar o el aspecto que tendría esta con las paredes pintadas de un determinado color son facilidades que pueden hacer que un cliente se decida a comprar un producto concreto. Dentro de esta área, se aborda la implementación de una aplicación orientada al negocio textil, en concreto, de venta de sombreros.
• Educación. Cada vez son más las aplicaciones de este tipo utilizadas como apoyo a la formación. A modo de ejemplo, se desarrolla un programa que podría emplearse para enseñar las principales figuras geométricas a los más pequeños, ya que es capaz de identificar las situadas delante de una cámara.
Por supuesto, la visión artificial puede aplicarse a muchos más campos, como la clasificación y búsqueda de imágenes, la conducción autónoma, la medicina, la agricultura, el turismo, los videojuegos, etc. Tratar de enumerarlos todos daría como resultado una extensa lista que quedaría rápidamente obsoleta, ya que va creciendo de día en día.
Para alcanzar el propósito perseguido en cada una de dichas áreas, esta disciplina se apoya, tal como se indicó al principio del capítulo, en tres pilares que permiten adquirir, procesar y analizar imágenes procedentes del mundo real. Veamos en qué consiste cada uno de ellos.

Mediante la adquisición de imágenes, el mundo analógico de luces y sombras que nos rodea se traduce a datos binarios compuestos por ceros y unos. Es lo que habitualmente se hace con una cámara (ya integrada en el ordenador o conectada por USB).
El procesamiento usa diferentes algoritmos matemáticos que, ocultos por librerías, resaltan determinadas características de las imágenes, preparándolas de esta forma al tipo de algoritmos de análisis que se lleve a cabo posteriormente. Operaciones como el recorte o el escalado de imágenes, el cambio de espacio de color o la aplicación de filtros permiten centrar la atención en los objetos relevantes, al facilitar la detección de sus bordes, esquinas, contornos o cualquier otro rasgo que los defina.
El análisis de las imágenes va un paso más allá del procesamiento. Basado también en algoritmos matemáticos (utilizados, de nuevo, a través de librerías que oculten su complejidad), partirá de los resultados del paso anterior para realizar un reconocimiento de objetos, su clasificación, identificación y seguimiento en la escena, por poner algún ejemplo.
En este libro aprenderá a usar la librería OpenCV para realizar estos procesos, y se ocultará la complejidad matemática de los algoritmos en los que se basa cada uno de ellos. Eso le permitirá desarrollar, de forma sencilla y práctica, aplicaciones de visión artificial en tiempo real.
Adéntrese en este fascinante mundo con Pyhton de la mano de OpenCV.
1.2 OPENCV
OpenCV (Open Source Computer Vision Library) es una librería orientada al procesamiento y análisis de imágenes. Desarrollada inicialmente por Intel en 1999, ha ido ganando popularidad rápidamente, siendo hoy en día una de las más empleadas en el desarrollo de aplicaciones de visión artificial.

OpenCV (https://docs.opencv.org/) es muy utilizada en grupos de investigación, organismos gubernamentales y empresas como Google, Yahoo, Microsoft, Intel, IBM, Sony, Honda y Toyota. Sus ámbitos de aplicación son muchos, y entre ellos destacan:
• Reconocimiento facial
• Identificación de objetos o personas
• Inspección y vigilancia
• Juegos y controles
• Recuento de objetos
• Análisis de imágenes médicas
• Robótica
• Realidad aumentada
En 2008, Willow Garage se hizo cargo del soporte de OpenCV 2.3.1, por lo que a partir de dicha versión viene con una interfaz de programación C++, Java y Python. Además, está disponible en diferentes plataformas, incluidas Windows, Linux, Mac OS X y Android. Este libro se centrará en OpenCV-Python, que combina las mejores cualidades del API (application programming interface, interfaz de programación de aplicaciones) de OpenCV y el lenguaje Python.
Además de ser multiplataforma y multilenguaje, es destacable el hecho de que OpenCV sea un producto con licencia BSD (Berkeley Software Distribution), con menos restricciones que otras como GPL (General Public License, Licencia Pública General de GNU), ya que permite incluso el uso de su código fuente en software comercial.
1.3 PYTHON
Como se acaba de comentar, OpenCV está disponible en los principales lenguajes de programación. De todos ellos, utilizará Python, quizás el empleado por un mayor número de programadores en el mundo. Los motivos de la enorme aceptación de este lenguaje seguramente estén en su sencillez de aprendizaje y facilidad de uso.

No solo permite el clásico modo de programación imperativo, sino que también admite una programación orientada a objetos, con lo que se abre al uso de infinidad de librerías que, como OpenCV, están basadas en este paradigma.
Su entorno multiplataforma hace que se pueda trabajar con él, independientemente de si dispone de un ordenador con Windows, Mac o Linux (incluso Raspberry). Como requisito adicional, necesitará disponer de una webcam, que podrá ser la integrada en el propio ordenador o cualquier otra conectada vía USB.
La versión de Python utilizada será la 3.x (a fecha de publicación del libro es la 3.7). No tiene sentido utilizar Python2 cuando fue oficialmente descontinuado el 1 de enero de 2020.
¿Todavía no conoce Python? Al final del libro dispone de un amplio anexo que representa en sí mismo un curso de iniciación a este lenguaje. En primer lugar, aprenderá cómo se instala y utiliza su entorno de desarrollo. Haciendo uso de él, practicará con los tipos de datos básicos, los operadores y las estructuras de datos o de control imprescindibles del lenguaje. La orientación a objetos tendrá un tratamiento especial, así como el desarrollo de funciones o el uso de módulos (librerías). Se explica todo lo necesario (incluso más) para que pueda entender el código de las múltiples prácticas con las que se demuestran los conceptos de visión artificial introducidos en cada capítulo de la mano de OpenCV. El objetivo final es que, inspirándose en estos programas de prueba, usted pueda desarrollar sus propios proyectos. Los límites que pueda llegar a alcanzar estarán en su imaginación.
Unidad 2
INSTALACIÓN DE OPENCV
Puesto que va a trabajar con la librería OpenCV para Python, deberá tener instalado el entorno de desarrollo de este lenguaje en su ordenador. Si no fuera así, en el anexo final se indica cómo hacerlo. Aunque esta librería funciona tanto con la versión 2.7 como con la 3.4 y superiores, tal como se ha comentado en el apartado anterior, se utilizará únicamente Python 3.7.
Evidentemente, para usar la librería OpenCV, primero hay que instalarla. Para ello, abra una ventana de símbolo del sistema y ejecute el comando:
pip install opencv-python


Recuerde que en algunas distribuciones de Linux el comando que debe utilizar es pip3.

La forma más sencilla de abrir una ventana de símbolo del sistema en Windows es escribiendo su nombre en el campo de búsqueda situado en la parte inferior izquierda del escritorio. Enseguida le aparecerá un icono sobre el que podrá pulsar para abrir dicha ventana.

En la instalación de este paquete, puede observar que también se carga la librería NumPy, utilizada para trabajar con matrices.

Aunque no entraremos en conceptos matemáticos, para entender por qué la visión artificial y las matrices están tan unidas, piense que una imagen puede definirse como una función f(x, y) cuyos argumentos son las coordenadas x, y de cada píxel, y el resultado es el nivel de luz (o color) del píxel situado en dichas coordenadas.
En otras palabras, una imagen no es más que un array de dos dimensiones (de tres en el caso de imágenes en color) cuyos elementos contienen el valor de cada uno de sus píxeles. A los arrays de dos dimensiones se les llama matrices. Si tuvieran tres serían tensores. Sin embargo, por simplicidad, generalmente se empleará el término matriz.
Para saber si OpenCV está correctamente instalado, entre en el intérprete de Python (comando python) y ejecute las siguientes sentencias:

El resultado debe devolver la versión de la librería instalada (en este caso la 4.4.0).


Toda la documentación de esta librería se encuentra en:
https://docs.opencv.org/4.4.0/
Aunque no es imprescindible, aproveche también para instalar la librería matplotlib ejecutando el siguiente comando:
pip install matplotlib
Dicha librería le permitirá generar gráficos a partir de datos contenidos en listas.

Por similitud con la terminología utilizada en otros lenguajes de programación, el término librería se utilizará como sinónimo de módulo o paquete (aunque no sean conceptos exactamente iguales).
Unidad 3
PRIMEROS PASOS
En los siguientes apartados estudiará las operaciones básicas de creación o carga de imágenes desde un archivo y su visualización en pantalla, así como la modificación de su contenido y posterior almacenamiento en disco.
Pero antes deberá conocer el sistema de coordenadas utilizado, en el que cada píxel se localiza en la imagen por su posición horizontal y vertical (x, y). Como puede ver en la siguiente figura, el origen (0, 0) no está localizado en el centro de la imagen, sino en la parte superior izquierda. El píxel marcado en color rojo estaría situado en la coordenada (3, 1).

Otro de los conceptos básicos previos al uso de las funciones de OpenCV es el color, formado por los componentes B, G, R (Blue, Green, Red; Azul, Verde, Rojo), o lo que es lo mismo, los valores de la intensidad de sus componentes azul, verde y rojo. Estos tres colores se consideran primarios porque cualquier otro color se puede obtener a partir de una mezcla de ellos. El valor mínimo de intensidad que pueden tener es 0, y el máximo, 255.


Seguramente le resulte más familiar el espacio de color RGB; por eso, preste especial atención al hecho de que OpenCV utiliza por defecto BGR. Más adelante sabrá cómo pasar de uno a otro o incluso a HSV, ampliamente utilizado en las técnicas de reconocimiento y seguimiento de objetos por su mayor eficiencia.
Ejemplos de especificación de colores utilizando la nomenclatura BGR serían:
• Blanco: (255, 255, 255)
• Negro: (0, 0, 0)
• Rojo: (0, 0, 255)
• Verde: (0, 255, 0)
• Azul: (255, 0, 0)
• Amarillo: (0, 255, 255)
• Púrpura: (255, 0, 255)
• Cian: (255, 255, 0)

Opcionalmente, un píxel puede tener asociado un cuarto parámetro que define su transparencia: el canal alfa. Sus valores van desde 0 (completamente trasparente) a 100 (totalmente opaco).
3.1 CARGA Y VISUALIZACIÓN DE LA IMAGEN ALMACENADA EN UN ARCHIVO
Para mostrar una imagen en pantalla, previamente hay que cargarla con la función de OpenCV:
imread(archivo)
Su único argumento contiene el nombre del archivo donde se encuentra la imagen (o la ruta, en el caso de que no esté en la misma carpeta que el programa). Esta función también admite un segundo argumento opcional para especificar la información que se carga de la imagen. Su valor podrá ser el de cualquiera de estas constantes:
• IMREAD_GRAYSCALE. Carga la imagen en blanco y negro. Su valor es 0.
• IMREAD_COLOR. Carga la imagen en color (se ignora el canal alfa). Su valor es 1.
• IMREAD_UNCHANGED. Carga la imagen incluyendo el canal alfa. Su valor es -1.

Si no se indica este argumento, la función toma por defecto el valor IMREAD_COLOR.

El canal alfa define la opacidad de un píxel, es decir, su grado de transparencia. No todos los formatos de imagen admiten este canal (que se añade a los tres correspondientes a los colores primarios). Así, por ejemplo, mientras JPG no lo permite, PNG sí contiene este tipo de información.

Entre los formatos de imágenes que se soportan destacan BMP, JPEG, PNG y TIFF, entre otros. OpenCV no tiene códec para GIF, por lo que el manejo de imágenes en este formato requiere la librería imageio, que tendrá que haber cargado previamente en su entorno Python. Si quiere saber cómo utilizarla, visite https://pypi.org/project/imageio/.
Esta función devolverá un objeto de la clase ndarray, perteneciente a la librería NumPy. Dicho objeto mantiene una matriz cuyos elementos establecen el nivel de luz o color de los píxeles de la imagen.
Una vez obtenida la imagen, la forma de mostrarla en pantalla es llamando a la función de OpenCV:
imshow(ventana, imagen)
El primer argumento determina el título de la ventana, mientras que el segundo contiene la imagen.

El tamaño de la ventana será el de la imagen. Si la resolución de esta fuera muy alta, solo se vería parte de ella.
En el siguiente programa se utilizan ambas funciones:

Con la primera sentencia se importa la librería OpenCV. A continuación, se carga la imagen “cuadro.jpg” (que está dentro de la carpeta “imagenes”) con la función imread(). Su segundo argumento es 0, lo que significa que la imagen se obtiene en blanco y negro. La última sentencia la muestra en una ventana llamada “Cuadro”.

En la ruta de acceso al fichero de una imagen, el carácter ‘.’ simboliza el directorio en el que se encuentra el programa que se está ejecutando. Para acceder al directorio padre, se utilizaría ‘..’, como en este caso.

En los programas desarrollados para cada una de las prácticas, se supone que las imágenes se encuentran en archivos dentro de la carpeta “imagenes” (sin acento), situada al mismo nivel que las que contengan los programas que vaya desarrollando, siguiendo la estructura:

Ejecute el programa. El resultado obtenido lo puede ver a continuación.


Si observara que la ventana se cierra inmediatamente, añada al final del programa las sentencias:

Aunque se estudiarán en detalle más adelante, le avanzo que su función es la de esperar que se pulse una tecla antes de cerrar la ventana.
Pese a que OpenCV es multiplataforma y todos los programas se han probado en Windows, puede que se produzca este efecto en otros sistemas operativos. En ese caso, acuérdese de incluir dichas sentencias en el resto de programas que no las tengan, especialmente en los utilizados en los primeros capítulos.
Cambiando el segundo argumento de la función imread() por 1, la imagen se recuperaría con la información del color.
img = cv2.imread(‘../imagenes/cuadro.jpg’, 1)

Si la imagen ya estuviera cargada en color, una forma de convertirla posteriormente a blanco y negro sería utilizando la función:
cvtColor(imagen, espacio de color)
El primer argumento es la imagen que va a cambiar de espacio de color, que será el determinado por el segundo argumento. En el caso de tener una imagen BGR (como en este caso) y querer pasarla a blanco y negro, su valor sería COLOR_BGR2GRAY.

Existen multitud de códigos de conversión de espacios de color. A lo largo de este libro tendrá ocasión de utilizar frecuentemente el que cambia de BGR al HSV (tendrá ocasión de conocerlo más adelante) y viceversa, cuyos valores son COLOR_BGR2HSV y COLOR_HSV2BGR, respectivamente. Si quiere conocer todas las posibilidades de transformación de que dispone esta función, consulte https://docs.opencv.org/3.4/d8/d01/group__imgproc__color__conversions.html.
El siguiente código muestra un ejemplo de uso de esta función.

Aunque la imagen se carga en color (img), posteriormente se pasa a blanco y negro (img_byn) con la función cvtColor(), que es como finalmente se muestra.
Este proceso de conversión de una imagen en color a otra en blanco y negro es de gran importancia en visión artificial porque:
• Reduce el tamaño de la imagen, al no tener que mantener la información del color.
• Aumenta la eficiencia de los algoritmos de procesamiento de imágenes, al trabajar con un valor por píxel (en color serían 3). Esto es especialmente crítico cuando se utilizan algoritmos que consumen muchos recursos computacionales (especialmente si se usan ordenadores de bajas prestaciones) o es necesario dar respuesta en tiempo real (como sucede cuando se trabaja con aplicaciones de realidad aumentada).
• Hay filtros de procesamiento de imágenes que solo funcionan en escala de grises.
Para extraer los diferentes colores de la imagen, deberá utilizar la función:
split(imagen)
Esta función devuelve tres imágenes, una por cada uno de los colores primarios que la componen. Dichas imágenes se muestran en blanco y negro. Cuanto más claro sea un píxel, mayor será la intensidad del color primario correspondiente. Para comprobarlo, observe las siguientes figuras geométricas, cada una de las cuales se ha pintado en uno de los colores primarios.

Ahora escriba y ejecute el siguiente programa:

En este código, primero se importa la librería OpenCV y luego se carga la imagen anterior en color. A continuación, se ejecuta la función split(), que devuelve como resultado tres imágenes, una por cada color primario (img_azul, img_verde y img_roja). Finamente, dichas imágenes se muestran en ventanas independientes llamando tres veces a la función imshow().
Por lo tanto, el resultado de la ejecución del programa anterior serán tres ventanas, en cada una de la cuales se mostrará un solo color (que se verá blanco en su máxima intensidad y negro en su ausencia). Puesto que cada figura geométrica es de un color primario, en cada ventana se mostrará en blanco solo una de ellas (la que tiene el color por el que se filtra), y desaparecen el resto, que se funden con el negro del fondo. En visión artificial, este tipo de filtros de color son de gran utilidad, ya que sirven para seleccionar objetos en la imagen sobre los que interesa poner atención y descartar el resto.


Para volver a juntar los componentes de cada color en la misma imagen, utilice la función:
merge(imagen azul, imagen verde, imagen roja)
Dicha función devolvería la imagen en color original.
3.2 OBTENCIÓN DE LAS CARACTERÍSTICAS DE UNA IMAGEN
Las imágenes se almacenan como matrices de píxeles en objetos de la clase ndarray, de los que se pueden extraer características clave como sus dimensiones, el número de canales que las componen, el tipo de dato con el que se almacena la información o el tamaño que ocupa en disco.
Para conocer las dimensiones y el número de canales de una imagen, acceda a su atributo:
shape
El valor de dicho atributo es una tupla con tres valores:
(alto, ancho, n.° canales)
El tamaño que ocupa en disco una imagen vendrá dado por su ancho y alto, así como por el número de canales que contiene. Una imagen en blanco y negro tiene un canal, mientras que las de color se componen de tres (uno por cada color primario). Si cada color se almacenara en un byte, el tamaño en bytes de una imagen sería:
tamaño imagen = ancho x alto x n.° canales
Como se ha indicado, este cálculo es válido cuando los elementos de la matriz de la imagen ocupen un byte, por ejemplo, del tipo uint8. No todos los tipos de datos ocupan lo mismo en disco. Si quiere conocer cuál es el utilizado en una imagen, consulte el atributo:
dtype
Sin embargo, la forma más sencilla de conocer el tamaño en disco de una imagen es accediendo a su atributo:
size
Añada las siguientes sentencias al final del programa visto anteriormente (aquel con el que se cargaba y visualizaba la imagen en color de la niña):

Ahora, además de mostrarse la imagen en pantalla, en la shell obtendrá como resultado que tiene un tamaño de 921 600 píxeles, sus dimensiones son de 640 × 480 píxeles y se compone de tres canales, cuyos valores son del tipo uint8.


Compruebe que el tamaño de la imagen es el resultado de multiplicar el ancho por el alto y el número de canales (colores por píxel). En este caso, 640 × 480 × 3 = 921 600.
3.3 CREACIÓN DE UNA IMAGEN A PARTIR DE UNA MATRIZ DE PÍXELES
Como ya sabe, una imagen es un objeto ndarray que contiene una matriz cuyos elementos establecen el nivel de luz (o color) de cada píxel. Si la imagen fuera en blanco y negro, el valor de dichos elementos sería un entero comprendido entre el 0 (oscuridad absoluta) y el 255 (máximo nivel de luz). Para entenderlo mejor, en la siguiente figura se muestra una imagen negra de 5 × 5 píxeles y su representación como una matriz.

En Python, esta matriz de píxeles sería la siguiente:

Para convertir dicha matriz en un objeto de la clase ndarray, que es como realmente se representan las imágenes en OpenCV, tendría que utilizar la función array() de la propia librería NumPy. El código sería el siguiente:

Más abajo puede ver el resultado de la ejecución de este programa, que muestra en la shell la matriz que representa una imagen negra de 5 × 5.


Observe que una matriz NumPy no muestra sus elementos separados por comas.
Sin embargo, esta forma de trabajar no sería práctica cuando se manejan imágenes con una resolución de miles de píxeles. Por eso, la librería NumPy permite crear esta matriz de forma más sencilla, mediante la función:
zeros(dimensiones, tipo)
El primer argumento es una tupla con las dimensiones de la matriz. El segundo es el tipo de los elementos que contiene. Así, el programa anterior podría sustituirse por:

Antes de ejecutar la función que crea la imagen, es necesario importar la librería NumPy, ya que la clase ndarray pertenece a dicha librería, no a OpenCV. La imagen devuelta por la función zeros() es de 5 × 5 píxeles, mientras que la información almacenada en cada píxel son valores enteros sin signo entre 0 y 255 (uint8). El resultado de la ejecución de este programa sería el mismo que el anterior.
Una vez que ya tiene creada la imagen, solo resta visualizarla con la función imshow():

En este caso, la imagen creada es de 300 × 300 píxeles. El resultado de la ejecución de este programa puede verlo a continuación.

Para crear una imagen blanca, tal como se muestra gráficamente en la siguiente imagen, los valores de cada uno de los píxeles deben ser 255.

Con el fin de facilitar la creación de dicha matriz de píxeles, NumPy dispone de una función similar a zeros(), solo que ahora el valor de todos sus elementos es 1.
ones(dimensiones, tipo)
Así, para crear una imagen completamente blanca, solo habría que multiplicar por 255 cada uno de los elementos de la matriz generada por dicha función. Por lo tanto, el código del programa que mostraría una imagen blanca de 300 × 300 píxeles es:

El resultado de la ejecución del programa anterior es la siguiente ventana.

Si lo que se busca es una imagen en color, los píxeles deberán contener información del nivel de intensidad de cada uno de los colores primarios, los cuales pueden tomar un valor entre 0 y 255. Por lo tanto, para crear una imagen de color azul, el código que tendría que escribir sería:

Como puede observar, ahora la matriz tiene una nueva dimensión, ya que sus elementos se componen de tres valores, correspondientes a los niveles de intensidad de los tres colores primarios en cada píxel.
img = numpy.ones((alto, ancho, 3),numpy.uint8)*255

También podría haberse utilizado la función zeros(), ya que, posteriormente, se cambia el valor de todos los elementos de la matriz para colorear la imagen.
La siguiente sentencia asigna el mismo color a todos los píxeles (en este caso, el azul).


Aunque se ha usado una tupla para asignar el color, también se podría haber empleado una lista. Por ese motivo, la siguiente sentencia sería igualmente válida:

De las imágenes que hay más abajo, la de la izquierda corresponde al resultado de la ejecución de este programa. Las otras dos se obtendrían modificando la sentencia que asigna el color a los píxeles. En el caso del verde:

O si prefiere el rojo:


3.4 MODIFICACIÓN DEL VALOR DE LOS PÍXELES DE UNA IMAGEN
Como sabe, una imagen se representa por una matriz de elementos, cada uno de los cuales es una tupla cuyos valores determinan la intensidad del color azul, verde y rojo de cada píxel (o únicamente el nivel de luz, en caso de tratarse de una imagen en blanco y negro). Para entender la forma de acceder a un píxel, tanto para obtener su valor como para asignarle otro diferente, observe la siguiente imagen, en la que hay un píxel negro situado en la posición (3, 1).

Puesto que la imagen está formada por una matriz, en la que sus elementos se distribuyen por filas y columnas, si estuviera almacenada en la variable img, la forma de obtener el valor de dicho píxel (nivel de luz) sería:
valor = img[fila, columna]
La fila viene dada por la coordenada y, mientras que la columna es la coordenada x. Por eso, para obtener el color del punto (3, 1), se deberá usar la expresión:
color = img[1, 3]

Esto suele ser motivo de confusión. Por eso, preste especial atención cada vez que tenga que acceder a un píxel de la imagen.
Una vez que se accede a un píxel, no solo es posible obtener su valor, sino también modificarlo. Eso es precisamente lo que hará en el siguiente programa, con el que, partiendo de la imagen blanca generada en el apartado anterior, dibujará una rejilla. La forma de hacerlo será recorriendo todos sus píxeles para asignarles el valor 0 (negro) a intervalos regulares. Puesto que dichos intervalos se tomarán tanto en el eje horizontal como en el vertical, las líneas que se dibujen conformarán una rejilla.
El código del programa es el siguiente.


La primera parte del programa crea una imagen en blanco de 300 × 300 píxeles de tamaño. Su código ya lo conoce, por lo que no se va a dar ninguna explicación al respecto.

Una vez creada dicha imagen, se asigna el valor 0 a cada píxel en las coordenadas x e y, a intervalos regulares de 50 píxeles. Es decir, se dibujan de color negro los píxeles situados en las filas o columnas 0, 50, 100, 150, 200 y 250. El resultado será una imagen formada por 6 cuadrados de lado.
La forma de recorrer todos los píxeles de una imagen es mediante un doble bucle, cada uno de los cuales se desplaza por un eje.


Por simplicidad del código, no se dibujan las últimas líneas horizontal y vertical de la rejilla.
Para identificar los píxeles que deben pintarse en negro, es decir, aquellos cuyas coordenadas x o y sean múltiplos de 50, se utiliza el operador ‘%’, que devolverá el valor 0 en esos casos.

La sentencia clave del programa es la que se ejecuta cuando se cumple la condición del if anterior, es decir, la que pinta de negro los píxeles de las líneas. Para ello, solo hay que acceder a ellos, tal como se ha explicado, y asignarles el valor 0.

Por último, se muestra la rejilla en pantalla con la función imshow().
cv2.imshow(‘Rejilla ‘, img)
Ejecute el programa. Cuando lo haga, obtendrá el resultado mostrado a continuación.

¿Y si hubiera querido pintar la rejilla en color? En ese caso, la imagen blanca de fondo se debería haber creado con tres canales. Suponiendo que se quiere dibujar las líneas de color rojo, el programa sería:

El código utilizado para crear una imagen blanca de tres canales (en vez de uno, como en el programa anterior) es el mismo desarrollado para crear ventanas con el fondo de color, solo que ahora ese color es el blanco.

Para la asignación del color rojo a los píxeles que forman parte de las líneas de la rejilla, ahora se utiliza una terna (en este caso la que identifica el color rojo), en vez de un nivel de luz.

El resultado de la ejecución de este programa será el mostrado a continuación.

3.5 ALMACENAMIENTO DE UNA IMAGEN EN UN ARCHIVO
Ya sabe cómo mostrar en pantalla las imágenes cargadas previamente de un archivo o creadas dentro del propio programa como un objeto de la clase ndarray. Ahora va a aprender a guardarlas en un fichero. Para ello, OpenCV proporciona la función:
imwrite(archivo, imagen)
El primer argumento de esta función es el nombre del archivo (o un path) en el que se va a guardar la imagen contenida en el segundo.
Para practicar con esta nueva función, completará el programa del apartado anterior, en el que dibujaba una rejilla, para que, en vez de mostrarla, se almacene en un archivo. El código del programa es el siguiente.

La principal diferencia de este código con el descrito en el apartado anterior es que se sustituye la función imshow(), que muestra la rejilla en pantalla, por imwrite(), que la almacena en el archivo “rejilla.jpg”, dentro de la carpeta “imagenes”.
cv2.imwrite(‘../imagenes/rejilla.jpg’, img)

Los nombres de los archivos siempre deben tener una extensión, correspondiente a cualquiera de los formatos admitidos. De lo contrario, obtendrá el error: “could not find a writer for the specified extension in function ‘cv::imwrite”.

Si, por equivocación, indicara mal la ruta en la que debe situarse el archivo y esta no existiera, el programa no avisaría del error. Simplemente, no haría nada.
Ejecute el programa y, cuando en la shell de Python aparezca el mensaje “Imagen almacenada”, compruebe que en el directorio “imagenes” exista el archivo creado. Al abrirlo, verá la rejilla que acaba de dibujar.


Si quiere ver las imágenes de la carpeta donde se almacenan sin necesidad de abrirlas, en el explorador de Windows seleccione la opción “Iconos grandes” del menú “Vista”.

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.

5.2 GESTIÓN DE EVENTOS DEL TECLADO
En este apartado aprenderá a gestionar los eventos del teclado, que utilizará básicamente para capturar la pulsación de una tecla. Para ello, OpenCV ofrece la función:
waitKey(milisegundos)
Su único argumento fija el número de milisegundos que estaría bloqueado el programa en este punto hasta que se pulsara cualquier tecla. Si su valor fuera 0, sería de forma indefinida.
El resultado de dicha función es el código Unicode de la tecla pulsada (o -1, si transcurrido el número de milisegundos indicado, no se hubiera pulsado ninguna).

Para llamar a esta función, es necesario que al menos haya una ventana activa.
En el siguiente programa, se amplía el de la pizarra electrónica del apartado anterior para poder salir de la aplicación pulsando las teclas ‘Esc’ o ‘q’. Además, al presionar el retorno de carro, pasará alternativamente del modo dibujo al de borrado. El código es el siguiente:

Solo se explicarán las diferencias con el código del programa anterior, empezando por la declaración inicial de variables, ya que se ha añadido una de tipo booleano (borrado), cuyo valor True indicaría que la aplicación está en modo borrado. Inicialmente tiene el valor False porque se comienza en modo dibujo.
borrado = False
Lo realmente novedoso de este nuevo programa es el bucle while, cuya condición True indica que podría estar ejecutándose indefinidamente.

Dentro, lo primero que se hace es llamar a la función waitKey() para esperar cierto tiempo (se ha puesto 100 ms, aunque podría ser otro diferente) hasta que se pulse una tecla. A continuación, hay una serie de condiciones que detectan si se ha pulsado el retorno de carro para cambiar del modo dibujo al de borrado o viceversa, o las teclas con las que se quiere abandonar la aplicación (‘Esc’ o ‘q’). En este último caso únicamente se ejecutaría la sentencia break, que permite salir del bucle y, en consecuencia, de la aplicación.


El código Unicode del retorno de carro es 13, mientras que el de la tecla ‘Esc’ es 27.
Si se pulsara retorno de carro, lo primero que se haría es cambiar de modo (invertir el valor de la variable borrado) y, en función de este, modificar los valores de las variables que establecen el color del trazo (color) y su grosor (grosor). En el modo borrado, el color del trazo será el mismo que el del fondo (blanco), cuyo efecto será la eliminación de cualquier línea roja que se hubiera dibujado previamente. Además, se amplía el grosor del trazo para facilitar dicho borrado.


Una vez fuera del bucle, lo que se hace es cerrar la ventana con destroyAllWindows().
cv2.destroyAllWindows()
Ya solo queda probarlo. Esta vez, podrá corregir los trazos que no le gusten.
Unidad 6
OPERACIONES BÁSICAS DE MANEJO DE IMÁGENES
En este capítulo aprenderá cómo utilizar el color de los píxeles de una imagen. También estudiará cómo seleccionar áreas de interés, sobre las que podrán aplicarse posteriormente técnicas específicas de análisis de imágenes. La creación de regiones de interés es importante, por ejemplo, para la identificación de la matrícula de un coche aplicando técnicas de OCR (optical character recognition, reconocimiento óptico de caracteres), que requiere previamente extraer de la imagen original el área en la que se encuentra. Lo mismo sucede cuando se pretende localizar los ojos o la sonrisa de una persona, proceso que solo puede llevarse a cabo sobre la imagen de una cara, que previamente deberá haber sido identificada y separada de la principal.

El área de una imagen que tiene información relevante se conoce con el acrónimo ROI (region of interest – región de interés)
También resulta de gran utilidad la operación de escalado, ya que se emplea en procesos de comparación de imágenes. Así, por ejemplo, para saber si un objeto se corresponde con el buscado, antes de confrontar las imágenes de ambos, una de ellas deberá escalarse al tamaño de la otra.
Por último, aprenderá a realizar operaciones de adición y sustracción de imágenes, que ofrecen la posibilidad de mezclarlas u obtener sus diferencias. Además de la edición de imágenes, entre sus múltiples aplicaciones prácticas están, por ejemplo, el desarrollo de sistemas de detección de movimiento o control de calidad.
6.1 OBTENCIÓN DEL COLOR DE UN PÍXEL
En este apartado conocerá cómo extraer y utilizar el color asociado a los píxeles de una imagen. Sus aplicaciones prácticas son muy diversas, desde la modificación del aspecto de la imagen, pasando por la identificación de objetos, hasta el seguimiento de sus movimientos en escena mediante técnicas de color tracking que estudiará más adelante.
Cuando dio los primeros pasos en el manejo de imágenes, aprendió cómo acceder a un píxel para obtener o modificar su valor. Incluso hizo una práctica en la que utilizaba estos conocimientos para dibujar una rejilla, tanto en blanco y negro como en color. En dichas prácticas se asignaba un color a un píxel, pero ¿cómo se almacena realmente en una matriz ndarray? Para entenderlo, se recupera la imagen utilizada en dicho capítulo, pero, en vez de mostrar un píxel de color negro, la posición (3, 1) estará ocupada por otro de color rojo.

La sentencia que le permitiría obtener el color de este punto es:
color = img[1, 3]
Podría llegar a pensar que, en la expresión anterior, la variable color tomaría como valor una tupla o una lista con los tres colores del píxel, en concreto:
[0, 0, 255]
Sin embargo, el color de los píxeles se almacena como un array NumPy, es decir, de la siguiente forma:
[0 0 255]

Observe que los tres componentes de color no están separados por comas.
Por lo tanto, para obtener los colores primarios que componen un píxel, previamente deberá convertirlo a una lista con el método:
tolist()
Así, las sentencias que tendría que utilizar para obtener dichos colores serían:


Si la imagen fuera en blanco y negro, el valor del píxel sería el nivel de luz, por lo que no sería necesario utilizar este método.
Con el fin de practicar esta forma de obtener el color de un píxel, desarrollará un programa que, al pulsar el ratón sobre un punto de la imagen, aparecerá un círculo con el color del píxel seleccionado. Su código es el siguiente:

Una vez importada la librería OpenCV, se carga la imagen del cuadro de la niña utilizada en prácticas anteriores. Como novedad, ahora se hace una copia de dicha imagen. Para ello, se utiliza el comando copy() de Python. Esta copia, que tiene la imagen originalmente cargada del archivo, se utilizará para volver a mostrarla una vez que se deje de presionar el ratón. Cuando se pulsó, quedó modificada con un círculo del color del píxel en el que estaba situado.


Si se hubiera utilizado la sentencia de asignación:
img_original = img
La variable img_original no contendría una copia de la imagen contenida en img, sino que ambas variables compartirían la referencia al mismo objeto (imagen). Eso significa que, cuando se modificara una de dichas variables, también cambiaría la otra, ya que ambas comparten el mismo valor (apuntan al mismo objeto).
Una vez cargada la imagen, se muestra con la función imshow().
cv2.imshow(‘Cuadro’, img)
Saltando al final de programa, se encuentran las sentencias que asignan a la función color() el manejo de los eventos del ratón.
cv2.setMouseCallback(‘Cuadro’,color)
Por último, se espera a que se pulse cualquier tecla para cerrar la ventana y finalizar la ejecución del programa.

Como habrá supuesto, el código principal está situado en la función color(), dentro de la cual lo primero que se hace es definir la variable img como global, ya que se quiere utilizar fuera de la estructura de control (un if) en la que se modifica. A continuación, se discrimina si se ha pulsado el botón izquierdo del ratón o se ha dejado de pulsar, ya que en cada caso se realizarán tareas diferentes (las verá más adelante). Finalmente, se muestra la imagen con las modificaciones realizadas, que será la incorporación (o desaparición) de un círculo cuyo color de fondo sea el del píxel sobre el que se ha pulsado.

Si se hubiera pulsado el botón izquierdo del ratón, se obtendría el color del píxel en el que estuviera situado, cuyas coordenadas son los argumentos x, y de la función. A continuación, se dibujaría un círculo cuyo centro estuviera en dichas coordenadas, con un radio de 40 píxeles y, como color de fondo, el del píxel seleccionado. Esto último se consigue asignando el valor -1 al último argumento de la función circle().

Cuando se deje de pulsar el botón, lo único que se hace es volver a restaurar de nuevo la imagen original, de manera que desaparece el círculo dibujado.

Ejecute el programa y observe los diferentes tonos de color de cada zona de la imagen. Así, por ejemplo, más abajo puede ver el color de una de las franjas azules del tirante del bañador de la niña.

6.2 RECORTE DE REGIONES
Hay veces que es necesario centrarse en determinadas áreas de una imagen. Por ejemplo, si quisiera saber si una persona está sonriendo, primero tendría que localizar la región en la que está situada su cara para, después de aislarla, aplicar las técnicas de procesamiento y análisis específicas, que solo funcionarían en dichas áreas de interés.
Con el fin de entender cómo se recorta una imagen, observe la siguiente figura, en la que se pretende seleccionar los píxeles componentes del área marcada.

La forma de extraer los píxeles contenidos en la región sombreada es mediante la siguiente expresión, cuyas coordenadas se corresponden con las esquinas inferior derecha y superior izquierda:
imagen[y1:y2, x1:x2]
Por lo tanto, si la imagen de más arriba estuviera almacenada en la variable img_original, la forma de recortar la zona señalada sería:
img_recortada = img_original[2:4, 1:3]
Con el objeto de practicar esta técnica, va a desarrollar un programa que le permita fijar el área de una imagen. Al mover el ratón con el botón izquierdo pulsado, aparecerá un rectángulo de color rojo que delimitará la zona en cuestión. Cuando deje de pulsarlo, se extraerán los píxeles encerrados dentro de dicho rectángulo para crear una nueva imagen, que se mostrará en otra ventana. El código es el siguiente:

Después de importar la librería OpenCV, se declaran las variables que fijan el color (color) y el grosor (grosor) del rectángulo con el que se va a delimitar la zona que hay que recortar.

La siguiente variable establece el ancho mínimo que deberá tener esta área (ancho_min). La necesidad de esta última variable se debe a que, en Windows, la cabecera de una ventana tiene tres iconos: el de la aplicación, otro para maximizarla y el aspa que permite cerrarla. Situados, uno al lado del otro, ocupan (aproximadamente) 125 píxeles, lo que hace que este sea el ancho mínimo de una ventana. Si el área de la imagen seleccionada fuera menor, el resto de la ventana mostraría información basura. Para evitarlo, dicha variable se utilizará más adelante en una condición que solo permitirá recortes con ese ancho mínimo.
min_ancho = 125
A continuación, se carga la conocida imagen del cuadro de la niña, de la que se hace una copia (img_original). El motivo es porque, cada vez que se modifique el área que hay que recortar (enmarcada por un rectángulo de color rojo), se tendrá que borrar el rectángulo que limitaba la seleccionada anteriormente. La forma de hacerlo es restaurando la imagen original a partir de esta copia.

Después, se muestra la imagen.
cv2.imshow(‘Cuadro’, img)
Ahora, vaya al final del programa. Allí encontrará la sentencia que asigna la gestión de eventos del ratón a la función region().
cv2.setMouseCallback(‘Cuadro’,region)
Las últimas líneas de código esperan que se pulse cualquier tecla para cerrar todas las ventanas (puede haber dos, la original y la del recorte) y salir del programa.

Como cabía esperar, el código principal del programa está en la función region(), encargada de la gestión de eventos del ratón. En dicha función, se definen como globales las variables que deberán compartirse cada vez que se llame. Luego, se comprueba si el evento es el producido al pulsar el botón izquierdo del ratón (EVENT_LBUTTONDOWN), su movimiento mientras sigue pulsado (EVENT_MOUSEMOVE) o su liberación (EVENT_LBUTTONUP). Por último, se muestra la imagen modificada (aquella en la que aparece la nueva región seleccionada en ese instante) con la función imshow().


Si lo que se ha detectado es la pulsación del botón izquierdo del ratón, la posición del píxel en el que se ha producido (contenida en los argumentos x, y) se convierte en la esquina superior izquierda del área que hay que recortar (x1 e y1).

La siguiente condición detecta si se mueve el ratón mientras se mantiene pulsado el botón izquierdo. Por eso, se comprueba que, además de que el evento producido sea el de dicho movimiento (EVENT_MOUSEMOVE), el argumento flags tenga el valor EVENT_FLAG_LBUTTON, lo que indica que el botón izquierdo sigue pulsado. Si se cumpliera dicha condición, se cargaría una copia de la imagen original. Con esto se consigue (de forma indirecta) borrar el perímetro del área dibujado anteriormente, de manera que se deja la imagen preparada para trazar el nuevo. La esquina superior izquierda del nuevo rectángulo seguiría siendo la misma en la que se pulsó el ratón, mientras que la inferior derecha corresponderá a las coordenadas en las que se encuentra en cada momento (contenidas en los argumentos x, y).

Cuando se deje de pulsar el botón izquierdo del ratón (se genera el evento EVENT_LBUTTONUP), se obtendrán los píxeles contenidos dentro de los límites del rectángulo dibujado, que se utilizarán para crear una nueva imagen que se mostrará en otra ventana (img_recortada). Pero antes de hacerlo, se comprueba que las coordenadas de la esquina inferior derecha sean mayores que las de las de la superior izquierda (los píxeles de una imagen se recorren en orden creciente) y que el ancho de la región sea superior al de la ventana más pequeña que se puede crear en Windows. Si se cumplieran todas estas condiciones, se recortaría la imagen tal como se ha explicado anteriormente y se mostraría en otra ventana, cuyo título es “Recorte”.

En la siguiente imagen puede ver el resultado obtenido tras seleccionar la cara de la niña, que, una vez recortada, se muestra en otra ventana a su derecha.

6.3 ESCALADO
El escalado permite obtener una imagen con un tamaño diferente al original. Esto es especialmente importante cuando se aplican técnicas de comparación de imágenes, las cuales requieren que sean de las mismas dimensiones.
La función utilizada para el escalado de una imagen es:
resize(imagen, tamaño)
El primer argumento es la imagen que se va a escalar, mientras que el segundo es una tupla que fija el ancho y alto de la imagen escalada.
Esta función admite un tercer argumento opcional, que determina el método de interpolación que se va a utilizar. Su valor podrá ser una de las siguientes constantes:
• INTER_AREA. Úselo cuando quiera reducir el tamaño de la imagen.
• INTER_LINEAR. Recomendado para hacer zoom. Es el método de interpolación predeterminado en OpenCV.
• INTER_CUBIC. Es el más eficiente (aunque el más lento).
Para practicar con esta nueva función, va a reducir a la mitad la imagen de Notre Dame con el siguiente código:

Tras importar la librería OpenCV y cargar la imagen, se declaran las variables utilizadas en el programa. En concreto, el alto y ancho de la imagen (alto y ancho), que se obtiene del atributo shape; el nivel de escala (escala), que, como se pretende reducir a la mitad, tomará el valor 0.5; así como el ancho y alto que tendrá la imagen escalada (ancho_escalado y alto_escalado), que será el resultado de multiplicar el de la imagen original por la escala. Puesto que el número de píxeles de una imagen tiene que ser entero, deberá realizarse la operación de casting correspondiente con la función int():

La sentencia principal del programa es la que realiza el escalado con la función resize().
imagen_escalada = cv2.resize(img, (ancho_escalado, alto_escalado))
Las siguientes sentencias muestran la imagen original y la escalada en ventanas independientes.

Como ya sabe, las últimas sentencias esperan que se pulse una tecla para cerrar dichas ventanas y finalizar el programa.

A continuación, puede ver el resultado obtenido tras la ejecución de este programa. Experimente con él, modificando el valor de la variable escala, o añada un método de interpolación diferente al utilizado por defecto, para analizar así la calidad de la imagen escalada.

6.4 ADICIÓN
Si dispone de dos imágenes, es posible crear otra nueva combinando ambas con la función:
add(imagen1, imagen2)
La imagen devuelta por esta función es aquella en la que el valor de cada píxel es la suma de los valores del correspondiente en las imágenes pasadas como argumento (si la imagen fuera en color, de cada uno de los componentes de color por separado). Esto podría expresarse como:
píxel imagen compuesta = píxel imagen1 + píxel imagen2
Otra forma de sumar imágenes es aplicando un peso a cada una de ellas, de forma que, cuanto menor sea este, menos influencia tendrá en la composición final. Es como si fuera más transparente. Para ello, la función utilizada sería:
addWeighted(imagen1, peso1, imagen2, peso2, valor gamma)
En este caso, el valor de cada uno de los píxeles de la imagen compuesta sería el resultado de aplicar la fórmula:
píxel imagen compuesta = píxel imagen1*peso1 + píxel imagen2*peso2 + valor gamma
Dicha fórmula determina una suma ponderada que establece la relevancia de cada una de las imágenes en la compuesta, a la que se añade un valor gamma que aumenta el nivel de luminosidad (su valor está en el rango 0-255).

Puesto que la operación de suma se realiza píxel a píxel, ambas imágenes deberán tener las mismas dimensiones y el mismo número de canales.
Para comprobar el resultado de esta función, observe las siguientes imágenes:

¿Quiere saber cuál sería el resultado de sumarlas? Escriba el siguiente código:

En este programa, después de importar la librería OpenCV, se cargan ambas imágenes para, posteriormente, sumarlas de forma ponderada con la función addWeighted(). Como puede observar, se fija el doble de peso a la imagen de la tierra (0.8) que a la de las estrellas (0.4), por lo que destacará más.
img = cv2.addWeighted(img1, 0.4, img2, 0.8, 0)
Una vez creada la imagen compuesta, se muestra, como viene siendo habitual, con la función imshow().
El resultado obtenido tras la ejecución de este programa se muestra a continuación. Pruebe a realizar distintas combinaciones de pesos. Los resultados que puede llegar a obtener son impactantes.

Darmowy fragment się skończył.