十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
理论
成都创新互联公司坚持“要么做到,要么别承诺”的工作理念,服务领域包括:网站建设、成都做网站、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的桂阳网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
在前面的关于轮廓的几节里,我们介绍了轮廓相关的一些函数。但当我们用cv2.findContours()函数来找轮廓的时候,我们传入了一个参数,Contour Retrieval Mode。我们一般传的是cv2.RETR_LIST或者cv2.RETR_TREE这样就可以了。但是这个参数实际是什么意思呢?
并且在输出时我们得到了三个数组,第一个是图像,第二个是我们的轮廓,第三个输出名字是hierarchy。但是我们一直没用这个。
什么是层级?
一般来说我们用cv2.findContours()函数来检测图像里的目标,有时候目标在不同的地方,但是在有些情况下,有些图形在别的图形里面,就像图形嵌套,在这种情况下,我们把外面那层图形叫做parent,里面的叫child。这样图形里的轮廓之间就有了关系。我们可以指定一个轮廓和其他之间的是如何连接的,这种关系就是层级。
看下面的例子:
在这个图像里,不同的图形我标注了0-5,2和2a表示了最外层盒子的外部和内部轮廓。
这里轮廓0,1,2是外部的。我们可以说他们是hierarchy-0,或者他们是同层级的。
接下来是contour-2a,可以认为是轮廓-2的孩子,或者反过来,contour-2是contour-2a的父亲,所以它在hierarchy-1里。类似的contour-3是contour-2的孩子,在下一层级。最后contour4,5是contour-3a的孩子,它们在最后的层级。
OpenCV里的层级表示
每个轮廓有他自己的关于层级的信息,谁是他的孩子,谁是他的父亲等。OpenCV用一个包含四个值得数组来表示:[Next, Previous, First_Child, Parent]
"Next表明同一层级的下一个轮廓"
比如,在我们的图片里的contour-0,水上hi他相同层级的下一个轮廓?是contour-1,所以Next=1,对于Contour-1,下一个是contour-2,所以Next=2
那对于contour-2呢?没有同层级的下一个轮廓,所以Next=-1。那么对于contour-4呢?同层级的下一个是contour-5,所以下一个轮廓是contour-5.Next=5
"Previous指同层级的前一个轮廓"
和上面一样,contour-1的前一个是contour-0.contour-2的前一个contour-1.对于contour-0没有前序,所以-1
"First_Child指它的第一个孩子轮廓"
不用解释,对于contour-2,孩子是contour-2a,所以这里是contour-2a的索引,contour-3a有两个孩子,但我们只取第一个,是contour-4,所以First_Child=4.
"Parent指它的父轮廓索引"
和First_Child相反,contour-4和contour-5的parent都是contour-3a,对于contour-3a,是contour-3
注意:
如果没有孩子或者父亲,就为-1
我们知道了层级,现在来看OpenCV里的轮廓获取模式,四个标志cv2.RETR_LIST, cv2.RETR_TREE, cv2.RETR_CCOMP, cv2.RETR_EXTERNAL表示啥?
轮廓获取模式
1.RETR_LIST
这是最简单的一个,它获取所有轮廓,但是不建立父子关系,他们都是一个层级。
所以,层级属性第三个和第四个字段(父子)都是-1,但是Next和Previous还是有对应值。
下面是结果,每行是对应轮廓的层级信息。
hierarchy
2.RETR_EXTERNAL
如果用这个模式,它返回最外层的。所有孩子轮廓都不要,我们可以说在这种情况下,只有家族里最老的会被照顾,其他都不管。
所以在我们的图像里,有多少最外层的轮廓呢,有3个,contours 0,1,2
3.RETR_CCOMP
这个模式获取所有轮廓并且把他们组织到一个2层结构里,对象的轮廓外边界在等级1里,轮廓内沿(如果有的话)放在层级2里。如果别的对象在它里面,里面的对象轮廓还是放在层级1里,它的内沿在层级2.
看下面的例子,轮廓的顺序为红色,他们的层级是绿色,
看第一个轮廓,contour-0,他的层级是1,他有两个洞,contours1和2,他们都属于层级2,所以对于contour-0,Next是contour-3,没有前序,他的第一个孩子是contour-1,没有parent,所以层级数组是[3,-1,1,-1]
看contour-1,他在层级2里,Next是contour-2,没有前序,没有孩子,parent是contour-0,所以数组是[2,-1,-1,0]
同样对于contour-2,也在层级2里,没有next,前序是contour-1,没有孩子,parent是contour-0,所以[-1,1,-1,0]。
contour-3:next是contour-5,Previous是contour-0,Child是contour-4,没有parent,所以[5,0,4,-1]
contour-4:在层级2里,没有兄弟,所以没有Next,没有Previous,没有孩子,parent是contour-3,[-1,-1,-1,3]
4.RETR_TREE
最后,Mr.Perfect。它取回所有的轮廓并且创建完整的家族层级列表,它甚至能告诉你谁是祖父,父亲,儿子,孙子。。
比如把上面的图形用cv2.RETR_TREE,
对于contour-0:层级是0,Next是contour-7,没有previous,孩子是contour-1,没有parent,所以[7,-1,1,-1]
contour-1:在层级1里,没有同级的其他轮廓,没有previous,孩子是contour-2,所以[-1,-1,2,0]
OpenCV里的直方图
1图像矩
帮你计算一些属性,比如重心,面积等。
函数cv2.moments()会给你一个字典,包含所有矩值
你可以从这个里面得到有用的数据比如面积,重心等。重心可以用下面的式子得到:
2.轮廓面积
轮廓面积由函数cv2.contourArea()得到或者从矩里得到M['m00']
3.轮廓周长
可以用cv2.arcLength()函数得到。第二个参数指定形状是否是闭合的轮廓(如果传True)。或者只是一个曲线。
4.轮廓近似
这会把轮廓形状近似成别的边数少的形状,边数由我们指定的精确度决定。这是Douglas-Peucker算法的实现。
要理解这个,假设你试图找一个图像里的方块,但是由于图像里的一些问题,你得不到一个完美的方块,只能得到一个“坏方块”。现在你可以使用这个函数来近似,第二个参数叫epsilon,是从轮廓到近似轮廓的最大距离。是一个准确率参数,好的epsilon的选择可以得到正确的输出。
在下面第二个图像里,绿线显示了epsilon = 10% of arc length 的近似曲线。第三个图像显示了epsilon = 1% of the arc length。第三个参数指定曲线是否闭合。
5.凸形外壳
凸形外壳和轮廓近似类似,但是还不一样(某些情况下两个甚至提供了同样的结果)。这儿,cv2.convexHull()函数检查凸面曲线缺陷并修复它。一般来说,凸面曲线总是外凸的,至少是平的,如果它内凹了,这就叫凸面缺陷。比如下面这张图,红线显示了手的凸形外壳。双向箭头显示了凸面缺陷,是轮廓外壳的最大偏差。
参数详情:
·points 是我们传入的轮廓
·hull 是输出,一般我们不用传
·clockwise: 方向标示,如果是True,输出凸形外壳是顺时针方向的。否则,是逆时针的。
·returnPoints:默认是True。然后会返回外壳的点的坐标。如果为False,它会返回轮廓对应外壳点的索引。
所以要获得凸形外壳,下面
但是如果你想找到凸面缺陷,你需要传入returnPoints = False。我们拿上面的矩形图形来说,首先我找到他的轮廓cnt,现在用returnPoints = True来找他的凸形外壳,我得到下面的值:[[[234 202]], [[51 202]], [51 79]], [[234 79]]] 是四个角的点。如果你用returnPoints = False,我会得到下面的结果:[[129], [67], [0], [142]]. 这是轮廓里对应点的索引,比如cnt[129] = [234, 202]],这和前面结果一样。
6.检查凸面
有一个函数用来检查是否曲线是凸面, cv2.isContourConvex().它返回True或False。
7.边界矩形
有两种边界矩形
7.a.正边界矩形
这个矩形不考虑对象的旋转,所以边界矩形的面积不是最小的,函数是cv2.boundingRect()。
假设矩形左上角的坐标是(x,y), (w, h)是它的宽和高
7.b.渲染矩形
这个边界矩形是用最小面积画出来的,所以要考虑旋转。函数是cv2.minAreaRect()。它返回一个Box2D结构,包含了(左上角(x,y),(width, height),旋转角度)。但是要画这个矩形我们需要4个角。这四个角用函数cv2.boxPoints()得到
8.最小闭包圆
我们找一个目标的外接圆可以用函数cv2.minEnclosingCircle().这个圆用最小面积完全包围目标。
9.椭圆
用一个椭圆来匹配目标。它返回一个旋转了的矩形的内接椭圆
10. 直线
类似的我们可以匹配一根直线,下面的图像包含一系列的白色点,我们可以给它一条近似的直线。
END
等高线可以简单地解释为连接所有连续点(沿着边界)的曲线,具有相同的颜色或强度。等高线是形状分析、目标检测和识别的有用工具。
让我们看看如何找到二值图像的轮廓:
cv2.findcontours() 函数中有三个参数,
第一个是源图像
第二个是轮廓检索模式
第三个是轮廓近似方法
它输出图像、轮廓和层次。轮廓是图像中所有轮廓的python列表。每个轮廓都是对象边界点(x,y)坐标的numpy数组。
使用 cv2.drawContours, 它也可以用来绘制任何给出边界点的形状。它的第一个参数是源图像,第二个参数是应该作为等高线列表,第三个参数是等高线索引(在绘制个体轮廓时使用)。要绘制所有轮廓,传递-1)、颜色、厚度等。
要绘制图像中的所有轮廓:
要绘制第4个轮廓:
但大多数情况下,下面的方法更适合:
这是 cv2.findContours 函数中的第三个参数的说明。
如果传递 cv2.CHAIN_APPROX_NONE,则存储所有边界点。但实际上我们需要所有的要点吗?例如,你发现了一条直线的轮廓。你需要这条线上所有的点来代表这条线吗?不,我们只需要这条线的两个端点。这就是 cv2.CHAIN_APPROX_SIMPLE所做的。它删除所有冗余点并压缩轮廓,从而节省内存。
你可以提取有用的数据,如面积、质心等。质心可以通过下面的方式来获取
它将一个轮廓形状近似为另一个形状,根据我们指定的精度,顶点数量较少。
为了理解这一点,假设您试图在图像中找到一个正方形,但是由于图像中的一些问题,您没有得到一个完美的正方形,而是一个“坏形状”(如下面第一幅图像所示)。现在可以使用此函数来近似形状。在这里,第二个参数称为epsilon,它是从轮廓到近似轮廓的最大距离。这是一个精度参数。为了得到正确的输出,需要明智地选择epsilon。
下图,在第二幅图像中,绿线显示了epsilon=10%弧长的近似曲线。第三张图片显示的epsilon=弧长的1%相同。第三个参数指定曲线是否关闭。
凸面船体看起来类似于轮廓近似,但事实并非如此(在某些情况下两者都可能提供相同的结果)。在这里,cv2.converxHull()函数检查曲线是否存在凸性缺陷,并对其进行修正。一般来说,凸曲线是指总是凸出的曲线,或者至少是平的曲线。如果它在内部膨胀,就称为凸性缺陷。例如,检查下面的手图像。红线表示手的凸面外壳。双面箭头标记显示了凸性缺陷,即船体与轮廓的局部最大偏差。
points 是我们经过的轮廓。
hull 是输出,通常我们避免它。
clockwise :方向标志。如果为真,则输出凸壳为顺时针方向。否则,它是逆时针方向的。
returnPoints :默认为真。然后返回船体点的坐标。如果为false,则返回与外壳点对应的轮廓点索引。
因此,如上图所示,要得到一个凸面外壳,以下就足够了:
有一个函数可以检查曲线是否是凸的,cv2.isContourConvex()。它只是返回正确还是错误。
直的矩形,不考虑物体的旋转。所以边界矩形的面积不会是最小的。它由函数 cv2.boundingrect() 找到。
设(x,y)为矩形的左上角坐标,(w,h)为矩形的宽度和高度。
在这里,边界矩形是用最小面积绘制的,因此它也考虑了旋转。使用的函数是 cv2.minareect() 。但是要画这个矩形,我们需要四个角。它是通过函数 cv2.boxpoints() 获得的。
在这里,我们将学习提取一些经常使用的对象属性,如坚固性、等效直径、遮罩图像、平均强度等。
范围是轮廓区域与边界矩形区域的比率。
坚固性是指轮廓面积与其凸面外壳面积之比。
等效直径是面积与轮廓面积相同的圆的直径。
方向是指向对象的角度。下面的方法也给出了长轴和短轴的长度。
在某些情况下,我们可能需要包含该对象的所有点。可以这样做:
这里,有两种方法,一种是使用numpy函数,另一种是使用opencv函数(最后一行注释)。结果也相同,但有轻微的差异。numpy以(行、列)格式给出坐标,而opencv以(x、y)格式给出坐标。所以基本上答案会互换。注意,row=x,column=y。
极端点是指物体的最上面、最下面、最右边和最左边的点。
一.查找轮廓
cv2.findContours()
[image,] contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
轮廓检索方式:
轮廓近似方法:
二.绘制轮廓
cv2.drawContours()
cv2.drawContours(img, contours, -1, (0, 0, 255), 2)
上一篇已经讲解了很多算子用来检测边缘,其中用得最多的是canny边缘检测。只有边缘还不够,有很多时候我们还需要获得图片上的某物体轮廓。
轮廓可以简单认为成连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
Opencv提供了一个函数findContours()用于发现轮廓,它有三个参数,第一个是输入图像,第二个是轮廓检索模式,第三个是轮廓近似方法。
findContours()的返回值有三个,第一个是图像,第二个是轮廓,第三个是(轮廓的)层析结构。最常用的是第二个返回值。
轮廓(第二个返回值)是一个Python列表,其中储存这图像中所有轮廓。每一个轮廓都是一个Numpy数组,包含对象边界点(x,y)的坐标。
Opencv提供了一个函数drawContours()用于绘制轮廓。
轮廓特征计算的结果并不等同于像素点的个数,而是根据几何方法算出来的,所以有小数。
参数2表示轮廓是否封闭
形状的外接矩形有两种,如下图,绿色的叫外接矩形,表示不考虑旋转并且能包含整个轮廓的矩形。蓝色的叫最小外接矩,考虑了旋转。
其中np.int0(x)是把x取整的操作,比如377.93就会变成377,也可以用x.astype(np.int)
外接圆跟外接矩形一样,找到一个能包围物体的最小圆:
我们可以用得到的轮廓拟合出一个椭圆:
第一个参数是某一轮廓。第二个参数是像素点坐标。第三个参数如果为True则输出该像素点到轮廓最近距离;如果为False,则输出为正表示在轮廓内,0为轮廓上,负为轮廓外。