Detecta FIGURAS GEOMÉTRICAS y sus COLORES ❤️???? OpenCV – Python
En el post anterior vimos como detectar algunas figuras geométricas simples, y además en otros posts y videos te he mostrado como hago para detectar colores usando OpenCV y Python. Así que en este video voy a unir estos dos en una sola aplicación. Acompáñame y recuerda que todo el código usado para esta aplicación estará al final de este post y en mi repositorio de github.
Hoy veremos lo siguiente:
- Creando función para detectar los colores: rojo, naranja, amarillo, verde, violeta y rosa.
- Creando función para detectar las figuras geométricas: triángulo, cuadrado, rectángulo, pentágono, hexágono y círculo.
- Leer la imagen de entrada, aplicar escala de grises, detección de bordes y encontrar sus contornos.
- Convertir imagen de entrada de BGR a HSV.
- Analizando cada contorno.
- Extrayendo cada contorno.
- Combinando la imagen binaria auxiliar y la de HSV.
- Detectando figuras geométricas y su color
- Programa completo
- Referencias
Creando función para detectar los colores: rojo, naranja, amarillo, verde, violeta y rosa
Vamos en un principio a desarrollar una función que nos ayude a detectar los colores de las figuras geométricas presentes en la siguiente imagen:
import cv2 import numpy as np def figColor(imagenHSV): # 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) # Naranja naranjaBajo = np.array([11, 100, 20], np.uint8) naranjaAlto = np.array([19, 255, 255], np.uint8) #Amarillo amarilloBajo = np.array([20, 100, 20], np.uint8) amarilloAlto = np.array([32, 255, 255], np.uint8) #Verde verdeBajo = np.array([36, 100, 20], np.uint8) verdeAlto = np.array([70, 255, 255], np.uint8) #Violeta violetaBajo = np.array([130, 100, 20], np.uint8) violetaAlto = np.array([145, 255, 255], np.uint8) #Rosa rosaBajo = np.array([146, 100, 20], np.uint8) rosaAlto = np.array([170, 255, 255], np.uint8) # Se buscan los colores en la imagen, según los límites altos # y bajos dados maskRojo1 = cv2.inRange(imagenHSV, rojoBajo1, rojoAlto1) maskRojo2 = cv2.inRange(imagenHSV, rojoBajo2, rojoAlto2) maskRojo = cv2.add(maskRojo1, maskRojo2) maskNaranja = cv2.inRange(imagenHSV, naranjaBajo, naranjaAlto) maskAmarillo = cv2.inRange(imagenHSV, amarilloBajo, amarilloAlto) maskVerde = cv2.inRange(imagenHSV, verdeBajo, verdeAlto) maskVioleta = cv2.inRange(imagenHSV, violetaBajo, violetaAlto) maskRosa = cv2.inRange(imagenHSV, rosaBajo, rosaAlto) cntsRojo = cv2.findContours(maskRojo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsNaranja = cv2.findContours(maskNaranja, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsAmarillo = cv2.findContours(maskAmarillo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsVerde = cv2.findContours(maskVerde, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsVioleta = cv2.findContours(maskVioleta, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsRosa = cv2.findContours(maskRosa, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 if len(cntsRojo)>0: color = 'Rojo' elif len(cntsNaranja)>0: color = 'Naranja' elif len(cntsAmarillo)>0: color = 'Amarillo' elif len(cntsVerde)>0: color = 'Verde' elif len(cntsVioleta)>0: color = 'Violeta' elif len(cntsRosa)>0: color = 'Rosa' return color
Línea 1 y 2: Importamos los paquetes necesarios que vamos a usar, en este caso OpenCV y Numpy.
Línea 4: Declaramos la función figColor
, como entrada necesita de una imagen en HSV que veremos luego.
Línea 6 a 29: Aquí establecemos los límites iniciales y finales de los colores que vamos a detectar, esto ya lo vimos antes en otro post, pero si es la primera vez que te encuentras con mi blog te los dejo aquí para más información: Detección de colores en OpenCV – Python (En 4 pasos) y DETECCIÓN DE COLORES Y Tracking en OpenCV – Parte2.
Línea 33 a 40: Se crean imágenes binarias, es decir imágenes a blanco y negro, donde el blanco representará la presencia de dicho color en la imagen, mientras que en negro la no presencia del mismo. Estas imágenes nos servirán para poder encontrar los contornos correspondientes a cada color.
Línea 42 a la 47: Ahora necesitamos encontrar los contornos de cada imagen binaria por cada color. Como te habrás dado cuenta aquí estoy empleando una alternativa en su uso, ubicando al final [0]
para indicar que se está tomando el primer argumento que nos devuelve cv2.findContours
, esto para OpenCV 4. Si tuvieses OpenCV3, en vez de 0 deberías poner 1 ya que los contornos se encontrarán en la posición 1.
Línea 49 a 54: Luego, lo que hice fue contar cuantos contornos hay por cada color. Si por ejemplo la cantidad de contornos en color rojo es mayor a 0, se asignará ‘Rojo’ a la variable color. En cambio si la cantidad de contornos en naranja es mayor a 0, la variable color será ‘Naranja’, y así para todos los colores restantes. Esto nos servirá, para que esta función nos devuelva el nombre del color que ha sido detectado.
Línea 56: Esta función nos devolverá la variable color
que contiene el nombre del color detectado.
Creando función para detectar las figuras geométricas: triángulo, cuadrado, rectángulo, pentágono, hexágono y círculo
Ahora desarrollaremos una función para poder determinar de que figura geométrica se trata y que nos devuelva el nombre de la misma. Para ello vamos a usar el procedimiento que vimos en el post anterior: Detectando FIGURAS GEOMÉTRICAS (??⬛) con OpenCV – Python. Por lo que si tienes alguna duda te recomiendo que leas primero ese post.
def figName(contorno,width,height): epsilon = 0.01*cv2.arcLength(contorno,True) approx = cv2.approxPolyDP(contorno,epsilon,True) if len(approx) == 3: namefig = 'Triangulo' if len(approx) == 4: aspect_ratio = float(width)/height if aspect_ratio == 1: namefig = 'Cuadrado' else: namefig = 'Rectangulo' if len(approx) == 5: namefig = 'Pentagono' if len(approx) == 6: namefig = 'Hexagono' if len(approx) > 10: namefig = 'Circulo' return namefig
Línea 58: A la función figName
se le entrega un contorno, su ancho y su altura (que nos servirá en el caso que se tenga que diferenciar entre cuadrado y rectángulo), y procederá a distinguir si se trata de un triángulo, un cuadrado o un rectángulo, un pentágono, un hexágono o un círculo.
Línea 59 a la 79: Aquí necesitamos emplear la función cv2.approxPolyDP
para obtener approx
y a su vez calculamos su longitud, para diferenciar de entre las distintas figuras geométricas. Por ello en la línea 62 si len(approx)
es igual a 3 se tratará de un triángulo, en la línea 65 si es 4 puede tratarse de un cuadrado o un rectángulo (que diferenciamos calculando su aspect ratio), y así sucesivamente. En el caso del círculo en la línea 78 he establecido que si len(approx)
es mayor a 10 sea considerado un círculo (como un polígono con 10 lados), pero esta consideración puede cambiar dependiendo de tu aplicación.
Si quieres una explicación más extensa del procedimiento para detectar figuras geométricas puedes visitar mi anterior post.
Línea 81: Esta función nos devolverá la variable namefig
que corresponde al nombre de la figura geométrica detectada.
Una vez que se tienen estas dos funciones:
figColor
, para que nos devuelva el color de la figura geométrica.figName
, que nos devolverá el nombre correspondiente a la figura geométrica.
Podremos proceder a aplicarlas en una imagen. El procedimiento que vamos a seguir es similar al que vimos en el video/post anterior.
Leer la imagen de entrada, aplicar escala de grises, detección de bordes y encontrar sus contornos
Este procedimiento es igual al que usamos anteriormente por lo que lo explicaré brevemente.
imagen = cv2.imread('figurasColores2.png') gray = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY) canny = cv2.Canny(gray, 10,150) canny = cv2.dilate(canny,None,iterations=1) canny = cv2.erode(canny,None,iterations=1) _,cnts,_ = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #OpenCV 3 cnts,_ = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #OpenCV 4
Leemos la imagen con cv2.imread
. Luego transformamos la imagen de BGR a escala de grises para después aplicar detección de bordes con cv2.Canny
y aplicamos transformaciones morfológicas con cv2.dilate
y cv2.erode
para poder mejorar la imagen binaria obtenida.
Después aplicamos cv2.findContours
para encontrar los contornos. Recuerda usar la línea 88 u 89 dependiendo de la versión de OpenCV que tengas.
Convertir imagen de entrada de BGR a HSV
También vamos a transformar la imagen de entrada de BGR a HSV, que nos servirá para alimentar a la función figColor
y que permitirá detectar los colores de las figuras.
imageHSV = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV)
Analizando cada contorno
for c in cnts: x, y, w, h = cv2.boundingRect(c)
Línea 92: Vamos a tratar cada contorno con un for
.
Línea 93: Se emplea la función cv2.boundingRect
para obtener los puntos x, y, ancho y alto del contorno (estos dos últimos nos ayudarán en la función figName
para diferenciar entre rectángulo y cuadrado).
Extrayendo cada contorno
Vamos a crear una imagen axuliar del tamaño de la imagen de entrada para tratar cada contorno veamos:
imAux = np.zeros(imagen.shape[:2], dtype="uint8") imAux = cv2.drawContours(imAux, [c], -1, 255, -1)
NOTA: Esta técnica es usada en pyimagesearch.
Línea 94: Creamos una imagen auxiliar de negro de dos dimensiones con el mismo ancho y alto de la imagen de entrada, para ello usamos np.zeros
y especificaremos su tamaño con imagen.shape[:2]
.
Línea 95: Se dibuja un contorno en la imagen auxiliar imAux
, toma en cuenta que el contorno estará lleno (ya que en el tercer argumento de cv2.drawContours
hemos colocado -1
), es decir que veremos un área en blanco que corresponderá a cada figura.
Combinando la imagen binaria auxiliar y la de HSV
Ahora será necesario con ayuda de la imagen auxiliar realizada, plasmar en sus zonas blancas la imagen en HSV.
maskHSV = cv2.bitwise_and(imageHSV,imageHSV, mask=imAux)
Aquí estamos usando cv2.bitwise_and
(da clic aquí para más información de esta función) para poder visualizar en las zonas blancas de la imagen auxiliar a la imagen en HSV. Puedes ver este proceso en el video que subí a yotube justo en este minuto.
Pero te preguntarás ¿Por qué es necesario este paso?, pues bien esto es simplemente para poder detectar el color de cada figura geométrica.
Detectando figuras geométricas y su color
Bien, ahora solo nos queda llamar a las funciones que habíamos creado en un principio y etiquetar a cada figura geométrica con su nombre y color.
name = figName(c,w,h) color = figColor(maskHSV) nameColor = name + ' ' + color cv2.putText(imagen,nameColor,(x,y-5),1,0.8,(0,255,0),1) cv2.imshow('imagen',imagen) cv2.waitKey(0)
Línea 97: Llamamos a la función figName
dándole el contorno c
, ancho w
y alto h
. Entonces nos devolverá name
que es el nombre de la figura geométrica.
Línea 98: En esta llamamos a la función figColor
, a ella le entregamos maskHSV
y nos devolverá el color de la figura.
Línea 99: Ahora para imprimir el texto con la infomación de la figura necesitamos concatenar name
y color
, almacenando esto en una nueva variable llamada nameColor
.
Línea 100: Visualizamos nameColor
en la imagen de entrada.
Línea 101 y 102: Visualizamos la imagen y esperamos a que una tecla sea presionada para seguir el proceso con cada figura de la imagen.
Programa completo
import cv2 import numpy as np def figColor(imagenHSV): # 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) # Naranja naranjaBajo = np.array([11, 100, 20], np.uint8) naranjaAlto = np.array([19, 255, 255], np.uint8) #Amarillo amarilloBajo = np.array([20, 100, 20], np.uint8) amarilloAlto = np.array([32, 255, 255], np.uint8) #Verde verdeBajo = np.array([36, 100, 20], np.uint8) verdeAlto = np.array([70, 255, 255], np.uint8) #Violeta violetaBajo = np.array([130, 100, 20], np.uint8) violetaAlto = np.array([145, 255, 255], np.uint8) #Rosa rosaBajo = np.array([146, 100, 20], np.uint8) rosaAlto = np.array([170, 255, 255], np.uint8) # Se buscan los colores en la imagen, segun los límites altos # y bajos dados maskRojo1 = cv2.inRange(imagenHSV, rojoBajo1, rojoAlto1) maskRojo2 = cv2.inRange(imagenHSV, rojoBajo2, rojoAlto2) maskRojo = cv2.add(maskRojo1, maskRojo2) maskNaranja = cv2.inRange(imagenHSV, naranjaBajo, naranjaAlto) maskAmarillo = cv2.inRange(imagenHSV, amarilloBajo, amarilloAlto) maskVerde = cv2.inRange(imagenHSV, verdeBajo, verdeAlto) maskVioleta = cv2.inRange(imagenHSV, violetaBajo, violetaAlto) maskRosa = cv2.inRange(imagenHSV, rosaBajo, rosaAlto) cntsRojo = cv2.findContours(maskRojo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsNaranja = cv2.findContours(maskNaranja, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsAmarillo = cv2.findContours(maskAmarillo, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsVerde = cv2.findContours(maskVerde, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsVioleta = cv2.findContours(maskVioleta, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 cntsRosa = cv2.findContours(maskRosa, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0] #Reemplaza por 1, si tienes OpenCV3 if len(cntsRojo)>0: color = 'Rojo' elif len(cntsNaranja)>0: color = 'Naranja' elif len(cntsAmarillo)>0: color = 'Amarillo' elif len(cntsVerde)>0: color = 'Verde' elif len(cntsVioleta)>0: color = 'Violeta' elif len(cntsRosa)>0: color = 'Rosa' return color def figName(contorno,width,height): epsilon = 0.01*cv2.arcLength(contorno,True) approx = cv2.approxPolyDP(contorno,epsilon,True) if len(approx) == 3: namefig = 'Triangulo' if len(approx) == 4: aspect_ratio = float(width)/height if aspect_ratio == 1: namefig = 'Cuadrado' else: namefig = 'Rectangulo' if len(approx) == 5: namefig = 'Pentagono' if len(approx) == 6: namefig = 'Hexagono' if len(approx) > 10: namefig = 'Circulo' return namefig imagen = cv2.imread('figurasColores2.png') gray = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY) canny = cv2.Canny(gray, 10,150) canny = cv2.dilate(canny,None,iterations=1) canny = cv2.erode(canny,None,iterations=1) #_,cnts,_ = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #OpenCV 3 cnts,_ = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) #OpenCV 4 imageHSV = cv2.cvtColor(imagen, cv2.COLOR_BGR2HSV) for c in cnts: x, y, w, h = cv2.boundingRect(c) imAux = np.zeros(imagen.shape[:2], dtype="uint8") imAux = cv2.drawContours(imAux, [c], -1, 255, -1) maskHSV = cv2.bitwise_and(imageHSV,imageHSV, mask=imAux) name = figName(c,w,h) color = figColor(maskHSV) nameColor = name + ' ' + color cv2.putText(imagen,nameColor,(x,y-5),1,0.8,(0,255,0),1) cv2.imshow('imagen',imagen) cv2.waitKey(0)
Finalmente obtendremos lo siguiente:
Y hemos llegado al final de este tutorial, espero que te haya sido de ayuda y que te animes a probarlo. Si tienes alguna duda, sugerenia, corrección o mensaje, puedes hacérmelo saber en el área de comentarios.
¿Puedo usar videostreaming y hacerlo en tiempo real?
oye, pero no te parece que es muy guai que apoye a la comunidad!!
^^
seguro!! Mucha suerte con tiu trabajo y que todos tus sueños se cumplan Abel.
Un beso desde Murcia y que DIOS te bendiga, mis bendiciones…
lee mr wonderful y la biblia.
Muchas gracias por tu aporte! pero quisiera saber que algoritmo aplicaste para la deteccion? por favor!
Hola quisiera corregir con respecto a la explicación de la Linea 95, asi no haya confusiones.
«Línea 95: Se dibuja un contorno en la imagen auxiliar imAux, toma en cuenta que el contorno estará lleno (ya que en el tercer argumento de cv2.drawContours hemos colocado -1), es decir que veremos un área en blanco que corresponderá a cada figura.»
imAux = cv2.drawContours(imAux, [c], -1, 255, -1)
– En sí el tercer argumento representa al índice del contorno a dibujar. Auque se le pude definir el indice 0 también
– El quinto argumento representa al grosor del contorno, si es -1 te pinta todo el area
Saludos.
Hola Gaby! Necesito hacer algo justo como esto, pero mediante la captura de vídeo de la webcam, pero no logro adaptarlo.
Tengo el mismo problema, lo lograste?
si quisiera detectar el color negro, como lo haría? tengo una figura rectangular de color amarillo(café claro-madera), y quiero saber si dentro hay o no una figura negra