轮廓(opencv_python学习)

文章介绍了如何使用OpenCV在二进制图像中找到轮廓,包括轮廓检测的基本步骤、轮廓近似方法、特征矩、轮廓面积、周长等属性计算,以及轮廓的凸包检测、最小闭合圈、椭圆和直线拟合等高级特性。同时,文章还探讨了轮廓的属性如长宽比、坚实度和等效直径等形状特征。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

入门

什么是轮廓

OpenCV中,找到轮廓就像从黑色背景中找到白色物体。因此请记住,要找到的对象应该是白色,背景应该是黑色。

为了获得更高的准确性,请使用二进制图像。因此,在找到轮廓之前,请应用阈值或canny边
缘检测。

cv.findCountours()函数
findContours()函数是OpenCV中用于查找图像中轮廓的函数。它的语法如下:

contours, hierarchy = cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]])

参数解释:

  • image:输入的二值图像。通常应该是灰度图像,并且要求前景为白色,背景为黑色。
  • mode:轮廓检索模式。指定要查找轮廓的类型。常用的模式包括:
  • cv2.RETR_EXTERNAL:只检测最外层的轮廓。
  • cv2.RETR_LIST:检测所有的轮廓,并将它们存储在一个列表中,不建立层次关系。
  • cv2.RETR_TREE:检测所有的轮廓,并将它们组织成一个树状结构的层次关系。

method:轮廓逼近方法。指定如何逼近轮廓的方法。常用的方法包括:

  • cv2.CHAIN_APPROX_NONE:保存所有的轮廓点。
  • cv2.CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,仅保留其端点。
  • offset(可选):轮廓偏移量。

findContours()函数返回两个值:

  • contours:一个包含所有轮廓的列表。每个轮廓都是一个numpy数组,其中包含点的坐标。
  • hierarchy:一个包含轮廓之间层次关系的数组。此参数通常在处理具有嵌套轮廓的图像时有用。

如何找到二进制图像的轮廓

在OpenCV中找到二进制图像的轮廓可以通过以下步骤完成:

  1. 准备二进制图像:确保您已经获得了一个二进制图像,其中对象是白色,背景是黑色。如果您还没有一个二进制图像,可以通过应用阈值或Canny边缘检测来将灰度图像转换为二进制图像。

  2. 导入库和读取图像:首先,导入OpenCV库并读取您的二进制图像。

  3. 查找轮廓:使用cv.findContours函数来查找图像中的轮廓。该函数需要输入三个参数:二进制图像、轮廓检索模式和轮廓逼近方法。常用的轮廓检索模式包括cv.RETR_EXTERNAL(仅检测外部轮廓)和cv.RETR_TREE(检测所有轮廓并建立轮廓之间的层次关系)。常用的轮廓逼近方法为cv.CHAIN_APPROX_SIMPLE(压缩水平、垂直和对角线段,仅保留它们的端点)。这个函数将返回一个包含轮廓的列表以及层次结构信息。

  4. 绘制轮廓:使用cv.drawContours函数来绘制轮廓。该函数需要输入绘制轮廓的图像、轮廓列表、要绘制的轮廓的索引(-1表示绘制所有轮廓)、颜色和线宽。您可以选择在原始图像上绘制轮廓,也可以创建一个空白图像并在其中绘制轮廓。

代码:

import numpy as np
import cv2 as cv

# 读取图像
img = cv.imread('pic1.jpg', 0)

# 获取图像尺寸
rows, cols = img.shape

# 自适应阈值法处理图像
max_value = 255
adaptive_method = cv.ADAPTIVE_THRESH_MEAN_C
threshold_type = cv.THRESH_BINARY
block_size = 11
constant = 2
threshold_img = cv.adaptiveThreshold(img, max_value, adaptive_method, threshold_type, block_size, constant)

# 使用十字结构元素和闭运算消除黑点
kernel = cv.getStructuringElement(cv.MORPH_CROSS, (5, 5))
erosion = cv.morphologyEx(threshold_img, cv.MORPH_CLOSE, kernel, iterations=1)

# 得到图像边界
contours, hierarchy = cv.findContours(erosion, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

# 创建空白图像用于绘制轮廓
contour_image = np.ones((rows, cols, 3), dtype=np.uint8) * 255

# 绘制轮廓
cv.drawContours(contour_image, contours, -1, (0, 255, 0), 2)

# 显示结果
cv.imwrite('pic.jpg', contour_image)
cv.waitKey(0)
cv.destroyAllWindows()

在这里插入图片描述

轮廓近似方法

如果传递cv.CHAIN_APPROX_NONE,则将存储所有边界点。但是实际上我们需要所有这些要点吗?
例如,找到了一条直线的轮廓。是否需要线上的所有点来代表该线?不,我们只需要该线的两个端点即可。这就是cv.CHAIN_APPROX_SIMPLE所做的。它删除所有冗余点并压缩轮廓,从而节省内存。
列:
在这里插入图片描述

使用cv.CHAIN_APPROX_NONE得到的图像:
在这里插入图片描述
没有观察出明显区别

轮廓特征

特征矩

特征矩可以帮助计算物体的一些特征,例如物体的质心和面积。使用OpenCV中的cv2.moments()函数可以计算出这些特征矩。下面是一些步骤示例:

# 遍历每个轮廓
for contour in contours:
    # 计算轮廓的矩
    moments = cv2.moments(contour)
    
    # 计算质心
    c_x = int(moments['m10'] / moments['m00'])
    c_y = int(moments['m01'] / moments['m00'])
    
    # 计算面积
    area = moments['m00']

轮廓面积

轮廓区域由函数cv.contourArea()或从矩 M[‘m00’] 中给出。

area = cv.contourArea(cnt)

轮廓周长

也称为弧长。可以使用cv.arcLength()函数找到它。第二个参数指定形状是闭合轮廓( True )还是
曲线。

perimeter = cv.arcLength(cnt,True)

轮廓近似

轮廓近似是指根据指定的精度将轮廓形状近似为顶点数量较少的形状。

epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)

