Template matching | OpenCV con Python

Por Administrador

Bienvenidos a un nuevo tutorial. En este veremos como aplicar template matching / emparejamiento de patrones / emparejamiento de plantillas, usando OpenCV en Python. ¡Así que vamos a empezar!

CONTENIDO:

  • ¿Qué es template matching?
  • ¿Cómo funciona el template matching?
  • Métodos que usa OpenCV para aplicar Template matching
  • Programa ejemplo para aplicar template matching con OpenCV y Python
    • ¿Qué pasa si queremos cambiar el método de la línea 9?
  • Programa aplicando los 6 métodos del template matching que ofrece OpenCV
  • Limitaciones del template matching

¿Qué es template matching?

Figura 1: Dada una imagen (izquierda), y una imagen plantilla (derecha). El template matching busca encontrar el mejor emparejamiento en la imagen, dada una imagen plantilla.

El template matching o emparejamiento de plantillas es una técnica que nos permite encontrar en una imagen, áreas similares a las de una plantilla o imagen más pequeña. De este modo podremos encontrar el lugar en la imagen más grande donde se encuentre el mejor emparejamiento o que sea similar a la imagen del template (plantilla). 

¿Cómo funciona el template matching?

Figura 2: Imagen I (izquierda), imagen plantilla (derecha).

Para aplicar esta técnica necesitamos de: 

  • Imagen (I), que es la imagen de entrada de donde esperamos realizar el emparejamiento. 
  • Template o imagen plantilla (T), que es la imagen que va a ser comparada en toda la imagen I, de tal modo que se pueda obtener el mejor emparejamiento. 

Para realizar el emparejamiento, la imagen template se deslizará por toda la imagen I de izquierda a derecha y de arriba abajo, pixel por pixel. De este modo se irán obteniendo métricas por cada área analizada que a su vez formarán una matriz resultante. A continuación podremos ver la matriz resultante a partir de la imagen I y T:

Figura 3: Imagen I (izquierda), imagen plantilla T (centro), matriz resultante que ha sido obtenida al aplicar el método cv2.TM_COEFF_NORMED (derecha).

De esta matriz resultante entonces, se procede a encontrar el valor más alto o valor más bajo dependiendo de la métrica aplicada, que corresponderá al mejor emparejamiento o mejor coincidencia. 

Te estarás preguntando, ¿valor más alto o más bajo?. Sí, esto es debido a los métodos que podemos escoger para aplicar el template matching. Veremos estos a continuación. 

Métodos que usa OpenCV para aplicar template matching

Existen 6 métodos que con OpenCV podemos aplicar para el template matching. Dependiendo de estos buscaremos el valor más alto o más bajo para determinar el mejor emparejamiento.

Métodos para la coincidencia de plantillas con OpenCV:

  • cv2.TM_SQDIFF
  • cv2.TM_SQDIFF_NORMED
  • cv2.TM_CCORR
  • cv2.TM_CCORR_NORMED
  • cv2.TM_CCOEFF
  • cv2.TM_CCOEFF_NORMED

NOTA: En la documentación de OpenCV puedes encontrar las operaciones que lleva a cabo cada método.

De todos estos métodos, para poder encontrar las mejores coincidencias debemos encontrar los valores más bajos o altos de la matriz resultante.

Figura 4: Métodos que usa OpenCV para aplicar el emparejamiento de plantillas y como estos representan el mejor emparejamiento.

Y es que si eliges usar cv2.TM_SQDIFF y cv2.TM_SQDIFF_NORMED, tendremos que encontrar el valor más bajo, ya que este corresponderá al mejor emparejamiento. Mientras que si usamos: cv2.TM_CCORR, cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF y cv2.TM_CCOEFF_NORMED. El valor más alto corresponderá al mejor emparejamiento.

Ahora que tenemos todos estos aspectos en mente, ¡vamos con la programación!. 

Programa ejemplo para aplicar template matching con OpenCV y Python

Vamos a realizar un pequeño programa llamado template_matching.py, en el cual estaremos aplicando el emparejamiento de plantillas, para ello usaremos las imágenes contenidas en la figura 2.

La imagen template o imagen plantilla (carita del perrito), ha sido recortada de la imagen I para este ejemplo.

import cv2

image = cv2.imread("imagen_001.jpg")
template = cv2.imread("template_001.jpg")

image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)

res = cv2.matchTemplate(image_gray, template_gray, cv2.TM_SQDIFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(min_val, max_val, min_loc, max_loc)

x1, y1 = min_loc
x2, y2 = min_loc[0] + template.shape[1], min_loc[1] + template.shape[0]

cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 3)
cv2.imshow("Image", image)
cv2.imshow("Template", template)
cv2.waitKey(0)
cv2.destroyAllWindows()

Línea 1: Importamos OpenCV.

Línea 3 y 4: Leemos las imágenes de entrada. image almacenará la imagen I, mientras que template, la imagen T.

