目录
- 3.9 OpenCV中的轮廓线
- 3.9.1 轮廓线:入门
- 目标
- 什么是轮廓线?
- 如何绘制轮廓线?
- 轮廓线逼近法
- 3.9.2 轮廓线的特征
- 1. 矩
- 2. 轮廓线面积
- 3. 轮廓线周长
- 4. 轮廓逼近
- 5. 凸面体
- 6. 检查凸性
- 7. 边界矩形
- 8. 最小包围圈
- 9. 拟合椭圆
- 10. 拟合直线
- 3.9.3 轮廓属性
- 1.纵横比
- 2.外延
- 3.实体性
- 4.等效直径
- 5.方向
- 6.掩膜和像素点
- 7.最大值、最小值和它们的位置
- 8.平均颜色或平均灰度
- 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个点)。看,它节省了多少内存!!!。
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%时的情况。第三个参数指定曲线是否是封闭的。
5. 凸面体
凸面体看起来与轮廓逼近相似,但它不是(两者在某些情况下可能提供相同的结果)。在这里,cv.convexHull()函数检查曲线是否有凸性缺陷并进行修正。一般来说,凸形曲线是指总是凸出来的曲线,或者至少是平的。而如果是向内隆起,则被称为凸性缺陷。例如,请看下面的手的图片。红线表示手的凸体。双面的箭头标志显示了凸性缺陷,这是局部最大凸包与轮廓的偏差。
关于它的语法,有一点需要讨论。
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)
两个矩形都显示在一张图片上。绿色矩形显示的是正常的边界矩形。红色矩形是旋转后的矩形。
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)
9. 拟合椭圆
下一个是将一个椭圆拟合到一个物体上。它返回旋转后的矩形以及内接的椭圆。
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,(0,255,0),2)
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)
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 \times 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])
例如,如果我把它应用于印度地图,我得到以下结果。