Как я могу отсортировать контуры слева направо и сверху вниз?
Я пытаюсь построить программу распознавания символов с использованием 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