其中,cnt是一个轮廓(表示为一个点集),cv.arcLength(cnt, True)计算了轮廓的弧长。然后,通过将该弧长乘以0.1得到epsilon的值。

接下来,使用cv.approxPolyDP()函数进行轮廓近似,传入轮廓和epsilon值作为参数。最后,近似的结果存储在approx中。

请注意,这里的参数True表示轮廓是闭合的。如果轮廓是开放的,则可以将此参数设置为False。

在这里插入图片描述

在第二张图片中,绿线显示了ε=弧长的10%时的近似曲线。第三幅图显示了ε=弧长度的1%时的情况。

轮廓凸包

凸包和轮廓近似在某些情况下可能会提供相似的结果,但它们确实具有不同的作用。

cv.convexHull()函数用于计算给定点集的凸包。凸包是一个多边形,它是能够完全包围原始点集的最小凸多边形。凸包的外观与轮廓逼近相似,但凸包是通过检查曲线是否存在凸凹缺陷来校正的。凸凹缺陷是指曲线上的凹陷或局部的堆叠。

使用cv.convexHull()函数时,需要传入一个点集作为输入,并指定一个参数来决定是否返回输出多边形的方向(顺时针或逆时针)。返回的凸包是通过连接凸边形上的点来构建的。

凸包函数语法:

hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]

参数详细信息:

  • points是我们传递到的轮廓。(一般只需此即可)
  • hull包是输出,通常我们忽略它。
  • clockwise:方向标记。如果为True,则输出凸包为顺时针方向。否则,其方向为逆时针方向。
  • returnPoints:默认情况下为True。然后返回凸包的坐标。如果为False,则返回与凸包点相对应
    的轮廓点的索引。

但是,如果要查找凸度缺陷,则需要传递 returnPoints = False 。为了理解它,我们将拍摄上面的矩形图像。首先,我发现它的轮廓为 cnt 。现在,我发现它的带有 returnPoints = True 的凸 包,得到以下值:[[[234 202]],[[51 202]],[[51 79]],[[234 79]]] ,它们是四个角 矩形的 点。现在,如果对returnPoints = False 执行相同的操作,则会得到以下结果: [[129],[67], [0],[142]] 。这些是轮廓中相应点的索引。例如,检查第一个值: cnt [129] = [[234,202]] 与第一个结果相同(对于其他结果依此类推)。

检测凸度

cv.isContourConvex()具有检查曲线是否凸出的功能。它只是返回True还是False。没什么大不了
的。

k = cv.isContourConvex(cnt)

边界矩阵

  1. 直角矩阵

它是一个矩形,不考虑物体的旋转。所以边界矩形的面积不是最小的。它是由函数
cv.boundingRect()找到的。
令 (x,y) 为矩形的左上角坐标,而 (w,h) 为矩形的宽度和高度。

x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
  1. 旋转矩阵

这里,边界矩形是用最小面积绘制的,所以它也考虑了旋转。使用的函数是cv.minAreaRect()。它返回一个Box2D结构,其中包含以下细节 -(中心(x,y),(宽度,高度),旋转角度)。但要画出这个矩形,我们需要矩形的四个角。它由函数cv.boxPoints()获得

rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img,[box],0,(0,0,255),2)

在这里插入图片描述

最小闭合圈

接下来,使用函数**cv.minEnclosingCircle(*()查找对象的圆周。它是一个以最小面积完全覆盖物体
的圆。

(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(img,center,radius,(0,255,0),2)

在这里插入图片描述

拟合一个椭圆

下一个是把一个椭圆拟合到一个物体上。它返回内接椭圆的旋转矩形。

ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)

在这里插入图片描述

拟合直线

同样,我们可以将一条直线拟合到一组点。下图包含一组白点。我们可以近似一条直线。

rows,cols = img.shape[:2]
[vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)

轮廓属性

长宽比

它是对象边界矩形的宽度与高度的比值。

x,y,w,h = cv.boundingRect(cnt)
aspect_ratio = float(w)/h

范围

范围是轮廓区域与边界矩形区域的比值。

area = cv.contourArea(cnt)
x,y,w,h = cv.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area

坚实度

坚实度是等高线面积与其凸包面积之比。

area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area)/hull_area