Línea 6 y 7: Transformamos las imágenes leídas a escala de grises

Línea 9: Comparamos las imágenes T e I, para ello nos ayudamos de la función cv2.matchTemplate, en ella tendremos que especificar:

  • La imagen de entrada I.
  • La imagen del template T.
  • Y finalmente el método que estaremos empleando.

Entonces obtendremos res, que es la matriz resultante.

NOTA: Para más información sobre esta función da clic aquí.

Línea 10: res será nuestra matriz resultante, y como te había comentado antes, necesitaremos conocer el valor más alto o bajo para obtener el mejor emparejamiento. Esto lo lograremos con la función cv2.minMaxLoc. Esta función nos devolverá:

  • Valor mínimo.
  • Valor máximo.
  • Ubicación del valor mínimo de la matriz resultante.
  • Ubicación del valor máximo de la matriz resultante.

NOTA: Para más información sobre esta función da clic aquí.

Línea 11: Imprimimos los datos obtenidos en la línea 10. Esto podemos hacerlo para darnos cuenta de lo que contiene cada una de estas variables.

Línea 13 y 14: Como en la línea 9 especificamos cv2.TM_SQDIFF, necesitamos de la posición del valor mínimo para rodear el mejor emparejamiento, entonces vamos a definir los puntos x1, y1 y x2, y2 que nos ayudarán a dibujar un rectángulo.  

Entonces x1, y1 lo igualamos a min_loc que es la ubicación del valor mínimo. 

Para obtener x2, y2 vamos a tomar la información de min_loc con el número de filas y columnas de la imagen de template. Es así que para x2 tenemos min_loc[0] y le sumamos template.shape[1] que corresponde al número de columnas de template, mientras que para y2 sumamos min_loc[1] con template.shape[0] que corresponde a las filas de template. 

Línea 16: Dibujamos un rectángulo en la imagen image con los puntos obtenidos en las líneas 13 y 14.

Línea 17 y 18: Visualizamos las imágenes: image  y template.

Línea 19 y 20: Indicamos que la visualización se realice hasta que una tecla sea presionada y se cierren las ventanas.

Si ejecutamos el código, obtendríamos lo siguiente:

Figura 5: Resultado del programa aplicando el método cv2.TM_SQDIFF.

¿Qué para sí quisiéramos cambiar de método de la línea 9?

En la línea 9 usamos cv2.TM_SQDIFF, de donde se obtendrá el mejor emparejamiento con el valor mínimo de la matriz resultante. ¿Pero que pasará si lo que deseamos es cambiar este método por cv2.TM_CCOEFF o por cualquier otro método en el que se use el valor más alto como mejor emparejamiento?.

Pues bien, tomando el programa que acabamos de desarrollar lo primero que tendríamos que hacer es cambiar la línea 9 por:

res = cv2.matchTemplate(image_gray, template_gray, cv2.TM_CCOEFF)

Además tendríamos que cambiar las coordenadas de min_loc  por max_loc, que es en donde se encontrará el mejor emparejamiento. Entonces las líneas 13 y 14 quedarían de la siguiente manera:

x1, y1 = max_loc
x2, y2 = max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]

Con estas nuevas coordenadas ya podríamos dibujar el rectángulo que rodee el mejor emparejamiento.  Si ejecutamos el programa con estos cambios, obtendríamos:

Figura 6: Resultado del programa aplicando el método cv2.TM_CCOEFF.

Hemos probado el template matching con estos dos métodos, pero ¿qué te parece si realizamos un programa para probar los resultados que obtenemos con cada uno de los 6 métodos usando las imágenes que tenemos de entrada? 

Programa aplicando los 6 métodos del template matching que ofrece OpenCV

El programa que veremos a continuación es similar al anterior, por lo que me centraré en las nuevas líneas.

Creamos un nuevo script llamado template_matching_6methods.py.

import cv2

orig = cv2.imread("imagen_001.jpg")
image = orig.copy()
template = cv2.imread("template_001.jpg")

image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)

methods = [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED, cv2.TM_CCORR,
            cv2.TM_CCORR_NORMED, cv2.TM_CCOEFF, cv2.TM_CCOEFF_NORMED]

Línea 1 a 8: Importamos OpenCV, leemos las imágenes de entrada y las transformamos a escla de grises.

En la línea 4 estoy haciendo una copia de la imagen de entrada I, que luego usaremos para visualizar la imagen con cada resultado dado por los métodos. Además esto nos ayudará luego para que el rectángulo resultante que dibujemos no se sobreponga en cada uno de métodos. 

Línea 10: Creamos un vector llamado methods, y dentro ubicamos cada uno de los 6 métodos que tenemos en OpenCV para aplicar template matching.

for method in methods:
    res = cv2.matchTemplate(image_gray, template_gray, method=method)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    print(min_val, max_val, min_loc, max_loc)

Línea 13: Creamos un for para que recorra cada uno de los elementos de methods.

