【python】OpenCV—Coordinates Sorted Clockwise

news2024/9/23 13:16:01

在这里插入图片描述

文章目录

  • 1、需求介绍
  • 2、算法实现
  • 3、完整代码

1、需求介绍

调用 opencv 库,绘制轮廓的矩形边框,坐标顺序为右下→左下→左上→右上,我们实现一下转化为熟悉的 左上→右上→右下→左下 形式

按照这样的顺序组织边界框坐标是执行透视转换或匹配对象角点(例如计算对象之间的距离)等操作的先决条件

2、算法实现

#!/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
from scipy.spatial import distance as dist

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

def order_points_old 前,边界框四个顶点的坐标顺序
右下→左下→左上→右上

def order_points_old 后,边界框四个顶点的坐标顺序
左上→右上→右下→左下

假设矩形框比较正,无倾斜,左上角坐标为 (x,y)

  • 右上角(x+w,y)
  • 左下角(x,y+h)
  • 右下角(x+w,y+h)

横纵坐标之差的大小

  • 左上角(y-x)
  • 右上角(y-x-w)
  • 左下角(y-x+h)
  • 右下角(y-x+h-w)

这么对比分析的话,确实差值右上角最小,最下角最大

缺陷

当两点的和或差相同时会发生什么?如果数组 和 或数组 diff 有相同的值,我们有选择不正确索引的风险,这会对我们的排序产生严重影响。

改进:

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("1.jpg")

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.jpg", gray)

gray.jpg
在这里插入图片描述

gray = cv2.GaussianBlur(gray, (7, 7), 0)
cv2.imwrite("GaussianBlur.jpg", gray)

GaussianBlur.jpg

在这里插入图片描述

# 执行边缘检测,然后执行膨胀+腐蚀以缩小对象边缘之间的间隙
edged = cv2.Canny(gray, 50, 100)
cv2.imwrite("Canny.jpg", edged)

Canny.jpg

在这里插入图片描述

edged = cv2.dilate(edged, None, iterations=1)
cv2.imwrite("dilate.jpg", edged)

dilate.jpg

在这里插入图片描述

edged = cv2.erode(edged, None, iterations=1)
cv2.imwrite("erode.jpg", edged)

erode.jpg
在这里插入图片描述

# 在边缘图中找到轮廓
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("detect order:\n", box)

    # 对轮廓中的点进行排序,使它们以左上、右上、右下和左下的顺序出现,然后绘制旋转边界框的轮廓
    rect = order_points_old(box)
    print("old order:\n", rect)

    rect_new = order_points(box)
    print("new order1:\n", rect_new)
    # 检查是否应使用新方法对坐标进行排序
    if args["new"] > 0:
        rect = perspective.order_points(box)
    # 显示重新排序的坐标
    print("new order2:\n", 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.imwrite(f"result_{i+1}.jpg", image)
    cv2.imshow("Image", image)
    cv2.waitKey(0)

输出

result_1.jpg

在这里插入图片描述

Object #1:
detect order:
 [[ 90 236]
 [ 42 236]
 [ 42 190]
 [ 90 190]]
old order:
 [[ 42. 190.]
 [ 90. 190.]
 [ 90. 236.]
 [ 42. 236.]]
new order1:
 [[ 42. 190.]
 [ 90. 190.]
 [ 90. 236.]
 [ 42. 236.]]
new order2:
 [[ 42 190]
 [ 90 190]
 [ 90 236]
 [ 42 236]]

result_2.jpg

在这里插入图片描述

Object #2:
detect order:
 [[192 192]
 [171  94]
 [342  59]
 [362 157]]
old order:
 [[171.  94.]
 [342.  59.]
 [362. 157.]
 [192. 192.]]
new order1:
 [[171.  94.]
 [342.  59.]
 [362. 157.]
 [192. 192.]]
new order2:
 [[171  94]
 [342  59]
 [362 157]
 [192 192]]

result_3.jpg

在这里插入图片描述

Object #3:
detect order:
 [[395 229]
 [348 229]
 [348 183]
 [395 183]]
old order:
 [[348. 183.]
 [395. 183.]
 [395. 229.]
 [348. 229.]]
new order1:
 [[348. 183.]
 [395. 183.]
 [395. 229.]
 [348. 229.]]
new order2:
 [[348 183]
 [395 183]
 [395 229]
 [348 229]]

result_4.jpg

在这里插入图片描述

Object #4:
detect order:
 [[400 383]
 [377 258]
 [489 237]
 [512 363]]
old order:
 [[377. 258.]
 [489. 237.]
 [512. 363.]
 [400. 383.]]
new order1:
 [[377. 258.]
 [489. 237.]
 [512. 363.]
 [400. 383.]]
new order2:
 [[377 258]
 [489 237]
 [512 363]
 [400 383]]

result_5.jpg

在这里插入图片描述

Object #5:
detect order:
 [[495 211]
 [392 161]
 [450  42]
 [553  92]]
old order:
 [[450.  42.]
 [553.  92.]
 [495. 211.]
 [392. 161.]]
new order1:
 [[450.  42.]
 [553.  92.]
 [495. 211.]
 [392. 161.]]
new order2:
 [[450  42]
 [553  92]
 [495 211]
 [392 161]]

result_6.jpg

在这里插入图片描述

Object #6:
detect order:
 [[520 255]
 [491 226]
 [520 197]
 [549 226]]
old order:
 [[491. 226.]
 [520. 197.]
 [520. 255.]
 [520. 255.]]
new order1:
 [[491. 226.]
 [520. 197.]
 [549. 226.]
 [520. 255.]]
new order2:
 [[491 226]
 [520 197]
 [549 226]
 [520 255]]

注意到目标 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

和、差里面出现了相等的元素,old order 方法可能会出现错误顺序的赋值导致错误,新的方法则不会

3、完整代码

#!/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
from scipy.spatial import distance as dist

def order_points_old(pts):
    # 初始化将被排序的坐标列表,这样,列表中的第一个条目是左上,第二个条目是右上,第三个条目是右下,第四个条目是左下
    rect = np.zeros((4, 2), dtype="float32")
    """
    array([[ 90, 236],
       [ 42, 236],
       [ 42, 190],
       [ 90, 190]])
    """
    # 左上点的总和最小,而右下点的总和最大
    s = pts.sum(axis=1)  # x+y array([326, 278, 232, 280])
    rect[0] = pts[np.argmin(s)]  # 2, array([ 42., 190.], dtype=float32)
    rect[2] = pts[np.argmax(s)]  # 0, array([ 90., 236.], dtype=float32)
    # 现在,计算点之间的差值,右上角的差值最小,而左下角的差值最大
    diff = np.diff(pts, axis=1)  # array([[146], [194], [148], [100]])
    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("1.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imwrite("gray.jpg", gray)
gray = cv2.GaussianBlur(gray, (7, 7), 0)
cv2.imwrite("GaussianBlur.jpg", gray)
# 执行边缘检测,然后执行膨胀+腐蚀以缩小对象边缘之间的间隙
edged = cv2.Canny(gray, 50, 100)
cv2.imwrite("Canny.jpg", edged)
edged = cv2.dilate(edged, None, iterations=1)
cv2.imwrite("dilate.jpg", edged)
edged = cv2.erode(edged, None, iterations=1)
cv2.imwrite("erode.jpg", edged)
# 在边缘图中找到轮廓
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("detect order:\n", box)

    # 对轮廓中的点进行排序,使它们以左上、右上、右下和左下的顺序出现,然后绘制旋转边界框的轮廓
    rect = order_points_old(box)
    print("old order:\n", rect)

    rect_new = order_points(box)
    print("new order1:\n", rect_new)
    # 检查是否应使用新方法对坐标进行排序
    if args["new"] > 0:
        rect = perspective.order_points(box)
    # 显示重新排序的坐标
    print("new order2:\n", 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.imwrite(f"result_{i+1}.jpg", image)
    cv2.imshow("Image", image)
    cv2.waitKey(0)

参考学习来自:OpenCV基础(26)使用 Python 和 OpenCV 顺时针排序坐标

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1934026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

13. C++继承 | 详解 | 虚拟继承及底层实现

目录 1.定义 1.1继承的概念 1.2 继承的定义 2. 对象赋值转换 3. 继承中的作用域 a. 隐藏/重定义 (Hiding/Redefinition) b. 重载 (Overloading) c. 重写/覆盖 (Overriding) d. 编译报错 (Compilation Error) 4. 派生类的默认成员函数 构造 拷贝构造 运算符重载 析…

处理uniapp刷新后,点击返回按钮跳转到登录页的问题

在使用uniapp的原生返回的按钮时&#xff0c;如果没有刷新会正常返回到对应的页面&#xff0c;如果刷新后会在当前页反复横跳&#xff0c;或者跳转到登录页。那个时候我第一个想法时&#xff1a;使用浏览器的history.back()方法。因为浏览器刷新后还是可以通过右上角的返回按钮…

Vscode+Pyside6开发之虚拟环境配置以及错误解决

Pyside开发之虚拟环境配置以及错误解决 开发环境一、项目创建以及虚拟环境设置1.创建项目2. 新建py文件,新建虚拟环境3.激活虚拟环境二、项目位置改变pip命令报错1.删除原来的虚拟环境2. 产生包列表文件requirements.txt3.重新创建虚拟环境4.重新安装包文件5.其他错误开发环境…

操作系统 输入输出系统

输入输出系统 I/O系统的功能、模型和接口 功能 隐藏物理设备的细节&#xff1a;仅向上层进程提供少量的、抽象的读/写命令 与设备无关性&#xff1a;用户不仅可以使用抽象的I/O命令&#xff0c;还可使用抽象的逻辑设备名来使用设备 提高处理机和I/O设备的利用率&#xff1a;…

IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)

目录 1. 前言 2. 创建SpringBoot项目 3. Maven依赖引入 4. 创建定时任务 5. 问题&#xff1a;执行时间延迟和单线程执行 5.1 问题原因 5.2 解决方式 1. 前言 在现代化应用中&#xff0c;定时任务&#xff08;Scheduled Tasks&#xff09;是不可或缺的一部分&#xff…

pytorch学习(五)tensorboard使用

1. 创建环境 首先创建一个环境: conda create -n pytorch conda activate pytorch 然后安装tensorboard pip install tensorboard 安装opencv pip install opencv-python 2. 简单的案例 标量和图像的显示&#xff1a; 2.1标量实现的方法是add_scalar,第一个参数是给显…

Stable Diffusion:质量高画风清新细节丰富的二次元大模型二次元插图

今天和大家分享一个基于Pony模型训练的二次元模型&#xff1a;二次元插图。关于该模型有4个不同的分支版本。 1.5版本&#xff1a;loar模型&#xff0c;推荐底模型niji-动漫二次元4.5。 xl版本&#xff1a;SDXL模型版本 mix版本&#xff1a;光影减弱&#xff0c;减少SDXL版本…

21天学通C++:第十三、十四章节

第十三章&#xff1a;类型转换运算符 类型转换是一种机制&#xff0c;让程序员能够暂时或永久性改变编译器对对象的解释。注意&#xff0c;这并不意味着程序员改变了对象本身&#xff0c;而只是改变了对对象的解释。可改变对象解释方式的运算符称为类型转换运算符。 为何需要…

数据库端口LookUp功能:从数据库中获取并添加数据到XML

本文将为大家介绍如何使用知行之桥EDI系统数据库端口的Lookup功能&#xff0c;从数据库中获取数据&#xff0c;并添加进输入的XML中。 使用场景&#xff1a;期待以输入xml中的值为判断条件从数据库中获取数据&#xff0c;并添加进输入xml中。 例如&#xff1a;接收到包含采购…

Linux 06-01:简易shell编写

考虑一下这个与shell典型的互动&#xff1a;ls、ps 用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表&#xff0c;它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程&#xff0c;然后在那个进程中运…

Three.js 实战【2】—— 船模型海上场景渲染

停止了好久没有更新three这方面的文章了&#xff0c;从上两年还是vue2&#xff0c;一下子都换到vue3了&#xff0c;下面这些three都是基于vue3来进行开发的哈&#xff0c;先看一下这篇文章实现的效果哈。其中关于模型什么的资源都放在Git上了 初始化场景 安装three就直接通过n…

Java——集合(Queue)

1.Queue 接口的常用功能 除了基本的 Collection 操作外&#xff0c;队列还提供其他的插入、提取和检查操作。每个方法都存在 两种形式&#xff1a;一种抛出异常&#xff08;操作失败时&#xff09;&#xff0c;另一种返回一个特殊值&#xff08; null 或 false &#xff…

RPA鼠标按键使用技巧

RPA鼠标按键使用技巧 Mouse.MouseAuto.Action命令出错&#xff0c;调用的目标发生了异常&#xff0c;Exception in Mouse.Action元素不可用怎么解决 出现问题 1.想要实现的效果鼠标移动到录屏工具的小球上2.点击开始按钮开始录屏现象&#xff0c;鼠标没有移动痕迹&#xff0c…

爬虫案例(读书网)(下)

上篇链接&#xff1a; CSDN-读书网https://mp.csdn.net/mp_blog/creation/editor/139306808 可以看见基本的全部信息&#xff1a;如(author、bookname、link.....) 写下代码如下&#xff1a; import requests from bs4 import BeautifulSoup from lxml import etreeheaders{…

SSD实现

一、模型 此模型主要由基础网络组成&#xff0c;其后是几个多尺度特征块。基本网络用于从输入图像中提取特征&#xff0c;因此它可以使用深度卷积神经网络。 单发多框检测选用了在分类层之前截断的VGG&#xff0c;现在也常用ResNet替代&#xff1b;可以设计基础网络&#xff0c…

【LeetCode】162. 寻找峰值

1. 题目 2. 分析 这道题的难点有二&#xff1a;第一&#xff0c;知道用二分法求解&#xff1b;第二&#xff0c;二分判断的标准是什么&#xff1f;传统的题目的二分标注都是跟某个固定的值做比较&#xff0c;但是此题不然。此题的比较对象是相邻的元素。 不要硬凭自己的脑子…

spring是如何解决循环依赖的,为什么不是两级

1. Spring使用三级缓存来解决循环依赖问题 Spring使用三级缓存来解决循环依赖问题&#xff0c;‌而不是使用两级缓存。‌ 在Spring框架中&#xff0c;‌解决循环依赖的关键在于正确地管理Bean的生命周期和依赖关系。‌循环依赖指的是两个或多个Bean相互依赖&#xff0c;‌如果…

FastApi地理坐标数据存取实践

说明&#xff1a; 应用Pydantic Model 验证/出入 数据&#xff0c; SqlAlchemy Model数据实体&#xff0c;Fastapi提供API机制支持。数据表的坐标字段采用Mysql的GEOMETRY类型目前还没成功使用Pydantic的Coordinate类型&#xff0c;待后续改良 要点&#xff1a; 输出的结果是…

多级表头固定列问题

父级的width&#xff0c;是需要固定的列的width的总和 参考&#xff1a; el-table 多级表头下对应列的固定

Android Studio 不再支持windows 7

Android Studio 一打开就报错&#xff1a; 无法找到入口 无法定位程序输入点 CreateAppContainerProfle 于动态链接库USERENV.dII 上。 截图如下&#xff1a; 经调查&#xff0c;是因为系统版本不兼容。 我目前的电脑环境&#xff1a;windows 7,但是现在的Android Studio要…