Transformación de perspectiva | OpenCV con Python

Por Administrador

En el post de hoy trataremos la transformación de perspectiva mediante el uso de OpenCV en Python. Esta técnica nos permitirá alinear imágenes, y por lo tanto mejorar la vista que tenemos sobre ellas para que podamos usarlas posteriormente en otros procesos.

Para aplicar esta técnica será necesario especificar 4 puntos, en donde están las 4 esquinas de la imagen a transformar, estas las especificaremos de forma manual y además mediante la ayuda de los eventos del mouse tanto para una imagen como para video. Para el próximo tutorial en cambio iremos más allá y trataré de automatizar la búsqueda de estos 4 puntos.

CONTENIDO

Transformación de perspectiva

  • cv2.getPerspectiveTransform
  • cv2.warpPerspective
  • Aplicando la transformación de perspectiva a una imagen
  • Usando los eventos del mouse y aplicando la transformación de perspectiva a una imagen con OpenCV
  • Usando los eventos del mouse y aplicando la transformación de perspectiva en un video con OpenCV

Transformación de perspectiva

Figura 1: (Izq. Imagen de entrada, Der. Imagen resultante)Ejemplo de transformación de perspectiva.

En esta ocasión tendremos como objetivo aplicar la transformación de perspectiva de una imagen, por ejemplo en la figura 1 tenemos la imagen de entrada, de donde hemos tomado 4 puntos correspondientes al área en donde está presente el perrito. Luego basándonos en estos 4 puntos obtenemos una nueva imagen alineada a la cual podremos aplicar otro tipo de procesos, dependiendo de la aplicación que se necesite realizar.

La imagen de entrada de la figura 1 la he manipulado manualmente, para darle aquella deformación, pero hay que tomar en cuenta que estas deformaciones pueden darse en la vida real, por diferentes factores tales como la ubicación de la cámara sobre el objeto que se desee analizar.

Para la aplicación de esta técnica necesitaremos principalmente 2 funciones: cv2.getPerspectiveTransform y cv2.warpPerspective que veremos a continuación.

cv2.getPerspectiveTransform

Esta función calcula la transformación de perspectiva a partir de 4 pares de puntos, por lo tanto de esta se obtendrá una matriz de 3×3 (para más información sobre esta función puedes visitar este link).

Los parámetros que debemos especificar son:

  • Coordenadas de los 4 puntos correspondientes a los vértices cuadrangulares de la imagen de entrada.
  • Coordenadas de los 4 puntos correspondientes a los vértices cuadrangulares de la imagen de destino.

cv2.warpPerspective

Esta función aplica la transformación de perspectiva sobre una imagen.

Los parámetros que debemos especificar son:

  • Imagen de entrada
  • M, matriz de transformación de 3×3.
  • Tamaño de la imagen de salida

Para más información sobre esta función puedes visitar la documentación de OpenCV.

Ahora si podemos pasar a lo divertido, ¡la programación!.

Aplicando la transformación de perspectiva a una imagen

Para realizar este ejemplo práctico, vamos a usar la siguiente imagen:

Figura 2: Imagen de entrada.

Entonces el objetivo será alinear la imagen correspondiente al gatito, ya verás que solo nos llevará unas cuantas líneas de código. Crearemos un script con el nombre perspective_transformation_points.py.

import cv2
import numpy as np

imagen = cv2.imread('gato.jpeg')

cv2.circle(imagen, (84, 69), 7, (255,0,0), 2)
cv2.circle(imagen, (513, 77), 7, (0,255,0), 2)
cv2.circle(imagen, (113, 358), 7, (0,0,255), 2)
cv2.circle(imagen, (542, 366), 7, (255,255,0), 2)
    
pts1 = np.float32([[84,69], [513,77], [113, 358], [542,366]])
pts2 = np.float32([[0,0], [480,0], [0,300], [480,300]])

M = cv2.getPerspectiveTransform(pts1, pts2)
dst = cv2.warpPerspective(imagen, M, (480,300))

cv2.imshow('Imagen', imagen)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

Línea 1 y 2: Importamos OpenCV y numpy.

Línea 4: Leemos la imagen de entrada.

