¡Esta es una revisión vieja del documento!
Tabla de Contenidos
OpenCV
OpenCV (Open Source Computer Vision) es una biblioteca de funciones centradas en la visión artificial en tiempo real. Creada por Intel en 1999. Aunque esté programada en C++, hay bindings en Python.
En Python el módulo se instala pip install opencv-python
Imágenes en Jupyter
Hasta que se diga lo contrario, el código que se muestra en este apartado es para abrir imágenes y mostrarlas en un cuaderno de Jupyter.
import numpy as np import matplotlib.pyplot as plt %matplotlib inline # Importamos el módulo de OpenCV import cv2 # Lectura de una imagen img = cv2.imread('ruta/imagen.png') type(img) # numpy.ndarray # Mostrar la imagen: plt.imshow(img)
El módulo de OpenCV nos ahorra la creación del array de NumPy. Sencillamente cargamos la imagen y la habrá creado como un array de NumPy
La imagen que mostramos mediante Matplotlib se verá rara porque OpenCV lee los canales en un orden diferente:
- Matplotlib: Rojo, Verde y Azul
- OpenCV: Azul, Verde y Rojo.
Para solucionarlo:
# Convertir Blue-Green-Red en Red-Green-Blue fix_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Lo mostramos: plt.imshow(fix_img)
Podemos leer una imagen en escala de grises:
img_gray = cv2.imread(''ruta/imagen.png'', cv2.IMREAD_GRAYSCALE) img_gray.shape # Ya no tendremos la parte de los canales de color # (1080,1920) # Elegimos representarla con el colormap de escala de grises plt.imshow(img_gray,cmap='gray') # Elegimos representarla con el colormap de magma plt.imshow(img_gray,cmap='magma')
Redimensionar
# 720x480 serán las nuevas dimensiones new_img = cv2.resize(fix_img, (720,480))
Podemos redimensionar por ratio:
w_ratio = 0.5 # ratio de la anchura, 50 % del original h_ratio = 0.5 # ratio de la altura, 50 % del original new_img = cv2.resize(fix_img, (0,0), fix_img, w_ratio, h_ratio) new_img.shape # (540, 960, 3)
Girar
Ponerla al revés:
new_img = cv2.flip(fix_img, 0) plt.imshow(new_img)
Girarla sobre el eje Y (espejo):
new_img = cv2.flip(fix_img, 1) plt.imshow(new_img)
Para hacer los dos giros a la vez:
new_img = cv2.flip(fix_img, -1) plt.imshow(new_img)
Guardar
cv2.imwrite('/ruta/nueva_imagen.png', fix_img)
Definir tamaño lienzo Matplotlib
# Dimensiones del lienzo/canvas en pulgadas fig = plt.figure(figsize=(10,8)) # El lienzo afectará a 1 imagen ax = fig.add_subplot(111) # Mostramos una imagen en el nuevo lienzo ax.imshow(fix_img)
Imágenes en OpenCV
import cv2 img = cv2.imread('../101/foo.png') # 'Foobar' será el título de la ventana que mostrará la imagen cv2.imshow('Foobar', img) cv2.waitKey()
Se abrirá una ventana con la imagen. La imagen se abrirá en su tamaño original. No se puede redimensionar la ventana, solo cerrarla.
Si el código de arriba genera una ventana que no responde, podemos arreglarlo con el siguiente código modificado:
import cv2 img = cv2.imread('../101/foo.png') while True: cv2.imshow('Foobar', img) // Si hemos esperado al menos 1 milisegundo Y pulsamos la tecla 'q' if cv2.waitKey(1) & 0xFF == ord('q'): break cv2.destroyAllWindows()
La ventana se cerrará cuando pulsemos Q
Dibujar en imágenes
Los siguientes ejemplos son para realizar en un cuaderno de Jupyter
import cv2 import numpy as np import matplotlib.pyplot as plt %matplotlib inline # Creamos una imagen de 512x512 rellenando el array NumPy de ceros para obtener una imagen en negro blank_img = np.zeros(shape=(512,512,3), dtype=np.int16) # La mostramos: plt.imshow(blank_img)
Rectángulos
Para dibujar un rectángulo sobre esa imagen, utilizamos la función rectangle():
cv2.rectangle(blank_img, pt1=(384, 10), pt2=(500, 150), color=(0, 255,0), thickness=10)
pt1: coordenadas de partida del rectángulo, esquina superior izquierda.pt2: coordenadas finales, esquina inferior derecha.color: color del rectángulo en RGBthickness: grosor (en pixels) de la línea del rectángulo
Si quisiéramos hacer un rectángulo justo en el medio para el ejemplo de arriba:
cv2.rectangle(blank_img, pt1=(200, 200), pt2=(300, 300), color=(0, 0, 255), thickness=10)
Círculos
Para dibujar círculos se utiliza la función circle():
cv2.circle(img = blank_img, center=(100, 100), radius=50, color=(255,0,0), thickness=8)
center: coordenadas que marcan el centro del círculoradius: radio del círculo en píxeles.color: color del círculo en RGBthickness: grosor (en pixels) de la línea del círculo.
Si queremos que las figuras tengan relleno, el argumento thickness tiene que valer -1:
cv2.circle(img = blank_img, center=(400, 400), radius=50, color=(255,0,0), thickness=-1)
El código anterior dibujará un círculo totalmente rojo, tanto en su línea como relleno.
Líneas
Para dibujar líneas se usa el método line():
cv2.line(blank_img, pt1=(0, 0), pt2=(512, 512), color=(102,255,255), thickness=5)
Texto
Primero cargamos una tipografía:
font = cv2.FONT_HERSHEY_SIMPLEX
Dibujamos el texto:
cv2.putText(blank_img, text = 'Hola', org=(10, 500), fontFace = font, fontScale=4, color=(255,255,255), thickness=3, lineType=cv2.LINE_AA)
Polígonos
Partimos de una imagen nueva:
blank_img = np.zeros(shape=(512,512,3), dtype=np.int32)
Creamos los vértices del polígono:
# Coordenadas del polígono vertices = np.array([ [100,300], [200,200], [300,300], [200,400] ], dtype=np.int32) # Como OpenCV espera un array de 3 dimensiones vertices.shape #(4, 2) # Recreamos el array para que tenga 3 dimensiones: pts = vertices.reshape((-1, 1, 2)) pts.shape # (4, 1, 2)
Finalmente creamos el polígono:
cv2.polylines(blank_img, [pts], isClosed=True, color=(255,0,0), thickness=5)
Dibujar en imágenes con el ratón
Podemos usar CallBacks para conectar imágenes con funciones de eventos mediante OpenCV. Esto nos permite interactuar directamente con imágenes o vídeos.
import cv2 import numpy as np # Función def draw_circle(event, x, y, flags, param): # Los argumentos son cubiertos por el método setMouseCallback() # Si se recibe el evento de pulsación de botón del ratón, # dibujaremos en la imagen un círculo if event == cv2.EVENT_LBUTTONDOWN: cv2.circle(img, (x,y), 100, (0,255,0), -1) # Si pulsamos con el botón derecho, dibujaremos un # círculo de otro color elif event == cv2.EVENT_RBUTTONDOWN: cv2.circle(img, (x,y), 100, (255, 0, 0), -1) # Conectaremos la función anterior con la imagen gracias al # nombre de la ventana y la siguiente función de Callback cv2.namedWindow(winname = 'foobar') cv2.setMouseCallback('foobar', draw_circle) # Creamos una imagen img = np.zeros(shape=(512,512,3), dtype=np.int8) # Mostramos la imagen con OpenCV while True: cv2.imshow('foobar', img) # Mostramos la imagen mientras no se pulse 'q' if cv2.waitKey(20) & 0xFF == ord('q'): break cv2.destroyAllWindows()
En el siguiente código se muestra cómo dibujar rectángulos sobre una imagen:
import cv2 import numpy as np # Variables # True cuando estemos pulsando el botón # False cuando lo dejemos de pulsar dibujando = False ix = -1 iy = -1 # Función def draw_rectangle(event, x, y, flags, params): global ix, iy, dibujando # Si pulsamos el botón izquierdo del ratón if event == cv2.EVENT_LBUTTONDOWN: dibujando = True ix, iy = x, y # Si movemos el ratón elif event == cv2.EVENT_MOUSEMOVE: if dibujando == True: cv2.rectangle(img, (ix, iy), (x,y), (0,255,0), -1) # Soltamos el botón izquierdo del ratón elif event == cv2.EVENT_LBUTTONUP: dibujando = False cv2.rectangle(img, (ix,iy), (x,y), (0,255,0), -1) # Mostrar imagen # Imagen en negro img = np.zeros((512,512,3)) cv2.namedWindow(winname='foobar') cv2.setMouseCallback('foobar', draw_rectangle) while True: cv2.imshow('foobar', img) # Mostramos la imagen mientras no se pulse 'q' if cv2.waitKey(20) & 0xFF == ord('q'): break cv2.destroyAllWindows()
Procesamiento de imágenes
Espacio de colores
Un espacio de color es un sistema de interpretación del color, es decir, una organización específica de los colores en una imagen o vídeo.
Un modelo de color es un modelo matemático abstracto que describe la forma en la que los colores pueden representarse como tuplas de números. Por ejemplo, en el modelo RGB los colores son modelados como combinación de rojo (red), verde (green) y azul (blue).
Otras alternativas son HSL (hue, saturation, lightness) y HSV (hue, saturation, value). Son más cercanos a cómo la visión humana realmente percibe el color.
El modelo RGB se representa como un cubo y HSL y HSV como un cilindro.
Conversión de espacio de colores con OpenCV:
import cv2 import matplotlib.pyplot as plt %matplotlib inline img = cv2.imread(r'D:\apps\python\WPy64-3850\scripts\opencv\lenna.png') # Si ahora la mostramos con Matplotlib, se verá azulada porque # OpenCV espera las imágenes en formato azul, verde, rojo plt.imshow(img) # Para cambiar el espacio de color: img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
Si queremos pasar al modelo HSL o HSV:
# RGB -> HSV img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV) # RGV -> HSL img = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
Umbrales
Thresholding es un método que permite segmentar una imagen en diferentes partes. Una imagen se convierte en un conjunto de dos valores: blanco o negro.
import cv2 import matplotlib.pyplot as plt %matplotlib inline # Leemos una imagen en escala de grises: img = cv2.imread("/ruta/imagen.png", 0) # Para mostrarla correctamente con Matplotlib, indicamos el mapa de colores: plt.imshow(img, cmap='gray') # Threshold: con el segundo parámetro indicamos el umbral, es decir, # los que estén por debajo de ese valor serán 0 (negro), y por encima 1 (blanco) # El último argumento es el tipo de threshold ret, thresh1 = cv2.threshold(img, 127, 255, cv.THRESH_BINARY) # Mostramos: plt.imshow(thresh1, cmap = 'gray')
Vídeo
Conexión con webcam
import cv2 # Cogemos la cámara por defecto cap = cv2.VideoCapture(0) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # Mostramos la imagen while True: # El frame será cada una de las imágenes # que va capturando la cámara ret, frame = cap.read() cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break # Dejamos de capturar el vídeo cap.release() cv2.destroyAllWindows()
Si queremos guardar el stream en un vídeo, primero crearemos el fichero indicando su ruta, el códec que usaremos, los frames por segundo y las dimensiones del vídeo:
# Guardar el stream en un fichero de vídeo # Windows -> *'DIVX' # MacOS, Linux -> *'XVID' writer = cv2.VideoWriter("video-cap.mp4", cv2.VideoWriter_fourcc(*'DIVX'), 20, (width, height))
Luego tendremos que ir guardando los frames que vayamos capturando de la cámara:
# Mostramos la imagen while True: # El frame será cada una de las imágenes # que va capturando la cámara ret, frame = cap.read() # Operaciones writer.write(frame)
Código final:
import cv2 # Cogemos la cámara por defecto cap = cv2.VideoCapture(0) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # Guardar el stream en un fichero de vídeo # Windows -> *'DIVX' # MacOS, Linux -> *'XVID' writer = cv2.VideoWriter("video-cap.mp4", cv2.VideoWriter_fourcc(*'DIVX'), 20, (width, height)) # Mostramos la imagen while True: # El frame será cada una de las imágenes # que va capturando la cámara ret, frame = cap.read() # Operaciones writer.write(frame) cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break # Dejamos de capturar el vídeo cap.release() cv2.destroyAllWindows()
Reproducir vídeos
import cv2 import time cap = cv2.VideoCapture("video-cap.mp4") if cap.isOpened() == False: print("ERROR: Archivo no encontrado o codec incorrecto") while cap.isOpened(): ret, frame = cap.read() if ret == True: cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break else: break cap.release() cv2.destroyAllWindows()
La reproducción será muy rápida ya que no está pensado para ser visto por humanos, sino que la función hace que el ordenador lea todos los frames del vídeo a la velocidad que sea capaz. Esto es útil si queremos manipular los vídeos sin esperar que se reproduzcan a velocidad humana.
Si queremos reproducirlo a la misma velocidad que fue grabado, añadimos un par de instrucciones:
import cv2 import time cap = cv2.VideoCapture("video-cap.mp4") if cap.isOpened() == False: print("ERROR: Archivo no encontrado o codec incorrecto") while cap.isOpened(): ret, frame = cap.read() if ret == True: # Según sea el FPS del vídeo. Para este ejemplo, # Hacemos que se muestren 20 imágenes por segundo time.sleep(1/20) cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break else: break cap.release() cv2.destroyAllWindows()
Dibujando en streaming
El procedimiento es similar a dibujar sobre una imagen, solo que lo hacemos sobre un frame.
import cv2 cap = cv2.VideoCapture(0) width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # Esquina superior izquierda x = width // 2 # Nos aseguramos de obtener un entero y = height // 2 # anchura y altura del rectángulo w = width // 4 h = height // 4 # Esquina inferior derecha: x + w, y + h while True: ret, frame = cap.read() cv2.rectangle(frame, (x,y), (x+w,y+h), color=(0,0,255), thickness=4) cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
Si queremos dibujar mientras el vídeo se reproduce:
import cv2 # Función callback def draw_rectangle(event, x, y, flags, params): global pt1, pt2, topLeft_clicked, botRight_clicked if event == cv2.EVENT_LBUTTONDOWN: # Reiniciamos / borramos el rectángulo cuando # ya hayamos pulsado en dos puntos para hacer # el rectángulo if topLeft_clicked == True and botRight_clicked == True: pt1 = (0,0) pt2 = (0,0) topLeft_clicked = False botRight_clicked = False if topLeft_clicked == False: pt1 = (x,y) topLeft_clicked = True elif botRight_clicked == False: pt2 = (x,y) botRight_clicked = True # Variables globales pt1 = (0,0) pt2 = (0,0) # Seguimiento de las pulsaciones del ratón topLeft_clicked = False botRight_clicked = False # Conexión con la función de callback cap = cv2.VideoCapture(0) cv2.namedWindow('Test') cv2.setMouseCallback('Test', draw_rectangle) while True: ret, frame = cap.read() # Dibujamos sobre el frame basado en las variables globales if topLeft_clicked: cv2.circle(frame, center=pt1, radius=5, color=(0,0,255), thickness=-1) if topLeft_clicked and botRight_clicked: cv2.rectangle(frame, pt1, pt2, color=(0,0,255), thickness=3) cv2.imshow('Test', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
Detección de objetos
Varios métodos:
- Coincidencias con modelos (template matching): busca una copia exacta de una imagen en otra imagen.
- Detección de esquinas: búsqueda de esquinas en las imágenes
- Detección de filos (edge detection): búsqueda de los contornos de los objetos.
- Detección de rejillas (grid detection): combinación de los dos anteriores métodos.
- Detección de contornos (contour detection):
- Coincidencia de características (feature matching)
- Algoritmo Watershed:
- Haar cascades: para la detección de caras y ojos.
Template matching
Es la forma más simple de detección de objetos. Escanea una imagen más grande con una plantilla para comprobar si hay coincidencia.
for m in metodos: # Creamos una copia full_copy = full.copy() metodo = eval(m) # Template matching res = cv2.matchTemplate(full_copy, face, metodo) # A través del mapa de calor (que indica la coincidencia) # obtenemos varios valores min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # Para estos dos métodos, el mapa de calor funciona diferente if metodo in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]: top_left = min_loc # (x, y) else: top_left = max_loc height, width, channels = face.shape bottom_right = (top_left[0]+width, top_left[1]+height) cv2.rectangle(full_copy, top_left, bottom_right, (255,0,0), 10) # Mostramos un par de imágenes: # el mapa de calor de la detección y la detección plt.subplot(121) plt.imshow(res) plt.title('Mapa de calor') plt.subplot(122) plt.imshow(full_copy) plt.title("Detección de plantilla") # Título del método usado: plt.suptitle('Método ' + m) plt.show() print("\n") print("\n")
Detección de caras
Haar Cascades, componente clave del framwork de detección de objetos Viola-Jones.
Con este método detectaremos si existe alguna cara en una imagen, aunque no a quién pertenece ya que eso es parte de deep learning.
El algoritomo Haar cascades en lugar de recorrer la imagen en busca de una imagen, pasa una cascada de clasificadores.
Pasos:
- Imagen con una cara mirando hacia la cámara
- Convertir la imagen a escala de grises
- Comienza la búsqueda de características Haar Cascade
- Primero los ojos
- Luego el tabique nasal
- …
La búsqueda se hace con la idea de características de línea y de filo/margen
Cuando la imagen pasa todos los clasificadores, se puede concluir que se ha detectado una cara.
El problema de este método es que necesita un conjunto muy grande de información. La parte buena es que OpenCV viene con ficheros XML pre-entrenados para Haar.
