您现在的位置是:首页 >其他 >OpenCV中的图像处理3.9(六)轮廓线特征与属性网站首页其他

OpenCV中的图像处理3.9(六)轮廓线特征与属性

王者与CV 2024-06-17 10:24:50
简介OpenCV中的图像处理3.9(六)轮廓线特征与属性


翻译及二次校对:cvtutorials.com

编辑者:廿瓶鲸(和鲸社区Siby团队成员)

3.9 OpenCV中的轮廓线

3.9.1 轮廓线:入门

目标

  • 理解什么是轮廓线。
  • 学习查找轮廓、绘制轮廓等。
  • 你将看到这些函数:cv.findContours(), cv.drawContours()

什么是轮廓线?

轮廓线可以简单地解释为连接所有连续点(沿边界)的曲线,具有相同的颜色或灰度。轮廓线是形状分析和物体检测与识别的一个有用工具。

  • 为了获得更好的准确性,使用二进制图像。因此,在寻找轮廓线之前,应用阈值或Canny边缘检测。
  • 从OpenCV 3.2开始,findContours()不再修改源图像了。
  • 在OpenCV中,寻找轮廓线就像从黑色背景中寻找白色物体。所以请记住,要找到的物体应该是白色的,背景应该是黑色的。

让我们来看看如何找到二进制图像的轮廓线。

import numpy as np
import cv2 as cv
im = cv.imread('test.jpg')
imgray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

cv.findContours()函数中有三个参数,第一个是源图像,第二个是轮廓检索模式,第三个是轮廓逼近方法。然后它输出轮廓线和层次结构。轮廓线是一个包含图像中所有轮廓线的Python列表。每个单独的轮廓线是一个Numpy数组,包含物体边界点的(x,y)坐标。

注意:我们将在后面详细讨论第二个和第三个参数以及层次结构。在那之前,代码样本中给它们的值对所有的图像都能正常工作。

如何绘制轮廓线?

为了绘制轮廓线,我们使用了cv.drawContours函数。它也可以用来绘制任何形状,只要你有它的边界点。它的第一个参数是源图像,第二个参数是轮廓线,应该以Python列表的形式传递,第三个参数是轮廓线的索引(在绘制单个轮廓线时很有用。 要绘制所有轮廓线,传递-1),其余参数是颜色、厚度等。

  • 绘制一幅图像中的所有轮廓线。
cv.drawContours(img, contours, -1, (0,255,0), 3)
  • 要画一个单独的轮廓,比如说第4个轮廓。
cv.drawContours(img, contours, 3, (0,255,0), 3)
  • 但在大多数时候,下面的方法会很有用。
cnt = contours[4]
cv.drawContours(img, [cnt], 0, (0,255,0), 3)

注意事项:最后两种方法是一样的,但是你会发现最后一种方法更有用。

轮廓线逼近法

这是cv.findContours函数的第三个参数。它实际上表示什么呢?

上面我们说过,轮廓线是具有相同灰度的形状的边界。它存储了一个形状的边界的(x,y)坐标。但它是否存储了所有的坐标?这是由这个轮廓逼近方法指定的。

如果你传递cv.CHAIN_APPROX_NONE,所有的边界点都会被存储。但实际上我们需要所有的点吗?例如,你找到了一条直线的轮廓。你需要这条线上的所有点来表示这条直线吗?不,我们只需要那条线的两个端点。这就是cv.CHAIN_APPROX_SIMPLE的作用。它删除了所有多余的点并压缩了轮廓,从而节省了内存。

下面是一个矩形的图片,演示了这个技术。只要在轮廓线数组中的所有坐标上画一个圆(用蓝色画)。第一张图片显示了我用cv.CHAIN_APPROX_NONE得到的点(734个点),第二张图片显示了用cv.CHAIN_APPROX_SIMPLE的点(只有4个点)。看,它节省了多少内存!!!。

Image Name

3.9.2 轮廓线的特征

在这篇文章中,我们将学习

  • 找到轮廓的不同特征,如面积、周长、中心点、边界盒等。
  • 你会看到很多与轮廓线有关的函数。

1. 矩

图像矩帮助你计算一些特征,如物体的质心、物体的面积等。

函数cv.ments()给出了一个所有计算出的矩的字典。见下文:

import numpy as np
import cv2 as cv
img = cv.imread('star.jpg',0)
ret,thresh = cv.threshold(img,127,255,0)
contours,hierarchy = cv.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv.moments(cnt)
print(M)

从这个矩,你可以提取有用的数据,如面积、中心点等。中心点是由Cx=M10/M00和Cy=M01/M00的关系给出的。这可以按以下方式进行。

cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])

2. 轮廓线面积

轮廓线面积由函数cv.contourArea()或从矩M[‘m00’]给出。

area = cv.contourArea(cnt)

3. 轮廓线周长

它也被称为弧长。它可以用cv.arcLength()函数计算出来。第二个参数指定形状是一个封闭的轮廓(如果传递的是True),还是只是一条曲线。

perimeter = cv.arcLength(cnt,True)

4. 轮廓逼近

它根据我们指定的精度,将一个轮廓形状逼近到另一个顶点数量较少的形状。它是Douglas-Peucker算法的一个实现。

