内容简介:轮廓可以简单地解释为连接所有连续点(沿边界)的曲线,具有相同的颜色或强度,是边缘的一部分。轮廓是形状分析和目标检测与识别的有效工具。为了获得更高的精度,一般会使用二值化的图像来进行处理,在寻找轮廓之前,首先需要检测一下边缘,如前面文档中提到的canny边缘检测算法。opencv通过findContos函数来寻找物体轮廓,从OpenCV 3.2开始,此函数不再修改源图像,而是将修改后的图像作为三个返回参数中的第一个参数返回。
轮廓可以简单地解释为连接所有连续点(沿边界)的曲线,具有相同的颜色或强度,是边缘的一部分。轮廓是形状分析和目标检测与识别的有效工具。
为了获得更高的精度,一般会使用二值化的图像来进行处理,在寻找轮廓之前,首先需要检测一下边缘,如前面文档中提到的canny边缘检测算法。
opencv通过findContos函数来寻找物体轮廓,从OpenCV 3.2开始,此函数不再修改源图像,而是将修改后的图像作为三个返回参数中的第一个参数返回。
在OpenCV中,寻找轮廓就像从黑色背景中寻找白色物体一样,要找到的物体应该是白色的,背景应该是黑色的。
下面是一个例子:
import numpy as np import cv2 im = cv2.imread('test.jpg') imgray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(imgray, 127, 255, 0) im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
输入有三个参数,一个是原图二值化图像,第二个是轮廓检索模式,第三个是轮廓逼近方法。并输出修改后的图像、轮廓和层次结构。输出的轮廓是图片中所有轮廓的一个列表,每个单独的轮廓都是对象边界点(x,y)坐标的Numpy数组。
1.2 绘制轮廓
opencv使用cv2.drawContours函数来绘制轮廓,它也可以用来绘制任何形状,只要你有它的边界点。第一个参数是原图像,第二个参数是轮廓列表,第三个参数是要绘制的轮廓索引(在绘制单个轮廓时有用),若要绘制所有轮廓,则传入-1。剩余的参数是颜色,厚度等。
# 绘制图片中所有轮廓 cv2.drawContours(img, contours, -1, (0,255,0), 3) # 绘制单个轮廓,比如第4个轮廓 cv2.drawContours(img, contours, 3, (0,255,0), 3) #但是更加推荐使用下面的方法。这俩看上去是一样的,后面会提到为啥下面这种更好 cnt = contours[4] cv2.drawContours(img, [cnt], 0, (0,255,0), 3)
1.3 轮廓逼近方法
轮廓逼近方法是cv2.findContos函数中的第三个参数。它有什么作用呢?
上面关于轮廓的定义中提到,轮廓是一个形状的具有相同强度的边界。它存储着形状边界的(x,y)坐标。但是它能存储所有的坐标吗?这就是由轮廓逼近方法指定的。
如果传入cv2.CHAIN_Approx_NONE,则存储所有边界点。但我们真的需要所有的顶点吗?例如,你找到了一条直线的轮廓线。你需要这条线上的所有点来表示这条线吗?不,我们只需要那条线的两个端点,这就是cv2.CHAIN_Approx_Simple所做的事情,它删除所有冗余点并压缩轮廓,从而节省内存。
下面的矩形图像演示了这个配置,在轮廓数组中的所有坐标上画一个圆圈(用蓝色绘制)。第一张图是用cv2.CHAIN_Approx_NONE获得的所有轮廓点(734点),第二张图绘制cv2.CHAIN_Approx_SIMPLE(只有4个点)的点,这种情况下可以节省大量的内存!
2. 轮廓特征
2.1 图像矩
图像矩可以用来计算出物体的质心、面积等特征,更多的用途可以在 其wiki页 上看到。
opencv用cv2.moments()函数来计算所有矩值:
import cv2 import numpy as np img = cv2.imread('star.jpg',0) ret,thresh = cv2.threshold(img,127,255,0) im2,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'])
2.2 轮廓面积
Contour area is given by the function cv2.contourArea() or from moments, M[‘m00’].
轮廓的面积可以用函数 cv2.contourArea()
函数来计算,或者可以直接读取矩值中的 M['m00']
area = cv2.contourArea(cnt)
2.3 轮廓周长
轮廓周长也称为弧长,可以使用cv2.arcLength()函数计算。第二个参数指定轮廓是封闭的形状(True),还是不封闭的曲线。
perimeter = cv2.arcLength(cnt,True)
2.4 轮廓近似
轮廓近似法根据我们设置的精度,将轮廓近似成另一个顶点数较少的其它形状。它是 Douglas-Peucker算法(wiki) 的实现。
为了理解这一点,假设你试图在图像中找到一个正方形,但是由于图像形状的问题,你没有得到一个完美的正方形,而是一个“糟糕的形状”(如下面的第一张图片所示)。这种情况下就可以使用这个函数来得到一个近似形状。决定近似程度的参数称为epsilon,它是从轮廓到近似轮廓的最大距离。这是一个精确的参数。为了得到正确的输出,需要传入合适的epsilon。
下面的例子中,第一张图是原图,第二张图是将epsilon设置为周长10%的结果,第二张图是设置为周长的1%。
epsilon = 0.1*cv2.arcLength(cnt,True) approx = cv2.approxPolyDP(cnt,epsilon,True) #第三个参数为轮廓是否封闭
2.5 凸包
凸包看起来有点像轮廓近似,但事实并非如此(在某些情况下两者可能提供相同的结果)。cv2.conexHull()函数检查曲线的凸性缺陷并加以修正。一般说来,凸曲线是指凸出的,或者至少是平坦的曲线。如果存在向内部凹陷的部分,就叫做凸性缺陷。例如下面的手的图像,红线显示手的凸包,双面箭头表示凸性缺陷,这是凸包和轮廓的局部最大偏差。
hull = cv2.convexHull(points[, hull[, clockwise[, returnPoints]]
- points 传入的轮廓
- hull 输出凸包结果,一般不传,从返回值获取
- clockwise 是否顺时针
- returnPoints 默认是True,结果会返回凸包中的顶点列表,若设置为False,则只返回轮廓顶点中,处于凸包中顶点的索引列表
可以通过下面的代码计算得到凸包:
hull = cv2.convexHull(cnt)
但是,如果要查找凸性缺陷,则需要传入returPoint=false。为了理解它,例如上面的矩形图像,首先找到它的轮廓,随后找到它的凸包,它的返回点为True,得到了下面的值:[234 202],[51 202],[51 79],[234 79],这些值是矩形的四个角点,即:[234 202],[51 202],[51 79],[234 79]。现在,如果对returPoint=false执行同样的操作,我将得到以下结果:[129],[67],[0],[142]。这些是轮廓中对应点的索引。例如第一个值:CNT[129]=[234,202],这与第一个结果相同(对于其他结果依此类推)。
2.6 检查凸性
可以用cv2.isConourConvex()函数来检查曲线是否是凸的,它只会返回True或False。
k = cv2.isContourConvex(cnt)
2.7 矩形边框
矩形边框有两种:
水平/垂直矩形边框,它不考虑对象的旋转,因此矩形面积可能不是最小的包裹矩形面积,可以用cv2.boundingRect()函数计算的到。
旋转矩形边框,边框矩形是按最小的面积绘制的,考虑了旋转,可以用cv2.minAreaRect()函数计算得到。它返回一个Box2D结构,包含下面这些属性:中心点(x,y),宽度,高度,旋转角度。绘制这个矩形的时候,需要的是4个角点坐标,可以由函数cv2.boxPoint()计算得到。
rect = cv2.minAreaRect(cnt) box = cv2.boxPoints(rect) box = np.int0(box) cv2.drawContours(img,[box],0,(0,0,255),2)
两种矩形都显示在了下面的图像中,绿色矩形是正常边界矩形,红色矩形是旋转的矩形:
2.8 最小包围圆
使用函数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)
2.9 拟合椭圆
cv2.fitEllipse()返回拟合到对象区域的椭圆,函数返回椭圆内接的旋转矩形。
ellipse = cv2.fitEllipse(cnt) cv2.ellipse(img,ellipse,(0,255,0),2)
2.10 拟合直线
类似地,可以将一条直线拟合到一组点上,下图包含一组白点,可以近似拟合出一条直线。
ows,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)
3. 轮廓属性
opencv可以提取一些常用的对象属性,如紧密性,等效直径,图像掩码,平均强度等。更多特性可以在 Matlab regionprops 文档 中找到。
(注:第2节中提到的质心、面积、周长等也属于轮廓属性,这里就不再重复提及了) 。
3.1 纵横比
轮廓的宽高比值
x,y,w,h = cv2.boundingRect(cnt) aspect_ratio = float(w)/h
3.2 面积区域
面积区域是轮廓面积与轮廓包围矩形的面积之比
area = cv2.contourArea(cnt) x,y,w,h = cv2.boundingRect(cnt) rect_area = w*h extent = float(area)/rect_area
3.3 紧密性
紧密性是轮廓面积与其凸包面积之比。
area = cv2.contourArea(cnt) hull = cv2.convexHull(cnt) hull_area = cv2.contourArea(hull) solidity = float(area)/hull_area
3.4 等效直径
等效直径是面积与轮廓面积相同的圆的直径。
area = cv2.contourArea(cnt) equi_diameter = np.sqrt(4*area/np.pi)
3.5 方向
方向是对象定向的角度,虾米东东函数还提供了长轴和次轴的长度。
(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)
3.6 掩码和像素点
在某些情况下,我们可能需要构成该对象的所有点,可以通过下面的操作得到:
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)格式提供坐标。所以基本上结果会是相反的(行=x和列=y)。
3.7 最大值最小值及其位置
可以通过掩码来获取这些特征
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)
3.8 平均颜色或平均强度
可以计算对象的平均颜色,也可以是灰度模式下对象的平均强度,同样也是借助掩码来计算。
mean_val = cv2.mean(im,mask = mask)
3.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])
例如,对印度地图求极值点,会得到以下结果:
4. 其它相关函数
4.1 凸性缺陷检测
在第2节中,描述了什么是凸包。物体与凸包之间的任何偏离均可视为凸性缺陷。
OpenCV提供了cv2.conexityDefects()函数用来检测凸性缺陷:
hull = cv2.convexHull(cnt,returnPoints = False) defects = cv2.convexityDefects(cnt,hull)
这里要注意,在计算凸包时,必须传参returPoint=False,以检测凸性缺陷。
此函数返回一个数组,其中每行都包含这些值:轮廓上起点、轮廓上终点、轮廓上最远点、到最远点的近似距离。用图像来表示就是,画一条线连接起点和终点,然后在最远的点画一个与连线相切的圆,半径就是到最远点的近似距离。注意返回的前三个值是轮廓cnt的索引,必须从cnt数据中得到这些值。
import cv2 import numpy as np img = cv2.imread('star.jpg') img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) ret,thresh = cv2.threshold(img_gray, 127, 255,0) im2,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()
4.2 点到多边形的最短距离
opencv提供了cv2.pointPolygonTest函数来计算图像中的点与轮廓之间的最短距离,当点在轮廓内部时返回负数,在外部时返回正数,如果在轮廓上则为0。
例如,计算点(50,50)到轮廓的最短距离:
dist = cv2.pointPolygonTest(cnt,(50,50),True)
在函数中,第三个参数表示是否计算距离,如果为True,则返回带符号的距离,如果为False,则会计算该点是在轮廓内部还是外部还是在轮廓上(分别返回+1、-1、0)。
如果不想计算距离,第三个参数一定要传False,因为距离计算是一个耗时的过程,设置为False将提供大约2-3倍的性能提升。
4.3 形状匹配
OpenCV提供了函数cv2.matShapes()来比较两个形状或两个轮廓,并返回一个表示相似性的值,值越小,表示匹配度越好。相似度是基于hu-moment值来计算的,对计算结果进行分析得到相似度, 文档 中描述了不同的计算方法。
import cv2 import numpy as np img1 = cv2.imread('star.jpg',0) img2 = cv2.imread('star2.jpg',0) ret, thresh = cv2.threshold(img1, 127, 255,0) ret, thresh2 = cv2.threshold(img2, 127, 255,0) im2,contours,hierarchy = cv2.findContours(thresh,2,1) cnt1 = contours[0] im2,contours,hierarchy = cv2.findContours(thresh2,2,1) cnt2 = contours[0] ret = cv2.matchShapes(cnt1,cnt2,1,0.0) print( ret )
在下面三个形状中进行匹配:
结果:
- A,A: 0.0
- A,B: 0.001946
- A,C: 0.326911
从结果中可以看到,这个匹配是具有一定的旋转不变性的。
( Hu-Moments 矩阵是对平移、旋转和缩放不变的七个值,第七个是斜不变量。可以使用 cv2.HuMoments 函数来计算这些值)
5. 轮廓层次
本节讨论对象轮廓的层次结构,即多个轮廓间的父子关系。
在前面几节中,使用了OpenCV提供的与轮廓相关的一些函数。但是,当使用cv2.findContures()函数在图像中找到轮廓时,传入了一个参数,轮廓检索模式,通常使用cv2.RETR_LIST或cv2.RETR_tree,一般能取得不错的效果,但这到底意味着什么呢?
此外,在输出中,得到了三个数组,第一个是图像,第二个是轮廓,还有一个我们命名为层次结构的输出(前面小节的代码中可见)。不过在之前的章节中,从来没有使用过这个层次结构数据。那么这个层次是什么,能用来做什么,与传入的轮廓模式参数有什么关系呢?
5.1 轮廓层次概念
使用cv2.findContures函数寻找轮廓时,某些形状可能会位于其他形状的内部,就像嵌套的图形一样。在这种情况下,我们称外部为父级,内部为子级。这样,图像中的轮廓彼此之间就有了某种关系,此时可以描述轮廓间是如何相互连接的,比如是其他轮廓的子轮廓,还是父轮廓等等。这种关系的表示就是层次结构。例如:
在这张图中,有几个形状,分别从0到5编号。2和2a表示最外框的外部轮廓和内部轮廓。
在这里,轮廓0,1,2是外部的或或者说是最外面的轮廓,它们就在层级0中,简而言之,它们处于相同的层次结构级别。
接下来是轮廓2a,可以将其视为轮廓2的子级(或者说,轮廓2是轮廓2a的父级),所以让它处于层级1。类似地,轮廓3是轮廓2的子级,它位于下一个层次中。最后,轮廓4,5是轮廓3a的子级,它们位于最后一个层次级别。
5.2 轮廓层次在opencv中的表示
每个轮廓都有自己的层次信息,它是什么层次,谁是它的子对象,谁是它的父对象等等。OpenCV将其表示为一个由四个值组成的数组:[Next,Previous,First_Child,Parent],next/previous表同一层级的下一个/上一个轮廓。First_Child表示第一个子轮廓, Parent表示福轮廓,如果这些轮廓不存在,那么值都是-1.
例如在上图中取轮廓0。谁是它的同一层级的下一个轮廓呢?答案是是轮廓1。也就是说,next=1。同样,对于轮廓1,下一个是轮廓2,所以next=2。轮廓2同一级别中没有下一个轮廓,则next=-1。轮廓4与轮廓5处于同一层级,它的下一个轮廓是轮廓5,next=5。previous同理。
OpenCV对层次结构的表示已经明了,我们可以借助上面的相同图像来测试OpenCV中的轮廓检索模式,例如cv2.RETR_LIST、cv2.RETR_TREE、cv2.RETR_CCOMP、cv2.RETR_EXTERNAL等。
5.3 轮廓检索模式
5.3.1 RETR_LIST
这是四个检索中最好理解的一个,它只检索所有轮廓,但不创建任何父子关系。在这种模式下,父轮廓和子轮廓是平级的,都属于同一层次。
因此,层次数组中的第三项和第四项总是-1。而next和previous还是会有其相应的值。
下面是示例结果,每一行都是相应轮廓的层次细节。例如,第一行对应于轮廓0,下一个轮廓是轮廓1,所以next=1,没有上一个轮廓,因此previous=-1,parent和first_child都是-1。
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]]])
如果不使用层次特征的话,代码中推荐使用这种模式。
5.3.2 RETR_EXTERNAL
这种模式仅返回最外部轮廓,所有的子轮廓都会被丢弃,即只返回层级为0的轮廓。刚刚的例子中,就只会返回轮廓0,1,2。
array([[[ 1, -1, -1, -1], [ 2, 0, -1, -1], [-1, 1, -1, -1]]])
5.3.3 RETR_CCOMP
此模式会检索所有轮廓,并将其排列为两级层次结构。对象的外部轮廓放置在”层次1“中。对象内部孔的轮廓(如果有的话)放置在“层次2”中。如果对象中还有子对象,则其轮廓将再次放置在“层次1”中,内部空的轮廓放在层次2中,以此类推。
如下图,红色标记了轮廓的顺序,绿色标记了轮廓所属的层次(1或2)。该顺序与OpenCV检测轮廓的顺序相同。
得到的结果:
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]]])
5.3.4 RETR_TREE
此模式检索所有轮廓并创建一个完整的层次列表,包含前面说的next,previous,parent,first_child。
最终得到的结果:
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图像处理系列(六)—— 图像梯度
- Python 图像处理 OpenCV (15):图像轮廓
- Opencv图像处理系列(三)——图像二值化
- Opencv图像处理系列(八)—— 图像金字塔
- Facebook 开源图像处理库 Spectrum,优化移动端图像生成
- Spectrum:Facebook 开源的图像处理库,优化移动端图像生成
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。