OpenCV 17: 轮廓特征
目标
在本文中,将学习
- 如何找到轮廓的不同特征,例如面积,周长,质心,边界框等。
- 将看到大量与轮廓有关的函数
图像矩
图像矩是指图像的某些特定像素灰度的加权平均值(矩),或者是图像具有类似功能或意义的属性, 可以帮助计算一些特征,例如物体的质心,物体的面积等。函数cv2.moments()
提供了所有计算出的矩值。见下文:
import cv2
import numpy as np
img = cv2.imread('start.png', 0)
ret, thresh = cv2.threshold(img, 127, 255, 0)
contours, hierarchy = cv2.findContours(thres, 1, 2)
cnt = contours[0]
M = cv2.moments(cnt)
print(M)
{'m00': 13724.5, 'm10': 1797933.3333333333, 'm01': 1873571.8333333333, 'm20': 246067666.25, 'm11': 245442859.54166666, 'm02': 279142772.0833333, 'm30': 34995570121.700005, 'm21': 33592122871.399998, 'm12': 36568730961.566666, 'm03': 44488834889.950005, 'mu20': 10535277.37527883, 'mu11': 1695.8166170418262, 'mu02': 23375974.410556376, 'mu30': -729.2795791625977, 'mu21': 258212.5407886505, 'mu12': 80070.37276697159, 'mu03': -11313.452514648438, 'nu20': 0.055931042112529486, 'nu11': 9.002970424438718e-06, 'nu02': 0.12410139406924131, 'nu30': -3.304857392468866e-08, 'nu21': 1.170135087607162e-05, 'nu12': 3.628528357540433e-06, 'nu03': -5.126887992711116e-07}
此时,可以提取有用的数据,例如面积,质心等。 质心由关系给出 Cx=M10M00C_x = \frac{M_{10}}{M_{00}}Cx=M00M10 Cy=M01M00C_y = \frac{M_{01}}{M_{00}}Cy=M00M01
可以按照以下步骤进行:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
轮廓面积
轮廓区域由函数cv2.contourArea()
或从矩M['m00']
中给出。
area = cv2.contourArea(cnt)
area = int(M['m00'])
轮廓周长
周长也被称为弧长。可以使用cv2.arcLength()
函数找到它。第二个参数指定形状是闭合轮廓(True)还是曲线。
perimeter = cv2.arcLength(cnt, True)
轮廓近似
根据指定的精度,可以将轮廓形状近似为顶点数量较少的其他形状。它是Douglas-Peucker算法(是一种将线段组成的曲线降采样为点数较少的类似曲线的算法)的实现。
为了理解这一点,假设试图在图像中找到一个正方形,但是由于图像中的某些问题,没有得到一个完美的正方形,而是一个“坏形状”(如下图所示)。现在,可以使用此函数来近似形状。
在这种情况下,第二个参数称为epsilon
,它是从轮廓到近似轮廓的最大距离, 是一个精度参数。需要正确选择epsilon才能获得正确的输出。
epsilon = 0.1*cv.arcLength(cnt,True)
approx = cv.approxPolyDP(cnt,epsilon,True)
下面,在第二张图片中,绿线显示了ε=弧长*0.1
时的近似曲线。第三幅图显示了ε=弧长*0.01
时的情况。第三个参数指定曲线是否闭合。
轮廓凸包
凸包外观看起来与轮廓逼近相似,但不相似(在某些情况下两者可能提供相同的结果)。cv2.convexHull()
函数检查曲线是否存在凸凹缺陷并对其进行校正。一般而言,凸曲线是始终凸出或至少平坦的曲线。如果在内部凸出,则称为凸度缺陷。例如,检查下面的手的图像。红线显示手的凸包。双向箭头标记显示凸度缺陷,这是凸包与轮廓线之间的局部最大偏差。
函数参数:
hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]
参数详细信息: *points:传递到的轮廓。
- hull: 输出
- clockwise:方向标记。如果为True,则输出凸包为顺时针方向。否则,其方向为逆时针方向。
- 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]]
与第一个结果相同(对于其他结果依此类推)。
检查凸度
cv2.isContourConvex()
具有检查曲线是否凸出的函数。它只是返回True还是False
k = cv.isContourConvex(cnt)
边界矩形
有两种类型的边界矩形。
1.直角矩形
它是一个矩形,不考虑物体的旋转。所以边界矩形的面积不是最小的。它是由函数cv2.boundingRect()
找到的
cv2.boundingRect(img) img是一个二值图,也就是它的参数; 返回四个值,分别是x,y,w,h;x,y是矩阵左上点的坐标,w,h是矩阵的宽和高
import cv2
import numpy as np
img = cv2.imread('box.png', 0)
ret, thresh = cv2.threshold(img, 127, 255, 0)
cnt, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
x, y, w, h = cv2.boundingRect(cnts[0])
cv2.rectangle(img, (x, y), (x+w, y+h), (0,255,0), 2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.旋转矩形
边界矩形是用最小面积绘制的,所以它考虑了旋转。使用的函数是cv2.minAreaRect()
。它返回一个Box2D结构,其中包含以下细节
(中心(x,y),(宽度,高度),旋转角度)。但要画出这个矩形,我们需要矩形的四个角。它由函数cv2.boxPoints()
获得
img = cv2.imread('approx.png', 0)
ret, thresh = cv2.threshold(img, 127, 250, 0)
cnt, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
rect = cv2.minAreaRect(cnt[0])
box = cv2.boxPoints(rect)
box = np.int64(box)
cv2.drawContours(img, [box], 0, (255,255,0), 2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
两个矩形都显示在一张单独的图像中。绿色矩形显示正常的边界矩形。红色矩形是旋转后的矩形。
最小闭合圈
接下来,使用函数cv2.minEnclosingCircle()
查找对象的圆。它是一个以最小面积完全覆盖物体的圆
img = cv2.imread('approx.png', 0)
ret, thresh = cv2.threshold(img, 127, 250, 0)
cnt, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
(x, y), radius = cv2.minEnclosingCircle(cnt[0])
center = (int(x), int(y))
radius = int(radius)
cv2.circle(img, center, radius, (255,255,0), 2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
拟合一个椭圆
下一个是把一个椭圆拟合到一个物体上。它返回内接椭圆的旋转矩形。
img = cv2.imread('approx.png', 0)
ret, thresh = cv2.threshold(img, 127, 250, 0)
cnt, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
ellipse = cv2.fitEllipse(cnt[0])
cv2.ellipse(img, ellipse, (255,255,0), 3)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
拟合直线
同样,可以将一条直线拟合到一组点。下图包含一组白点。我们可以近似一条直线。
import cv2
# 直线
import cv2
img = cv2.imread('approx.png', 0)
rows, cols = img.shape[:2]
ret, thresh = cv2.threshold(img, 127, 250, 0)
cnt, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
[vx,vy,x,y] = cv2.fitLine(cnt[0], cv2.DIST_L2, 0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv2.line(img,(cols-1,righty),(0,lefty),(255,255,0),2)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
附加资源
转载自:https://juejin.cn/post/7202531472719314999