【问题标题】:How can I sort contours from left to right and top to bottom? [duplicate]如何从左到右和从上到下对轮廓进行排序?
【发布时间】:2016-12-03 21:42:28
【问题描述】:

我正在尝试使用 Python 构建一个字符识别程序。我坚持对轮廓进行分类。我使用this page 作为参考。

我设法使用以下代码找到了轮廓:

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”中添加排序操作即可解决此问题,但相同的解决方案不适用于多行文档。

所以我的问题是:如何从左到右和从上到下对轮廓进行排序?

【问题讨论】:

  • 您能添加一个rect 的内容示例吗?
  • rect 包含 (x,y,w,h) 的每个检测到的轮廓 [(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, 3 2, 26, 49), (91, 32, 30, 48), (333, 31, 33, 50)] 这是针对上述示例的。 (0-9)
  • @ZdaR 我先问了。另一个是重复的。
  • 好的,我明白了,但是您可以从该问题中获取一些建议来解决您的问题。

标签: python


【解决方案1】:

我不认为您将能够直接以正确的顺序生成轮廓,但是如下简单的排序应该可以满足您的需要:

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_height = np.max(c[::, 3])
nearest = max_height * 1.4

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

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

这将显示以下输出:

  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 值分组为行值,然后按矩形的 x 偏移量排序。关键是一个包含估计行的列表,然后是 x 偏移量。

计算单个矩形的最大高度以确定nearest 的合适分组值。 1.4 值是一个行距值。这也可以自动计算。所以对于你的两个例子,nearest 大约是 70。

计算也可以直接在numpy中完成。

【讨论】:

  • 这确实适用于上面的数字,但是它们似乎无法对文本轮廓进行排序。这是为示例文本检测到的轮廓:i.imgur.com/b3fnDFP.jpg 轮廓(排序后)不是按照文本的顺序,而是按照“Dkhdf?oyou....”的顺序无论如何我可以解决这个问题吗?
  • 你能给我更新的矩形列表吗?这应该只是调整nearest 值的问题,它应该大致是一个单词的预期高度。即尝试 20。
  • 这是我使用的另一张图片。 i.imgur.com/VUHrGDQ.jpg 这是矩形列表drive.google.com/open?id=0BwuAHXrh5YRTSlV0UG5XSEsxVlU
  • 对于您给出的示例,大约 70 的值似乎有效。我已经更新了脚本,以根据最高的矩形和估计的行距值来估计这个值。
  • 对于 OpenCV3,我收到 contours.sort... 行的错误 TypeError: only length-1 arrays can be converted to Python scalars。我有什么需要改变的吗?
【解决方案2】:

当我解决了我的任务时,我做了这样一种方法(我猜这个方法没有优化,可以改进):

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

原始排序: 从上到下从左到右: 原始图像,如果您想测试它:

【讨论】:

    【解决方案3】:

    给定一个二值图像-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,y,w,h = cv2.boundingRect(i) 中返回 x 用于 ith 轮廓。

    同样,你可以使用从上到下。

    【讨论】:

      【解决方案4】:

      contours.sort(key=lambda r: round( float(r[1] / nearest))) 会产生类似(int(nearest * round(float(r[1])/nearest)) * max_width + r[0]) 的效果

      【讨论】:

        【解决方案5】:

        使用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 将按从左到右和从上到下排序。

        【讨论】:

          【解决方案6】:

          用轮廓从左到右,从上到下的边界框(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
          

          该方法并未针对所有案例进行广泛测试,但发现对我正在做的项目确实有效。

          链接到sorted函数文档以供参考https://docs.python.org/3/howto/sorting.html

          【讨论】:

            【解决方案7】:
            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')
            

            【讨论】:

              猜你喜欢
              • 2022-09-24
              • 2015-01-24
              • 2016-12-12
              • 1970-01-01
              • 2021-08-08
              • 1970-01-01
              • 2019-09-27
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多