? Contando por COLORES en Python-OpenCV
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:
- Leer la imagen de entrada.
- Transformar de BGR a HSV y determinar los rangos en donde se encuentren los colores a detectar
- Encontrar y dibujar cada contorno encontrado por cada color
- Enumerar cada uno de los círculos de acuerdo al color que poseen
- Elaborar una imagen resumen
Leer la 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).
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 maskVioleta
obtenida:
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)
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:
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.
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.
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.
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.
Todos los círculos enumerados
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:
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!
Excelentes todos tus tutoriales Gabriela, los he seguido todos y de verdad explicas muy bien, estoy intentando hacer algo como lo que aparece en este video https://www.youtube.com/watch?v=y02Y-IQ2QLs, tienes idea de como se puede hacer ese tipo de contador al cruzar cada color la linea?
Hola Jair, muchas gracias por el apoyo que le das al contenido. 🙂 Me alegra mucho que esté siendo útil. Mira creo que podrías aplicar algo así: https://www.pyimagesearch.com/2015/11/02/watershed-opencv/ luego tomas la posición central, su en y es mayor al valor de la línea entonces incremente 1.
Hola, Gabriela. Tengo una pregunta. Necesito hacer algo justo como esto, pero mediante la captura de video de la webcam, pero no logro adaptarlo, ¿es posible contar los objetos de diferentes colores en video?
Hola Abraham, podrías tomar una línea para que cuando pase por esa línea se cuente el color correspondiente al objeto, también podrías aplicar object tracking para que no cuente varias veces el mismo objeto.
Hola Gaby! muchas gracias por el apoyo, me encanta ver tus tutoriales. Oye necesito hacer un codigo muy similar a este pero que de acuerdo al orden del color que vaya leyendo la webcam me arroje un valor de una resistencia en ohms (lector de resistencias por color) es decir, si yo le muestro a la web cam el color amarillo, morado y por ultimo el rojo me diga que vale 4.7k, eso se puede hacer?
Hola Alex, lo que podrías hacer en el caso de una resistencia es tomar en cuenta las coordenadas en la imagen en donde se presenta cada color, para que según ello puedas calcular el valor total de los ohmios.
Buenas noches
Primer quería darte las gracias por brindar tu conocimiento, de verdad es de mucha ayuda
Tengo una pregunta quiero realizar algo parecido, pero quisiera saber como se podría identificar si el color que tiene la imagen esta en un rango determinado.
Disculpa la ignorancia algo así, :
if cv2.inRange(imagenHSV, amarilloBajo, amarilloAlto):
print(‘Esta en el rango’)
else:
print(‘No esta en el rango’)
Hola Juan Carlos muchas gracias por tus palabras 😀 que bueno que te esté ayudando el contenido. Lo que podrías hacer es analizar la imagen que te da de vuelta cv2.inRange(imagenHSV, amarilloBajo, amarilloAlto), ya que esta será una imagen a blanco y negro, donde el blanco representará la presencia de dicho color (según el rango que hayas establecido), mientras que en negro se mostrarán las áreas donde no esté presente. Podrías también tomar las posiciones del área en blanco para de la imagen de entrada (imagen a color), obtener los valores contenidos en los pixeles.
Hola Gaby, tus videos son geniales y nos han ayudado mucho en el tema de Open CV, tengo un problema, estoy intentando contar por color otro tipo de imagen, pero me reclama un error de división por cero en la la línea 9
x = int(M[«m10»]/M[«m00»])
ZeroDivisionError: float division by zero
Me puedes dar algun consejo para solucionarlo? muchísimas gracias!!!!
Creo que lo solucione en la seccion if va un solo =, en la nueva version de python
Hola me marca el mismo problema como lo soluciono yo uso visual code con python para realizar la practica
Hola Gabriela, podria hacer este ejercicio a nivel pixel de la imagen?
Saludos.
Hola Gabriela, una consulta, donde puedo dar me gusta ? a cada uno de tus Posts por que son excelentes, gracias.
He agregado algunas cositas al código, no mucho en verdad, si a alguien le sirve:
import cv2
import numpy as np
# 1. Leer la imagen de entrada.
imagen = cv2.imread(‘lunares.png’)
imgResumen = 255*np.ones((210, 100, 3), dtype=np.uint8)
# 2. Transformar de BGR a HSV y determinar los rangos en donde se encuentren los colores a detectar
imagenHSV = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)
redBajo1 = np.array([0, 100, 20], np.uint8)
redAlto1 = np.array([10, 255, 255], np.uint8)
redBajo2 = np.array([175, 100, 20], np.uint8)
redAlto2 = np.array([180, 255, 255], np.uint8)
yellowBajo = np.array([20, 100, 20], np.uint8)
yellowAlto = np.array([32, 255, 255], np.uint8)
violetaBajo = np.array([130, 100, 20], np.uint8)
violetaAlto = np.array([145, 255, 255], np.uint8)
greenBajo = np.array([36, 100, 20], np.uint8)
greenAlto = np.array([70, 255, 255], np.uint8)
# Y para poder encontrar los colores usaremos cv2.inRange
maskYellow = cv2.inRange(imagenHSV, yellowBajo, yellowAlto)
maskVioleta = cv2.inRange(imagenHSV, violetaBajo, violetaAlto)
maskGreen = cv2.inRange(imagenHSV, greenBajo, greenAlto)
maskRed1 = cv2.inRange(imagenHSV, redBajo1, redAlto1)
maskRed2 = cv2.inRange(imagenHSV, redBajo2, redAlto2)
maskRed = cv2.add(maskRed1, maskRed2)
def dibujar(mask, color, desColor, imgResumen_ejeY):
# 3. Encontrar y dibujar cada contorno encontrado por cada color
contours, _ = cv2.findContours(
mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Puede usar Python enumerate()para obtener un contador y el valor del iterable al mismo tiempo!
for (i, c) in enumerate(contours):
M = cv2.moments(c)
if M[«m00»] == 0:
M[«m00»] = 1
x = int(M[«m10»]/M[«m00»])
y = int(M[«m01»]/M[«m00»])
# 4. Enumerar cada uno de los círculos de acuerdo al color que poseen
cv2.putText(imagen, str(i+1), (x-10, y+10), 1, 2, (0, 0, 0), 2)
cv2.putText(imagen, ‘{}’.format(desColor),
(x-20, y+30), 1, 1, (0, 0, 0), 1)
# Para que el contorno sea mas limpio
nuevoContorno = cv2.convexHull(c)
# Y para dibujar los contornos encontrados usaremos cv2.drawContours.
cv2.drawContours(imagen, [nuevoContorno], 0, color, 2)
cv2.circle(imgResumen, (30, imgResumen_ejeY), 15, color, -1)
cv2.putText(imgResumen, ‘{}’.format(len(contours)),
(50, imgResumen_ejeY + 10), 1, 1.5, (0, 0, 0), 2)
return len(contours)
lenContourYellow = dibujar(maskYellow, (0, 255, 255), «Amarillo», 30)
lenContourVioleta = dibujar(maskVioleta, (140, 40, 120), «Violeta», 70)
lenContourGreen = dibujar(maskGreen, (0, 255, 0), «Verde», 110)
lenContourRed = dibujar(maskRed, (0, 0, 255), «Rojo», 150)
totalCnts = lenContourYellow + lenContourVioleta + lenContourGreen + lenContourRed
cv2.putText(imgResumen, ‘{} {}’.format(‘Total:’, totalCnts),
(15, 190), 1, 1, (0, 0, 0), 2)
cv2.imshow(‘imagen’, imagen)
cv2.imshow(‘imgResumen’, imgResumen)
cv2.waitKey(0)
cv2.destroyAllWindows()
Saludos.