OpenCV基础(26)使用 Python 和 OpenCV 顺时针排序坐标

1.使用 Python 和 OpenCV 顺时针排序坐标

这篇博文的目标有两个:

  • 1、主要目的是学习如何按照左上、右上、右下和左下的顺序排列与旋转边界框相关联的(x, y)坐标。按照这样的顺序组织边界框坐标是执行透视转换或匹配对象角点(例如计算对象之间的距离)等操作的先决条件。
  • 2、第二个目的是解决 imutils 包的 order_points 方法中一个微妙的、难以发现的错误。

话虽如此,让我们通过回顾按顺时针顺序排列边界框坐标的原始的、有缺陷的方法来开始这篇博文。

2.原始(有缺陷的)方法

在我们学习如何按(1)顺时针顺序(2)左上、右上、右下和左下顺序排列一组边界框坐标之前,我们应该首先回顾一下最初的4点getPerspectiveTransform博客文章中详细介绍的order_points方法。

我已将(有缺陷的)order_points 方法重命名为 order_points_old,以便我们可以比较原始方法和更新后的方法。首先,打开一个新文件并将其命名为 order_coordinates.py

# 导入包
from __future__ import print_function
from imutils import perspective
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
def order_points_old(pts):
	# 初始化将被排序的坐标列表,这样,列表中的第一个条目是左上,第二个条目是右上,第三个条目是右下,第四个条目是左下
	rect = np.zeros((4, 2), dtype="float32")
	# 左上点的总和最小,而右下点的总和最大
	s = pts.sum(axis=1)
	rect[0] = pts[np.argmin(s)]
	rect[2] = pts[np.argmax(s)]
	# 现在,计算点之间的差值,右上角的差值最小,而左下角的差值最大
	diff = np.diff(pts, axis=1)
	rect[1] = pts[np.argmin(diff)]
	rect[3] = pts[np.argmax(diff)]
	# 返回有序坐标
	return rect

导入本示例所需的 Python 包。我们将在本博文稍后使用 imutils 包。
定义我们的 order_points_old 函数。该方法只需要一个参数,即我们将按左上、右上、右下和左下顺序排列的一组点;虽然,正如我们将看到的,这种方法有一些缺陷。
我们定义一个形状为 (4, 2) 的 NumPy 数组,该数组将用于存储我们的四个 (x, y) 坐标集。
给定这些 pts ,我们将 x 和 y 值加在一起,然后找到最小和最大的和。这些值分别为我们提供了左上角和右下角坐标。
然后我们取 x 和 y 值之间的差异,其中右上角的差异最小,左下角的差异最大。
最后,将有序的 (x, y) 坐标返回给调用函数。

说了这么多,你能发现我们逻辑中的缺陷吗?
当两点的和或差相同时会发生什么?如果数组 和 或数组 diff 有相同的值,我们有选择不正确索引的风险,这会对我们的排序产生严重影响。

选择错误的索引意味着我们从 pts 列表中选择了错误的点。如果我们从 pts 中取错误的点,那么我们顺时针的左上、右上、右下、左下的顺序将被破坏。

为了解决这个问题,我们需要使用更可靠的数学原理设计一个更好的 order_points 函数。这正是我们将在下一节中介绍的内容。

3.使用 OpenCV 和 Python 顺时针排序坐标的更好方法

现在我们已经查看了 order_points 函数的一个有缺陷的版本,让我们回顾一下更新的、正确的实现。

# import the necessary packages
from scipy.spatial import distance as dist
import numpy as np
import cv2
def order_points(pts):
	# 根据点的 x 坐标对点进行排序
	xSorted = pts[np.argsort(pts[:, 0]), :]
	# 从根据点的 x 坐标排序的坐标点中获取最左和最右的点
	leftMost = xSorted[:2, :]
	rightMost = xSorted[2:, :]
	# 现在,根据y坐标对最左边的坐标排序,这样我们就可以分别获取左上角和左下角的点
	leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
	(tl, bl) = leftMost
	# 现在我们有了左上角的坐标,用它作为锚点来计算左上角和右下角点之间的欧氏距离;根据勾股定理,距离最大的点就是右下点
	D = dist.cdist(tl[np.newaxis], rightMost, "euclidean")[0]
	(br, tr) = rightMost[np.argsort(D)[::-1], :]
	# 按左上、右上、右下和左下顺序返回坐标
	return np.array([tl, tr, br, bl], dtype="float32")

