OpenCV Python script desde el procedimiento de Gimp – Detección de borde de superficie dura / dura

Me gustaría desarrollar un script de Python OpenCV para duplicar / mejorar un procedimiento de Gimp que he desarrollado. El objective del procedimiento es proporcionar una matriz de puntos x, y que siga la línea divisoria entre la hierba y las superficies duras. Esta matriz me permitirá terminar mi robot de lavado de alta presión de 500 lb 54 “, que tiene una Raspberry Pi Zero (y una cámara), para que pueda seguir ese límite a una velocidad de un par de pulgadas por segundo. Estaré monitoreando y / o controlar el bot a través de su transmisión de video wifi y una aplicación de iPhone mientras veo la televisión en mi sofá.

Aquí hay una muestra de la imagen original (60×80 píxeles):

introduzca la descripción de la imagen aquí

El procedimiento de Gimp es:

  1. Convertir imagen a 2 colores indexados. Básicamente hierba en un lado y ladrillos o pavimento en el otro lado. SOMBRAS DE OSCURIDAD Ups, esa soy yo 🙂

introduzca la descripción de la imagen aquí

  1. De los dos colores, tome el valor de Tono más bajo y la varita mágica en un píxel de ese valor con la siguiente configuración de varita. La configuración de Tono de 23 es la forma en que elimino las sombras y la configuración de la pluma de 15 es cómo elimino las islas / jaggies (hierba en las grietas :).

introduzca la descripción de la imagen aquí

  1. Realice una selección avanzada a la ruta con los siguientes valores de configuración avanzada (los cambios respecto a los valores predeterminados son amarillos). Básicamente quiero solo segmentos de línea y mi matriz de puntos (x, y) serán los puntos de ruta amarilla.

introduzca la descripción de la imagen aquí

  1. A continuación, exporto la ruta a un archivo .xml desde el cual puedo analizar y aislar los puntos amarillos en la imagen anterior. Aquí está el archivo .xml:
     

Mi objective para el tiempo de ejecución de este procedimiento OpenCV en mi Pi Zero es de aproximadamente 1-2 segundos o menos (actualmente toma ~ 0.18 segundos).

He acumulado algo que resulta en los mismos puntos que se encuentran en el archivo xml de Gimp. No estoy seguro si está haciendo lo que Gimp hace con respecto al rango de matiz de la máscara. Todavía no he descubierto cómo aplicar el radio mínimo en la máscara, estoy bastante seguro de que necesitaré eso cuando la máscara tenga un grupo de “hierba” en el borde de la superficie dura como parte de la máscara. Aquí están todos los puntos de contorno hasta ahora (ptscanvas.bmp):

introduzca la descripción de la imagen aquí

A partir del 7/6/2018 5:08 pm EST, aquí está la secuencia de comandos “todavía desordenada” que funciona y encontró esos puntos;

 import numpy as np import time, sys, cv2 img = cv2.imread('2-60.JPG') cv2.imshow('Original',img) # get a blank pntscanvas for drawing points on pntscanvas = np.zeros(img.shape, np.uint8) print (sys.version) if sys.version_info[0] < 3: raise Exception("Python 3 or a more recent version is required.") def doredo(): start_time = time.time() # Use kmeans to convert to 2 color image hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) Z = hsv_img.reshape((-1,3)) Z = np.float32(Z) # define criteria, number of clusters(K) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) K = 2 ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS) # Create a mask by selecting a hue range around the lowest hue of the 2 colors if center[0,0]  max_area: cnt = cont max_area = cv2.contourArea(cont) # Make array of all edge points of the largets contour, named allpnts perimeter = cv2.arcLength(cnt,True) epsilon = 0.01*cv2.arcLength(cnt,True) # 0.0125*cv2.arcLength(cnt,True) seems to work better allpnts = cv2.approxPolyDP(cnt,epsilon,True) end_time = time.time() print("Elapsed cv2 time was %g seconds" % (end_time - start_time)) # Convert back into uint8, and make 2 color image for saving and showing center = np.uint8(center) res = center[label.flatten()] res2 = res.reshape((hsv_img.shape)) # Save, show and print stuff cv2.drawContours(pntscanvas, allpnts, -1, (0, 0, 255), 2) cv2.imwrite("pntscanvas.bmp", pntscanvas) cv2.imshow("pntscanvas.bmp", pntscanvas) print('allpnts') print(allpnts) print("center") print(center) print('lowv',lowv) print('higv',higv) cv2.imwrite('mask.bmp',mask) cv2.imshow('mask.bmp',mask) cv2.imwrite('CvKmeans2Color.bmp',res2) cv2.imshow('CvKmeans2Color.bmp',res2) print ("Waiting for 'Spacebar' to Do/Redo OR 'Esc' to Exit") while(1): ch = cv2.waitKey(50) if ch == 27: break if ch == ord(' '): doredo() cv2.destroyAllWindows() 

