Tengo una cámara Pi apuntando a una tarjeta sobre un fondo blanco. Sin embargo, las sombras locales parecen estar impidiendo el cierre de los contornos que utilizo para la detección de tarjetas, lo que significa que la detección falla en general. Aquí hay una captura de pantalla de lo que quiero decir:
Puede ver que se rasga alrededor de las esquinas inferiores en particular. Este es el código que estoy usando para llegar tan lejos:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) gray = cv2.blur(gray, (5,5)) gray = cv2.bilateralFilter(gray, 11, 17, 17) #blur. very CPU intensive. cv2.imshow("Gray map", gray) edges = cv2.Canny(gray, 30, 120) cv2.imshow("Edge map", edges) #find contours in the edged image, keep only the largest # ones, and initialize our screen contour # use RETR_EXTERNAL since we know the largest (external) contour will be the card edge. _, cnts, _ = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:1] screenCnt = None # loop over our contours for c in cnts: # approximate the contour peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.3 * peri, True) cv2.drawContours(image, [cnts[0]], -1, (0, 255, 0), 2) # if our approximated contour has four points, then # we can assume that we have found our card if len(approx) == 4: screenCnt = approx; break
¿Hay alguna forma de obligarlo a cerrar contornos específicos? Si desenfoco más la imagen para suavizar las sombras, tampoco funciona, ya que simplemente ignora esas esquinas por no tener un borde. Es molesto que solo esté a unos pocos píxeles de cerrar los contornos, pero nunca lo hace...
editar: ahora tengo una configuración más realista donde el fondo es de color beige y con muchas más sombras interfiriendo. El beige es necesario porque hay algunas tarjetas con bordes blancos, por lo que el blanco no funcionaría. La detección de bordes falla principalmente en el lado izquierdo donde están las sombras.
Como mencioné en mi comentario a su respuesta, una de las formas más fáciles de "conectar" las líneas en el borde es usar operadores morfológicos. En el siguiente código, los bordes de la imagen se dilatan usando una forma de elipsoide. Esta técnica nos permite fusionar las líneas que están cerca y llenar algunos de los espacios vacíos. Puedes tener más información sobre este tema en la Documentación de OpenCV .
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(9,9)) dilated = cv2.dilate(image, kernel) _, cnts, _ = cv2.findContours(dilated.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Aquí puede ver la imagen del borde original, la imagen dilatada y el contorno obtenido usando los bordes dilatados (imagen obtenida usando una región recortada de su captura de pantalla original):
Pero, como puede ver y también imaginar, resolver este problema para casos más generales es más complejo y requerirá el uso de otros enfoques, y probablemente sea más amplio que una pregunta SO (o al menos en la forma en que se formula ahora).
Al observar su caso más difícil, podría recomendarle que use otras representaciones de imagen para reemplazar la imagen de entrada en escala de grises (como el canal H del espacio de color HSV) para reducir o atenuar los efectos que tiene con las sombras. También podría explorar algunas de las restricciones en su problema: las tarjetas siempre tienen líneas rectas como bordes y usan un método capaz de manejar formas paramétricas, como el detector de líneas de Hough. Eche un vistazo a esta pregunta, puede darle algunas ideas sobre cómo mejorar sus resultados: ¿Cómo identificar un cuadrado o un rectángulo con longitudes y anchos variables usando javacv?
Observación : el filtrado bilateral es muy costoso computacionalmente, especialmente si está utilizando un RPi para ejecutar su aplicación. Recomendaría invertir en otras alternativas, como un filtrado gaussiano, para reducir la cantidad de ruido en la imagen (suponiendo que realmente necesite hacerlo).