轮廓可以简单地解释为连接所有连续点(沿着边界)的曲线,具有相同的颜色或者亮度。轮廓是形状分析和目标检测与识别的有效工具。
让我们看看如何找到二值图像的轮廓:
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)坐标。
为了画出轮廓,使用cv.drawContours函数。它也可以用来绘制任何形状,只要您有其边界点。它的第一个参数是source image,
第二个参数是应该作为Python列表传递的轮廓,第三个参数是轮廓的索引(在绘制单独的轮廓时很有用。要绘制所有的轮廓,传递-1),
剩下的参数是颜色,厚度等。
cv.drawContours(im, contours, -1, (0, 255, 0), 3)
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做的事。它去除所有冗余点并压缩轮廓,从而节省内存。
图像矩可以帮助你计算一些特征,如物体的质心,物体的面积等。
函数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'])
轮廓面积由函数cv.contourArea()或矩M[‘m00’]给出。
area = cv.contourArea(cnt)
它也被称为弧长。可以使用cv.arcLength()函数。第二个参数指定形状是闭合轮廓(如果传递True),还是只是曲线。
perimeter = cv.arcLength(cnt, closed=True)
它将一个轮廓形状近似为另一个形状,其顶点数量较少,这取决于我们指定的精度。它是Douglas-Peucker算法的一个实现。
为了理解这一点,假设您试图在图像中找到一个正方形,但由于图像中的一些问题,您没有得到一个完全的正方形,
而是一个“糟糕的形状”(如下面的第一张图像所示)。现在你可以用这个函数来近似这个形状。在这里,第二个参数称为epsilon,
它是轮廓到逼近轮廓的最大距离。
epsilon = 0.1 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
下图第二幅图中,绿线显示了 epsilon=弧长10%的近似曲线。第三个图片显示epsilon=弧长1%的近似曲线。第三个参数指定曲线是否闭合。
凸包看起来类似于轮廓逼近,但它不是(两者可能提供相同的结果在某些情况下)。在这里,cvv.convexhull()函数检查曲线的凸性
缺陷并对其进行修正。一般来说,凸曲线是指总是凸起的曲线,或者至少是平坦的曲线。如果内部有凸起,则称为凸性缺陷。
例如,检查下面的图像。红线表示手的凸包。双向箭头表示凸缺陷,即凸包与轮廓的局部最大偏差。
关于它的语法有一点需要讨论
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
参数说明:
因此,要得到上图中的凸包,以下代码足够的:
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]],它与第一个结果相同(以此类推)
有一个函数来检查曲线是否是凸的, cv.isContourConvex()。它只返回True还是False。
k = cv.isContourConvex(cnt)
有两种类型的边框
它是一个直边界矩形,它不考虑对象的旋转。所以矩形的面积不会最小。由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()
在这里,边界矩形是用最小面积绘制的,所以它也考虑了旋转。使用的函数是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)
两个矩形都显示在一个单独的图像中。绿色矩形显示普通边界矩形。红色矩形是旋转后的矩形。
接下来,我们使用函数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)
下一个是将椭圆拟合到对象。它返回内接旋转矩形椭圆。
print("cnt:", cnt)
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)
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