Python opencv clasificación de contornos

Estoy siguiendo esta pregunta:

¿Cómo puedo ordenar los contornos de izquierda a derecha y de arriba a abajo?

para ordenar los contornos de izquierda a derecha y de arriba a abajo. Sin embargo, mis contornos se encuentran utilizando este (OpenCV 3):

im2, contours, hierarchy = cv2.findContours(threshold,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) 

y se formatean así:

  array([[[ 1, 1]], [[ 1, 36]], [[63, 36]], [[64, 35]], [[88, 35]], [[89, 34]], [[94, 34]], [[94, 1]]], dtype=int32)] 

Cuando corro el codigo

 max_width = max(contours, key=lambda r: r[0] + r[2])[0] max_height = max(contours, key=lambda r: r[3])[3] nearest = max_height * 1.4 contours.sort(key=lambda r: (int(nearest * round(float(r[1])/nearest)) * max_width + r[0])) 

Estoy recibiendo el error

 ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() 

Así que lo cambié a esto:

 max_width = max(contours, key=lambda r: np.max(r[0] + r[2]))[0] max_height = max(contours, key=lambda r: np.max(r[3]))[3] nearest = max_height * 1.4 contours.sort(key=lambda r: (int(nearest * round(float(r[1])/nearest)) * max_width + r[0])) 

Pero ahora me sale el error:

TypeError: only length-1 arrays can be converted to Python scalars

EDITAR:

Después de leer la respuesta a continuación modifiqué mi código:

Editar 2

Este es el código que uso para “dilatar” los caracteres y encontrar los contornos.

 kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(35,35)) # dilate the image to get text # binaryContour is just the black and white image shown below dilation = cv2.dilate(binaryContour,kernel,iterations = 2) 

FIN DE EDICIÓN 2

 im2, contours, hierarchy = cv2.findContours(dilation,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) myContours = [] # Process the raw contours to get bounding rectangles for cnt in reversed(contours): epsilon = 0.1*cv2.arcLength(cnt,True) approx = cv2.approxPolyDP(cnt,epsilon,True) if len(approx == 4): rectangle = cv2.boundingRect(cnt) myContours.append(rectangle) max_width = max(myContours, key=lambda r: r[0] + r[2])[0] max_height = max(myContours, key=lambda r: r[3])[3] nearest = max_height * 1.4 myContours.sort(key=lambda r: (int(nearest * round(float(r[1])/nearest)) * max_width + r[0])) i=0 for x,y,w,h in myContours: letter = binaryContour[y:y+h, x:x+w] cv2.rectangle(binaryContour,(x,y),(x+w,y+h),(255,255,255),2) cv2.imwrite("pictures/"+str(i)+'.png', letter) # save contour to file i+=1 

Contornos antes de ordenar:

 [(1, 1, 94, 36), (460, 223, 914, 427), (888, 722, 739, 239), (35,723, 522, 228), (889, 1027, 242, 417), (70, 1028, 693, 423), (1138, 1028, 567, 643), (781, 1030, 98, 413), (497, 1527, 303, 132), (892, 1527, 168, 130), (37, 1719, 592, 130), (676, 1721, 413, 129), (1181, 1723, 206, 128), (30, 1925, 997, 236), (1038, 1929, 170, 129), (140, 2232, 1285, 436)] 

Contornos después de la clasificación:

( NOTA: este no es el orden en el que quiero que se ordenen los contornos. Consulte la imagen en la parte inferior)

 [(1, 1, 94, 36), (460, 223, 914, 427), (35, 723, 522, 228), (70,1028, 693, 423), (781, 1030, 98, 413), (888, 722, 739, 239), (889, 1027, 242, 417), (1138, 1028, 567, 643), (30, 1925, 997, 236), (37, 1719, 592, 130), (140, 2232, 1285, 436), (497, 1527, 303, 132), (676, 1721, 413, 129), (892, 1527, 168, 130), (1038, 1929, 170, 129), (1181, 1723, 206, 128)] 

Imagen con la que estoy trabajando

introduzca la descripción de la imagen aquí

Quiero encontrar los contornos en el siguiente orden: introduzca la descripción de la imagen aquí

Imagen de dilatación utilizada para encontrar contornos. introduzca la descripción de la imagen aquí

Lo que realmente necesita es idear una fórmula para convertir su información de contorno en un rango y utilizar ese rango para ordenar los contornos, ya que necesita clasificar los contornos de arriba a abajo y de izquierda a derecha, de modo que su fórmula debe incluir el origin de una Contorno dado para calcular su rango. Por ejemplo podemos usar este sencillo método:

 def get_contour_precedence(contour, cols): origin = cv2.boundingRect(contour) return origin[1] * cols + origin[0] 

