12篇--图像轮廓绘制与最小外接问题

何为轮廓?

轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。与边缘有什么区别与联系呢?

  • 相对于边缘,轮廓是连续的,边缘不一定连续,如下图所示。
  • 其实边缘主要是作为图像的特征使用,比如
    • 可以用边缘特征可以区分脸和手,
    • 而轮廓主要用来分析物体的形态,比如物体的周长和面积等。
  • 可以说边缘包括轮廓。

抓取轮廓

在OpenCV中,使用cv2.findContours()来进行寻找轮廓,其原理比较复杂,这里只进行一个简单的介绍,具体的实现原理,有头宝子们可参考下方链接:

https://zhuanlan.zhihu.com/p/107257870

寻找轮廓需要将图像做一个二值化处理,并且根据图像的不同选择不同的二值化方法来将图像中要绘制轮廓的部分置为白色,其余部分置为黑色。也就是说,我们需要对原始的图像进行灰度化、二值化的处理,令目标区域显示为白色,其他区域显示为黑色,如下图所示。

之后,对图像中的像素进行遍历,当一个白色像素相邻(上下左右及两条对角线)位置有黑色像素存在或者一个黑色像素相邻(上下左右及两条对角线)位置有白色像素存在时,那么该像素点就会被认定为边界像素点,轮廓就是有无数个这样的边界点组成的。

cv2.findContours()函数

功能:用于检测图像轮廓的函数

参数:

  • ‌image‌ (必需): 输入图像,通常是一个二值图像(即只包含黑白两种颜色的图像),其中白色部分代表要检测的对象,黑色部分代表背景。也可以是非二值图像,但通常需要先进行某种形式的预处理,如阈值分割或边缘检测。
  • ‌mode‌ (可选,某些版本中必需): 轮廓检索模式。它决定了函数如何检测轮廓。常见的模式有:
    • cv2.RETR_EXTERNAL:只检索最外层轮廓。
    • cv2.RETR_LIST:检索所有轮廓,但不创建任何父子关系。
    • cv2.RETR_CCOMP:检索所有轮廓,并将它们组织为两层结构,其中顶层是连通域的外边界,底层是孔的内边界。
    • cv2.RETR_TREE:检索所有轮廓,并重建完整的层次结构。
  • ‌method‌ (可选,某些版本中必需): 轮廓近似方法。它决定了轮廓的近似程度。常见的方法有:
    • cv2.CHAIN_APPROX_NONE:存储所有的轮廓点,不进行任何近似。
    • cv2.CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,只保留它们的终点。
    • cv2.CHAIN_APPROX_TC89_L1、cv2.CHAIN_APPROX_TC89_KCOS:应用 Teh-Chin 链式近似算法的一种变体。

对于mode和method这两个参数来说,一般使用RETR_EXTERNAL和  CHAIN_APPROX_SIMPLE这两个选项。

返回值:

  • ‌contours‌ (某些版本中作为返回值): 在某些 OpenCV 版本中,这个参数是用来存储检测到的轮廓的。它是一个 Python 列表,其中每个元素都是一个轮廓,轮廓是由点组成的 NumPy 数组。
  • ‌hierarchy‌ (某些版本中作为返回值): 轮廓的层次结构信息。这是一个 NumPy 数组,包含了关于轮廓之间关系的信息(例如,哪个轮廓是另一个轮廓的父轮廓或子轮廓)。
  • ‌offset‌ (可选,默认为 (0, 0)): 轮廓点的偏移量。这个参数允许你在原始图像坐标系的基础上对轮廓点进行平移。

绘制轮廓

轮廓找出来后,其实返回的是一个轮廓点坐标的列表,因此我们需要根据这些坐标将轮廓画出来,因此就用到了绘制轮廓的组件。

要用到两个函数

cv2.drawContours()函数

功能:用于在图像上绘制轮廓的函数

