python视觉识别坐标_使⽤Python和OpenCV顺时针排序坐标点击上⽅"蓝⾊⼩字"关注我呀
本⽂来⾃光头哥哥的博客【Ordering coordinates clockwise with Python and OpenCV
】,仅做学习分享。
前⾔
本⽂我认为我翻译的并不太成功,因为原⽂光头哥就写的很啰嗦,⼤概理顺⼀下思路就是:
光头哥想写⼀个把识别物体⽤矩形框起来,并将矩形四个顶点按左上,右上,右下,左下的顺序来排列,他之前写了⼀个排序算法,但有bug,所以本⽂介绍了⼀下旧⽅法,并介绍了⼀个新的没有bug的⽅法。
⽽这个算法将在本系列后续中发挥作⽤。
下⾯是正⽂:
今天,我们将开始⼀个系列的第⼀篇,这个系列为计算对象的⼤⼩,并测量它们之间的距离,⼀共三篇。
⽽在这之前,⾸先需要实现⼀个对四个顶点进⾏的排序算法。
这篇博⽂的主要⽬的是学习如何按左上、右上、右下和左下顺序排列矩形四个顶点。按照这样的顺序排列顶点是执⾏诸如透视变换或匹配对象⾓点(例如计算对象之间的距离)等操作的先决条件。
⾸先回顾⼀下之前写的原始的、有缺陷的按顺时针顺序排列四个顶点的⽅法。
原始的(有缺陷的)⽅法
在原来的
博客⽂章中有详细介绍order_points⽅法。
原始排序算法:
# import the necessary packagesfrom __future__ import print_functionfrom imutils import perspectivefrom imutils import contoursimport numpy as npimport argparseimport imutilsimport cv2def order_points_old(pts):# initialize a list of coordinates that will be ordered# such that the first entry in the list is the top-left,# the second entry is the top-right, the third is the# bottom-right, and the fourth is the bottom-leftrect = np.zeros((4, 2), dtype="float32")# the top-left point will have the smallest sum, whereas# the bottom-right point will have the largest sums = pts.sum(axis=1)rect[0] =
pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# now, compute the difference between the points, the# top-right point will have the smallest difference,# whereas the bottom-left will have the largest differencediff = np.diff(pts, axis=1)rect[1] =
pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]# return the ordered coordinatesreturn rect
第2-8⾏句柄⽤于导⼊本例所需的Python包。稍后我们将在这篇博客中使⽤imutils包。
第9⾏定义了order_points_old函数。这个⽅法只需要⼀个参数,即我们将要按左上⾓,右上⾓,右下⾓和左下⾓顺序排列的点集。
我们从第14⾏开始,定义⼀个形状为(4,2)的NumPy数组,它将⽤于存储我们的四个(x, y)坐标集。
给定这些pts,我们将x和y值相加,然后出最⼩和最⼤的和(第17-19⾏)。这些值分别为我们提供了左上⾓和右下⾓的坐标。
然后我们取x和y值之间的差值,其中右上⾓的点的差值最⼩,⽽左下⾓的距离最⼤(第23-25⾏)。
最后,第31⾏将有序的(x, y)坐标返回给调⽤函数。
说了这么多,你能发现我们逻辑上的缺陷吗?我给你个提⽰:
当这两点的和或差相等时会发⽣什么?简⽽⾔之,悲剧。
如果计算的和s或差diff具有相同的值,我们就有选择错误索引的风险,这会对排序造成级联影响。
选择错误的索引意味着我们从pts列表中选择了错误的点。如果我们从pts中取出错误的点,那么左上⾓,右上⾓,右下⾓和左下⾓顺序排列就会被破坏。
那么我们如何解决这个问题并确保它不会发⽣呢?为了处理这个问题,我们需要使⽤更合理的数学原理设计⼀个更好的order_points函数。这正是我们下⾯要讲的内容。
顺时针排列坐标的更好⽅法
我们将要介绍的,新的,没有bug的order_points函数的实现可以在imutils包中到,确切的说是在perspective.py⽂件中(这个包应该是作者⾃⼰发布的,⾥⾯包含的是⼀系列⽅便的,作者⾃⼰定义的辅助算法函数)。
# import the necessary packagesfrom scipy.spatial import distance as distimport numpy as npimport cv2def
order_points(pts):# sort the points based on their x-coordinatesxSorted = pts[np.argsort(pts[:, 0]), :]#
grab the left-most and right-most points from the sorted# x-roodinate pointsleftMost = xSorted[:2, :]rightMost = xSorted[2:, :]# now, sort the left-most coordinates according to their# y-coordinates so we can grab the top-left and bottom-left# points, respectivelyleftMost = leftMost[np.argsort(leftMost[:, 1]), :](tl, bl) = leftMost# now that we have the top-left coordinate, use it as an# anchor to calculate the Euclidean distance between the# top-left and right-most points; by the Pythagorean# theorem, the point with the largest distance will be# our bottom-right pointD = dist.cdist(waxis], rightMost, "euclidean")[0](br, tr) = rightMost[np.argsort(D)[::-1], :]# return the coordinates in top-left, top-right,# bottom-right, and bottom-left orderreturn np.array([tl, tr, br, bl], dtype="float32")
同样,我们从第2-4⾏开始导⼊所需的Python包。然后在第5⾏定义order_points函数,该函数只需要⼀个参数——我们想要排序的点pts 列表。
第7⾏根据x-values对这些pts进⾏排序。给定已排序的xordered列表,我们应⽤数组切⽚来获取最左边的两个点和最右边的两个点(第12⾏和第13⾏)。
因此,最左边的点将对应于左上和左下的点,⽽最右边的点将对应于右上和右下的点——诀窍在于分清哪个是哪个。
幸运的是,这并不太具有挑战性。如果我们根据它们的y值对最左边的点进⾏排序,我们可以分别推出
左上⾓和左下⾓的点(第15⾏和第16⾏)。
然后,为了确定右下⾓和左下⾓的点,我们可以应⽤⼀点⼏何图形的知识。
使⽤左上点作为锚点,我们可以应⽤勾股定理计算左上点和最右点之间的欧式距离。根据三⾓形的定义,斜边是直⾓三⾓形最⼤的边。
因此,通过将左上⾓的点作为锚点,右下⾓的点将具有最⼤的欧⼏⾥得距离,从⽽允许我们提取右下⾓和右上⾓的点(第22⾏和第23⾏)。
最后,第26⾏返回⼀个NumPy数组,表⽰按左上⾓、右上⾓、右下⾓和左下⾓顺序排列的有序边界框坐标。
测试排序算法
现在我们已经有了order_points的原始版本和更新版本,让我们继续实现order_coordinates.py脚本,并尝试它们:
# import the necessary packagesfrom __future__ import print_functionfrom imutils import perspectivefrom imutils import contoursimport numpy as npimport argparseimport imutilsimport cv2d
ef order_points_old(pts):# initialize a list of coordinates that will be ordered# such that the first entry in the list is the top-left,# the second entry is the top-right, the third is the# bottom-right, and the fourth is the bottom-leftrect = np.zeros((4, 2), dtype="float32")# the top-left point will have the smallest sum, whereas# the bottom-right point will have the largest sums = pts.sum(axis=1)rect[0] =
sort命令排序pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# now, compute the difference between the points, the# top-right point will have the smallest difference,# whereas the bottom-left will have the largest differencediff = np.diff(pts, axis=1)rect[1] =
pts[np.argmin(diff)]rect[3] = pts[np.argmax(diff)]# return the ordered coordinatesreturn rect# construct the argument parse and parse the argumentsap = argparse.ArgumentParser()ap.add_argument("-n", "--new", type=int, default=-
1,help="whether or not the new order points should should be used")args = vars(ap.parse_args())# load our input image, convert it to grayscale, and blur it slightlyimage = cv2.imread("example.png")gray = cv2.cvtColor(image,
cv2.COLOR_BGR2GRAY)gray = cv2.GaussianBlur(gray, (7, 7), 0)# perform edge detection, then perform a dilation + erosion to# close gaps in between object edgesedged = cv2.Canny(gray, 50, 10
0)edged = cv2.dilate(edged, None,
iterations=1)edged = de(edged, None, iterations=1)
第29-32⾏解析命令⾏参数。我们只需要⼀个参数--new,它⽤于指⽰应该使⽤新的还是原始的order_points函数。我们将默认使⽤原始实现。
然后,我们从磁盘加载example.png,并通过将图像转换为灰度并使⽤⾼斯滤波器平滑它来执⾏⼀些预处理。
我们继续通过使⽤Canny边缘检测器来处理图像,然后通过膨胀+侵蚀来缩⼩边缘图中轮廓之间的任何缝隙。
进⾏边缘检测后,我们的图像应该是这样的:
正如你所看到的,我们已经能够确定图像中物体的轮廓。
现在我们有了边缘图的轮廓,我们可以应⽤cv2.findContours函数,实际提取对象的轮廓:
# find contours in the edge mapcnts = cv2.py(),
cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)cnts = ab_contours(cnts)# sort the contours from left-to-right and initialize the bounding box# point colors(cnts, _) = contours.sort_contours(cnts)colors = ((0, 0, 255), (240, 0, 159), (255, 0, 0), (255, 255, 0))
然后,我们从左到右对对象轮廓进⾏排序,这不是必需的,但是可以更容易地查看脚本的输出。下⼀步是在每个轮廓线上分别循环:
# loop over the contours individuallyfor (i, c) in enumerate(cnts):# if the contour is not sufficiently large, ignore itif
cv2.minAreaRect(c)box = cv2.cv.BoxPoints(box) if imutils.is_cv2() else cv2.boxPoints(box)box = np.array(box,
dtype="int")cv2.drawContours(image, [box], -1, (0, 255, 0), 2)# show the original coordinatesprint("Object #{}:".format(i + 1))print(box)
第2⾏开始循环我们的轮廓线。如果轮廓不够⼤(由于边缘检测过程中的“噪声”),我们放弃轮廓区域(第
4和5⾏)。
否则,第8-11⾏处理计算轮廓的旋转包围框(注意使⽤cv2.cv.BoxPoints)[如果使⽤的是OpenCV 2.4]或cv2.boxPoints[如果我们使⽤OpenCV 3]),并在图像上绘制轮廓。
我们还将打印原始的旋转包围框,这样我们就可以在对坐标排序后⽐较结果。
我们现在准备好按顺时针⽅向排列边界框坐标:
# order the points in the contour such that they appear# in top-left, top-right, bottom-right, and bottom-left# order, then draw the outline of the rotated bounding# boxrect = order_points_old(box)# check to see if the new method should be used for# ordering the coordinatesif args["new"] > 0:rect = der_points(box)# show the re-ordered
coordinatesprint(rect.astype("int"))print("")
第5⾏应⽤原始的(即有缺陷的)order_points_old函数来按照左上⾓、右上⾓、右下⾓和左下⾓的顺序排列边框坐标。
如果——new标识符已经传递给函数,那么我们将应⽤更新后的order_points函数(第8和9⾏)。
就像我们将原始的边界框打印到控制台⼀样,我们也将打印有序的点,以确保函数正常⼯作。
最后,我们可以将结果可视化:
# loop over the original points and draw themfor ((x, y), color) in zip(rect, colors):cv2.circle(image, (int(x), int(y)), 5, color, -1)# draw the object num at the top-left cornercv2.putText(image, "Object #{}".format(i + 1),(int(rect[0][0] - 15), int(rect[0] [1] - 15)),cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 2)# show the imagecv2.imshow("Image",
image)cv2.waitKey(0)
我们在第2⾏对矩阵四个点坐标循环,并在图像上绘制它们。
根据color列表,左上的点应该是红⾊的,右上的点应该是紫⾊的,右下的点应该是蓝⾊的,最后左下的点应该是蓝绿⾊的。
最后,第5-7⾏在图像上绘制对象编号并显⽰输出结果。
正如我们所看到的,我们预期的输出是按顺时针顺序排列的,按左上⾓、右上⾓、右下⾓和左下⾓排列——但对象6除外!
看看我们的终端输出对象6,我们可以看到为什么:
求这些坐标的和,我们得到:
520 + 255 = 775
491 + 226 = 717
520 + 197 = 717
549 + 226 = 775
⽽这个差异告诉我们:
520 – 255 = 265
491 – 226 = 265
520 – 197 = 323
549 – 226 = 323
正如您所看到的,我们最终得到了重复的值!
由于存在重复的值,argmin()和argmax()函数不能像我们预期的那样⼯作,从⽽给我们提供了⼀组错误的“有序”坐标。
要解决这个问题,我们可以使⽤imutils包中更新的order_points函数。我们可以通过发出以下命令来验证我们更新的函数是否正常⼯作:$ python order_coordinates.py --new 1
这⼀次,我们所有的点被正确地排序,包括对象#6:
当使⽤透视转换(或任何其他需要有序坐标的项⽬)时,请确保使⽤我们更新的实现!
THE END
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论