Da un rango a cada contorno dependiendo del origen del contorno. Varía ampliamente cuando dos contornos consecutivos se encuentran verticalmente, pero varía marginalmente cuando los contornos se astackn horizontalmente. De esta manera, primero se agruparían los contornos de arriba a abajo y, en caso de Choque, se utilizaría el valor de menor variante entre los contornos horizontales.

 import cv2 def get_contour_precedence(contour, cols): tolerance_factor = 10 origin = cv2.boundingRect(contour) return ((origin[1] // tolerance_factor) * tolerance_factor) * cols + origin[0] img = cv2.imread("/Users/anmoluppal/Downloads/9VayB.png", 0) _, img = cv2.threshold(img, 70, 255, cv2.THRESH_BINARY) im, contours, h = cv2.findContours(img.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) contours.sort(key=lambda x:get_contour_precedence(x, img.shape[1])) # For debugging purposes. for i in xrange(len(contours)): img = cv2.putText(img, str(i), cv2.boundingRect(contours[i])[:2], cv2.FONT_HERSHEY_COMPLEX, 1, [125]) 

introduzca la descripción de la imagen aquí

Si ve de cerca, la tercera fila donde se colocan los contornos 3, 4, 5, 6 el 6 encuentra entre 3 y 5, la razón es que el contorno 6 está ligeramente por debajo de la línea de los contornos 3, 4, 5 .

Dígame si desea que la salida se get_contour_precedence otra manera. Podemos modificar get_contour_precedence para obtener 3, 4, 5, 6 rangos de contorno corregidos.

Esto es de Adrian Rosebrock para clasificar los contornos según el enlace de ubicación:

 # import the necessary packages import numpy as np import argparse import imutils import cv2 def sort_contours(cnts, method="left-to-right"): # initialize the reverse flag and sort index reverse = False i = 0 # handle if we need to sort in reverse if method == "right-to-left" or method == "bottom-to-top": reverse = True # handle if we are sorting against the y-coordinate rather than # the x-coordinate of the bounding box if method == "top-to-bottom" or method == "bottom-to-top": i = 1 # construct the list of bounding boxes and sort them from top to # bottom boundingBoxes = [cv2.boundingRect(c) for c in cnts] (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b:b[1][i], reverse=reverse)) # return the list of sorted contours and bounding boxes return (cnts, boundingBoxes) def draw_contour(image, c, i): # compute the center of the contour area and draw a circle # representing the center M = cv2.moments(c) cX = int(M["m10"] / M["m00"]) cY = int(M["m01"] / M["m00"]) # draw the countour number on the image cv2.putText(image, "#{}".format(i + 1), (cX - 20, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 2) # return the image with the contour number drawn on it return image # construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required=True, help="Path to the input image") ap.add_argument("-m", "--method", required=True, help="Sorting method") args = vars(ap.parse_args()) # load the image and initialize the accumulated edge image image = cv2.imread(args["image"]) accumEdged = np.zeros(image.shape[:2], dtype="uint8") # loop over the blue, green, and red channels, respectively for chan in cv2.split(image): # blur the channel, extract edges from it, and accumulate the set # of edges for the image chan = cv2.medianBlur(chan, 11) edged = cv2.Canny(chan, 50, 200) accumEdged = cv2.bitwise_or(accumEdged, edged) # show the accumulated edge map cv2.imshow("Edge Map", accumEdged) # find contours in the accumulated image, keeping only the largest # ones cnts = cv2.findContours(accumEdged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5] orig = image.copy() # loop over the (unsorted) contours and draw them for (i, c) in enumerate(cnts): orig = draw_contour(orig, c, i) # show the original, unsorted contour image cv2.imshow("Unsorted", orig) # sort the contours according to the provided method (cnts, boundingBoxes) = sort_contours(cnts, method=args["method"]) # loop over the (now sorted) contours and draw them for (i, c) in enumerate(cnts): draw_contour(image, c, i) # show the output image cv2.imshow("Sorted", image) cv2.waitKey(0) 

Parece que la pregunta que vinculó no funciona con los contornos en bruto, sino que primero obtiene un rectángulo delimitador utilizando cv2.boundingRect . Solo entonces tiene sentido calcular max_width y max_height . El código que publicaste sugiere que estás tratando de ordenar los contornos en bruto, no delimitando rectangularjs. Si ese no es el caso, ¿puede proporcionar una parte más completa de su código, incluida una lista de múltiples contornos que está tratando de ordenar?