Контуры сортировки Python opencv

Я слежу за этим вопросом:

Как я могу отсортировать контуры слева направо и сверху вниз?

сортировать контуры слева направо и сверху вниз. Тем не менее, мои контуры можно найти с помощью этого (OpenCV 3):

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

и они отформатированы так:

   array([[[ 1,  1]],

   [[ 1, 36]],

   [[63, 36]],

   [[64, 35]],

   [[88, 35]],

   [[89, 34]],

   [[94, 34]],

   [[94,  1]]], dtype=int32)]

Когда я запускаю код

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]))

Я получаю ошибку

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

поэтому я изменил это на это:

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]))

но теперь я получаю ошибку:

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

РЕДАКТИРОВАТЬ:

Прочитав ответ ниже, я изменил свой код:

РЕДАКТИРОВАТЬ 2

Это код, который я использую, чтобы "расширить" символы и найти контуры

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)

Конец редактирования 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

Контуры перед сортировкой:

[(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)]

Контуры после сортировки:

(ПРИМЕЧАНИЕ. Это не тот порядок, в котором я хочу отсортировать контуры. См. Изображение внизу)

[(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)]

Изображение, с которым я работаю

enter image description here

Я хочу найти контуры в следующем порядке: enter image description here

Дилатационное изображение, используемое для нахождения контуров enter image description here

Ответ 1

На самом деле вам нужно разработать формулу для преобразования информации о контуре в ранг и использовать этот ранг для сортировки контуров. Поскольку вам нужно отсортировать контуры сверху донизу и слева направо, чтобы ваша формула включала origin данного контура для вычисления его ранга. Например, мы можем использовать этот простой метод:

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

Он дает ранг каждому контуру в зависимости от начала контура. Он изменяется в значительной степени, когда два последовательных контура лежат вертикально, но изменяется незначительно, когда контуры уложены горизонтально. Итак, таким образом, сначала контуры будут сгруппированы от вершины к нижней, а в случае Clash будет использоваться меньшее значение варианта среди горизонтальных отложенных контуров.

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])

введите описание изображения здесь

Если вы видите внимательно, третья строка, в которой расположены контуры 3, 4, 5, 6, 6 находится между 3 и 5, причина в том, что контур 6 th немного ниже линии контуров 3, 4, 5.

Скажите, что вы хотите получить результат по-другому, мы можем настроить get_contour_precedence, чтобы получить 3, 4, 5, 6 ранги исправленного контура.

Ответ 3

Это от Адриана Роузброка для сортировки контуров по ссылке на местоположение:

# 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)

Ответ 4

Похоже, что question, который вы связали, работает не с необработанными контурами, а сначала получает ограничивающий прямоугольник с помощью cv2.boundingRect. Только тогда имеет смысл рассчитать max_width и max_height. Выведенный код подсказывает, что вы пытаетесь сортировать необработанные контуры, а не ограничивать прямоугольники. Если это не так, можете ли вы предоставить более полную часть своего кода, включая список нескольких контуров, которые вы пытаетесь отсортировать?