Queda por hacer:

  1. Agregue un radio de máscara en los píxeles sin borde para ocuparse de las máscaras en bruto como esta que crea Gimp antes de ejecutar un radio mínimo en la máscara:

introduzca la descripción de la imagen aquí

1a. EDITAR: Desde el 9 de julio de 2018, me he concentrado en este tema, ya que parece ser mi mayor problema. No puedo hacer que cv2.findcontours suavice la ‘hierba del borde’ como Gimp lo hace con su función de radio de varita mágica. Aquí a la izquierda, hay una máscara de ‘problema’ de 2 colores y los puntos ‘Rojos’ resultantes superpuestos que se encuentran directamente utilizando cv2.findcontours y, a la derecha, la máscara de radio Gimp aplicada a la máscara de ‘problema’ de las imágenes de la izquierda antes de cv2. Findcontours se aplica a él, lo que da como resultado la imagen y los puntos correctos:

introduzca la descripción de la imagen aquí introduzca la descripción de la imagen aquí

He intentado buscar en el código fuente de Gimps pero está más allá de mi comprensión y no puedo encontrar ninguna rutina de OpenCV que pueda hacer esto. ¿Hay alguna manera de aplicar un suavizado de radio mínimo a los píxeles “sin borde” de una máscara de borde en OpenCV? Por ‘no-borde’ quiero decir que, como puede ver, Gimp no tiene un radio de estas ‘esquinas’ (resaltado amarillo) pero solo parece aplicar el suavizado de radio a los bordes ‘dentro’ de la imagen (Nota: el algoritmo de radios Gimps elimina todos los islas pequeñas en la máscara, lo que significa que no es necesario encontrar el contorno del área más grande después de aplicar cv2.findcontours para obtener los puntos de interés):

introduzca la descripción de la imagen aquí

  1. Elimine puntos de matriz irrelevantes de todos los puntos que se encuentran en el borde de la imagen.
  2. Averiguar por qué los puntos de la matriz que encuentran parecen bordear la hierba verde en lugar de la superficie dura, pensé que estaba trabajando con el tono de la superficie dura.
  3. Averigüe por qué el color de la superficie dura en CvKmeans2Color.bmp aparece naranja y no beige como en la conversión de Gimps Y ¿por qué esto no coincide con el píxel por píxel con la conversión de Gimps? Aquí está CvKmeans2Color.bmp y Gimps:

introduzca la descripción de la imagen aquí introduzca la descripción de la imagen aquí

EDITAR: A partir de las 5 p. M. EST 12 de julio de 2018: he recurrido al lenguaje con el que puedo crear el código más fácilmente, VB6, ughh, lo sé. De todos modos, he podido hacer una rutina de suavizado de líneas / bordes que funciona en el nivel de píxeles para hacer la máscara de radio mínimo que quiero. Funciona como un PacMan que recorre el lado derecho de un borde lo más cerca que puede y deja un rastro de migajas en el lado izquierdo del Pac. No estoy seguro de poder hacer un script en Python a partir de ese código, pero al menos tengo un lugar donde comenzar ya que nadie ha confirmado que existe una forma alternativa de OpenCV para hacerlo. Si a alguien le interesa, aquí hay un archivo .exe comstackdo que debería ejecutarse en la mayoría de los sistemas de Windows sin una instalación (creo). Aquí hay una captura de pantalla de ella (los píxeles de Blue / GreenyBlue son el borde no suavizado y los píxeles de Green / GreenyBlue son el borde redondeado):

introduzca la descripción de la imagen aquí