Línea 14: Realizamos el mismo procedimiento que en el anterior programa, por ello usamos cv2.matchTemplate, especificamos la imagen I, la imagen del template y method, que será el que se esté siendo analizado en ese momento. 

Línea 15 y 16: Aplicamos cv2.minMaxLoc a la matriz resultante res, e imprimimos los valores obtenidos.

if method == cv2.TM_SQDIFF or method == cv2.TM_SQDIFF_NORMED:
    x1, y1 = min_loc
    x2, y2 = min_loc[0] + template.shape[1], min_loc[1] + template.shape[0]
else:
    x1, y1 = max_loc
    x2, y2 = max_loc[0] + template.shape[1], max_loc[1] + template.shape[0]

Línea 18 a 23: Añadimos una condición para cuando el método sea cv2.TM_SQDIFF o cv2.TM_SQDIFF_NORMED, para obtener las coordenadas basadas en la localización del valor mínimo. Mientras que para todos los otros métodos las coordenadas se obtendrán a partir del las coordenadas del valor máximo. 

    cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 3)
    cv2.imshow("Image", image)
    cv2.imshow("Template", template)
    image = orig.copy()
    cv2.waitKey(0)
cv2.destroyAllWindows()

Línea 25 a 27: Dibujamos un rectángulo con las coordenadas x1, y1, x2, y2 que rodeará al mejor resultado. Y visualizamos las imágenes contenidas en image y template.

Línea 28: Añadimos esta línea para crear una copia de la imagen leída orig, esto permitirá que no se sobreescriban los rectángulos en image .

Líneas 29 y 30: Indicamos que la visualización se realice hasta que una tecla sea presionada y pase al siguiente método. Una vez que se termine con los 6 métodos, las ventanas de visualización se cerrarán.

Si ejecutamos el programa podremos visualizar resultados similares a los de las figuras 5 y 6. A continuación te mostraré las imágenes obtenidas con este programa y su respectivo método de aplicación.

Figura 7: Resultados del programa template_matching_6methods.py usando las imágenes de la figura 2.

En la figura 7 podemos ver los resultados de haber aplicado cada uno de los 6 métodos sobre las imágenes de la figura 2. Para este ejemplo no se obtuvieron buenos resultados con cv2.TM_CCORR_NORMED.

Podemos aplicar este procedimiento para otras imágenes, como veremos a continuación:

Figura 8: Resultados del programa template_matching_6methods.py usando nuevas imágenes I e T.

En la figura 8 tenemos los resultados de haber aplicando este programa con las imágenes de un gatito. En este caso no se obtuvieron buenos resultados con cv2.TM_CCORR.

Límitaciones del template matching

Hay que tener en cuenta que el template matching no suele ser muy robusto ante ciertos aspectos tales como el tamaño o la rotación en las imágenes (a menos que tanto la imagen I, como el template presenten las mismas condiciones). Podríamos experimentar un poco con el programa template_matching_6methods.py que acabamos de crear y con nuevas imágenes de entrada.

Para ello por ejemplo de una imagen I, recorto la cabecita del perrito para obtener el template.

Figura 9: Resultados del programa template_matching_6methods.py usando nuevas imágenes I e T.

En el caso de la figura 9 tenemos los resultados de haber especificados nuevas imágenes al programa realizado, en esta podemos ver que con el único método que no obtuvimos buenos resultados fue en cv2.TM_CCORR.

Ahora probemos con una imagen I más grande, pero con el mismo template.

Figura 10: Resultados del programa template_matching_6methods.py usando una imagen I más grande pero el mismo template T.

En la figura 10 tenemos los resultados de haber aplicado el programa template_matchin_6methods.py. Si quisieramos mejorar los resultados con respecto al tamaño de las imágenes podemos considerar aplicar multiescalado, un gran ejemplo de como lograrlo está en este post de pyimagesearch.

Ahora, ¿qué pasará si rotamos un poco la imagen I, y aplicamos el mismo template?, veamos:

Figura 11: Resultados del programa template_matching_6methods.py usando una imagen I rotada pero el mismo template T.

En la figura 11 tenemos los resultados de una image I rotada, con el mismo template que habíamos aplicado en un principio.

Por último veamos que pasa si oscurecemos un poco la imagen I, y seguimos manteniendo el mismo template.

Figura 12: Resultados del programa template_matching_6methods.py usando una imagen I oscura, pero el mismo template T.

Por último en la figura 12 hemos experimentado oscurenciendo la imagen I, y manteniendo el mismo template que usamos en los otros experimentos. Para este caso obtuvimos buenos resultados con los últimos 3 métodos.

Estos fueron algunos aspectos que quería mostrarles sobre el template matching. Por lo que si deseas emplearlo, te recomiendo realizar pruebas con cada uno de los métodos, para que puedas determinar cual de ellos se desempeña mejor en su aplicación. 

Y hemos llegado al final de este post, espero que te haya gustado. Nos vemos en un siguiente tutorial. 🤗