我正在关注这个问题:
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)]
我正在使用的图像
你真正需要的是设计一个公式来将轮廓信息转换为等级并使用该等级对轮廓进行排序,因为你需要从上到下和从左到右对轮廓进行排序,所以你的公式必须涉及到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之间,原因是6
th轮廓略低于3, 4, 5
轮廓线。
告诉我你想要以其他方式输出我们可以调整get_contour_precedence
以获得3, 4, 5, 6
轮廓校正的行列。
这是来自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)
看来你链接的question不是原始轮廓,而是首先使用cv2.boundingRect
获得一个边界矩形。只有这样才能计算max_width
和max_height
。您发布的代码表明您正在尝试对原始轮廓进行排序,而不是对矩形进行排序。如果不是这样,您是否可以提供更完整的代码,包括您要排序的多个轮廓列表?