【问题标题】:Python opencv sorting contours [duplicate]Python opencv排序轮廓
【发布时间】:2017-01-17 02:37:21
【问题描述】:

我正在关注这个问题:

How can I sort contours from left to right and top to bottom?

从左到右和从上到下对轮廓进行排序。但是,我的轮廓是使用这个(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)]

我正在使用的图像

我想按以下顺序查找轮廓:

用于寻找轮廓的膨胀图像

【问题讨论】:

  • 你能解释一下你的目标吗?你在最终输出中需要什么?您想在哪个基础上找到区域、原点位置或其他标准的轮廓?
  • 我上传了另一张图片来描述我希望轮廓的排序顺序。我只需要按位置对轮廓进行排序并按该顺序将它们保存到文件中。
  • 你能把创建dilation的代码贴出来吗?
  • 我已经添加了代码以及膨胀图像

标签: python python-2.7 opencv lambda opencv3.0


【解决方案1】:

您链接的question 似乎不适用于原始轮廓,而是首先使用cv2.boundingRect 获得一个边界矩形。只有这样计算max_widthmax_height 才有意义。您发布的代码表明您正在尝试对原始轮廓进行排序,而不是对矩形进行排序。如果不是这样,您能否提供一段更完整的代码,包括您尝试排序的多个轮廓的列表?

【讨论】:

    【解决方案2】:

    您实际需要的是设计一个公式将您的轮廓信息转换为等级并使用该等级对轮廓进行排序,因为您需要从上到下和从左到右对轮廓进行排序,因此您的公式必须涉及给定轮廓的origin 以计算其排名。例如我们可以使用这个简单的方法:

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

    它根据轮廓的原点对每个轮廓进行排名。当两个连续的轮廓垂直放置时变化很大,但当轮廓水平堆叠时变化很小。因此,以这种方式,首先轮廓将从上到下分组,并且在发生冲突的情况下,将使用水平放置的轮廓中较小的变量值。

    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 之间,原因是 6th 轮廓线略低于 3, 4, 5 轮廓线。

    告诉我您是否希望以其他方式输出,我们可以调整get_contour_precedence 以校正3, 4, 5, 6 的轮廓等级。

    【讨论】:

    • 是的,我需要等高线在 3、4、5、6 中。排序必须能够“忽略”稍高的轮廓并仍然按该顺序排序。是否也有助于进行一些预处理,以便在同一行中找到的轮廓具有相同的高度?
    • 添加容差值以满足您的需求。
    • 这真是巧妙的+1!!
    • 你能解释一下这里的“cols”是什么吗?
    • 是图片行的像素数吗?我做了类似的事情,如果你在 (2,4) 和 (3,1) 有框并且图像长 100 像素,你会让它们的位置 2*100 + 4 = 204 和 3*100+1 = 301,所以它们总是按从左到右的顺序排列(假设),但是如果你开始的坐标不是都在同一条线上怎么办?就像你有四个盒子都排成一行,但第二个盒子稍微低一点。现在该框将被放置乱序。我想这就是容忍因素的来源?
    【解决方案3】:

    这是由 Adrian Rosebrock 提供的,用于根据位置 link 对轮廓进行排序:

    # 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】:

      您可以简单地检查轮廓的距离并对其进行排名,听一个例子

      def get_distance(x,y):
          return math.sqrt(x*x+y*y)
      
      
      img = cv2.imread("/image.png", 0)
      
      res, img = cv2.threshold(img, 70, 255, cv2.THRESH_BINARY)
      
      contours, h = cv2.findContours(img.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_A
      
      for i in xrange(len(contours)):
          [x, y, w, h] = cv2.boundingRect(contours[i])
          img = cv2.putText(img, str(get_distance(x,y)), 
          cv2.boundingRect(contours[i])[:2], cv2.FONT_HERSHEY_COMPLEX, 1, [125])
      

      【讨论】:

        猜你喜欢
        • 2021-09-14
        • 2021-05-08
        • 2015-12-16
        • 2013-05-29
        • 1970-01-01
        • 1970-01-01
        • 2012-11-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多