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

Я пытаюсь построить программу распознавания символов с использованием Python. Я застрял на сортировке контуров. Я использую эту страницу в качестве ссылки.

Мне удалось найти контуры, используя следующий фрагмент кода:

mo_image = di_image.copy()
contour0 = cv2.findContours(mo_image.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
contours = [cv2.approxPolyDP(cnt,3,True) for cnt in contour0[0]]

И добавил ограничивающие прямоугольники и сегментировал изображение, используя эту часть кода:

maxArea = 0
rect=[]
for ctr in contours:
    maxArea = max(maxArea,cv2.contourArea(ctr))

if img == "Food.jpg":
    areaRatio = 0.05
elif img == "Plate.jpg":
    areaRatio = 0.5

for ctr in contours:    
    if cv2.contourArea(ctr) > maxArea * areaRatio: 
        rect.append(cv2.boundingRect(cv2.approxPolyDP(ctr,1,True)))

symbols=[]
for i in rect:
    x = i[0]
    y = i[1]
    w = i[2]
    h = i[3]
    p1 = (x,y)
    p2 = (x+w,y+h)
    cv2.rectangle(mo_image,p1,p2,255,2)
    image = cv2.resize(mo_image[y:y+h,x:x+w],(32,32))
    symbols.append(image.reshape(1024,).astype("uint8"))

testset_data = np.array(symbols)

cv2.imshow("segmented",mo_image)
plt.subplot(2,3,6)
plt.title("Segmented")
plt.imshow(mo_image,'gray')
plt.xticks([]),plt.yticks([]);

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

сегменты

Затем программа выводит каждый сегмент отдельно, но в следующем порядке: 4 1 9 8 7 5 3 2 0 6 и не 0 1 2 3 4 5 6 7 8 9, Простое добавление операции сортировки в "rect" исправляет это, но это же решение не будет работать для документа с несколькими строками.

Итак, мой вопрос: как отсортировать контуры слева направо и сверху вниз?

7 ответов

Я не думаю, что вы сможете генерировать контуры непосредственно в правильном порядке, но простая сортировка следующим образом должна делать то, что вам нужно:

import numpy as np

c = np.load(r"rect.npy")
contours = list(c)

# Example - contours = [(287, 117, 13, 46), (102, 117, 34, 47), (513, 116, 36, 49), (454, 116, 32, 49), (395, 116, 28, 48), (334, 116, 31, 49), (168, 116, 26, 49), (43, 116, 30, 48), (224, 115, 33, 50), (211, 33, 34, 47), ( 45, 33, 13, 46), (514, 32, 32, 49), (455, 32, 31, 49), (396, 32, 29, 48), (275, 32, 28, 48), (156, 32, 26, 49), (91, 32, 30, 48), (333, 31, 33, 50)] 

max_width = np.sum(c[::, (0, 2)], axis=1).max()
max_height = np.max(c[::, 3])
nearest = max_height * 1.4

contours.sort(key=lambda r: (int(nearest * round(float(r[1])/nearest)) * max_width + r[0]))

for x, y, w, h in contours:
    print "{:4} {:4} {:4} {:4}".format(x, y, w, h) 

Это будет отображать следующий вывод:

  36   45   33   40
  76   44   29   43
 109   43   29   45
 145   44   32   43
 184   44   21   43
 215   44   21   41
 241   43   34   45
 284   46   31   39
 324   46    7   39
 337   46   14   41
 360   46   26   39
 393   46   20   41
 421   45   45   41
 475   45   32   41
 514   43   38   45
  39  122   26   41
  70  121   40   48
 115  123   27   40
 148  121   25   45
 176  122   28   41
 212  124   30   41
 247  124   91   40
 342  124   28   39
 375  124   27   39
 405  122   27   43
  37  210   25   33
  69  199   28   44
 102  210   21   33
 129  199   28   44
 163  210   26   33
 195  197   16   44
 214  210   27   44
 247  199   25   42
 281  212    7   29
 292  212   11   42
 310  199   23   43
 340  199    7   42
 355  211   43   30
 406  213   24   28
 437  209   31   35
 473  210   28   43
 506  210   28   43
 541  210   17   31
  37  288   21   33
  62  282   15   39
  86  290   24   28
 116  290   72   30
 192  290   23   30
 218  290   26   41
 249  288   20   33

Работает, группируя похожие y значения, а затем умножение на ширину, результат является ключом, который увеличивается строка за строкой. Максимальная высота одного прямоугольника рассчитывается для определения подходящего значения группировки для nearest, 1.4 значение является значением межстрочного интервала. Это также может быть рассчитано автоматически. Так что для обоих ваших примеров nearest около 70

Расчеты также могут быть сделаны непосредственно в NumPy.

Пока я решил свою задачу, я применил такой подход (этот не оптимизирован и, наверное, можно улучшить):

import pandas as pd
import cv2
import cv2
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize'] = (20.0, 10.0)
matplotlib.rcParams['image.cmap'] = 'gray'

imageCopy = cv2.imread("./test.png")
imageGray = cv2.imread("./test.png", 0)
image = imageCopy.copy()

contours, hierarchy = cv2.findContours(imageGray, cv2.RETR_EXTERNAL, 
                                           cv2.CHAIN_APPROX_SIMPLE)
bboxes = [cv2.boundingRect(i) for i in contours]
bboxes=sorted(bboxes, key=lambda x: x[0])

df=pd.DataFrame(bboxes, columns=['x','y','w', 'h'], dtype=int)
df["x2"] = df["x"]+df["w"] # adding column for x on the right side
df = df.sort_values(["x","y", "x2"]) # sorting

for i in range(2): # change rows between each other by their coordinates several times 
# to sort them completely 
    for ind in range(len(df)-1):
    #     print(ind, df.iloc[ind][4] > df.iloc[ind+1][0])
        if df.iloc[ind][4] > df.iloc[ind+1][0] and df.iloc[ind][1]> df.iloc[ind+1][1]:
            df.iloc[ind], df.iloc[ind+1] = df.iloc[ind+1].copy(), df.iloc[ind].copy()
num=0
for box in df.values.tolist():

    x,y,w,h, hy = box
    cv2.rectangle(image, (x,y), (x+w,y+h), (255,0,255), 2)
    # Mark the contour number
    cv2.putText(image, "{}".format(num + 1), (x+40, y-10), cv2.FONT_HERSHEY_SIMPLEX, 1, 
                (0, 0, 255), 2);
    num+=1
plt.imshow(image[:,:,::-1])

Исходная сортировка: Сверху вниз слева направо: исходное изображение, если вы хотите его протестировать:

Учитывая двоичное изображение - thresh, Я думаю, самый короткий путь -

      import numpy as np
import cv2 

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NON) #thresh is a bia
cntr_index_LtoR = np.argsort([cv2.boundingRect(i)[0] for i in contours])

