🧮 Contando por COLORES en Python-OpenCV

Por Administrador

En este post vamos a contar objetos (en este caso círculos) de acuerdo a su color. El programa completo estará al final de este post.

El proceso que vamos a realizar es el siguiente:

  1. Leer la imagen de entrada.
  2. Transformar de BGR a HSV y determinar los rangos en donde se encuentren los colores a detectar
  3. Encontrar y dibujar cada contorno encontrado por cada color
  4. Enumerar cada uno de los círculos de acuerdo al color que poseen
  5. Elaborar una imagen resumen

Leer la imagen de entrada

Figura 1: Imagen de entrada

import cv2
import numpy as np

imagen = cv2.imread('lunares.png')

Empezamos importando opencv y numpy con un alias np, luego leemos la imagen con la que vamos a trabajar, esta lleva el nombre de lunares con su extensión png. La imagen presenta círculos de colores amarrillo, violeta, verde y rojo, que son los que contaremos para cada color. 

Transformar de BGR a HSV y determinar los límites en donde se encuentren los colores a detectar

Transformamos la imagen de BGR que es por defecto como lee OpenCV a las imágenes, a HSV con la función cv2.cvtColor:

imagenHSV = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)

Determinar los límites en donde se encuentren los colores a detectar

Necesitamos determinar los límites alto y bajo en donde esté presente cada uno de los colores que deseamos detectar, para ello construiremos un vector con la ayuda de numpy, y para poder encontrar los colores usaremos cv2.inRange. Veamos este proceso para cada color.

Recuerda que puedes visitar el post Detección de colores en OpenCV – Python (En 4 pasos) y DETECCIÓN DE COLORES Y Tracking en OpenCV – Parte2 si quieres los pasos más detallados de como detectar distintos colores.

Detectando el color amarillo

Lo primero que vamos a hacer es determinar los límites altos y bajos en donde se encuentra este color en HSV. Luego usaremos cv2.inRange (como hemos hecho anteriormente).

amarilloBajo = np.array([20, 100, 20], np.uint8)
amarilloAlto = np.array([32, 255, 255], np.uint8)

maskAmarillo = cv2.inRange(imagenHSV, amarilloBajo, amarilloAlto)

Si visualizamos maskAmarillo, obtendremos la siguiente imagen binarizada (derecha).

Figura 2: Izq: Imagen de entrada. Der: Imagen binarizada (detectando el color amarillo).

La región en blanco representará el color amarillo detectado, mientras que en negro la no presencia del mismo.

Detectando el color violeta

Vamos a seguir con el mismo procedimiento que hicimos recientemente.

violetaBajo = np.array([130, 100, 20], np.uint8)
violetaAlto = np.array([145, 255, 255], np.uint8)

maskVioleta = cv2.inRange(imagenHSV, violetaBajo, violetaAlto)

Veamos la imagen binarizada maskVioletaobtenida:

Figura 3: Izq: Imagen de entrada. Der: Imagen binarizada (detectando el color violeta).

La región en blanco representará el color violeta detectado, mientras que en negro la no presencia del mismo.

Detectando el color verde

verdeBajo = np.array([36, 100, 20], np.uint8)
verdeAlto = np.array([70, 255, 255], np.uint8)

maskVerde = cv2.inRange(imagenHSV, verdeBajo, verdeAlto)

Figura 4: Izq: Imagen de entrada. Der: Imagen binarizada (detectando el color verde).

La región en blanco representará el color verde detectado, mientras que en negro la no presencia del mismo.

Detectando el color rojo

rojoBajo1 = np.array([0, 100, 20], np.uint8)
rojoAlto1 = np.array([10, 255, 255], np.uint8)
rojoBajo2 = np.array([175, 100, 20], np.uint8)
rojoAlto2 = np.array([180, 255, 255], np.uint8)

maskRojo1 = cv2.inRange(imagenHSV, rojoBajo1, rojoAlto1)
maskRojo2 = cv2.inRange(imagenHSV, rojoBajo2, rojoAlto2)
maskRojo =  cv2.add(maskRojo1, maskRojo2)

La imagen de entrada con la imagen binarizada para el color rojo serán:

Figura 5: Izq: Imagen de entrada. Der: Imagen binarizada (detectando el color rojo).

La región en blanco representará el color rojo detectado, mientras que en negro la no presencia del mismo.

Encontrar y dibujar cada contorno encontrado por cada color

Ahora que tenemos las imágenes binarias del amarillo, violeta, verde y rojo, podemos encontrar cada uno de sus contornos con cv2.findContours.

NOTA: Utiliza el código  para encontrar los contornos que se muestra a continuación de acuerdo a tu versión de OpenCV.

#Encontrando contornos
#OpenCV 3
#contornosAmarillo = cv2.findContours(maskAmarillo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
#contornosVioleta = cv2.findContours(maskVioleta, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
#contornosVerde = cv2.findContours(maskVerde, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
#contornosRojo = cv2.findContours(maskRojo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]

