本文将学习如下内容:
- 找到轮廓的不同特征,如面积、周长、质心、边界框等
- 将看到许多与轮廓相关的功能。
矩
图像矩可以帮助计算一些特征,如物体的质心、物体的面积等。
查看维基百科上的图像矩页面。
函数cv2.moments()
给出了一个所有计算出的力矩值的字典。参见如下示例:
%matplotlib inline
import matplotlib.pyplot as plt
import cv2
import numpy as np
# img = cv2.imread('/data/cvdata/star.jpg',0)
img = cv2.imread('/data/cvdata/star.png', 0)
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f38bb23ee40>
ret,thresh = cv2.threshold(img,127,255,0)
ret
127.0
plt.imshow(thresh)
<matplotlib.image.AxesImage at 0x7f38bb23dac0>
contours,hierarchy = cv2.findContours(thresh, 1, 2)
# contours.shape
hierarchy.shape
(1, 22, 4)
# hierarchy.shape.shape
cnt = contours[0]
M = cv2.moments(cnt)
print (M)
{'m00': 4.0, 'm10': 706.0, 'm01': 1352.0, 'm20': 124610.66666666666, 'm11': 238628.0, 'm02': 456977.0, 'm30': 21994371.0, 'm21': 42118405.333333336, 'm12': 80656440.5, 'm03': 154458902.0, 'mu20': 1.6666666666569654, 'mu11': 0.0, 'mu02': 1.0, 'mu30': 3.725290298461914e-09, 'mu21': 5.762558430433273e-09, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.10416666666606034, 'nu11': 0.0, 'nu02': 0.0625, 'nu30': 1.1641532182693481e-10, 'nu21': 1.800799509510398e-10, 'nu12': 0.0, 'nu03': 0.0}
从这一刻起,可以提取有用的数据,如面积、质心等。 质心由以下关系给出, $C_x = \frac{M_{10}}{M_{00}}$ 和 $C_y = \frac{M_{01}}{M_{00}}$. 可以通过以下方式完成:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
for x in contours:
area = cv2.contourArea(x)
print(area)
4.0 4.0 2.0 2.0 8.5 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 76185.5
for cnt in contours:
perimeter = cv2.arcLength(cnt,True)
print(perimeter)
7.656854152679443 7.656854152679443 5.656854152679443 5.656854152679443 11.071067690849304 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 5.656854152679443 1680.687520623207
轮廓近似
它将轮廓形状近似为另一个顶点数量较少的形状,具体取决于指定的精度。 它是Douglas-Peucker algorithm算法的一种实现。
为了理解这一点,假设试图在图像中找到一个正方形,但由于图像中的一些问题,没有得到一个完美的正方形,而是一个“糟糕的形状”(如下图所示)。
现在可以使用此函数来近似形状。
在这里,第二个参数称为epsilon
,它是从轮廓到近似轮廓的最大距离。
这是一个精度参数,需要明智地选择epsilon
以获得正确的输出。
for cnt in contours:
epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
print(epsilon, approx)
0.7656854152679444 [[[175 338]] [[176 337]] [[178 338]] [[177 339]]] 0.7656854152679444 [[[102 338]] [[103 337]] [[105 338]] [[104 339]]] 0.5656854152679444 [[[174 337]] [[175 336]] [[176 337]] [[175 338]]] 0.5656854152679444 [[[173 336]] [[174 335]] [[175 336]] [[174 337]]] 1.1071067690849306 [[[413 286]] [[416 285]] [[417 287]] [[415 288]]] 0.5656854152679444 [[[416 285]] [[417 284]] [[418 285]] [[417 286]]] 0.5656854152679444 [[[412 285]] [[413 284]] [[414 285]] [[413 286]]] 0.5656854152679444 [[[417 284]] [[418 283]] [[419 284]] [[418 285]]] 0.5656854152679444 [[[411 284]] [[412 283]] [[413 284]] [[412 285]]] 0.5656854152679444 [[[421 281]] [[422 280]] [[423 281]] [[422 282]]] 0.5656854152679444 [[[422 280]] [[423 279]] [[424 280]] [[423 281]]] 0.5656854152679444 [[[423 279]] [[424 278]] [[425 279]] [[424 280]]] 0.5656854152679444 [[[424 278]] [[425 277]] [[426 278]] [[425 279]]] 0.5656854152679444 [[[425 277]] [[426 276]] [[427 277]] [[426 278]]] 0.5656854152679444 [[[426 276]] [[427 275]] [[428 276]] [[427 277]]] 0.5656854152679444 [[[431 272]] [[432 271]] [[433 272]] [[432 273]]] 0.5656854152679444 [[[432 271]] [[433 270]] [[434 271]] [[433 272]]] 0.5656854152679444 [[[433 270]] [[434 269]] [[435 270]] [[434 271]]] 0.5656854152679444 [[[434 269]] [[435 268]] [[436 269]] [[435 270]]] 0.5656854152679444 [[[435 268]] [[436 267]] [[437 268]] [[436 269]]] 0.5656854152679444 [[[436 267]] [[437 266]] [[438 267]] [[437 268]]] 168.06875206232073 [[[639 316]] [[ 0 399]]]
在下面第二幅图像中,绿线显示了epsilon = 10% of arc length
的近似曲线。
第三张图片显示,epsilon = 1% of the arc length
也是如此。第三个参数指定曲线是否闭合。
凸包
凸包看起来类似于轮廓近似,但事实并非如此(在某些情况下,两者可能会提供相同的结果)。
cv2.convexHull()
函数检查曲线是否存在凸性缺陷并对其进行校正。
一般来说,凸曲线是总是凸出的曲线,或者至少是平坦的曲线。
如果内部凸起,则称为凸性缺陷。
例如,请查看下面的手部图像。
红线显示的是凸起的手掌。
双面箭头标记显示了凸起缺陷,即船体与轮廓的局部最大偏差。
关于它的语法,有一些事情要讨论:
hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]
参数详细信息:
points
: 是我们进入的轮廓。hull
: 是输出,通常会避开它。clockwise
:方向标志。如果为True,则输出凸包的方向为顺时针。否则,其方向为逆时针。returnPoints
:默认情况下为True。返回船体点的坐标。如果为False,则返回与船体点对应的轮廓点的索引。
因此,要获得如上图所示的凸包,以下步骤就足够了:
hull = cv2.convexHull(cnt)
但是如果想找到凸性缺陷,需要传递returnPoints=False
。
为了理解它将采用上面的矩形图像。
首先发现轮廓是cnt
。
现在找到了它的凸包,return points=True
,得到了以下值:[[[234 202]],[[51 202]]、[[51 79]]
和[[234 79]]]
,分别是矩形的四个角点。
现在如果对returnPoints=False
做同样的操作,得到以下结果:[[129],[67],[0],[142]]
。
这些是轮廓中对应点的索引。例如,检查第一个值:cnt[129]=[[234202]]
,与第一个结果相同(其他结果也是如此)。
当讨论凸性缺陷时将再次看到。
检查凸度
函数cv2.isContourConverx()
可以检查曲线是否是凸的,
只返回True
或False
。
k = cv2.isContourConvex(cnt)
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.intp(box)
im = cv2.drawContours(img,[box],0,(0,0,255),2)
plt.imshow(im)
<matplotlib.image.AxesImage at 0x7f38bb083b00>
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f38b177b4a0>
ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(im,ellipse,(0,255,0),2)
plt.imshow(im)
<matplotlib.image.AxesImage at 0x7f38b17927b0>
rows,cols = img.shape[:2]
[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
img = cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
/tmp/ipykernel_1168/1065711970.py:3: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.) lefty = int((-x*vy/vx) + y) /tmp/ipykernel_1168/1065711970.py:4: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.) righty = int(((cols-x)*vy/vx)+y)
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f38b11b3ec0>