Puede obtener la esencia de mi lógica de proceso mediante esta rutina VB6:

 Sub BeginFollowingEdgePixel() Dim lastwasend As Integer wasinside = False While (1) If HitFrontBumper Then GoTo Hit Else Call MoveForward End If If circr = orgpos(0) And circc = orgpos(1) Then orgpixr = -1 'resets Start/Next button to begin at first first found blue edge pixel GoTo outnow 'this condition indicates that you have followed all blue edge pixels End If Call PaintUnderFrontBumperWhite Call PaintGreenOutsideLeftBumper nomove: If NoLeftBumperContact Then Call MoveLeft Call PaintUnderLeftBumperWhite Call PaintGreenOutsideLeftBumper If NoLeftBumperContact Then If BackBumperContact Then Call MakeLeftTheNewForward End If End If ElseIf HitFrontBumper Then Hit: Call PaintAheadOfForwardBumperGreen Call PaintGreenOutsideLeftSide Call MakeRightTheNewForward GoTo nomove Else Call PaintAheadOfForwardBumperGreen Call PaintGreenOutsideLeftSide Call PaintUnderFrontBumperWhite End If If (circr = 19 + circrad Or circr = -circrad Or circc = 19 + circrad Or circc = -circrad) Then If lastwasend = 0 And wasinside = True Then 'finished following one edge pixel lastwasend = 1 GoTo outnow Call redrawit End If Else If IsCircleInsideImage Then wasinside = True End If lastwasend = 0 End If Pause (pausev) 'seconds between moves - Pressing Esc advances early Wend outnow: End Sub 

Bueno, finalmente tuve tiempo de ver esto. Abordaré cada punto tuyo y luego mostraré los cambios en el código. Déjame saber si tienes alguna pregunta o sugerencia.

  1. Parece que pudiste hacerlo lo suficientemente bien.

    1.a. Esto se puede solucionar difuminando la imagen antes de procesarla. Los siguientes cambios en el código se hicieron para lograr esto;

     ... start_time = time.time() blur_img = cv2.GaussianBlur(img,(5,5),0) #here # Use kmeans to convert to 2 color image hsv_img = cv2.cvtColor(blur_img, cv2.COLOR_BGR2HSV) ... 
  2. He cambiado el código para eliminar puntos que están en una línea que sigue perfectamente el lado de la imagen. Debería ser básicamente imposible que un borde de hierba también coincida con esto.

     ... allpnts = cv2.approxPolyDP(cnt,epsilon,True) new_allpnts = [] for i in range(len(allpnts)): a = (i-1) % len(allpnts) b = (i+1) % len(allpnts) if ((allpnts[i,0,0] == 0 or allpnts[i,0,0] == (img.shape[1]-1)) and (allpnts[i,0,1] == 0 or allpnts[i,0,1] == (img.shape[0]-1))): tmp1 = allpnts[a,0] - allpnts[i,0] tmp2 = allpnts[b,0] - allpnts[i,0] if not (0 in tmp1 and 0 in tmp2): new_allpnts.append(allpnts[i]) else: new_allpnts.append(allpnts[i]) ... cv2.drawContours(pntscanvas, new_allpnts, -1, (0, 0, 255), 2) ... 
  3. Debido a la forma en que se encuentran los contornos en la imagen, podemos simplemente cambiar la función de umbral y encontrar el contorno alrededor de la otra parte de la imagen. Los cambios están abajo;

     ... #Extract contours from the mask ret,thresh = cv2.threshold(mask,250,255,cv2.THRESH_BINARY) #here im2,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) ... 
  4. En cuanto a las diferencias de color, ha convertido su imagen a formato HSV y, antes de guardarla, no la cambia de nuevo a BGR. Este cambio a HSV le da mejores resultados, así que lo mantendría, pero es una paleta diferente. Los cambios están abajo;

     ... cv2.imshow('mask.bmp',mask) res2 = cv2.cvtColor(res2, cv2.COLOR_HSV2BGR) cv2.imwrite('CvKmeans2Color.bmp',res2) cv2.imshow('CvKmeans2Color.bmp',res2) ... 

Descargo de responsabilidad: estos cambios se basan en el código de Python desde arriba. Cualquier cambio en el código de Python que no esté en el código provisto puede hacer que mis cambios no sean efectivos.