Herramientas de usuario

Herramientas del sitio


informatica:programacion:python:modulos:opencv

¡Esta es una revisión vieja del documento!


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 RGB
  • thickness: 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írculo
  • radius: radio del círculo en píxeles.
  • color: color del círculo en RGB
  • thickness: 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 el objetivo
  • 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

informatica/programacion/python/modulos/opencv.1607607550.txt.gz · Última modificación: por tempwin