内容简介:轮廓可以简单地解释为连接所有连续点(沿边界)的曲线,具有相同的颜色或强度,是边缘的一部分。轮廓是形状分析和目标检测与识别的有效工具。为了获得更高的精度,一般会使用二值化的图像来进行处理,在寻找轮廓之前,首先需要检测一下边缘,如前面文档中提到的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 开源的图像处理库,优化移动端图像生成
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Art of Computer Programming, Volumes 1-3 Boxed Set
Donald E. Knuth / Addison-Wesley Professional / 1998-10-15 / USD 199.99
This multivolume work is widely recognized as the definitive description of classical computer science. The first three volumes have for decades been an invaluable resource in programming theory and p......一起来看看 《The Art of Computer Programming, Volumes 1-3 Boxed Set》 这本书的介绍吧!