目标(goals)

1.基本目的就是学习如何按照左上、右上、右下、左下这个顺时针顺序来排列旋转过的边框的(x,y)坐标,这是执行很多运用的前提,如透视变换或匹配对象角。
2.第二个目的是解决imutils软件包的order_points方法中的一个细微问题(难以发现的错误)。通过解决此错误,我们的order_points函数将不再容易受到破坏性错误的影响。

首先,我们先用有缺陷的方法来顺时针排列我们边框的坐标。
我们将有缺陷的order_points方法重新命名为order_points_old,这样我们就可以比较原来的和新的方法。打开一个文件并且命名为order_coordinates,py:

# import the necessary packages
from __future__ import print_function
from imutils import perspective
from imutils import contours
import imutils
import argparse
from scipy.spatial import distance as dist
import numpy as np
import cv2


def 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-left
    rect = np.zeros((4, 2), dtype="float32")

    # the top-left point will have the smallest sum, whereas
    # the bottom-right point will have the largest sum
    s = 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 difference
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]

    # return the ordered coordinates
    return rect

第2-8行import这个例子所需要的库。我们将使用到imutils库,如果你没有安装,可以通过pip来安装它(pip install imutils)。如果以前已经安装过imutils,可以将它更新到最新版本(pip install --upgrade imutils)。

第10行定义了我们的order_points_old函数。这个函数只需要一个参数,一系列我们将要去顺时针排序的坐标点(左上,右上,右下和左下);这个方法有很多缺陷。我们在15行定义了一个(4,2)的Numpy 数组,用来存储4个(x,y)坐标。根据给定的pts,我们将x和y的值加起来,最小值和最大值分别就是我们边框的左上和右下的坐标。然后,我们求出x和y值之间的差,其中右上角的点差最小,而左下角的距离最大(第26-28行)。
这个方法的缺陷在于如果两个坐标点的x,y求和值或者x,y坐标差值是一样的时候,我们就没有办法准确得出哪个坐标在哪个位置。

为了解决这个问题,我们用一种更好的方法来对边框坐标进行排序。
我们将要检查的order_points函数的实现可以在imutils包中找到;具体来说,在Perspective.py文件中。出于完整性考虑,我在此博客文章中包含了确切的实现:

# import the necessary packages
from scipy.spatial import distance as dist
import numpy as np
import cv2
 
def order_points(pts):
	# sort the points based on their x-coordinates
	xSorted = pts[np.argsort(pts[:, 0]), :]
 
	# grab the left-most and right-most points from the sorted
	# x-roodinate points
	leftMost = 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, respectively
	leftMost = 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 point
	D = dist.cdist(tl[np.newaxis], rightMost, "euclidean")[0]
	(br, tr) = rightMost[np.argsort(D)[::-1], :]
 
	# return the coordinates in top-left, top-right,
	# bottom-right, and bottom-left order
	return np.array([tl, tr, br, bl], dtype="float32")

第2-4行,我们import我们需要用的python库。然而我们在第6行定义order_points函数,该函数只需要一个参数,即我们想要排序的pts序列。

第8行我们根据x值来将这些pts排序。并且给出排序好的xSorted列表,我们因此可以应用数组切片来获取两个最左边的点和两个最右边的点(第12和第13行)。最左边的两个点则是左上和左下两个点。同理,最右边两个点则是右上和右下两个点。因此,我们可以根据两个点的y值来区分最左边两个点中,哪个点是左上,哪个点是左下。一个矩形边框中,我们以左上的点作为锚点,与左上的点最远的点肯定是右下的点。根据这个原理,我们可以区分出最右边两个点中,哪个是右上,哪个是右下。(第26行和第27行)

最后,第31行return一个顺时针排列好的Numpy数组(左上,右上,右下和左下)。

测试(Testing)

我们已经有了旧版和新版的order_points,接下来我们可以尝试测试一下它们:

# construct the argument parse and parse the arguments
ap = 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 slightly
image = cv2.imread("D://pics//1224a.jpg")
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 edges
edged = cv2.Canny(gray, 50, 100)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)

这段代码前几行处理解析我们的命令行参数,我们仅需要一个参数–new,这个参数用来指示被使用的order_points的版本。我们默认使用旧版。

紧接着,我们按照自己存储图片的路径加载一张提前准备好的图片1224a.jpg,并且将其转为灰度图,然后用高斯滤波(Gaussian filter)进行处理。(该过程为图片的预处理过程)

随后,应用边缘检测、膨胀及腐蚀来对我们的图片进一步处理。最后我们用cv2.findContours函数来提取对象的轮廓:

# find contours in the edge map
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_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 individually
for (i, c) in enumerate(cnts):
    # if the contour is not sufficiently large, ignore it
    if cv2.contourArea(c) < 100:
        continue

    # compute the rotated bounding box of the contour, then
    # draw the contours
    box = 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 coordinates
    print("Object #{}:".format(i + 1))
    print(box)

开始循环我们的轮廓。如果某个轮廓不足够大(可能由边缘检测过程种噪声引起),则丢弃该轮廓区域。

否则,处理计算轮廓的旋转边框(如果你使用的是OpenCV2.4,则使用cv2.cv.BoxPoints。如果使用的是OpenCV3,则使用cv2.boxPoints),并且在图片中画出轮廓。我们也打印最初的旋转边框box,所以我们能够比较在我们排列坐标后的结果。现在我们准备去用顺时针的顺序排列我们的方块边框坐标。

# 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
    # box
    rect = order_points_old(box)

    # check to see if the new method should be used for
    # ordering the coordinates
    if args["new"] > 0:
        rect = perspective.order_points(box)

    # show the re-ordered coordinates
    print(rect.astype("int"))
    print("")

其中rect = order_points_old(box)则是以左上、右上、右下和左下的顺序排列我们的方块边框坐标。
如果–new1的flag被传递至我们的脚本,这时我们将应用我们的更新版order_points函数。就像我们将原始边界框打印到控制台一样,我们还将打印有序点,以便确保功能正常运行。

最后,我们将结果可视化:

# loop over the original points and draw them
    for ((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 corner
    cv2.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 image
    cv2.imshow("Image", image)
    cv2.waitKey(0)

我们开始循环我们排列好的坐标并且将他们画在我们的图片上。

根据colors列表,这左上方坐标点应该是红色,右上方坐标点是紫色,右下方的坐标点是蓝色,左下方的坐标点是蓝绿色。测试效果如下图:

Python中如何给xy轴排序 python 坐标排序_ci

总结

在这篇博客中,我们对每个对象的旋转边框四个坐标点进行了排序。我们用新的order_points来确保我们的排序准确。既然我们已经能够用一种很稳定准确的方式来排列(x,y)坐标,接下来我们可以测量图片中对象的尺寸。