同样,导入所需的 Python 包。然后定义我们的 order_points 函数,它只需要一个参数——我们想要排序的点列表。
然后根据它们的 x 值对这些点进行排序。给定已排序的 xSorted 列表,我们应用数组切片来获取最左边的两个点和最右边的两个点。
因此,最左边的点将对应于左上角和左下角的点,而最右边的点将是我们的右上角和右下角的点——诀窍是弄清楚哪个是哪个。
幸运的是,这并不太具有挑战性。 如果我们根据 y 值对 leftMost 点进行排序,我们可以分别推导出左上角和左下角的点。
然后,为了确定右下角和左下角的点,我们可以应用一些几何知识。
使用左上角的点作为锚点,我们可以应用勾股定理并计算左上角和rightMost之间的欧几里得距离。根据三角形的定义,斜边将是直角三角形的最大边。
因此,通过将左上角点作为我们的锚点,右下角点将具有最大的欧几里德距离,从而允许我们提取右下角和右上角的点。
最后,返回一个 NumPy 数组,表示我们按左上、右上、右下和左下顺序排列的有序坐标。

4.实现我们的坐标排序

现在我们有了 order_points 的原始版本和更新版本,让我们继续实现我们的 order_coordinates.py 脚本并尝试一下:

# 导入包
from __future__ import print_function
from imutils import perspective
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
def order_points_old(pts):
	# 初始化将被排序的坐标列表,这样,列表中的第一个条目是左上,第二个条目是右上,第三个条目是右下,第四个条目是左下
	rect = np.zeros((4, 2), dtype="float32")
	# 左上点的总和最小,而右下点的总和最大
	s = pts.sum(axis=1)
	rect[0] = pts[np.argmin(s)]
	rect[2] = pts[np.argmax(s)]
	# 现在,计算点之间的差值,右上角的差值最小,而左下角的差值最大
	diff = np.diff(pts, axis=1)
	rect[1] = pts[np.argmin(diff)]
	rect[3] = pts[np.argmax(diff)]
	# 返回有序坐标
	return rect
def order_points(pts):
	# 根据点的 x 坐标对点进行排序
	xSorted = pts[np.argsort(pts[:, 0]), :]
	# 从根据点的 x 坐标排序的坐标点中获取最左和最右的点
	leftMost = xSorted[:2, :]
	rightMost = xSorted[2:, :]
	# 现在,根据y坐标对最左边的坐标排序,这样我们就可以分别获取左上角和左下角的点
	leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
	(tl, bl) = leftMost
	# 现在我们有了左上角的坐标,用它作为锚点来计算左上角和右下角点之间的欧氏距离;根据勾股定理,距离最大的点就是右下点
	D = dist.cdist(tl[np.newaxis], rightMost, "euclidean")[0]
	(br, tr) = rightMost[np.argsort(D)[::-1], :]
	# 按左上、右上、右下和左下顺序返回坐标
	return np.array([tl, tr, br, bl], dtype="float32")
# 构造参数解析并解析参数
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())
# 加载我们的输入图像,将其转换为灰度,并稍微模糊它
image = cv2.imread("example.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
# 执行边缘检测,然后执行膨胀+腐蚀以缩小对象边缘之间的间隙
edged = cv2.Canny(gray, 50, 100)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)

解析我们的命令行参数。我们只需要一个参数 --new ,它用于指示是否应该使用新的或原始的 order_points 函数。我们将默认使用原始实现。 从那里,我们从磁盘加载 example.png 并通过将图像转换为灰度并使用高斯滤波器对其进行平滑来执行一些预处理。 我们通过应用 Canny 边缘检测器继续处理我们的图像,然后进行膨胀 + 腐蚀以缩小边缘图中轮廓之间的任何间隙。 执行完边缘检测过程后,我们的图像应该是这样的:
在这里插入图片描述
计 算 输 入 图 像 的 边 缘 图 计算输入图像的边缘图
如您所见,我们已经能够确定图像中对象的轮廓。 现在我们有了边缘图的轮廓,我们可以应用 cv2.findContours 函数来提取对象的轮廓:

# 在边缘图中找到轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
	cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# 从左到右对轮廓进行排序并初始化边界框点颜色
(cnts, _) = contours.sort_contours(cnts)
colors = ((0, 0, 255), (240, 0, 159), (255, 0, 0), (255, 255, 0))

然后我们从左到右对对象轮廓进行排序,这不是必需的,但可以更轻松地查看脚本的输出。下一步是单独循环每个轮廓:

# 分别在轮廓上循环
for (i, c) in enumerate(cnts):
	# 如果轮廓不够大,则忽略它
	if cv2.contourArea(c) < 100:
		continue
	# 计算轮廓的旋转边界框,然后绘制轮廓
	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)
	# 显示原始坐标
	print("Object #{}:".format(i + 1))
	print(box)

开始在我们的轮廓上循环。如果轮廓不够大(由于边缘检测过程中的“噪声”),我们丢弃轮廓区域。否则,计算轮廓的旋转边界框(注意使用 cv2.cv.BoxPoints [如果我们使用 OpenCV 2.4] 或 cv2.boxPoints [如果我们使用 OpenCV 3])并在图像上绘制轮廓。
我们还将打印原始旋转边界框,以便我们可以在对坐标进行排序后比较结果。
我们现在准备按顺时针排列排列边界框坐标:

# 对轮廓中的点进行排序,使它们以左上、右上、右下和左下的顺序出现,然后绘制旋转边界框的轮廓
rect = order_points_old(box)
# 检查是否应使用新方法对坐标进行排序
if args["new"] > 0:
	rect = perspective.order_points(box)
# 显示重新排序的坐标
print(rect.astype("int"))
print("")

应用原始(即有缺陷的)order_points_old 函数以左上、右上、右下和左下的顺序排列我们的边界框坐标。
如果 --new 1 标志已传递给我们的脚本,那么我们将应用我们更新的 order_points 函数。
就像我们将原始边界框打印到控制台一样,我们还将打印有序点,以确保我们的功能正常工作。
最后,我们可以可视化我们的结果:

# 遍历原始点并绘制它们
for ((x, y), color) in zip(rect, colors):
	cv2.circle(image, (int(x), int(y)), 5, color, -1)
# 在左上角绘制对象编号
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)
# 显示
cv2.imshow("Image", image)
cv2.waitKey(0)

我们开始遍历我们(希望如此)有序的坐标并将它们绘制在我们的图像上。 根据颜色列表,左上角应该是红色,右上角应该是紫色,右下角应该是蓝色,最后是左下角应该是蓝绿色。 最后,在我们的图像上绘制对象编号并显示输出结果。
在这里插入图片描述

在这里插入图片描述
正如我们所看到的,我们的输出是按顺时针方向按左上角、右上角、右下角和左下角排列的点——除了对象 #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 函数。
在这里插入图片描述

5.完整代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/10/17 0:20
# @File    : order_coordinates.py.py
# @Software: PyCharm
# 导入包
from __future__ import print_function
from imutils import perspective
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2


def order_points_old(pts):
    # 初始化将被排序的坐标列表,这样,列表中的第一个条目是左上,第二个条目是右上,第三个条目是右下,第四个条目是左下
    rect = np.zeros((4, 2), dtype="float32")
    # 左上点的总和最小,而右下点的总和最大
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    # 现在,计算点之间的差值,右上角的差值最小,而左下角的差值最大
    diff = np.diff(pts, axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    # 返回有序坐标
    return rect


# 构造参数解析并解析参数
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())
# 加载我们的输入图像,将其转换为灰度,并稍微模糊它
image = cv2.imread("example_01.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
# 执行边缘检测,然后执行膨胀+腐蚀以缩小对象边缘之间的间隙
edged = cv2.Canny(gray, 50, 100)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)
# 在边缘图中找到轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
                        cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
# 从左到右对轮廓进行排序并初始化边界框点颜色
(cnts, _) = contours.sort_contours(cnts)
colors = ((0, 0, 255), (240, 0, 159), (255, 0, 0), (255, 255, 0))
# 分别在轮廓上循环
for (i, c) in enumerate(cnts):
    # 如果轮廓不够大,则忽略它
    if cv2.contourArea(c) < 100:
        continue
    # 计算轮廓的旋转边界框,然后绘制轮廓
    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)
    # 显示原始坐标
    print("Object #{}:".format(i + 1))
    print(box)

    # 对轮廓中的点进行排序,使它们以左上、右上、右下和左下的顺序出现,然后绘制旋转边界框的轮廓
    rect = order_points_old(box)
    # 检查是否应使用新方法对坐标进行排序
    if args["new"] > 0:
        rect = perspective.order_points(box)
    # 显示重新排序的坐标
    print(rect.astype("int"))
    print("")
    # 遍历原始点并绘制它们
    for ((x, y), color) in zip(rect, colors):
        cv2.circle(image, (int(x), int(y)), 5, color, -1)
    # 在左上角绘制对象编号
    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)
    # 显示
    cv2.imshow("Image", image)
    cv2.waitKey(0)

总结

我们已经在之前的博文中实现了对与每个对象的旋转边界框相关联的 4 个点进行排序功能。 然而,正如我们发现的,这个实现有一个致命的缺陷——它可以在非常特殊的情况下返回错误的坐标。 为了解决这个问题,我们定义了一个新的、更新的 order_points 函数并将它放在 imutils 包中。此实现可确保我们的点始终正确排序。 现在我们可以以可靠的方式对 (x, y) 坐标进行排序,我们可以继续测量图像中对象的大小,这正是我将在以后博文中讨论的内容。

参考目录

https://www.pyimagesearch.com/2016/03/21/ordering-coordinates-clockwise-with-python-and-opencv/?_ga=2.125978829.13056945.1634300872-1609619485.1590463372

  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用 OpenCV 的 `getRotationMatrix2D` 和 `warpAffine` 函数来实现图像旋转。具体步骤如下: 1. 使用 `cv2.getRotationMatrix2D` 函数计算旋转矩阵,该函数有三个参数:旋转中心坐标、旋转角度和缩放比例。 2. 使用 `cv2.warpAffine` 函数应用旋转矩阵到图像上,该函数有三个参数:要旋转的图像、旋转矩阵和输出图像的大小。 下面是一个简单的示例代码,将图像逆时针旋转30度: ```python import cv2 img = cv2.imread('example.jpg') height, width = img.shape[:2] # 计算旋转矩阵 center = (width / 2, height / 2) angle = 30 scale = 1 M = cv2.getRotationMatrix2D(center, angle, scale) # 应用旋转矩阵 rotated = cv2.warpAffine(img, M, (width, height)) cv2.imshow('rotated', rotated) cv2.waitKey(0) cv2.destroyAllWindows() ``` 注意,这里的角度是逆时针旋转的。如果要顺时针旋转,可以把角度改为负值。 ### 回答2: 在OpenCV使用Python旋转图像是相对简单的。可以通过cv2.getRotationMatrix2D()函数来获得旋转矩阵,并使用cv2.warpAffine()函数应用该矩阵来旋转图像。 首先,需要导入OpenCV库和NumPy库: import cv2 import numpy as np 然后,加载图像并获取其尺寸: img = cv2.imread('image.jpg', 1) rows, cols = img.shape[:2] 接下来,定义旋转中心点位置和旋转角度: center = (cols / 2, rows / 2) angle = 45 然后,使用cv2.getRotationMatrix2D()函数获得旋转矩阵: M = cv2.getRotationMatrix2D(center, angle, 1) 最后,使用cv2.warpAffine()函数应用旋转矩阵来旋转图像: rotated_img = cv2.warpAffine(img, M, (cols, rows)) 最后一行代码会生成一个旋转后的图像,保存在rotated_img中。 需要注意的是,在旋转过程中,如果旋转角度为负值,则图像会逆时针旋转;如果旋转角度为正值,则图像会顺时针旋转。所以根据需要调整angle的正负值来控制旋转方向。 以上就是使用OpenCVPython旋转图像的简单方法。 ### 回答3: 在opencv python中,可以使用getRotationMatrix2D()函数来创建一个旋转矩阵,然后使用warpAffine()函数来实现图像的旋转。 首先,我们需要导入必要的库: ```python import cv2 import numpy as np ``` 然后,我们可以使用getRotationMatrix2D()函数来创建一个旋转矩阵。此函数需要三个参数:旋转中心点(通常为图像中心),旋转角度和缩放因子。例如,如果我们想要将图像顺时针旋转90度,可以这样做: ```python image = cv2.imread("image.jpg") (h, w) = image.shape[:2] center = (w / 2, h / 2) angle = 90 scale = 1.0 M = cv2.getRotationMatrix2D(center, angle, scale) ``` 接下来,我们可以使用warpAffine()函数来应用旋转矩阵并获得旋转后的图像。此函数需要两个参数:输入图像和旋转矩阵。这将返回一个旋转后的图像。例如,我们将原始图像旋转90度并保存为新的图像: ```python rotated = cv2.warpAffine(image, M, (w, h)) cv2.imwrite("rotated_image.jpg", rotated) ``` 以上步骤将实现图像的旋转。要注意的是,旋转角度可以为正数(顺时针)或负数(逆时针),并且可以根据需要进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值