Visión artificial

Tekst
0
Recenzje
Przeczytaj fragment
Oznacz jako przeczytane
Czcionka:Mniejsze АаWiększe Aa

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”.