Здесь, cv2.boundingRect(i)[0] возвращается просто x из x,y,w,h = cv2.boundingRect(i) для iй контур.

Точно так же вы можете использовать сверху вниз.

      def sort_contours(contours, x_axis_sort='LEFT_TO_RIGHT', y_axis_sort='TOP_TO_BOTTOM'):
    # initialize the reverse flag
    x_reverse = False
    y_reverse = False
    if x_axis_sort == 'RIGHT_TO_LEFT':
        x_reverse = True
    if y_axis_sort == 'BOTTOM_TO_TOP':
        y_reverse = True
    
    boundingBoxes = [cv2.boundingRect(c) for c in contours]
    
    # sorting on x-axis 
    sortedByX = zip(*sorted(zip(contours, boundingBoxes),
    key=lambda b:b[1][0], reverse=x_reverse))
    
    # sorting on y-axis 
    (contours, boundingBoxes) = zip(*sorted(zip(*sortedByX),
    key=lambda b:b[1][1], reverse=y_reverse))
    # return the list of sorted contours and bounding boxes
    return (contours, boundingBoxes)
    

contours, hierarchy = cv2.findContours(img_vh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours, boundingBoxes = sort_contours(contours, x_axis_sort='LEFT_TO_RIGHT', y_axis_sort='TOP_TO_BOTTOM')

После нахождения контуров с помощью contours=cv2.findContours(), используйте -

boundary=[]
for c,cnt in enumerate(contours):
    x,y,w,h = cv2.boundingRect(cnt)
    boundary.append((x,y,w,h))
count=np.asarray(boundary)
max_width = np.sum(count[::, (0, 2)], axis=1).max()
max_height = np.max(count[::, 3])
nearest = max_height * 1.4
ind_list=np.lexsort((count[:,0],count[:,1]))

c=count[ind_list]

теперь c будет отсортирован слева направо и сверху вниз.

contours.sort(key=lambda r: round( float(r[1] / nearest))) вызовет подобный эффект, как (int(nearest * round(float(r[1])/nearest)) * max_width + r[0])

Простой способ сортировки контуров с помощью ограничивающей рамки (x, y, w, h) контуров слева направо, сверху вниз выглядит следующим образом.

Вы можете получить габаритный прямоугольник , используя boundingBoxes = cv2.boundingRect() метод

      def sort_bbox(boundingBoxes):
'''
function to sort bounding boxes from left to right, top to bottom
'''
    # combine x and y as a single list and sort based on that 
    boundingBoxes = sorted(boundingBoxes, key=lambda b:b[0]+b[1], reverse=False))
    return boundingboxes

Этот метод не был тщательно протестирован со всеми случаями, но оказался действительно эффективным для проекта, над которым я работал.

Ссылка на отсортированную документацию по функциям для справки https://docs.python.org/3/howto/sorting.html

Другие вопросы по тегам