#OpenCV 4
contornosAmarillo = cv2.findContours(maskAmarillo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
contornosVioleta = cv2.findContours(maskVioleta, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
contornosVerde = cv2.findContours(maskVerde, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
contornosRojo = cv2.findContours(maskRojo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]

Enumerar cada uno de los círculos de acuerdo al color

Vamos a enumerar cada círculo por su color, además que rodearemos a este, por ello vamos a crear una pequeña función:

def dibujarContorno(contornos, color):
	for (i, c) in enumerate(contornos):
		M = cv2.moments(c)
		if (M["m00"]==0): M["m00"]==1
		x = int(M["m10"]/M["m00"])
		y = int(M["m01"]/M["m00"])
		cv2.drawContours(imagen, [c], 0, color, 2)
		cv2.putText(imagen, str(i+1), (x-10,y+10), 1, 2,(0,0,0),2)

La función dibujarContorno necesita de contornos (encontrados por cada color) y un color en BGR para que sean rodeados dichos contornos. Luego vamos a tratar cada contorno encontrado dentro de contornos, por lo que estamos usando un for. Para poder enumerar cada círculo, es decir para poner un número dentro del círculo nos ayudaremos de cv2.moments, con este vamos a encontrar los puntos centrales x e y del contorno. Finalmente dibujamos los contornos con cv2.drawContours del color dado a la función, y añadimos el texto con cv2.putText que se visualizará en color negro.

Para una explicación más extensa de como trabajar con contornos y como dibujarlos haz clic aquí. Mientras que si necesitas información de como funciona cv2.putText, da clic aquí.

Usaremos esta función creada para cada color, veamos:

Enumerando el color amarillo

dibujarContorno(contornosAmarillo, (0, 255,255))

En este caso se está  rodeando cada círculo amarillo del mismo color, y en el centro de los mismo se ha asignado un número con lo que se puede enumerarlos.

Figura 6: Enumeración de cada uno de los círculos amarillos.

Enumerando el color violeta

dibujarContorno(contornosVioleta, (140, 40, 120))

Se está dibujando el contorno de color violeta, además se puede visualizar un número dentro de cada círculo con este color.

Figura 7: Enumeración de cada uno de los círculos violetas.

Enumerando el color verde

dibujarContorno(contornosVerde, (0, 255, 0))

Los contornos se van a dibujar de color verde, y los números dentro de los círculos de negro.

Figura 8: Enumeración de cada uno de los círculos verdes.

Enumerando el color rojo

dibujarContorno(contornosRojo, (0, 0, 255))

En este caso se va a dibujar cada contorno de color rojo, mientras que los números dentro de los círculos de color negro.

Figura 9: Enumeración de cada uno de los círculos rojos.

Todos los círculos enumerados

Figura 10: Enumeración de los círculos de acuerdo a los colores amarillo, violeta, verde y rojo

En la figura 10 podemos ver cada uno de los círculos enumerados de acuerdo al color que poseen, sin embargo aquí no se puede distinguir la cantidad de círculos por cada color, es por ello que es necesario construir una pequeña imagen resumen que veremos a continuación.

Imagen Resumen

Para construir esta imagen necesitaremos usar numpy. Esta imagen tendrá 210 pixeles de alto y 100 de ancho, la realizaremos de la siguiente manera:

imgResumen = 255 * np.ones((210,100,3), dtype = np.uint8)

Luego vamos a dibujar pequeños círculos de cada color a la izquierda de la imagen, uno bajo el otro, para ello usaremos la función cv2.circle.

cv2.circle(imgResumen, (30,30), 15, (0,255,255), -1)
cv2.circle(imgResumen, (30,70), 15, (140,40,120), -1)
cv2.circle(imgResumen, (30,110), 15, (0,255,0), -1)
cv2.circle(imgResumen, (30,150), 15, (0,0,255), -1)

Recuerda que especificamos -1 al final para formar un círculo y no una circunferencia.

Seguimos, asignando la cantidad de círculos encontrados por cada color a la derecha de los círculos recién creados, para  ello empleamos las siguientes líneas:

cv2.putText(imgResumen,str(len(contornosAmarillo)),(65,40), 1, 2,(0,0,0),2)
cv2.putText(imgResumen,str(len(contornosVioleta)),(65,80), 1, 2,(0,0,0),2)
cv2.putText(imgResumen,str(len(contornosVerde)),(65,120), 1, 2,(0,0,0),2)
cv2.putText(imgResumen,str(len(contornosRojo)),(65,160), 1, 2,(0,0,0),2)

Finalmente vamos a sumar todos los contornos encontrados para mostrarle al usuario la totalidad de círculos encontrados sin importar su color.

totalCnts = len(contornosAmarillo) + len(contornosVioleta) + len(contornosVerde) + len(contornosRojo)
cv2.putText(imgResumen,str(totalCnts),(55,200), 1, 2,(0,0,0),2)

Si visualizamos esta imagen con cv2.imshow veremos la siguiente imagen:

Figura 11: Imagen resumen, en donde se podrá ver la cantidad de círculos por cada color y además la totalidad de círculos.

Hasta aquí llega la aplicación realizada en este post. Es hora de que te deje todo el código usado:

import cv2
import numpy as np

def dibujarContorno(contornos, color):
  for (i, c) in enumerate(contornos):
    M = cv2.moments(c)
    if (M["m00"]==0): M["m00"]==1
    x = int(M["m10"]/M["m00"])
    y = int(M["m01"]/M["m00"])
    cv2.drawContours(imagen, [c], 0, color, 2)
    cv2.putText(imagen, str(i+1), (x-10,y+10), 1, 2,(0,0,0),2)

amarilloBajo = np.array([20, 100, 20], np.uint8)
amarilloAlto = np.array([32, 255, 255], np.uint8)

violetaBajo = np.array([130, 100, 20], np.uint8)
violetaAlto = np.array([145, 255, 255], np.uint8)

verdeBajo = np.array([36, 100, 20], np.uint8)
verdeAlto = np.array([70, 255, 255], np.uint8)

rojoBajo1 = np.array([0, 100, 20], np.uint8)
rojoAlto1 = np.array([10, 255, 255], np.uint8)
rojoBajo2 = np.array([175, 100, 20], np.uint8)
rojoAlto2 = np.array([180, 255, 255], np.uint8)

imagen = cv2.imread('lunares.png')
imagenHSV = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)

#Detectando colores 
maskAmarillo = cv2.inRange(imagenHSV, amarilloBajo, amarilloAlto)
maskVioleta = cv2.inRange(imagenHSV, violetaBajo, violetaAlto)
maskVerde = cv2.inRange(imagenHSV, verdeBajo, verdeAlto)
maskRojo1 = cv2.inRange(imagenHSV, rojoBajo1, rojoAlto1)
maskRojo2 = cv2.inRange(imagenHSV, rojoBajo2, rojoAlto2)
maskRojo =  cv2.add(maskRojo1, maskRojo2)

#Encontrando contornos
#OpenCV 3
#contornosAmarillo = cv2.findContours(maskAmarillo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
#contornosVioleta = cv2.findContours(maskVioleta, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
#contornosVerde = cv2.findContours(maskVerde, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
#contornosRojo = cv2.findContours(maskRojo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]

#OpenCV 4
contornosAmarillo = cv2.findContours(maskAmarillo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
contornosVioleta = cv2.findContours(maskVioleta, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
contornosVerde = cv2.findContours(maskVerde, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
contornosRojo = cv2.findContours(maskRojo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]

dibujarContorno(contornosAmarillo, (0, 255,255))
dibujarContorno(contornosVioleta, (140, 40, 120))
dibujarContorno(contornosVerde, (0, 255, 0))
dibujarContorno(contornosRojo, (0, 0, 255))

#Imagen Resumen
imgResumen = 255 * np.ones((210,100,3), dtype = np.uint8)
cv2.circle(imgResumen, (30,30), 15, (0,255,255), -1)
cv2.circle(imgResumen, (30,70), 15, (140,40,120), -1)
cv2.circle(imgResumen, (30,110), 15, (0,255,0), -1)
cv2.circle(imgResumen, (30,150), 15, (0,0,255), -1)

cv2.putText(imgResumen,str(len(contornosAmarillo)),(65,40), 1, 2,(0,0,0),2)
cv2.putText(imgResumen,str(len(contornosVioleta)),(65,80), 1, 2,(0,0,0),2)
cv2.putText(imgResumen,str(len(contornosVerde)),(65,120), 1, 2,(0,0,0),2)
cv2.putText(imgResumen,str(len(contornosRojo)),(65,160), 1, 2,(0,0,0),2)
totalCnts = len(contornosAmarillo) + len(contornosVioleta) + len(contornosVerde) + len(contornosRojo)
cv2.putText(imgResumen,str(totalCnts),(55,200), 1, 2,(0,0,0),2)
cv2.imshow('Resumen', imgResumen)

#cv2.imshow('maskAmarillo', maskAmarillo)
#cv2.imshow('maskVioleta', maskVioleta)
#cv2.imshow('maskVerde', maskVerde)
#cv2.imshow('maskRojo', maskRojo)
cv2.imshow('Imagen', imagen)
cv2.imwrite('conteo.png', imagen)
cv2.waitKey(0)
cv2.destroyAllWindows()

Y hemos llegado al final del post. No olvides visitar mi canal en YOUTUBE, mi repositorio en gitHub, así como ver los otros posts. ¡Cuídate mucho!