[Parte II: visión artificial] Primeros pasos con OpenCV y Numpy

lunes, 10 de abril de 2017


En esta segunda entrega sobre visión artificial con Python trataremos una introducción a OpenCV y Numpy. Crearemos imágenes desde cero y abriremos imágenes ya existentes.



¿Qué es OpenCV?

OpenCV (Open source Computer Vision) es un libreria bajo la licencia BSD diseñada y optimizada para hacer visión por computador y aprendizaje automático. Está disponible para los lenguajes C, C++, Java y Python. Además puede correr sobre Windows, Linux, MAC, IOS y Android.

La documentación de esta libreria se puede consultar (y descargar) desde acá.

¿Qué es NumPy?

Numpy es una libreria optimizada para hacer computación científica con Python, especialmente algebra lineal (matricial) que es lo que nos interesará para hacer visión por computador.

¿Es necesario usar estas librerias?

No, la respuesta es un rotundo no. La visión por computar es básicamente operaciones matriciales, operaciones que podemos implementar no solo en Python puro y duro, sino que en casi cualqueir otro lenguaje.

La ventaja, entonces, está en que estas librerias fueron creadas y son mantenidas por verdaderos expertos en el tema (científicos de la computación, matemáticos, etc) lo que da como resultado algoritmos realmente optimizados.

Configurando el entorno

Durante esta serie de entregas necesitaremos básicamente tres librerias:
  1. OpenCV: Para hacer visión por computador.
  2. Numpy: Para hacer operaciones matriciales.
  3. Matplotlib: Para graficar (especialmente histogramas)
Todas estas librerias están disponibles para descargar usando pip.

Instalando los paquetes necesarios


# pip install opencv-python

# pip install numpy

# pip install matplotlib

¿Estamos listos? Entonces... comencemos

Como vimos en la entrega anterior, una imagen no es más que un sistema de matrices (tantas como canales tenga la imagen) superpuestas donde cada entrada representa el nivel de gris para ese píxel.

Creando nuestra primer imagen

Como ya hemos mencionado antes una imagen no es más que una matriz de valores numéricos. Demostremos esto con un simple ejemplo.

import numpy as np
import cv2

# Creamos una matriz de unos de 100 filas y 100 columnas
imagen = np.ones((100, 100, 3), np.uint8)
# Mostramos la imagen
cv2.imshow("Mi primer imagen", imagen)
# Guardamos la imagen donde esta el script
cv2.imwrite("Mi primer imagen.png", imagen)
cv2.waitKey()  # Pausa la ejecuación hasta que se presione una tecla
cv2.destroyAllWindows()  # Cierra todas las ventanas abiertas

Una vez ejecutamos el script anterior tenemos que el resultado es el siguiente:

Imagen creada con Numpy.
Imagen creada con Numpy
En las primeras dos líneas importamos numpy (y le asignamos el alias np, para no tener que estar escribiendo numpy todo el rato) y OpenCV (cv2).

Luego creamos una matriz de unos con la función ones de numpy. Si imprimimos imagen (print(numpy)) veremos que, efectivamente, es una matriz llena de unos.

numpy.ones(shape, dtype=None): Crea un array de dimensión sahape donde cada entrada es del tipo dtype.

shape: es la forma que tendrá el array, si solo tiene un elemento será una lista, dos elementos (n, m) representan una matriz con n filas y m columnas. Y con tres elementos  (n, m, z) n filas, m columnas y z representa la cantidad de matrices o canales que tendrá el array.

En este ejemplo creamos una matriz llena de unos con 100 filas, 100 columnas y un solo canal donde todas las entradas son números enteros de 8 bits.

cv2.imshow(nombre, img): Muestra la imagen img. nombre es una cadena que representará la ventana donde se muestra la imagen. img debe ser una matriz.


cv2.imwrite(nombre, img): Guarda en el disco duro la matriz img como una imagen con el nombre nombre

Entonces... ¿cómo se representa una imagen a color?.

En el ejemplo anterior creamos una imagen de un solo canal (una sola matriz) lo que significa que solo podemos representar un color. Para este caso el gris o escala de grises.

Si queremos representar una imagen a color, vamos a necesitar más canales (ya no solo una matriz) dependiendo del modelo de color con el que queremos trabajar.

import numpy as np
import cv2