为了理解这一点,假设你试图在图像中找到一个正方形,但由于图像中的一些问题,你没有得到一个完美的正方形,而是一个 “坏形状”(如下图所示)。现在,你可以用这个函数来近似地处理这个形状。在这个函数中,第二个参数叫做epsilon,它是轮廓到近似轮廓的最大距离。它是一个精度参数。为了得到正确的输出,需要明智地选择epsilon。

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

下面,在第二张图片中,绿线显示了epsilon为弧长的10%时的近似曲线。第三张图显示的是epsilon为弧长的1%时的情况。第三个参数指定曲线是否是封闭的。

Image Name

5. 凸面体

凸面体看起来与轮廓逼近相似,但它不是(两者在某些情况下可能提供相同的结果)。在这里,cv.convexHull()函数检查曲线是否有凸性缺陷并进行修正。一般来说,凸形曲线是指总是凸出来的曲线,或者至少是平的。而如果是向内隆起,则被称为凸性缺陷。例如,请看下面的手的图片。红线表示手的凸体。双面的箭头标志显示了凸性缺陷,这是局部最大凸包与轮廓的偏差。

Image Name

关于它的语法,有一点需要讨论。

hull = cv.convexHull(point[, hull[, clockwise[, returnPoints])

参数细节:

  • points是我们传入的轮廓线。
  • hull是输出,通常我们避免使用它。
  • clockwise:方向标志。如果它是True,输出的凸面体是顺时针方向的。否则,它的方向是逆时针的。
  • returnPoints : 默认为 “真”。然后,它返回凸包点的坐标。如果是False,它返回与凸包点对应的轮廓点的索引。

因此,要得到上图中的凸包,只需按以下方法即可:

hull = cv.convexHull(cnt)

但是如果你想找到凸性缺陷,你需要传递returnPoints = False。为了理解它,我们将采取上面的矩形图像。首先,我发现它的轮廓为cnt。现在我用returnPoints = True找到了它的凸面,我得到了以下值。[[234 202]], [[51 202]], [[51 79]], [[234 79]]是矩形的四个角点。现在如果用returnPoints = False做同样的事情,我得到的结果是:[[129], [67], [0], [142]]。这些是轮廓线中相应的点的索引。例如,检查第一个值:cnt[129] = [[234, 202]],这与第一个结果相同(其他的也是如此)。

当我们讨论凸性缺陷时,你会再次看到它。

6. 检查凸性

有一个函数可以检查一条曲线是否是凸的,即cv.isContourConvex()。它只是返回True或False。没什么大不了的。

k = cv.isContourConvex(cnt)

7. 边界矩形

有两种类型的边界矩形。

7.a. 直线边界矩形

这是一个直线矩形,它不考虑物体的旋转。因此,边界矩形的面积不会是最小的。它是由函数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)

7.b. 旋转的矩形

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

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

两个矩形都显示在一张图片上。绿色矩形显示的是正常的边界矩形。红色矩形是旋转后的矩形。

Image Name

8. 最小包围圈

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

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

Image Name

9. 拟合椭圆

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

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

Image Name

10. 拟合直线

同样地,我们可以将一条线拟合到一组点上。下面的图片包含一组白色的点。我们可以对它进行近似的直线拟合。

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)

Image Name

3.9.3 轮廓属性

在这里,我们将学习如何提取一些常用的物体属性,如实体性、等效直径、掩膜图像、平均灰度等。更多的特征可以在Matlab regionprops文档中找到。

(注意:中心点、面积、周长等也属于这一类,但我们在上一章已经看到了)

1.纵横比

它是物体的边界矩形的宽度和高度的比率。
A s p e c t    R a t i o = W i d t h H e i g h t Aspect ; Ratio = frac{Width}{Height} AspectRatio=HeightWidth

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

2.外延

外延是指轮廓线面积与边界矩形面积的比率。
E x t e n t = O b j e c t    A r e a B o u n d i n g    R e c t a n g l e    A r e a Extent = frac{Object ; Area}{Bounding ; Rectangle ; Area} Extent=BoundingRectangleAreaObjectArea

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

3.实体性

实体性是指轮廓面积与凸包面积的比率。
S o l i d i t y = C o n t o u r    A r e a C o n v e x    H u l l    A r e a Solidity = frac{Contour ; Area}{Convex ; Hull ; Area} Solidity=ConvexHullAreaContourArea

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

4.等效直径

等效直径是指其面积与轮廓面积相同的圆的直径。
E q u i v a l e n t    D i a m e t e r = 4 × C o n t o u r    A r e a π Equivalent ; Diameter = sqrt{frac{4 imes Contour ; Area}{pi}} EquivalentDiameter=π4×ContourArea

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

5.方向

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

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

6.掩膜和像素点

在某些情况下,我们可能需要包括该对象的所有点。可以按以下方式进行:

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

这里给出了两种方法,一种是使用Numpy函数,另一种是使用OpenCV函数(最后一行注释)来做同样的事情。结果也是一样的,但有一点不同。Numpy给出的坐标是(行,列)格式,而OpenCV给出的坐标是(x,y)格式。所以基本上答案会互换。注意,row=y,column=x。

7.最大值、最小值和它们的位置

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

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

8.平均颜色或平均灰度

在这里,我们可以找到一个物体的平均颜色。也可以是灰度模式下物体的平均灰度。我们再次使用相同的掩膜来做这件事。

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

9.极点

极点指的是物体的最上面、最下面、最右边和最左边的点。

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])

例如,如果我把它应用于印度地图,我得到以下结果。

Image Name

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。