参数:

  • image‌ (必需): 这是要在其上绘制轮廓的输入图像。它应该是一个三通道图像(例如,彩色图像)或单通道图像(例如,灰度图像),但通常是三通道图像,以便可以使用不同的颜色来绘制轮廓。
  • ‌contours‌ (必需): 这是一个 Python 列表,包含要绘制的所有轮廓。每个轮廓都是一个点集,通常是由 cv2.findContours() 函数返回的 NumPy 数组。
  • ‌contourIdx‌ (可选,默认为 -1): 指定要绘制的轮廓的索引。如果为 -1,则绘制所有轮廓。否则,只绘制指定索引处的轮廓。
  • ‌color‌ (可选,默认为 (0, 255, 0)): 轮廓的颜色。它是一个三元组,表示 BGR(蓝、绿、红)颜色空间中的颜色。例如,(0, 255, 0) 表示绿色。
  • ‌thickness‌ (可选,默认为 1): 轮廓的厚度。如果为正数,则轮廓将被绘制为指定厚度的线条。如果为负数(例如 -1),则轮廓内部将被填充。
  • ‌lineType‌ (可选,默认为 cv2.LINE_8): 线条的类型。它决定了轮廓线条的平滑度。cv2.LINE_8 表示 8-连通线,cv2.LINE_4 表示 4-连通线,cv2.LINE_AA 表示抗锯齿线。
  • ‌hierarchy‌ (可选,默认为 None): 轮廓的层次结构信息。这是一个 NumPy 数组,通常由 cv2.findContours() 函数返回。它包含了关于轮廓之间关系的信息,例如哪个轮廓是另一个轮廓的父轮廓或子轮廓。在绘制所有轮廓时,这个参数通常不需要。
  • ‌maxLevel‌ (可选,默认为 INT_MAX): 用于控制绘制轮廓的层次深度。当 contourIdx 参数为 -1 时,这个参数才有效。它决定了要绘制的轮廓的最大层次深度。
  • ‌offset‌ (可选,默认为 (0, 0)): 轮廓点的偏移量。这个参数允许你在原始图像坐标系的基础上对轮廓点进行平移。

代码示例(先抓取,再绘制):

import cv2img = cv2.imread("./../day3/card.png")# 灰度化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 二值化
_, img_binary = cv2.threshold(img_gray, 127, 255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 寻找轮廓
contours, hierarchy = cv2.findContours(img_binary,cv2.RETR_LIST,   # 查询轮廓的方式cv2.CHAIN_APPROX_SIMPLE  # 保存轮廓点坐标的方式)# 绘制轮廓
img_copy = img.copy()
img_draw = cv2.drawContours(img_copy, # 要绘制轮廓的图像contours, # 轮廓的顶点坐标集 列表-1, # 轮廓列表的索引值,-1表示绘制所有轮廓(0, 0, 255),  # 颜色3   # 轮廓线条粗细)cv2.imshow('image', img)
cv2.imshow('image_draw', img_draw)
cv2.waitKey(0)

 凸包特征检测

在进行凸包特征检测之前,首先要了解什么是凸包。通俗的讲,凸包其实就是将一张图片中物体的最外层的点连接起来构成的凸多边形,它能包含物体中所有的内容。

我们以点集来举例,假如有这么一些点,其分布如下图所示:

那么经过凸包检测并绘制之后,其结果应该如下图所示:

 可以看到,原图像在经过凸包检测之后,会将最外围的几个点进行连接,剩余的点都在这些点的包围圈之内。那么凸包检测到底是怎么检测出哪些点是最外围的点呢?

我们还是以上面的点集为例,假设我们知道这些点的坐标,那么我们就可以找出处于最左边和最右边的点,接着将这两个点连接,并将点集分为上半区和下半区,我们以上半区为例,如下图所示:

 找到上面这些点离直线最远的点,其中,这条直线由于有两个点的坐标,所以其表示的直线方程是已知的,并且上面的点的坐标也是已知的,那么我们就可以根据点到直线的距离公式来进行计算哪个点到直线的距离最远,假设直线的方程为:$A x+B y+C=0$,那么点$(x{0},y{0})$到直线的距离公式为:然后我们就可以得到距离这条线最远的点,将其与左右两点连起来,并分别命名为y1和y2,如下图所示:

然后分别根据点的坐标求出y1和y2的直线方程,之后将上半区的每个点的坐标带入下面公式中: 

当d=0时,表明该点在直线上;当d>0时,表明点在直线的上方,在这里就代表该点在上图所围成的三角形的外面,也就意味着该三角形并没有完全包围住上半区的所有点,需要重新寻找凸包点;当d<0时,表明点在直线的下方,在这里就代表该点在上图所围成的三角形的里面,也就代表着这类点就不用管了。

当出现d>0时,我们需要将出现这种结果的两个计算对象:某点和y1或y2这条线标记,并在最后重新计算出现这种现象的点集到y1或y2的距离来获取新的凸包点的坐标。在本例子中,也就是如下图所示的点和y2这条直线:

由于本例子中只有这两个点在这个三角形之外,所以毫无疑问的它们就是凸包点,因此直接将它们与y2直线的两个端点相连即可。当有很多点在y2直线外时,就需要计算每个点到y2的距离,然后找到离得最远的点与y2构建三角形,并重新计算是否还有点在该三角形之外,如果没有,那么这个点就是新的凸包点,如果有,那就需要重复上面的步骤,直到所有的点都能被包围住,那么构建直线的点就是凸包点。这是上半区寻找凸包点的过程,下半区寻找凸包点的思路与此一模一样,只不过是需要筛选d

上面的过程都是基于我们知道点的坐标进行的,实际上,对于未经处理的图像,我们无法直接获取点的坐标。

特别是对于彩色图像,我们需要将其转换为二值图像,并使用轮廓检测技术来获取轮廓边界的点的坐标。然后,我们才能进行上述寻找凸包点的过程。

因此,在处理图像时,我们需要将彩色图像转换为二值图像,并通过轮廓检测技术来获取轮廓边界的点的坐标,然后才能进行凸包点的寻找过程。

示例代码

import cv2img = cv2.imread('./tubao.png')# 灰度化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 二值化
_, img_binary = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY + cv2.THRESH_OTSU)# 寻找轮廓
contours, _ = cv2.findContours(img_binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)# 查找凸包,获取凸包的点
cnt = contours[0]       # 指定第一个轮廓
# print(cnt)
hull = cv2.convexHull(cnt)
# print(hull)# 根据上面找到的凸包的点,进行连线
cv2.polylines(img, [hull], True, (0, 0, 255), 2)cv2.imshow('image', img)
cv2.waitKey(0)

图像轮廓特征查找

1. 外接矩形

形状的外接矩形有两种,如下图,绿色的叫外接矩形,表示不考虑旋转并且能包含整个轮廓的矩形。其中,外接矩形可根据获得到的轮廓坐标中最上、最下、最左、最右的点的坐标来绘制外接矩形,也就是下图中的绿色矩形。

2. 最小外接矩形

最小外接矩形就是上图所示的蓝色矩形,寻找最小外接矩形使用的算法叫做旋转卡壳法。下面简单说明一下旋转卡壳法的思路:

在上一章节中,我们了解到了凸包的概念,凸包就是一个点集的凸多边形,它是这个点集所有点的凸壳,点集中所有的点都处于凸包里,构成凸包的点我们叫做凸包点。而旋转卡壳法就是基于凸包点进行的,

旋转卡壳法有一个很重要的前提条件:对于多边形P的一个外接矩形存在一条边与原多边形的边共线。

假设某凸包图如下所示:

根据前提条件,上面的凸多边形的最小外接矩形与凸多边形的某条边是共线的。因此我们只需要以其中的一条边为起始边,然后按照逆时针方向计算每个凸包点与起始边的距离,并将距离最大的点记录下来。

 如上图所示,我们首先以a、b两点为起始边,并计算出e点离起始边最远,那么e到起始边的距离就是一个矩形的高度,因此我们只需要再找出矩形的宽度即可。对于矩形的最右边,以向量ab为基准,然后分别计算凸包点在向量ab上的投影的长度,投影最长的凸包点所在的垂直于起始边的直线就是矩形最右边所在的直线。

如上图所示,d点就是在向量ab上投影最长的凸包点,那么通过d点垂直于直线ab的直线就是矩形的右边界所在的直线。矩形的左边界的也是这么计算的,不同的是使用的向量不是ab而是ba

 如上图所示,h点垂直于ab的直线就是以ab为起始边所计算出来的矩形所在的左边界所在的直线。其中矩形的高就是e点到直线ab的距离,矩形的宽是h点在向量上ba的投影加上d点在向量ab上的投影减去ab的长度,即:

于是我们就有了以ab为起始边所构成的外接矩形的宽和高,这样就可以得到该矩形的面积。然后再以bc为起始边,并计算其外接矩形的面积。也就是说凸多边形有几个边,就要构建几次外接矩形,然后找到其中面积最小的矩形作为该凸多边形的最小外接矩形。

在OpenCV中,可以直接使用cv2.minAreaRect()来获取最小外接矩形

cv2.minAreaRect()函数

该函数只需要输入一个参数,就是凸包点的坐标,然后会返回最小外接矩形的中心点坐标、宽高以及旋转角度。通过返回的内容信息,即可绘制凸多边形的的最小外接矩形。

3. 最小外接圆

寻找最小外接圆使用的算法是Welzl算法。Welzl算法基于一个定理:对于平面上任意n个点,在其最小覆盖圆外取第n+1个点,那么第n+1个点一定在这n+1个点的最小覆盖圆的圆周上。

有了这个定理,就可以先取3个点建立一个圆(不共线的三个点即可确定一个圆,如果共线就取距离最远的两个点作为直径建立圆),然后遍历剩下的所有点,对于遍历到的点P来说:

如果该点在圆内,那么最小覆盖圆不变。

如果该点在圆外,根据上述定理,该点一定在想要求得的最小覆盖圆的圆周上,又因为三个点才能确定一个圆,所以需要枚举P点之前的点来找其余的两个点。当找到与P点组成的圆能够将所有点都包含在圆内或圆上,该圆就是这些点的最小外接圆。

在OpenCV中,可以直接使用cv2.minEnclosingCircle()来获取最小外接圆,通过该函数返回的内容信息即可绘制某点集的最小外接圆。如下图所示:

 cv2.minEnclosingCircle()函数

 该函数只需要输入一个参数,就是要绘制最小外接圆的点集的坐标,然后会返回最小外接圆的圆心坐标半径。

代码演示

import cv2
import numpy as npimg = cv2.imread("./outline.png")
# 灰度化
img_gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
# 二值化
_, img_binary = cv2.threshold(img_gray,127,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# 寻找轮廓
contours, _ = cv2.findContours(img_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
img_draw = img.copy()
cv2.drawContours(img_draw, contours, -1, (0, 0, 255), 2)# 给所有轮廓都绘制 外接
for i in contours:# 第一种:调用外接矩形函数,获取当前轮廓点的左上角的坐标(x, y) 宽w 高hx, y, w, h = cv2.boundingRect(i)# 画矩形cv2.rectangle(img_draw, [x, y], [x+w, y+h], (0, 255, 0), 2)# 第二种:调用最小面积外接矩形函数,获取包含三个元素的元组(中心点坐标、长宽、旋转角度)# ((center_x, center_y), (width, height), angle)ret = cv2.minAreaRect(i)# 调用cv2.boxPoints(ret)可以获取旋转矩阵的四个顶点box = np.int32(cv2.boxPoints(ret))# 绘制轮廓cv2.drawContours(img_draw, [box], -1, (255, 255, 0), 3)# 第三种:调用最小外接圆函数,获取圆心坐标 和 半径(x, y), radius = cv2.minEnclosingCircle(i)(x, y, radius) = np.int32((x, y, radius))# 画圆cv2.circle(img_draw, (x, y), radius, (255, 0, 255), 3)cv2.imshow('image', img)
cv2.imshow('image_draw', img_draw)
cv2.waitKey(0)

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

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

相关文章

3.8 路由选择器协议

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言1 静态路由选择2 动态路由选择3 自治系统&#xff08;AS&#xff09;4 域内路由选择5 域间路由选择7 路由器基本结构 前言 在计算机网络中&#xff0c;路由选择协议起着至…

#渗透测试#漏洞挖掘#红蓝攻防#SRC漏洞挖掘02之逻辑漏洞技巧

免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章读。 目录 逻辑漏洞技巧 1、任意用户 1.1 验证码可爆…

CVE-2024-38819:Spring 框架路径遍历 PoC 漏洞复现

操作&#xff1a; 根据CVE-2024-38819&#xff1a;Spring 框架路径遍历 PoC 漏洞搭建复现的靶场环境 拿到环境的源码使用docker搭建 cd vuln 创建容器并启动 docker build -t cve-2024-38819-poc .docker run -d -p 8080:8080 --name cve-2024-38819-poc cve-2024-38819-po…

C#调用C++接口时,如何使用结构体参数

在C#中调用C接口时&#xff0c;通常使用平台调用服务&#xff08;P/Invoke&#xff09;或通过C/CLI创建托管包装器来实现。当涉及到结构体参数时&#xff0c;处理方式取决于几个因素&#xff0c;包括结构体的复杂度、是否需要在C和C#之间传递结构体、以及性能考虑。 以下是几种…

在pycharm2024.3.1中配置anaconda3-2024-06环境

version: anaconda3-2024.06-1 pycharm-community-2024.3.1 1、安装anaconda和pycharm 最新版最详细Anaconda新手安装配置环境创建教程_anaconda配置-CSDN博客 【2024最新版】超详细Pycharm安装保姆级教程&#xff0c;Pycharm环境配置和使用指南&#xff0c;看完这一篇就够了…

5.日常算法

1. 面试题 17.14. 最小K个数 题目来源 设计一个算法&#xff0c;找出数组中最小的k个数。以任意顺序返回这k个数均可。 示例&#xff1a; 输入&#xff1a; arr [1,3,5,7,2,4,6,8], k 4 输出&#xff1a; [1,2,3,4] 方法一&#xff1a;堆 class Solution { public:vecto…

数据挖掘与机器学习(part 9) 规则挖掘Rules Mining关联规则(Association Rules) Apriori算法

基于规则的分类器&#xff1a;Classification using rule based classifier 互斥规则&#xff08;Mutually exclusive rules&#xff09;&#xff1a; 分类器包含互斥规则&#xff0c;如果这些规则彼此独立。 每条记录最多被一条规则覆盖。 穷尽规则&#xff08;Exhaustive …

pdf merge

在 Ubuntu 22.04 上&#xff0c;你可以使用以下命令行工具来合并多个 PDF 文件&#xff1a; 1. pdftk pdftk 是一个强大的 PDF 工具&#xff0c;支持合并、拆分和其他操作。安装和使用方法如下&#xff1a; sudo apt install pdftk pdftk file1.pdf file2.pdf cat output me…

Java Http 接口对接太繁琐?试试 UniHttp 框架吧

前言 从企业级项目来说&#xff0c;如果你项目里还在用传统的编程式Http客户端比如HttpClient、Okhttp去直接对接第三方Http接口&#xff0c; 那么你项目一定充斥着大量的对接逻辑和代码&#xff0c;并且针对不同的对接渠道方需要每次封装一次调用的简化&#xff0c;一旦封装不…

Laravel vs Symfony:哪个框架更适合你?

Laravel vs Symfony&#xff1a;哪个框架更适合你&#xff1f; 在当今的Web开发领域&#xff0c;PHP框架扮演着至关重要的角色。Laravel和Symfony是最受欢迎的两个PHP框架&#xff0c;各自拥有独特的特性和优势。本文将从多个方面对这两个框架进行比较&#xff0c;帮助开发者选…

Java基于SpringBoot的企业OA管理系统,附源码

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

Docker:国内加速源

阿里云docker加速云&#xff1a; sudo tee /etc/docker/daemon.json <<EOF { “registry-mirrors”: [“https://euf11uji.mirror.aliyuncs.com”] } EOFhttps://docker.mozhu.dev/ sudo tee /etc/docker/daemon.json <<EOF {"registry-mirrors": [&qu…

2.python变量

理解&#xff0c;我将提供更详细和深入的解释&#xff0c;包括一些进阶概念和实际应用的例子。我们将从变量类型开始&#xff0c;逐步深入到每种数据类型的特性、操作方法以及它们在编程中的应用场景。 文章目录 1. 变量赋值与作用域变量赋值变量作用域 2. 标准数据类型Number…

Linux shell的七大功能 --- history

1.直接输入“history” 这个命令可以显示出曾经使用过的命令&#xff08;最近时间的500条&#xff09; history 2.“history”命令也可以搭配其他命令一起使用。 例&#xff1a;history | grep "vim"&#xff0c;找出所有包含“vim”的记录&#xff1b; 也可以搭配…

【PHP】部署和发布PHP网站到IIS服务器

欢迎来到《小5讲堂》 这是《PHP》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言安装PHP稳定版本线程安全版解压使用 PHP配置配置文件扩展文件路径…

解锁SQL无限可能:如何利用HiveSQL实现0-1背包问题?

目录 1. 创建物品信息表 2. 设置背包容量(通过 Hive 变量设置) 3. 创建动态规划表并初始化 4. 动态规划填充表格过程

Parcel 插件开发指南:如何为 Parcel 创建自定义插件

前言 Parcel 是一个非常强大的打包工具&#xff0c;适用于快速构建现代 Web 应用程序。它默认提供了很多开箱即用的功能&#xff0c;但在某些场景下&#xff0c;我们可能需要自定义一些功能来满足特定需求。这个时候&#xff0c;编写自定义插件就显得尤为重要。本文将通过一个…

scala隐式转换

概念&#xff1a; 在Scala编程语言中&#xff0c;隐式转换是一种强大的功能&#xff0c;它允许程序在需要时自动转换数据类型或增强对象功能。这种转换通常是通过定义一个标记为implicit的函数来实现的&#xff0c;这个函数能够将一种类型转换为另一种类型。隐式转换的使用可以…

腾讯云COS跨域访问CORS配置

腾讯云COS跨域访问CORS配置方法如下&#xff0c;参考以下截图&#xff1a; 参考文章&#xff1a; 跨域及CORS-Nginx配置CORS

mac删除程序坞(Dock)中“无法打开的程序“

参考&#xff1a; Mac删除软件之后图标还在怎么办&#xff1f;https://blog.csdn.net/weixin_46500474/article/details/124284161Mac程序坞中软件删除出现残留“&#xff1f;”图标无法删除解决方法&#xff1a; https://blog.csdn.net/shenwenhao1990/article/details/12865…