# Creamos la matriz de uno 100 filas
# 100 columnas y 3 canales
imagen = np.ones((100, 100, 3), np.uint8)
# agregamos color a la imagen
# A todas las columas pero primeras 25 filas
# Asigna el valor (255, 0, 0)
imagen[:, 0:25] = (255, 0, 0)
# A todas las columnas pero desde la fila
# 25 hasta la 50 asigna el valor (0, 255, 0)
imagen[:, 25:50] = (0, 255, 0)
# A todas las columnas pero desde la fila 
# 50 hasta la 75 asigna el valor (0, 0, 255)
imagen[:, 50:75] = (0, 0, 255)

# Guardamos la imagen en el disco duro
cv2.imwrite("imagen_color.png", imagen)
# Mostramos la imagen
cv2.imshow("imagen", imagen)
# Esperamos que se presione una tecla
cv2.waitKey()
# Cerramos todas las ventanas
cv2.destroyAllWindows()

El resultado es el siguiente:

Imagen a color genrada con numpy.
Imagen a color generada con Numpy.

Y ahora sí obtenemos una imagen a color usando solamente numpy.

El cambio en este código está en la forma del array shape (100, 100, 3) donde el tres representa la cantidad de canales de la imagen, como trabajamos con el modelo RGB necesitamos tres canales, uno para el rojo, otro para el verde y un último para el azul.


OpenCV por defecto trabajo con el modelo BGR que es el mismo modelo RGB, pero con el orden de la matrices invertido.

Otra cosa nueva es la forma de asignar los colores a la imagen, esta forma se llama indexing y es muy parecido al slicing solo que para arrays multidimencionales. El formato es el siguiente:

matriz[a:b, c:d] = tupla

Donde a representa la primer fila a seleccionar y b la última fila a seleccionar. c representa la primer columa a seleccionar y d la última columna a seleccionar.

Si no se especifica a o c se selecciona desde el primer elemento.
Si no se especifica b o d se selecciona hasta el último elemento.

tupla debe ser una tupla o lista de elemetos a asignar a la matriz en la posicion señanala. Debe tener la misma longitud que canales la imagen. El primer elemento es asignado a la primer matriz y así sucesivamente hasta el último valor que es asignado a la última matriz.

Leer una imagen con OpenCV

OpenCV cuenta con una función que nos permite leer una imagen desde el disco y nos devuelve un array de numpy.

cv2.imread(name, flag): Lee la imagen name y devuelve una matriz.

Si el parámetro flag es positivo OpenCV interpretará la imagen como si tuviera tres canales y por lo tanto devolvera una matriz con tres canales.

Si el parámetro es cero  OpenCV leerá la imagen en esacala de grises y por lo tanto devolverá una matriz de un solo canal.

Si el parámetro es negativo OpenCV leerá la imagen tal cual como es y los canales de la matriz resultante dependen de la imagen a leer.


Obteniendo el tamaño de una imagen

Luego de abrir una imagen con OpenCV, el resultado es un array de numpy, por lo que tenemos todas las ventajas que nos brinda los objetos de numpy, uno de ellos es la propiedad shape.

La propiedad shape devuelve una lista que indica la cantidad de filas, columnas y canales que tiene un array respectivamente. Si la lista solo contiene dos elementos, indica que el array solo tiene un solo canal.

Su uso es muy simple:

import numpy as np
import cv2

#  Leemos la imagen
img = cv2.imread("imagen.jpg")
#  Imprimimos el tamanio
print(img.shape)

Comprendiendo el flag de la función cv2.imread()

Haciendo un par de cambios al ejemplo anterior, podemos comprender mejor el significado del falg en la función cv2.imread()

import numpy as np
import cv2

img = cv2.imread("logo.png")
print("Sin flag:", img.shape)

img = cv2.imread("logo.png", -1)
print("flag negativo:", img.shape)

img = cv2.imread("logo.png", 0)
print("flag cero:", img.shape)

img = cv2.imread("logo.png", 1)
print("flag positivo:", img.shape)

El resultado será algo así:

Sin flag: (99, 82, 3)
flag negativo: (99, 82, 4)
flag cero: (99, 82)
flag positivo: (99, 82, 3)

En todos los casos tenemos una matriz de 99 filas y 82 columnas, pero con distintos canales. Analicemos el significado.

  • flag negativo: La imagen se interpreta tal cual como es, por lo que tenemos tres canales para el color y uno más para la transparencia.
  • flag cero: La imagen se interpreta a escala de grises y por lo tanto sólo se necesita un canal.
  • flag positivo: La imagen se interpreta como si sólo tuviera tres canales (elimina la transparencia).

Diferencias según el flag.
Diferencias según el flag.
Saludos.
Once.

No hay comentarios:

Publicar un comentario