Línea 6 a 9: Aquí únicamente estamos visualizando los 4 puntos correspondientes a los vértices de la imagen del gatito. Estos puntos lo he encontrado a base de prueba y error. Si los visualizamos tendríamos lo siguiente:

Figura 3: Indicando con círculos los 4 vértices de la imagen de entrada.

He asignado para este ejemplo diferentes colores para cada punto, por lo que el vértice superior izquierdo será azul mientras que el derecho verde, el vértice inferior izquierdo rojo, y el derecho cian (podrías omitir la visualización de dichos puntos).

Línea 11: Declaramos un array np.float32, en él especificaremos las coordenadas de los 4 puntos que habíamos dibujado en la imagen de entrada. En este caso he tomado el siguiente orden: coordenada del vértice  superior izquierdo, coordenada del vértice superior derecho, inferior izquierdo e inferior derecho.

Línea 12: Declaramos un array np.float32, en él especificaremos las coordenadas de los 4 puntos de los vértices de la imagen de destino. Estos puntos tendrán el mismo orden que aquellos que habíamos declarado en la línea 11. Entonces la primera posición corresponderá al vértice superior izquierdo que se ubicará en [0,0], el vértice superior derecho en [480,0], vértice inferior izquierdo [0,300] y vértice inferior derecho [480,300].

Figura 4: Coordenadas de los vértices de la imagen de entrada y coordenadas de los vértices de la imagen de destino.

Toma en cuenta que las coordenadas de la línea 12 las he establecido yo, tu podrías cambiar 300 y 480, dependiendo del alto y ancho de la imagen que deseas obtener.

ATENCIÓN: Tú puedes definir el orden de las coordenadas de los 4 vértices, pero recuerda que ese mismo orden debes tomarlo tanto para los 4 puntos de entrada como los 4 puntos de destino, caso contrario podrías obtener una imagen de salida no deseada.

Línea 14: Calculamos la matriz de transformación mediante el uso de cv2.getPerspectiveTransform, en esta especificamos pts1pts2 que corresponden a las coordenadas de la imagen de entrada y salida respectivamente.

 Línea 15: Como último paso procedemos a realizar la transformación con ayuda de cv2.warpPerspective especificando a la imagen de entrada, la matriz M, y el ancho y alto de la imagen resultante. Nuestra nueva imagen se almacena en dst.

Línea 17 a 20: Aquí realizamos la visualización de la imagen de entrada y la de salida, veamos:

Figura 5: Izq. Imagen de entrada. Der. Imagen de destino.

En la figura 5, podemos ver a la izquierda la imagen de entrada, con los 4 puntos dibujados correspondientes a los 4 vértices, mientras que a la derecha tenemos nuestra imagen de salida. Como podemos ver esta imagen ya está alineada correctamente (en las esquinas también podemos ver los círculos dibujados en la imagen de entrada, si no queremos que esto aparezca podemos simplemente comentar las líneas 6 a 9).

Ya cumplimos con nuestro objetivo, pero… el tomar los 4 puntos de los vértices de la imagen de entrada mediante prueba y error puede ser un poquito cansado y poco eficiente, ¿qué te parece si los elegimos con ayuda del ratón, usando los eventos que nos ofrece OpenCV?

Usando los eventos del mouse y aplicando la transformación de perspectiva a una imagen con OpenCV

El ejemplo pasado nos ayudó a entender como aplicar la transformación de perspectiva sobre una imagen. Allí especificamos en un principio los 4 vértices de la imagen de entrada, ahora en este ejemplo procederemos a obtener estos 4 vértices mediante clics izquierdos.

Para este ejemplo crearemos un script llamado perspective_transformation_clics.py

import cv2
import numpy as np

def clics(event,x,y,flags,param):
    global puntos
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(imagen,(x,y),5,(0,255,0),2)
        puntos.append([x,y])

def uniendo4puntos(puntos):
    cv2.line(imagen,tuple(puntos[0]),tuple(puntos[1]),(255,0,0),1)
    cv2.line(imagen,tuple(puntos[0]),tuple(puntos[2]),(255,0,0),1)
    cv2.line(imagen,tuple(puntos[2]),tuple(puntos[3]),(255,0,0),1)
    cv2.line(imagen,tuple(puntos[1]),tuple(puntos[3]),(255,0,0),1)

