(三)OpenCV中的图像处理之轮廓
注释:本文翻译自OpenCV3.0.0 document->OpenCV-Python Tutorials,包括对原文档种错误代码的纠正
该章节分为以下四个小节:
(一) Contours:Getting Started(轮廓:开始)
(二) Contours Features(轮廓特征)
(三) Contours Properties(轮廓属性)
(四) Contours:More Functions(轮廓:更多方法)
(五) Contours Hierarchy(轮廓分级)
第一小节:Contours:GettingStarted
1.目标:
- 明白什么是轮廓
- 学会找到这些轮廓,绘制轮廓
- 学习这些函数:cv2.findContours(),cv2.drawContours()
2.什么是轮廓
轮廓可以简单地解释为连接所有连续点(沿着边界),具有相同颜色或强度的曲线。轮廓是形状分析和物体检测和识别的有用工具。
- 为了更高的准确率,使用二值图像。在寻找轮廓之前,应用阈值或canny边缘检测
- findContours函数修改源图像,所有想要在找到轮廓后保存源图像,提前把源图像赋值给其它变量
- 再OpenCV中,查找轮廓就像从黑色背景中找到白色物体,所以记住,找到的物体应该是白色的,背景应该是黑色的。
下面的栗子:在二值图像中找到轮廓
'''
Opencv中的轮廓:
demo1
'''
import cv2
img = cv2.imread('2.jpg')
# 图像灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3*3核的高斯滤波
gray = cv2.GaussianBlur(gray, (3, 3), 0)
# canny边缘检测
gray = cv2.Canny(gray, 100, 300)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# binary是最后返回的二值图像
#findContours()第一个参数是源图像、第二个参数是轮廓检索模式,第三个参数是轮廓逼近方法
#输出是轮廓和层次结构,轮廓是图像中所有轮廓的python列表,每个单独的轮廓是对象边界点的(x,y)坐标的Numpy数组
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)
cv2.imshow("1", binary)
cv2.imshow("win10", img)
cv2.waitKey(0) & 0xFF
结果:
3.怎样绘制轮廓
Cv2.drawContours()用来绘制轮廓,只要提供了边界点它也可以用来绘制各种形状。它的第一个边界是源图像,第二个参数是Python列表传递的轮廓,第三个参数是轮廓的索引(在绘制单个轮廓时有用,在绘制多个轮廓时传递-1),剩余的参数是颜色、厚度等。
绘制图像中的所有轮廓:
cv2.drawContours(img, contours, -1, (0,255,0), 3)
绘制个别轮廓,如第四轮廓:
cv2.drawContours(img, contours, 3, (0,255,0), 3)
但是通常情况下,下面的方式更有用:
cnt = contours[4]
cv2.drawContours(img, [cnt], 0, (0,255,0), 3)
4.轮廓近似法
轮廓近似法是cv2.findContours()中第三个参数。
前面,我们说轮廓是具有相同强度的形状的边界。它存储形状边界的(x,y)坐标,但是它是否存储所有坐标?这是通过这种轮廓法来逼近的。
如果传递的参数为cv2.CHIAN_APPROX_NONE,则会存储所有边界。但实际情况下我们可能并不需要所有的点,比如发现一条直线的轮廓,所需要的只是这条线的两个端点。这就是cv2.CHAIN_APPROX_SIMPLE所做的,它删除了所有冗余的点并压缩轮廓,从而大大节省了内存。
在下面矩形图像中显示了这种技术,在轮廓数组上的所有坐标上绘制一个圆,第一个用cv2.CHIAN_APPROX_NONE绘制(734个点),第二个用cv2.CHAIN_APPROX_SIMPLE(4个点)绘制。
第二小节:ContoursFeatures(轮廓特征)
5.目标
- 找不同的轮廓特征,如:面积、周长、重心、边框
- 学会关于轮廓的方法
6.时刻
图像时刻可以帮助你计算一些特征,如物体的质心、物体的面积等。关于图像时刻的概念,参考*。
函数cv2.moments()给出了计算所有时刻值的字典,详细如下:
import cv2
import numpy as np
img = cv2.imread('star.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
binary,contours,hierarchy = cv2.findContours(thresh, 1, 2)
cnt = contours[0]
M = cv2.moments(cnt)
print M
然后可以提取有用的信息,如面积、质心等。质心由下面关系式给出:
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
7.轮廓区域
轮廓区域由cv2.contourArea()计算,或者由时刻中的M[‘m00’]得到:
area = cv2.contourArea(cnt)
8.轮廓周长
也称为轮廓弧长,由cv2.arcLength()计算得到,第二个参数指定shape是封闭轮廓(True)或仅仅是曲线。
perimeter = cv2.arcLength(cnt,True)
9.轮廓近似
如果图像没有明确的轮廓,使用cv2.approxPolyDP近似此轮廓。
epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
10.Convex Hull
ConvexHull将看起来类似于轮廓近似,但它不是(两者可能在某些情况下提供相同的结果)。这里,cv2.convexHull()函数检查一个曲线的凸性缺陷并进行修正。一般来说,凸曲线是总是凸出的或至少平坦的曲线。如果内部膨胀,则称为凸面缺陷。例如,检查下面的图像。红线显示手的凸包。双面箭头标记显示凸度缺陷,这是船体与轮廓的局部最大偏差。
有必要去解释以下这个函数的参数:
hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]
参数详情:
- Points:是传入的轮廓;
- Hull:是输出,通常我们避免它;
- Clockwise:方向标志。如果是true,则顺时针输出凸包,否则逆时针方式输出;
- ReturnPoints:默认情况下为true。它返回hull点的坐标,如果是false,则返回与hull点对应的轮廓点的索引。
所以要得到如上图所示的凸包,下面这条语句就足够了:
hull = cv2.convexHull(cnt)
但是如果你想找到凸面缺陷,你需要传递returnPoints=False。为了理解它,我们将采用上面的矩形图像。首先,我发现它的轮廓为cnt。现在我发现它的凸包带有returnPoints=True,我得到了以下值:[[[234202]],[[51202]],[[5179]],[[23479]]]这是四角矩形点。现在,如果对returnPoints=False做同样的处理,我会得到如下结果:[[129],[67],[0],[142]]。这些是轮廓中相应点的指数。例如,检查第一个值:cnt[129]=[[234,202]],它与第一个结果相同(其它的点也是如此)。
11.检查凸度
这里有一个函数可以检查曲线是否凸起,cv2.isContourConvex().返回值为True或False.
k = cv2.isContourConvex(cnt)
12.边界矩形
1)直线边界矩形
它是一个直的矩形,不考虑对象的旋转。所以边界矩形的面积不会最小。这个矩形由函数cv2.boundingRect()发现。
设(x,y)为矩形的左上角坐标,(w,h)为其宽度和高度.
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
2)旋转矩形
这里,边界矩形是以最小面积绘制的,所以它也考虑旋转。使用的函数是cv2.minAreaRect().它返回一个Box2D的结构,使用包含以下结构——(中心(x,y),(宽度,高度),旋转角度)。但要绘制这个矩形,我们需要矩形的四个角。它是通过函数cv2.boxPoints()获得的。
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img,[box],0,(0,0,255),2)
两种边界矩形都在下图中显示,绿色是一般的边界矩形,红色的是旋转边界矩形
13.最小封闭圈
接下来我们使用函数cv2.minEnclosingCircle()找到对象的外接圆。它是一个以最小面积完全覆盖物体的圆圈。
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius =int(radius)
cv2.circle(img,center,radius,(0,255,0),2)
14.拟合椭圆
接下来将一个椭圆拟合到一个对象,它返回椭圆被刻在其中的旋转矩形。
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img,ellipse,(0,255,0),2)
15.拟合线条
同样我们可以用一条线来拟合一组点。下图包含一组白点,可以近似为一条线。
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)
cv2.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
第三小节:ContoursProperties(轮廓属性)
1.目标:
- 学会找到不同轮廓的属性,如solidity,mean intensity等
- 学习提取像Solidity、Equivalent Diameter, Mask image,Mean Intensity等对象的一些常用属性
2.纵横比(Aspect Ratio)
它是对象矩阵的宽高比:
x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h
3.范围(Extent)
范围是轮廓面积和边界矩形面积的比率:
area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
4.密实度(Solidity)
密实度是轮廓面积与其凸包面积的比率:
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area
5.等效直径(EquivalentDiameter)
等效直径是与轮廓面积相同的圆的直径
area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
6.方向(Orientation)
方向是物体指向的角度。以下方法也给出主轴和副轴长度。
(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)
7.蒙版和像素点(Maskand Pixel Points)
在某些情况下,我们可能需要包含该对象的所有点。它可以用以下方法做到:
mask = np.zeros(imgray.shape,np.uint8)
cv2.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))
#pixelpoints = cv2.findNonZero(mask)
这里有两个方法,一个使用Numpy函数,另一个使用OpenCV函数(最后一行注释)来执行相同操作。结果是一样的,但略有不同。Numpy以(行,列)格式给出坐标,而OpenCV以(x,y)格式给出坐标。所以基本上答案会互换。请注意,row=x和column=y.
8.最大最小值和它们的位置(maximumvalue,minimum value and their locations)
我们可以使用蒙版图像来找到这些参数。
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
9.平均颜色或平均强度(meanColor or mean Intensity)
在这里,我们可以找到一个物体的平均颜色。或者它可以是灰度模式下物体的平均强度。我们再次使用相同的掩膜来做到这一点。
mean_val = cv2.mean(im,mask = mask)
10.极值点(ExtremePoints)
极值点表示对象的最上面、最下面、最左边和最右边点。
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])
第四小节:Contours:More Functions (轮廓:更多方法)
1.目标:
- 如何找到凸起缺陷
- 寻找从一个点到多个点的最短距离
- 匹配不同的形状
2.凸起缺陷
我们在第二章看到什么是凸包,关于轮廓。物体与该hull之间的任何偏差均可视为凸度缺陷。
OpenCV提供了一个现成的函数来查找这个,cv2.convexityDefects().基本的函数调用如下:
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
Note:请记住,我们必须在找到凸包时通过returnPoints=False,以便找到凹凸缺陷。
它返回一个数组,其中每行包含这些值——[起点,终点,最远点,到最远点的近似距离]。我们可以使用图像对其进行可视化。我们绘制一条连接起点和终点的线,然后在最远点绘制一个圆。记得前三个返回的时cnt的索引。所以我们必须从cnt中提取这些点。
示例代码:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
img = cv2.imread('13.png')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(img_gray, 127, 255, 0)
binary, contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
hull = cv2.convexHull(cnt, returnPoints=False)
defects = cv2.convexityDefects(cnt, hull)
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
cv2.line(img, start, end, [0, 255, 0], 2)
cv2.circle(img, far, 5, [0, 0, 255], -1)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果如下:
3.点多边形测试
该函数可查找图像中某点与轮廓之间的最短距离。当点在轮廓之外时,它返回负值的距离,点在内部时返回正值,如果点在轮廓上则返回零。
例如,我们可以按如下方式检查点(50,50):
dist = cv2.pointPolygonTest(cnt,(50,50),True)
在这个函数中,点三个参数是measureDist。如果它是true,它会找到标记的距离。如果是false,它会查找该点是在内部还是在外部还是在轮廓上。(分别返回+1,-1,0).
Note:如果你不想查找距离,请确保第三个参数是false,因为这是一个耗时的过程。所以,设置为false可以加速2-3倍。
4.形状匹配
OpenCV带有一个函数cv2.matchShapes(),可以用来比较两个形状或两个轮廓,并返回一个显示相似度的度量。结果越低,匹配效果越好。它基于hu-moment值进行计算。文档中解释了不同的测量方法。
# -*- coding: utf-8 -*-
'''
形状匹配
'''
import cv2
import numpy as np
img1 = cv2.imread('car.png', 0)
img2 = cv2.imread('car1.png', 0)
ret, thresh = cv2.threshold(img1, 127, 255, 0)
ret, thresh2 = cv2.threshold(img2, 127, 255, 0)
binary, contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt1 = contours[0]
binary, contours, hierarchy = cv2.findContours(thresh2, 2, 1)
cnt2 = contours[0]
ret = cv2.matchShapes(cnt1, cnt2, 1, 0.0)
print(ret)
试着用上面的图片进行融合,输出结果:
得到的结果:
- 形状A和它自身,返回值:0
- 形状A和形状B,返回值:0.001946
- 形状A和形状C,返回值:0.326911
可以看出,即使图像旋转对此比较的影响也不大。
联系:
1. 检查cv2.pointPolygonTest()的文档,你可以找到一个红色和蓝色的漂亮图像。它表示从所有像素到白色曲线的距离。 取决于距离,曲线内的所有像素都是蓝色的。 同样的外点是红色的。 轮廓边缘用白色标记。 所以问题很简单。 编写一个代码来创建这样的距离表示
2. 使用cv2.matchShapes()比较数字或字母的图像。(这将是向OCR迈出的简单一步)
第五小节:ContourHierarchy(轮廓的层次结构)
1.目标:我们了解轮廓的层次结构,即轮廓中的亲子关系。
2.原理
在最近几篇关于轮廓的文章中,我们已经使用了OpenCV提供的与轮廓相关的几个函数。但当我们使用cv2.findContours()函数在图像中找到轮廓时,我们已经传递了一个参数ContourRetrieval Mode,我们通常会传递cv2.RETR_LIST或者cv2.RETR_TREE,而且运行效果很好。但它们实际上意味着什么呢?
另外,在输出中,我们得到了三个数组,第一个是图像,第二个是轮廓,还有一个我们命名为层次结构的输出。但是我们从来没有在任何地方使用这个层次,那么这个层次结构是什么?它有什么用途?它与前面提到的函数参数有什么关系?
这些就是我们要谈论的点。
3.什么是层次结构
通常我们使用cv2.findContours()函数来检测图像中的对象。有时物体位于不同的位置,但在某些情况下,某些形状是其它形状。就像嵌套数字一样。在这种情况下,我们称外层为父层,内层为子层。这样,图像中的轮廓彼此之间有一些关系。我们可以指定一个轮廓是如何互相连接的,例如,它是否是其它轮廓的子节点,或者它是否是父节点等。此关系的表示形式称为层次结构。
考虑下面的示例图片:
在这幅图像里,这里有一些形状被标记为0-5,2和2a表示最外面盒子的外部和内部轮廓。
这里,轮廓0,1,2是外部或者最外面的。我们可以说,它们在层次结构0中,或者只是它们处于同一层次结构中。
接下来是轮廓2a,它可以被认为是轮廓2的子轮廓(或者相反,轮廓2是轮廓2a的父轮廓)。所以,让它在层次1,类似的,轮廓3是轮廓2的子轮廓,并且它在下一层出现。最后,轮廓4,5是轮廓3a的子轮廓,它们进入最后一个层级。从我对盒子的编号方式来看,我会说轮廓4是轮廓3a的第一个子轮廓(也就是轮廓5)。
我提到了这些名词术语。相同的层级(same hierarchy level),外部轮廓(external contour),子轮廓(child contour),父轮廓(parent contour),第一个子轮廓(first child)。好了现在进入OpenCV.
4.OpenCV中的层次结构的表示(HierarchyRepresentation in OpenCV)
所以每个轮廓都有自己的信息,包括它是什么层级、谁是它的孩子,谁是它的父母等。OpenCV将它表示为一个包含四个值的数组:[Next,Previous,First_child,Parent].
5.轮廓检索模式(ContourRetrieval Mode)
1)RETR_LIST
这是四种flag中最简单(从解释的角度看)。它只检索所有轮廓,但不创建任何父子关系。在这条规则下,父母和孩子是平等的,他们只是轮廓。即它们都属于同一层级。
所以在这里,层级数组中的第三和第四项总是-1。但显然,next和previous项将具有相应的值。只需自己检查并验证它。
下面是我得到的结果,每行都是相应轮廓的层次结构细节。例如,第一行对应于轮廓0,下一个轮廓是轮廓1。因此,next=1没有先前的轮廓,所以previous=0.其余两个,如前所述,它是-1.
>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[ 3, 1, -1, -1],
[ 4, 2, -1, -1],
[ 5, 3, -1, -1],
[ 6, 4, -1, -1],
[ 7, 5, -1, -1],
[-1, 6, -1, -1]]])
如果你不使用任何层次结构功能,则这是在代码中使用的理想选择。
2)RETR_EXTERNAL
如果你使用这个标志,它只会返回极端的外部标志。所有的孩子轮廓都被留下。我们可以说,根据这条规定,只有每个家庭中年长的人才会得到照顾。它不关系家庭的其它成员。
那么,在我们的图像中,那里还有多少个极端的外部轮廓呢?即在层级0级别的?只有3个,即轮廓0,1,2对吗?现在尝试用这个flag查找轮廓。在这里,给每个元素的值与上面相同。将它与以上结果进行比较以下是我得到:
hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[-1, 1, -1, -1]]])
3)RETR_CCOMP
该flag检索所有轮廓并将它们排列为2级层次结构。即对象的外部轮廓(即其边界)被放置在层级-1中。对象内的孔的轮廓(如果有的话)放置在层次结构-2中。如果它内部有任何物体,其轮廓只能重新放置在层次1中。它在等级2中的孔洞等等。
只要考虑黑色背景上的“大白色的零”图像即可。零的外圈属于第一层次,零的内圈属于第二层次。
我们可以用一个简单的图像来解释它。在这里,我们已经用红色标出了轮廓的顺序以及它们所属的层次,颜色为绿色(1或2)。顺序与OpenCV检测轮廓的顺序相同。
所以考虑第一个轮廓,即轮廓0.它的层次结构是1.它有两个孔,轮廓1和2,它们属于层次2.因此对于轮廓0,同一层级中的下一个轮廓是轮廓3.其第一个孩子是在等级2中的轮廓-1。他没有父项,因此它在层次结构1中。所以它的层次数组是[3,-1,1,-1]。
现在轮廓-1。他在层次结构2中。下一个在同一层次中(在轮廓-1的父项之下)是轮廓2.没有前一个,也就是没有孩子。父母的轮廓是0,所以数组是[2,-1,-1,0].
同样的轮廓-2:它在层次-2.轮廓0下的同一层次中没有下一个轮廓。所以没有下一步。以前是轮廓-1.没有孩子啊,父母是轮廓-0.所以数组是[-1,1,-1,0].
轮廓-3:层次-1中的下一个轮廓是-5.以前是轮廓-0。孩子是轮廓4并且没有父母。所以数组是[5,0,4,-1].
轮廓4:在轮廓-3下的层次2中,他没有兄弟。所以没有下一个,没有以前,没有孩子,父母是轮廓3.所以数组是[-1,-1,-1,3].
这是我得到的最终答案:
>>> hierarchy
array([[[ 3, -1, 1, -1],
[ 2, -1, -1, 0],
[-1, 1, -1, 0],
[ 5, 0, 4, -1],
[-1, -1, -1, 3],
[ 7, 3, 6, -1],
[-1, -1, -1, 5],
[ 8, 5, -1, -1],
[-1, 7, -1, -1]]])
4)RETR_TREE
这是最后一个,它检索所有轮廓并创建完整的家庭层次列表。它甚至告诉我们,爷爷,父亲,儿子,孙子是谁,甚至还有….
例如,我拍摄了上面的图像,重写了cv2.RETR_TREE的代码,根据OpenCV给出的轮廓重新排序并对其进行分析。再次,红色字母给出等高线数字,绿色字母给出等级顺序。
取轮廓-0:它在层次结构-0中。同一层次中的下一个轮廓是林廓-7.没有以前的轮廓。孩子是轮廓-1.没有父母。所以数组是[7,-1,1,-1].
采取轮廓-2:它在层次1.没有同一级别的轮廓。没有前一个,孩子是轮廓2。父级轮廓为0。所以数组是[-1,-1,2,0].
以下是完整答案:
>>> hierarchy
array([[[ 7, -1, 1, -1],
[-1, -1, 2, 0],
[-1, -1, 3, 1],
[-1, -1, 4, 2],
[-1, -1, 5, 3],
[ 6, -1, -1, 4],
[-1, 5, -1, 4],
[ 8, 0, -1, -1],
[-1, 7, -1, -1]]])
最后一份代码示例:
'''
OpenCV中的轮廓:
1.绘制轮廓:cv2.drawCOntours(img,contours,index,color,thickness)
2.轮廓近似法:有时候并不需要存储所有边界,只需要存储这条线的两个端点。删除所有冗余的点并压缩轮廓,从而大大节省内存
3.轮廓特征:cv2.moments()计算所有时刻值的字典,如物体的质心、物体的面积、周长、重心等
'''
import cv2
import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
img = cv2.imread('temple.png', 0)
ret, thresh = cv2.threshold(img, 100, 255, 0)
binary, contours, hierarchy = cv2.findContours(thresh, 1, 2)
cv2.imshow('1', binary)
cnt = contours[0]
'''
M = cv2.moments(cnt)
# 轮廓周长
perimeter = cv2.arcLength(cnt, True)
# 轮廓区域
area = cv2.contourArea(cnt) # area=M['m00']
# 轮廓近似
epsilon = 0.1 * cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, epsilon, True)
# 轮廓凸包
hull = cv2.convexHull(cnt)
print('cnt:', cnt)
print('轮廓近似坐标:', approx)
print('轮廓周长:', perimeter)
print('轮廓区域:', area)
print('图像时刻:', M)
'''
# 直线边界矩形:是一个直的矩形,不考虑对象的旋转,所以边界矩形的面积不会最小,这个矩形由cv2.boundingRect()发现
x, y, w, h = cv2.boundingRect(cnt)
img = cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
# 旋转矩形:边界矩形以最小面积绘制的,所以它考虑旋转,使用的函数是cv2.minAreaRect(),
# 返回一个Box2D的结构(中心(x,y),(宽度,高度),旋转角度)
# 绘制这个矩形需要矩形的四个角,它是通过函数cv2.boxPoints()获得的
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
img = cv2.drawContours(img, [box], 0, (0, 0, 255), 0)
# 最小封闭圈:使用函数cv2.minEnclosingCircle()找到对象的外接圆;它是一个以最小面积完全覆盖物体的圆圈
(x, y), radius = cv2.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
img = cv2.circle(img, center, radius, (0, 255, 0), 2)
# 拟合椭圆:接下来将一个椭圆拟合到一个对象,它返回椭圆被刻在其中的旋转矩形
ellipse = cv2.fitEllipse(cnt)
img = cv2.ellipse(img, ellipse, (0, 255, 0), 2)
# 拟合线条:用一条线来拟合一组点,下图包含一组白点,可以近似为一条线
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)
cv2.imshow('6', img)
'''
titles = ['原图', '直线边界矩形', '旋转矩形', '最小封闭圈', '拟合椭圆', '拟合线条']
images = [img, rectContours, rotateRect, minCloseCircle, fitEllipse, fitLine]
for i in range(6):
plt.subplot(2, 3, i + 1), plt.title(titles[i]), plt.imshow(images[i])
plt.xticks([]), plt.yticks([])
plt.show()
'''
cv2.waitKey(0) & 0xFF
结果: