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 cv2def 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

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

  1. OpenCV基础(17)基于OpenCV、scikit-image和Python的直方图匹配

    在本教程中,您将学习如何使用OpenCV和scikit-image进行直方图匹配. 上周我们讨论了直方图均衡化,这是一种基本的图像处理技术,可以提高输入图像的对比度. 但是,如果你想自动匹配两幅图像的 ...

  2. python的opencv库_用于Python的Opencv可视化库安装,面向,OpenCV,视觉

    计算机视觉最重要的库莫过于OpenCV,ananconda功能很强大,但是也有比较重要的几个库,还没做成集成开发环境,需要自己安装. 在安装的过程中也走了许多弯路,因为安装的方法有许多许多,有些随着O ...

  3. python连接opencv库_利用Python和OpenCV库将URL转换为OpenCV格式的方法

    今天的博客是直接来源于我自己的个人工具函数库. 过去几个月,有些PyImageSearch读者电邮问我:"如何获取URL指向的图片并将其转换成OpenCV格式(不用将其写入磁盘再读回)&qu ...

  4. OpenCV基础(22)使用OpenCV生成及读取二维码以及与Zbar比较

    二维码(QR码)是一种矩阵条形码,是一种机器可读的光学标签,其中包含有关其所附着物品的信息.实际上,二维码(QR码)码通常包含指向网站或应用程序的定位器,标识符或跟踪器的数据. 最近,OpenCV 4 ...

  5. 计算机视觉基础系列(python与opencv的操作与运用/tensorflow的基础介绍)(八)---小例子(神经网络逼近股票收盘价格)

    这里我们运用简单的神经网络模拟一下股票的收盘价格,是一个学习的示例. 首先要知道,股票曲线图的参数意义: 这个曲线图主要记录股票的开盘价格和收盘价格,如果开盘价格低于收盘价格,那么证明这个股票在增长, ...

  6. python opencv图像匹配_关于python:OpenCV功能匹配多个图像

    如何使用FLANN优化许多图片的SIFT功能匹配? 我有一个从Python OpenCV文档中获取的工作示例.然而,这是将一个图像与另一个图像进行比较而且速度很慢.我需要它来搜索一系列图像(几千个)中 ...

  7. OpenCV基础(4)使用OpenCV裁剪图像

    首先,为什么我们需要作物?裁剪是为了从图像中移除所有不需要的物体或区域或者是突出图像的一个特殊特征. 与Numpy使用切片操作实现裁剪不同,OpenCV没有特定的函数来进行裁剪操作.读取的每个图像都存 ...

  8. Opencv 基础(三):使用OpenCV裁剪图像

    裁剪是从图像中移除所有不需要的物体或区域.甚至可以突出显示图像的特定特征. 没有使用OpenCV进行裁剪的特定函数,NumPy数组切片是做这项工作的.读取的每个图像都存储在一个2D数组中(对于每个颜色 ...

  9. OpenCV基础(3)使用OpenCV调整图像大小

    让我们学习如何使用OpenCV调整图像大小.要调整图像的大小,请根据指定的比例因素或设置所需的高度和宽度,沿着每个轴(高度和宽度)缩放图像. 当调整图像大小: 如果你想在调整后的图像中保持相同的大小, ...

最新文章

  1. 从设计原则谈软件开发(二)
  2. RAID与软RAID命令应用
  3. php 3个循环,For循环只发布数组的前3个元素 - PHP(For loop is only posting first 3 elements of array - PHP)...
  4. 浅谈PHP的Public、Protected、Private三种方法的区别
  5. boost::spirit模块演示 AST 生成的计算器示例,AST一旦创建就会被遍历
  6. matlab求借带参数的方程组
  7. python 工资管理软件_4_python之路之模拟工资管理系统
  8. java基于文件的map实现_Mybatis中返回Map的实现
  9. Djaongo 中间件
  10. Java异常的概念和分类
  11. (49)Verilog HDL SPI接收设计
  12. 源码安装php时出现configure: error: xml2-config not found. Please check your libxml2 installation...
  13. h5调微信支付 unkonw url_h5移动端调用支付宝、微信支付的实现
  14. 三、运算符、表达式和语句
  15. 阿里云DataV数据可视化工具的简单使用与地图组件区域下钻热力图下钻上钻控制缩放比例demo
  16. 统计学习方法详解之第十三章 无监督学习概论
  17. html table最小宽度,table宽度比tbody多1
  18. 题目 1567: 超级玛丽
  19. angularjs的SEO问题解决方案
  20. Google Earth Engine(GEE)——逐月降水数据下载和直方图表展示

热门文章

  1. 《古剑奇谭WEB》值得期待的三大理由
  2. Python函数认识(二)
  3. 生成在线图片地址,用于测试
  4. LIVE MINI ESP32开发板教程系列(三)drv2605L模块+手机常用振动器实现117种震动效果
  5. 美国Top50金融科技公司简介
  6. Android: Jetpack Compose如何禁用涟漪(水波纹)效果
  7. 探索移动端音视频与GSYVideoPlayer之旅 | Agora Talk
  8. 华为鸿蒙搁浅,华为系统突然搁浅 背后大棋浮出水面 太高了
  9. eclipse和vs2010 (visual studio)和BBEdit自动排版快捷键(auto format shortcuts)
  10. 动手写一个HTML5的无限循环滚动焦点图