puntos = []
imagen = cv2.imread('gato.jpeg')
aux = imagen.copy()
cv2.namedWindow('Imagen')
cv2.setMouseCallback('Imagen',clics)

while True:

    if len(puntos) == 4:
        uniendo4puntos(puntos)
        pts1 = np.float32([puntos])
        pts2 = np.float32([[0,0], [480,0], [0,300], [480,300]])

        M = cv2.getPerspectiveTransform(pts1,pts2)
        dst = cv2.warpPerspective(imagen, M, (480,300))

        cv2.imshow('dst', dst)
    cv2.imshow('Imagen',imagen)
    
    k = cv2.waitKey(1) & 0xFF
    if k == ord('n'): # Limpiar el contenido de la imagen
        imagen = aux.copy()
        puntos = []
        
    elif k == 27:
        break

cv2.destroyAllWindows()

Línea 1 y 2: Importamos OpenCV y numpy.

Línea 4 a 8: Declaramos la función clics que nos ayudará con los efectos del mouse. Indicamos que vamos a usar la variable puntos (esta irá almacenado cada coordenada de los vértices cuadrangulares), luego si el botón izquierdo del mouse es presionado se dibujará un círculo y además se almacenarán las coordenadas x e y en puntos.

NOTA: Si quieres profundizar un poquito más sobre los eventos del mouse puedes visitar este post: ?️ EVENTOS DEL MOUSE | OpenCV con Python.

Línea 10 a 14: Declaramos la función uniendo4puntos, la cual recibirá como parámetro la variable donde estén contenidos los 4 puntos correspondientes a las coordenadas de los vértices. Esta función simplemente dibujará líneas que unirán los 4 puntos de los vértices.

Ya que esta función es solo para visualización, podrías omitirla.

Línea 16: Declaramos un array vacío, puntos en el cual se almacenarán las coordenadas de los vértices conforme se presione el botón izquierdo del mouse.

Línea 17 y 18: Leemos la imagen de entrada y creamos una imagen aux, copia de la imagen de entrada. Esta última nos servirá para limpiar la imagen de entrada de los clics izquierdos realizados, ya lo veremos a más tarde en este mismo código.

Línea 19 y 20: Especificamos el nombre de la ventana de donde se tomarán los eventos del mouse, y usamos cv2.setMouseCallback para poder detectarlos. Nuevamente, si te sientes un poco confundido en esta parte, puedes revisar el post en donde hablo sobre los eventos del mouse en opencv.

Línea 24 a 25: Si hemos especificado 4 puntos, entonces llamaré a la función uniendo4puntospara la visualización.

Línea 26: Declaramos un array np.float32, en él especificaremos la variable puntos, que contiene las 4 coordenadas x e y, de cuando se presione el botón izquierdo del mouse.

Línea 27: Declaramos un array np.float32, en él especificaremos las coordenadas de los 4 puntos de los vértices de la imagen de destino.

Línea 29 y 30: Obtenemos la matriz M mediante, cv2.getPerspectiveTransform, y para aplizar la transformación de perspectiva usamos cv2.warpPerspective, tal y como lo realizamos en el programa anterior.

Línea 32: Visualizamos la nueva imagen.

Línea 33: Visualizamos la imagen de entrada.

Línea 36 a 38: Si presionamos la tecla ‘n’, se limpiarán los puntos dibujados por el clic izquierdo en la imagen de entrada, además que la variable puntos la declaramos como vacía.

Línea 40 a 43: Si se presiona ‘ESC’, el proceso terminará y se cerrarán las ventanas.

Veamos como se visualizaría cuando presionemos los clics izquierdos:

Figura 6: Selección de los 4 puntos correspondientes a los 4 vértices de la imagen de entrada.

En la figura 6, vemos capturas de cada uno de los clics sobre los 4 vérticecs, empezando por el vértice superior izquierdo, luego superior derecho, inferior izquierdo e inferior derecho. Toma en cuenta que cuando ya hemos especificado los 4 puntos, también se han dibujado líneas que conectan estos puntos y por lo tanto rodean la imagen del gatito.

