1.目标
- 理解什么叫做轮廓
- 学习如何寻找轮廓以及可视化轮廓
- 找出轮廓的不同特征,如面积、周长、质心、边框等
- 将看到许多与轮廓相关的函数。
2.什么叫做轮廓
轮廓可以简单地解释为连接所有连续点(沿着边界)的曲线,具有相同的颜色或者亮度。轮廓是形状分析和目标检测与识别的有效工具。
- 为了获得更好的准确性,使用二值图像。因此,在找到轮廓之前,应用阈值或canny边缘检测。
- 从OpenCV 3.2开始,findContours()不再修改源图像。
- 在OpenCV中,寻找轮廓就像从黑色背景中寻找白色物体。记住,要找到的物体应该是白色,背景应该是黑色。
让我们看看如何找到二值图像的轮廓:
import numpy as np
import cv2 as cv
im = cv.imread('star.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()函数中有三个参数,第一个是源图像,第二个是轮廓检索模式,第三个是轮廓逼近方法。它输出轮廓和层次结构。
Contours是图像中所有轮廓的Python列表。每个单独的轮廓都是一个Numpy数组,它是物体边界点的(x,y)坐标。
3.如何画轮廓?
为了画出轮廓,使用cv.drawContours函数。它也可以用来绘制任何形状,只要您有其边界点。它的第一个参数是source image,
第二个参数是应该作为Python列表传递的轮廓,第三个参数是轮廓的索引(在绘制单独的轮廓时很有用。要绘制所有的轮廓,传递-1),
剩下的参数是颜色,厚度等。
- 画所有轮廓
cv.drawContours(im, contours, -1, (0, 255, 0), 3)
- 要画一条单独的轮廓线,画第4条轮廓线:
cv.drawContours(im, contours, 3, (0, 255, 0), 3)
- 但大多数情况下,下面的方法是有用的:
cnt = contours[4]
cv.drawContours(im, [cnt], 0, (0, 255, 0), 3)
4.轮廓近似法
这是cv.findContours函数的第三个参数。它实际上表示什么?
上面,我们说过,轮廓是具有相同强度的形状的边界。它存储形状边界的(x,y)坐标。但是它能存储所有的坐标吗?这是由轮廓近似法确定的。
如果你传递cv.CHAIN_APPROX_NONE,存储所有边界点。但实际上我们需要所有的点吗?例如,你找到了一条直线的轮廓。
你需要直线上所有的点来表示这条直线吗?不,我们只需要这条线的两个端点。这就是cv.CHAIN_APPROX_SIMPLE做的事。它去除所有冗余点并压缩轮廓,从而节省内存。
5.Moments
图像矩可以帮助你计算一些特征,如物体的质心,物体的面积等。
函数cv.moments()给出了一个包含所有计算得到的矩值的字典。见下文:
img = cv.imread('star.jpg', 0)
ret2, thresh = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
contours, hierarchy2 = cv.findContours(thresh, 1, 2)
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cnt = contours[1]
M = cv.moments(cnt)
print(M)
从这个Moments,你可以提取有用的数据例如面积,质心等。质心获取如下,
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])
6.轮廓面积
轮廓面积由函数cv.contourArea()或矩M[‘m00’]给出。
area = cv.contourArea(cnt)
7.轮廓周长
它也被称为弧长。可以使用cv.arcLength()函数。第二个参数指定形状是闭合轮廓(如果传递True),还是只是曲线。
perimeter = cv.arcLength(cnt, closed=True)
8.轮廓近似
它将一个轮廓形状近似为另一个形状,其顶点数量较少,这取决于我们指定的精度。它是Douglas-Peucker算法的一个实现。
为了理解这一点,假设您试图在图像中找到一个正方形,但由于图像中的一些问题,您没有得到一个完全的正方形,
而是一个“糟糕的形状”(如下面的第一张图像所示)。现在你可以用这个函数来近似这个形状。在这里,第二个参数称为epsilon,
它是轮廓到逼近轮廓的最大距离。
epsilon = 0.1 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
下图第二幅图中,绿线显示了 epsilon=弧长10%的近似曲线。第三个图片显示epsilon=弧长1%的近似曲线。第三个参数指定曲线是否闭合。
9.Convex Hull(凸包)
凸包看起来类似于轮廓逼近,但它不是(两者可能提供相同的结果在某些情况下)。在这里,cvv.convexhull()函数检查曲线的凸性
缺陷并对其进行修正。一般来说,凸曲线是指总是凸起的曲线,或者至少是平坦的曲线。如果内部有凸起,则称为凸性缺陷。
例如,检查下面的图像。红线表示手的凸包。双向箭头表示凸缺陷,即凸包与轮廓的局部最大偏差。
关于它的语法有一点需要讨论
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
参数说明:
- points表示轮廓
- hull是输出,通常我们会避开它。
- clockwise方向标志。如果为真,则输出凸包为顺时针方向。否则,它是逆时针方向的。
- returnPoints:默认情况下,True。然后它返回凸包点的坐标。如果为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]],它与第一个结果相同(以此类推)
10.检查凸性
有一个函数来检查曲线是否是凸的, cv.isContourConvex()。它只返回True还是False。
k = cv.isContourConvex(cnt)
11.边框
有两种类型的边框
11.a 直边界矩形
它是一个直边界矩形,它不考虑对象的旋转。所以矩形的面积不会最小。由cv.boundingRect()函数实现。
# 设(x,y)为矩形的左上角坐标,(w,h)为矩形的宽和高。
x, y, w, h = cv.boundingRect(cnt)
print(x, y, w, h)
cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv.imshow("img", img)
cv.waitKey()
11.b 旋转矩形
在这里,边界矩形是用最小面积绘制的,所以它也考虑了旋转。使用的函数是cv.minarerect()。
它返回一个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)
两个矩形都显示在一个单独的图像中。绿色矩形显示普通边界矩形。红色矩形是旋转后的矩形。
12.最小闭合圆
接下来,我们使用函数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)
13.拟合椭圆
下一个是将椭圆拟合到对象。它返回内接旋转矩形椭圆。
print("cnt:", cnt)
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 2)
14.拟合直线
类似地,我们可以将一条直线拟合到点集上。下面的图像包含一组白色的点。我们可以把它近似成一条直线。
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)
cv.imshow("line", img)
cv.waitKey()
参考目录
https://docs.opencv.org/4.x/d5/d45/tutorial_py_contours_more_functions.html
https://docs.opencv.org/4.x/dd/d49/tutorial_py_contour_features.html