坚实度(Solidity)是等高线面积与其凸包面积之比的指标。它用于衡量一个闭合轮廓的形状的紧密程度或填充度。

通过计算等高线的面积和其凸包的面积,可以得到等高线相对于凸包的填充程度。坚实度越接近1,表示等高线的填充程度越高,形状越均匀、紧密;坚实度越接近0,表示等高线的填充程度越低,形状存在空洞或不规则。

例如,对于规则的几何形状如圆形或正方形,其等高线面积与凸包面积的比值非常接近1;而对于不规则的形状或包含空洞的形状,比值会较小。

在某些情况下,坚实度可以用来识别和区分不同类型的物体或形状。例如,在图像处理中,可以使用坚实度指标来检测并区分圆形、正方形和其他不规则形状。

此外,坚实度还可用于描述材料的孔隙度或细胞的膜结构的完整性。

总的来说,坚实度指标提供了关于闭合轮廓形状填充程度的信息,可以在形状分析、目标检测和分类等领域中发挥作用。

等效直径

等效直径是面积与轮廓面积相同的圆的直径。

area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)

取向

取向是物体指向的角度。以下方法还给出了主轴和副轴的长度。

(x,y),(MA,ma),angle = cv.fitEllipse(cnt)

掩码和像素点

在某些情况下,我们可能需要构成该对象的所有点。

mask = np.zeros(imgray.shape,np.uint8)
cv.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
#pixelpoints = cv.findNonZero(mask)

这段代码创建了一个与输入图像大小相同的空白掩码(mask),并使用cv.drawContours()函数绘制了指定轮廓(cnt)在该掩码上的内部区域。

然后,通过将掩码上非零像素的坐标转置得到了像素点的坐标(pixelpoints)。这里使用了np.transpose()函数将掩码中非零像素的行列坐标进行转置操作。

注释中的另一种方法是使用cv.findNonZero()函数来获取掩码上非零像素的坐标。这个函数可以直接返回非零像素的坐标数组。

两种方法都可以得到掩码上非零像素的坐标,只是使用了不同的函数和操作。

最大值,最小值和它们的位置

我们可以使用掩码图像找到这些参数。

min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imgray,mask = mask)

平均颜色或平均强度

在这里,我们可以找到对象的平均颜色。或者可以是灰度模式下物体的平均强度。我们再次使用
相同的掩码进行此操作。

mean_val = cv.mean(im,mask = mask)

极端点

极点是指对象的最顶部,最底部,最右侧和最左侧的点。

leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])

凸性缺陷

从这个凸包上的任何偏差都可以被认为是凸性缺陷。
OpenCV有一个函数来找到这个cv.convexityDefects()。一个基本的函数调用如下:

hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)

它返回一个数组,其中每行包含这些值—[起点、终点、最远点、到最远点的近似距离]。

for i in range(defects.shape[0]):
    s, e, f, d = defects[i, 0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv.line(img, start, end, [0, 255, 0], 2)
    cv.circle(img, far, 5, [0, 0, 255], -1)

这段代码的作用是遍历凸性缺陷,并对每个凸性缺陷进行处理。

在循环中,使用defects.shape[0]获取凸性缺陷的数量,并通过defects[i, 0]获取第i个凸性缺陷的四个元素:起始点索引(s),结束点索引(e),最远点索引(f),以及对应的到凸包(convex hull)的距离(d)。

然后,将这些索引应用于cnt数组,得到起始点(start)、结束点(end)和最远点(far)。这些点被转换为元组格式。

接下来,使用cv.line()函数在图像上绘制起始点到结束点的直线,颜色为[0, 255, 0],线宽为2。

然后,使用cv.circle()函数在图像上标记最远点,以红色圆圈的形式显示,半径为5。

通过这些操作,可以将凸性缺陷的连线和最远点标记在图像上。
在这里插入图片描述

点多边形测试

这个函数找出图像中一点到轮廓线的最短距离。它返回的距离,点在轮廓线外时为负,点在轮廓
线内时为正,点在轮廓线上时为零。

例如,我们可以检查点 (50,50) 如下

dist = cv.pointPolygonTest(cnt,(50,50),True)

在函数中,第三个参数是measureDist。如果它是真的,它会找到有符号的距离。如果为假,则查
找该点是在轮廓线内部还是外部(分别返回+1、-1和0)。

注意 如果您不想找到距离,请确保第三个参数为False,因为这是一个耗时的过程。因此,将其 设置为False可使速度提高2-3倍。

形状匹配

OpenCV附带一个函数cv.matchShapes(),该函数使我们能够比较两个形状或两个轮廓,并返
回一个显示相似性的度量。结果越低,匹配越好。

contours,hierarchy = cv.findContours(thresh,2,1)
cnt1 = contours[0]
contours,hierarchy = cv.findContours(thresh2,2,1)
cnt2 = contours[0]
ret = cv.matchShapes(cnt1,cnt2,1,0.0)

即使是图像旋转也不会对这个比较产生很大的影响

轮廓分层

理解层次结构以及轮廓检索模式

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蜡笔小新配吉良吉影

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值