Ahora veamos la imagen de salida, almacenada en dst:

Figura 7: Imagen de salida.

Esta es la imagen de salida, una imagen ya alineada gracias a la transformación de perspectiva. Toma en cuenta que esta imagen tiene los bordes azules y parte de los círculos dibujados de los vértices. Si no deseeas que estos se visualicen, simplemente omite la línea 7, y la función uniendo4puntos.

Usando los eventos del mouse y aplicando la transformación de perspectiva en un video con OpenCV

Ya vimos como emplear los eventos del mouse para elegir los 4 puntos, ahora veamos como realizarlo para un video. La programación será similar, así que solo me centraré en las líneas que difieren del ejemplo anterior.

Crearemos un script llamado perspective_transformation_clics_video.py.

import cv2
import numpy as np

def clics(event,x,y,flags,param):
    global puntos
    if event == cv2.EVENT_LBUTTONDOWN:
        puntos.append([x,y])

def dibujando_puntos(puntos):
    for x, y in puntos:
        cv2.circle(frame,(x,y),5,(0,255,0),2)

def uniendo4puntos(puntos):
    cv2.line(frame,tuple(puntos[0]),tuple(puntos[1]),(255,0,0),1)
    cv2.line(frame,tuple(puntos[0]),tuple(puntos[2]),(255,0,0),1)
    cv2.line(frame,tuple(puntos[2]),tuple(puntos[3]),(255,0,0),1)
    cv2.line(frame,tuple(puntos[1]),tuple(puntos[3]),(255,0,0),1)

puntos = []
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
#cap = cv2.VideoCapture('Video1.mp4')
cv2.namedWindow('frame')
cv2.setMouseCallback('frame',clics)

while True:
    ret, frame = cap.read()
    if ret == False: break
    dibujando_puntos(puntos)

    if len(puntos) == 4:
        uniendo4puntos(puntos)
        pts1 = np.float32([puntos])
        pts2 = np.float32([[0,0], [500,0], [0,300], [500,300]])

        M = cv2.getPerspectiveTransform(pts1,pts2)
        dst = cv2.warpPerspective(frame, M, (500,300))

        cv2.imshow('dst', dst)
    cv2.imshow('frame',frame)
    
    k = cv2.waitKey(1) & 0xFF
    if k == ord('n'): # Limpiar el contenido de la frame
        puntos = []
        
    elif k == 27:
        break
cap.release()
cv2.destroyAllWindows()

Línea 4 a 7: Estas son similares al programa anterior, pero en este caso he retirado la linea que correspondía a dibujar los círculos, ya que al probar con video, el círculo se dibujaba solo cuando se presionaba el botón izquierdo del ratón y luego desaparecía, es por ello que realicé otra función que nos ayudará a dibujar los puntos.

Línea 9 a 11: Declaramos la función dibujando_puntos, esta requiere de las coordenadas almacenadas en puntos para dibujar un círculo sobre ellos.

Línea 20 y 21: Podremos realizar un video streaming con la línea 20, o leer un video con la línea 21. Si elijes el segundo caso tendrás que cambiar el nombre del video a leer, en mi caso lo he dejado con 'Video1.mp4', como ejemplo.

Línea 28: Llamamos a la función dibujando_puntos, para que conforme hagamos clic se dibuje un círculo sobre ese lugar en la imagen.

Línea 33 y 36: Para probar con otras dimensiones para la imagen de salida, he cambiado los valores, esta vez de ancho será 500 pixeles mientras que de alto 300.

Línea 42 y 43: En esta condición para limpiar la pantalla, solo necesitaremos declarar a puntos como un array vacío.

¡Ahora si, probemos su funcionamiento!.

Figura 8: Aplicación de transformación de perspectiva empleando un video.

En la figura 8, a la derecha podemos ver los resultados de la transformación de perspectiva empleando un video. Allí al igual que con el ejemplo anterior seleccionamos los 4 vértices de la imagen de entrada, que quedan dibujados.

Y bien, hasta aquí llega este tutorial. Espero que te haya sido útil, cuídate mucho. Nos vemos en el siguiente post o video!. 🙂

REFERENCIAS: