【人工智能-14】OpenCV梯度处理、边缘检测、绘制轮廓、凸包检测、轮廓特征查找
摘要 本文介绍了OpenCV在图像梯度处理和边缘检测中的应用。首先阐述了图像梯度的概念,即通过卷积核计算像素差异来提取垂直和水平边缘(Sobel算子)。其次介绍了Laplacian算子,通过二阶差分检测边缘。最后详细讲解了Canny边缘检测的全流程,包括高斯模糊降噪、梯度计算、非极大值抑制和双阈值处理。文中提供了Python代码示例,展示了如何使用OpenCV的filter2D、Sobel、Lap
上一期【人工智能-13】OpenCV插值方法,边缘填充,图像矫正,图像掩膜,图像融合与噪点消除
文章目录
一、梯度处理
1.图像梯度
如果把图像看成一片山脉,那么梯度就是一个巨大的高低差。从图像上看,就是这一块的像素值和另一块的像素值的差距很大,然后按照一定规则相减后,求极值(0-255,0<的变为0,>255的变为255)这就是梯度。在知道了梯度后,我们就可以确定图像的边缘,从而更方便的提取出特定区域。
2.垂直边缘提取
想要获取梯度就要求差分,应用卷积来实现的,卷积的关键就是卷积核,我们来考察下面这个卷积核:
k 1 = [ − 1 0 1 − 2 0 2 − 1 0 1 ] k1=\left[\begin{array}{c c c}{{-1}}&{{0}}&{{1}}\\ {{-2}}&{{0}}&{{2}}\\ {{-1}}&{{0}}&{{1}}\end{array}\right] k1=
−1−2−1000121
将这个卷积核放在对应的像素上:
[[247,0,196,234],
[241,0,199,245],
[190,0,35,197]]
我们算第二行的0和199两个的值。
196x1-1x247+2x199-2x214+35x1-1x190=-290 求极值就是0
234x1-1x0+245x2-2x0+197x1-1x0=930 求极值就是 255
我们可以看到这两个像素的差距很大。这里大概就是边缘。
此处是提取垂直边缘,水平边缘将卷积核转置后上下相加就行。
当前列的左右两侧的元素进行差分,由于边缘的值明显小于(或大于)周边像素,所以边缘的差分结果会明显不同,这样就提取出了垂直边缘。
把上面那个矩阵转置一下,就是提取水平边缘。这种差分操作就称为图像的梯度计算:
k 2 = [ − 1 − 2 − 1 0 0 0 1 2 1 ] k2=\left[\begin{array}{c c c}{{-1}}&{{-2}}&{{-1}}\\ {{0}}&{{0}}&{{0}}\\ {{1}}&{{2}}&{{1}}\end{array}\right] k2=
−101−202−101
cv2.filter2D(src, ddepth, kernel)
filter2D函数是用于对图像进行二维卷积(滤波)操作。它允许自定义卷积核(kernel)来实现各种图像处理效果,如平滑、锐化、边缘检测等
-
src
: 输入图像,一般为numpy
数组,灰度化的图像。 -
ddepth
: 输出图像的深度,可以是负值(表示与原图相同)、正值或其他特定值(常用-1 表示输出与输入具有相同的深度)。 -
kernel
: 卷积核,一个二维数组(通常为奇数大小的方形矩阵),用于计算每个像素周围邻域的加权和。 -
先用数组模拟一下
import cv2 as cv
import numpy as np
# 模拟一张图像,灰度图
img = np.array(
[[100,0,109,110,98,20,19,18,21,22],
[109,0,98,108,102,20,21,19,20,21],
[109,0,105,108,98,20,22,19,19,18],
[109,0,102,108,102,20,23,19,20,22],
[109,0,105,108,98,20,22,19,20,18],
[100,0,108,110,98,20,19,18,21,22],
[109,0,98,108,102,20,22,19,20,21],
[109,0,108,108,98,20,22,19,19,18],
],dtype=np.uint8)
# 定义卷积核,
kernel=np.array([[-1,0,1],
[-2,0,2],
[-1,0,1]],dtype=np.float32)
# 二维卷积操作 cv.filter2D(src,ddepth,kernel) 此处为垂直卷积
# src 为输入图像 ddepth 为输出图像的深度(-1表示和原图相同) kernel 为卷积核
img2=cv.filter2D(img,-1,kernel)
# 打印卷积后的图
print(img2)
[[ 0 0 255 0 0 0 0 2 12 0]
[ 0 0 255 0 0 0 0 0 7 0]
[ 0 0 255 0 0 0 0 0 3 0]
[ 0 0 255 0 0 0 0 0 4 0]
[ 0 0 255 0 0 0 0 0 5 0]
[ 0 1 255 0 0 0 0 0 9 0]
[ 0 0 255 0 0 0 0 0 7 0]
[ 0 0 255 0 0 0 0 0 2 0]]
3.Sobel算子
上面的两个卷积核都叫做Sobel算子,只是方向不同,它先在垂直方向计算梯度:
G x = k 1 × s r c G_{x}=k_{1}\times s r c Gx=k1×src
再在水平方向计算梯度:
G y = k 2 × s r c G_{y}=k_{2}\times s r c Gy=k2×src
最后求出总梯度:
G = G x 2 + G y 2 G={\sqrt{G x^{2}+G y^{2}}} G=Gx2+Gy2
sobel_image = cv2.Sobel(src, ddepth, dx, dy, ksize)
src:这是输入图像,通常应该是一个灰度图像(单通道图像),因为 Sobel 算子是基于像素亮度梯度计算的。在彩色图像的情况下,通常需要先将其转换为灰度图像。
ddepth:这个参数代表输出图像的深度,即输出图像的数据类型。在 OpenCV 中,-1 表示输出图像的深度与输入图像相同。
dx,dy:当组合为dx=1,dy=0时求x方向的一阶导数,在这里,设置为1意味着我们想要计算图像在水平方向(x轴)的梯度。当组合为 dx=0,dy=1时求y方向的一阶导数(如果同时为1,通常得不到想要的结果,想两个方向都处理的比较好 学习使用后面的算子)
ksize:Sobel算子的大小,可选择3、5、7,默认为3。
# 读取图片
shu = cv.imread("../images/shudu.png",cv.IMREAD_GRAYSCALE)
# sobel算子 cv.Sobel(src,ddepth,dx,dy,ksize[,scale[,delta[,borderType]]])
# dx=1 dy=0表示水平方向上差分,提取垂直边缘
sobelx = cv.Sobel(shu,-1,1,0,ksize=3)
cv.imshow("shudu",shu)
cv.imshow("sobelx",sobelx)
# dx=0 dy=1表示垂直方向上差分,提取水平边缘
sobely = cv.Sobel(shu,-1,0,1,ksize=3)
cv.imshow("sobely",sobely)
# 不推荐使用sobel算子同时提取水平和垂直边缘,效果很差
cv.waitKey(0)
cv.destroyAllWindows()
4.Laplacian算子
用一阶导数求极值,在这些极值的地方,二阶导数为0,所以也可以通过求二阶导计算梯度:
d s t = ∂ 2 f ∂ x 2 + ∂ 2 f ∂ y 2 d s t={\frac{\partial^{2}f}{\partial x^{2}}}+{\frac{\partial^{2}f}{\partial y^{2}}} dst=∂x2∂2f+∂y2∂2f
一维的一阶和二阶差分公式分别为:
∂ f ∂ x = f ( x + 1 ) − f ( x ) {\frac{\partial f}{\partial x}}=f(x+1)-f(x) ∂x∂f=f(x+1)−f(x)
∂ 2 f ∂ x 2 = f ( x + 1 ) + f ( x − 1 ) − 2 f ( x ) {\frac{\partial^{2}f}{\partial x^{2}}}=f(x+1)+f(x-1)-2f(x) ∂x2∂2f=f(x+1)+f(x−1)−2f(x)
提取前面的系数,那么一维的Laplacian滤波核是:
k = [ 1 − 2 1 ] k=[1~~-2~~~1] k=[1 −2 1]
而对于二维函数f(x,y),两个方向的二阶差分分别是:
∂ 2 f ∂ x 2 = f ( x + 1 , y ) + f ( x − 1 , y ) − 2 f ( x , y ) {\frac{\partial^{2}f}{\partial x^{2}}}=f(x+1,y)+f(x-1,y)-2f(x,y) ∂x2∂2f=f(x+1,y)+f(x−1,y)−2f(x,y)
∂ 2 f ∂ y 2 = f ( x , y + 1 ) + f ( x , y − 1 ) − 2 f ( x , y ) {\frac{\partial^{2}f}{\partial y^{2}}}=f(x,y+1)+f(x,y-1)-2f(x,y) ∂y2∂2f=f(x,y+1)+f(x,y−1)−2f(x,y)
合在一起就是:
V 2 f ( x , y ) = f ( x + 1 , y ) + f ( x − 1 , y ) + f ( x , y + 1 ) + f ( x , y − 1 ) − 4 f ( x , y ) V^{2}f(x,y)=f(x+1,y)+f(x-1,y)+f(x,y+1)+f(x,y-1)-4f(x,y) V2f(x,y)=f(x+1,y)+f(x−1,y)+f(x,y+1)+f(x,y−1)−4f(x,y)
同样提取前面的系数,那么二维的Laplacian滤波核就是:
k = [ 0 1 0 1 − 4 1 0 1 0 ] k=\left[\begin{array}{c c c}{0}&{1}&{0}\\ {1}&{-4}&{1}\\ {0}&{1}&{0}\end{array}\right] k=
0101−41010
cv2.Laplacian(src, ddepth)
src:这是输入图像
ddepth:这个参数代表输出图像的深度,即输出图像的数据类型。在 OpenCV 中,-1 表示输出图像的深度与输入图像相同。
import cv2 as cv
import numpy as np
# 读取图片,读取为灰度图
shu = cv.imread("../images/shudu.png",cv.IMREAD_GRAYSCALE)
# Laplacian算子 cv.Laplacian(src,ddepth,ksize[,scale[,delta[,borderType]]])
laplacian = cv.Laplacian(shu,-1,ksize=3)
cv.imshow("shudu",shu)
cv.imshow("laplacian",laplacian)
cv.waitKey(0)
cv.destroyAllWindows()
二、边缘检测
这是OpenCV提供的一整套的方案,以下是cv2.Canny()封装的处理过程
1. 高斯模糊(降噪)
- 目的:消除图像噪声(噪声会导致伪边缘),边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理。这里使用的是一个5*5的高斯核对图像进行消除噪声。
- 操作:
blurred = cv2.GaussianBlur(img, (5,5), sigmaX=1.4)
-
原理:使用高斯核卷积图像,平滑噪声同时保留边缘结构
-
参数:核大小(通常3×3或5×5),σ控制平滑程度
2. 计算梯度强度和方向
-
目的:找出像素值变化剧烈的区域
-
操作:
sobel_x = cv2.Sobel(blurred, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(blurred, cv2.CV_64F, 0, 1, ksize=3)
-
输出:
- 梯度幅度:
mag = np.sqrt(sobel_x**2 + sobel_y**2)
- 梯度方向:
dir = np.arctan2(sobel_y, sobel_x)
- 梯度幅度:
-
方向量化:将连续角度离散化为4个方向(0°, 45°, 90°, 135°)
-
梯度方向和边缘方向的关系:梯度方向是像素之间差值的一个趋势,例地平线,地面和天空灰度化后的差距,从下往上是由暗变亮,这个梯度方向是90°。地面和天空的交界处,即边缘是一条大致水平的线,所以边缘方向是0°。由此可见,梯度方向和边缘方向是相互垂直的。
3. 非极大值抑制(NMS)
-
目的:细化边缘(仅保留局部最大值,消除粗边缘)
-
操作流程:
- 遍历每个像素
- 检查梯度方向上的相邻像素
- 如果当前像素梯度值 不是 该方向上最大值 → 抑制(设为0)
-
可视化:
原始边缘: [10, 30, 50, 30, 10] → 抑制后: [0, 0, 50, 0, 0]
^ 保留最大值
- 效果:边缘宽度从多像素变为单像素
4. 双阈值检测
-
目的:区分强边缘、弱边缘和非边缘
-
阈值设置:
threshold_high
:强边缘阈值(如150)threshold_low
:弱边缘阈值(如50)
-
分类规则:
像素值范围 分类 颜色标记 mag ≥ threshold_high 强边缘 白色(255) threshold_low ≤ mag < threshold_high 弱边缘 灰色(128) mag < threshold_low 非边缘 黑色(0)
5. 边缘连接(滞后阈值)
-
目的:连接断开的边缘(处理弱边缘的去留)
-
算法:
- 标记所有强边缘像素为最终边缘
- 遍历每个弱边缘像素:
- 如果与任何强边缘像素8邻域连通 → 提升为强边缘
- 否则 → 抑制为0
-
连接示例:
强边缘: ★ 弱边缘: ○ 非边缘: ·
处理前: · · ★ · ○ ○ ★ ·
处理后: · · ★—★—★—★ · (连接成连续边缘)
edges = cv2.Canny(image, threshold1, threshold2, apertureSize=3)
threshold1:低阈值(建议取图像梯度幅度中值的0.4倍)
threshold2:高阈值(建议是低阈值的2-3倍)
apertureSize:Sobel核大小(通常3)
import cv2 as cv
import numpy as np
# 读取图片,读取为灰度图
shu = cv.imread("../images/shudu.png",cv.IMREAD_GRAYSCALE)
# 二值化处理
_,thresh = cv.threshold(shu,0,255,cv.THRESH_OTSU)
# 使用canny边缘检测 cv.Canny(img,threshold1,threshold2)
# threshold1: 低阈值 threshold2: 高阈值 一般是2:1或3:1
# 低于低阈值的像素点将不会被检测为边缘 高于高阈值的像素点会被检测为边缘
# 高于低阈值且和高于高阈值的像素点相连接的像素点会被检测为边缘
edges = cv.Canny(thresh,30,70)
cv.imshow("edges",edges)
cv.waitKey(0)
cv.destroyAllWindows()
三、绘制轮廓
1.什么是轮廓
轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。相对于边缘,轮廓是连续的,而边缘不一定连续
轮廓的作用:
- 形状分析
- 目标识别
- 图像分割
2.寻找轮廓
在OpenCV中使用cv2.findContours()来进行寻找轮廓,寻找轮廓的图像首先要进行二值化,把目标区域显示为白色,其余的区域为黑色。之后,对图像中的像素进行遍历,当一个白色像素相邻(上下左右及两条对角线)位置有黑色像素存在或者一个黑色像素相邻(上下左右及两条对角线)位置有白色像素存在时,那么该像素点就会被认定为边界像素点,轮廓就是有无数个这样的边界点组成的。
方法介绍:
contours,hierarchy = cv2.findContours(image,mode,method)
-
返回值:[ 轮廓点坐标 ] 和 [ 层级关系 ]。
-
contours:表示获取到的轮廓点的列表。检测到有多少个轮廓,该列表就有多少子列表,每一个子列表都代表了一个轮廓中所有点的坐标。
-
hierarchy:表示轮廓之间的关系。对于第i条轮廓, h i e r a r c h y [ i ] [ 0 ] hierarchy[i][0] hierarchy[i][0], h i e r a r c h y [ i ] [ 1 ] hierarchy[i][1] hierarchy[i][1] , h i e r a r c h y [ i ] [ 2 ] hierarchy[i][2] hierarchy[i][2] , h i e r a r c h y [ i ] [ 3 ] hierarchy[i][3] hierarchy[i][3]分别表示其后一条轮廓、前一条轮廓、(同层次的第一个)子轮廓、父轮廓的索引(如果没有相应的轮廓,则对应位置为-1)。该参数的使用情况会比较少。
-
image:表示输入的二值化图像。
-
mode:表示轮廓的检索模式。
mode参数共有四个选项分别为:RETR_LIST,RETR_EXTERNAL,RETR_CCOMP,RETR_TREE。- RETR_EXTERNAL:表示只查找最外层的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
- RETR_LIST:表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
- RETR_CCOMP:表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照成对的方式显示。层级 0:所有外部轮廓(最外层的边界)。层级 1:所有内部轮廓(孔洞或嵌套的区域)。
- RETR_TREE:表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照树的方式显示,其中最外层的轮廓作为树根,其子轮廓是一个个的树枝。
-
method:轮廓的表示方法。
轮廓存储方法。轮廓近似方法。决定如何简化轮廓点的数量。就是找到轮廓后怎么去存储这些点。
method参数有三个选项:CHAIN_APPROX_NONE、CHAIN_APPROX_SIMPLE、CHAIN_APPROX_TC89_L1。-
CHAIN_APPROX_NONE
表示将所有的轮廓点都进行存储 -
CHAIN_APPROX_SIMPLE
表示只存储有用的点,比如直线只存储起点和终点,四边形只存储四个顶点,默认使用这个方法;
对于mode和method这两个参数来说,一般使用RETR_EXTERNAL和CHAIN_APPROX_SIMPLE这两个选项。
-
3.轮廓绘制
轮廓找出来后,其实返回的是一个轮廓点坐标的列表,因此我们需要根据这些坐标将轮廓画出来,因此就用到了绘制轮廓的方法。
cv2.drawContours(image, contours, contourIdx, color, thickness)
image
:原始图像,一般为单通道或三通道的 numpy 数组。contours
:包含多个轮廓的列表,每个轮廓本身也是一个由点坐标构成的二维数组(numpy数组)。contourIdx
:要绘制的轮廓索引。如果设为-1
,则会绘制所有轮廓。根据索引找到轮廓点绘制出来。默认是-1。color
:绘制轮廓的颜色,可以是 BGR 值或者是灰度值(对于灰度图像)。thickness
:轮廓线的宽度,如果是正数,则画实线;如果是负数,则填充轮廓内的区域。
import cv2 as cv
import numpy as np
# 读取图片,读取为灰度图
num = cv.imread("../images/num.png")
# 要将图片转为灰度图
num_gray = cv.cvtColor(num,cv.COLOR_BGR2GRAY)
# 二值化,将目标对象设置为白色,此处使用反阈值法(此处根据具体情况选择)
ret,thresh = cv.threshold(num_gray,127,255,cv.THRESH_BINARY_INV)
# 查找轮廓 cv.findContours(img,mode,method)
contours,hierarcy = cv.findContours(thresh,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
print(contours)
# 绘制轮廓 cv.drawContours(img,contours,index,color,thickness)
# img:绘制的图片(可以为彩图) contours:轮廓 index:绘制的轮廓索引(默认-1 表示绘制所有轮廓) color:轮廓颜色(BGR或灰度值) thickness:线条宽度
cv.drawContours(num,contours,-1,(0,255,0),2)
cv.imshow("num",num)
cv.waitKey(0)
cv.destroyAllWindows()
四、凸包检测
在进行凸包特征检测之前,首先要了解什么是凸包。通俗的讲,凸包其实就是将一张图片中物体的最外层的点连接起来构成的凸多边形,它能包含物体中所有的内容。
一般来说,凸包都是伴随着某类点集存在的,也被称为某个点集的凸包。
对于一个点集来说,如果该点集存在凸包,那么这个点集里面的所有点要么在凸包上,要么在凸包内。
凸包检测常用在物体识别、手势识别、边界检测等领域。
1.穷举法
将集中的点进行两两配对,并进行连线,对于每条直线,检查其余所有的点是否处于该直线的同一侧,如果是,那么说明构成该直线的两个点就是凸包点,其余的线依次进行计算,从而获取所有的凸包点。
缺点:时间复杂度高,不断使用for循环,耗时。
2.QuickHull
将所有点放在二维坐标系中,找到横坐标最小和最大的两个点 P 1 P_1 P1和 P 2 P_2 P2并连线。此时整个点集被分为两部分,直线上为上包,直线下为下包。
以上包为例,找到上包中的点距离该直线最远的点 P 3 P_3 P3,连线并寻找直线 P 1 P 3 P1P3 P1P3左侧的点和 P 2 P 3 P2P3 P2P3右侧的点,然后重复本步骤,直到找不到为止。对下包也是这样操作。
际上,对于未经处理的图像,我们无法直接获取点的坐标。特别是对于彩色图像,我们需要将其转换为二值图像,并使用轮廓检测技术来获取轮廓边界的点的坐标。然后,我们才能进行上述寻找凸包点的过程。因此,在处理图像时,我们需要将彩色图像转换为二值图像,并通过轮廓检测技术来获取轮廓边界的点的坐标,然后才能进行凸包点的寻找过程。方法介绍:
寻找凸包点
cv2.convexHull(points)
points
:输入参数,图像的轮廓
绘制
cv2.polylines(image, pts, isClosed, color, thickness=1)
image
:要绘制线条的目标图像,它应该是一个OpenCV格式的二维图像数组(如numpy数组)。pts
:一个二维 numpy 数组,每个元素是一维数组,代表一个多边形的一系列顶点坐标。isClosed
:布尔值,表示是否闭合多边形,如果为 True,会在最后一个顶点和第一个顶点间自动添加一条线段,形成封闭的多边形。color
:线条颜色,可以是一个三元组或四元组,分别对应BGR或BGRA通道的颜色值,或者是灰度图像的一个整数值。thickness
(可选):线条宽度,默认值为1。
import cv2 as cv
import numpy as np
# 读取图片
tu = cv.imread("../images/tu.png")
# 要将图片转为灰度图
gray = cv.cvtColor(tu,cv.COLOR_BGR2GRAY)
# 二值化 cv.threshold(img,thresh,maxval,type)
_,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
# 查找轮廓 cv.findContours(img,mode,method)
contours,hierarcy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
# 获取凸包点
hull = cv.convexHull(contours[0])
# 绘制凸包 cv.polylines(img,pts,isClosed,color,thickness) 或使用 cv.drawContours(参数一样,isClosed的位置改成索引)
# img 绘制的图片 pts 绘制的点(一个二维numpy数组)
# isClosed 是否闭合(如果为 True,会在最后一个顶点和第一个顶点间自动添加一条线段,形成封闭的多边形)
# color 颜色 thickness 线条宽度(默认为1)
cv.polylines(tu,[hull],True,(0,0,255),2)
cv.imshow('tu',tu)
cv.drawContours(tu,[hull],-1,(0,0,255),2)
cv.imshow('tu2',tu)
cv.waitKey(0)
cv.destroyAllWindows()
五、轮廓特征查找
图像轮廓特征查找其实就是他的外接轮廓。比如外接矩阵,最小外接矩阵,最小外接圆等
在图像分割、形状分析、物体检测与识别等方面有着应用。
当寻找轮廓自然也是要图像灰度再二值化的。
1. 外接矩阵
import cv2 as cv
import numpy as np
# 读取图片
num = cv.imread("../images/num.png")
# 要将图片转为灰度图
gray = cv.cvtColor(num,cv.COLOR_BGR2GRAY)
# 二值化
_,binary = cv.threshold(gray,200,255,cv.THRESH_BINARY_INV)
# 查找轮廓
conts,th = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
# 获取外接矩阵点,绘制外接矩阵(要在循环中绘制,不然就只会绘制最后一个的矩形)
for cont in conts:
# cv.boundingRect(cont)将获取轮廓中最小的x和y,以及宽和高。
# 之后我们将x+w和y+h即可获取x和y的最大值
x,y,w,h = cv.boundingRect(cont)
# 使用cv.rectangle(img,(x,y),(x+w,y+h),color,thickness)进行绘制
cv.rectangle(num,(x,y),(x+w,y+h),(0,0,255),2)
cv.imshow("num",num)
2.最小外接矩阵
需要使用到的API说明:
- contours, hierarchy = cv2.findContours(image_np_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours为二值图像上查找所有的外部轮廓
- rect = cv2.minAreaRect(cnt)
传入的cnt参数为contours中的轮廓,可以遍历contours中的所有轮廓,然后计算出每个轮廓的小面积外接矩形
- rect 是计算轮廓最小面积外接矩形:rect 结构通常包含中心点坐标
(x, y)
、宽度width
、高度height
和旋转角度angle
cv2.boxPoints(rect).astype(int)
cv2.boxPoints(rect)返回 是一个形状为 4行2列的数组,每一行代表一个点的坐标(x, y),顺序按照逆时针或顺时针方向排列
将最小外接矩形转换为边界框的四个角点,并转换为整数坐标
cv2.drawContours(image, contours, contourIdx, color, thickness)
- image:原图像,一般为 numpy 数组,通常为灰度或彩色图像。
- contours:一个包含多个轮廓的列表,可以用上一个api得到的 [box]
- contourIdx:要绘制的轮廓索引。如果设置为
-1
,则绘制所有轮廓。 - color:轮廓的颜色,可以是 BGR 颜色格式的三元组,例如
(0, 0, 255)
表示红色。 - thickness:轮廓线的粗细,如果是正数,则绘制实线;如果是 0,则绘制轮廓点;如果是负数,则填充轮廓内部区域。
# 最小外接矩阵
for cont in conts:
# cv.minAreaRect(cont) 返回矩形中心点,宽高,旋转角度
rect = cv.minAreaRect(cont)
# cv.boxPoints(rect) 获取最小外接矩阵的4个顶点 转换为整数
box = cv.boxPoints(rect).astype(int)
# cv.drawContours(img,contours,index,color,thickness) 绘制最小外接矩型
cv.drawContours(num,[box],0,(0,255,0),2)
cv.imshow("num2",num)

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)