OpenCV-Python 中文教程15——OpenCV 中的轮廓

一、初识轮廓

目标

• 理解什么是轮廓
• 学习找轮廓,绘制轮廓等

• 函数: cv2.findContours(), cv2.drawContours()

1、什么是轮廓

轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
      • 为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理或者 Canny 边界检测。
      • 查找轮廓的函数会修改原始图像。如果在找到轮廓之后还想使用原始图像的话,那应该将原始图像存储到其他变量中。
      • 在 OpenCV 中,查找轮廓就像在黑色背景中找白色物体。

那么如何在一个二值图像中查找轮廓?函数 cv2.findContours() 有三个参数,第一个是输入图像,第二个是轮廓检索模式,第三个是轮廓近似方法。返回值有三个,第一个是图像,第二个是轮廓,第三个是(轮廓的)层析结构。轮廓(第二个返回值)是一个 Python列表,其中存储这图像中的所有轮廓。每一个轮廓都是一个 Numpy 数组,包含对象边界点(x, y)的坐标。

2、如何绘制轮廓

函数 cv2.drawContours() 可以被用来绘制轮廓。它可以根据你提供的边界点绘制任何形状。它的第一个参数是原始图像,第二个参数是轮廓,一个 Python 列表。第三个参数是轮廓的索引(在绘制独立轮廓是很有用,当设置为 -1 时绘制所有轮廓)。接下来的参数是轮廓的颜色和厚度等。参考博客:Opencv—轮廓检测。

在一幅图像上绘制所有的轮廓:

import numpy as np
import cv2
img = cv2.imread('cat.jpg')imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
image1, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)# 绘制独立轮廓,如第四个轮廓:
image2 = cv2.drawContours(thresh, contours, -1, (0,255,0), 3)# image3 = cv2.drawContours(thresh, contours, 3, (0,0,255), 3)cv2.namedWindow("img")
cv2.imshow("img",img)cv2.namedWindow("image1")
cv2.imshow("image1",image1)cv2.namedWindow("image2")
cv2.imshow("image2",image2)
#
# cv2.namedWindow("image3")
# cv2.imshow("image3",image3)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:

3、轮廓的近似方法

这是函数 cv2.findCountours() 的第三个参数。它到底代表什么意思呢?由于轮廓是一个形状具有相同灰度值的边界,会存贮形状边界上所有的 (x, y) 坐标。但需要将所有的这些边界点都存储吗?这就是这个参数要告诉函数 cv2.findContours 的。

这个参数如果被设置为 cv2.CHAIN_APPROX_NONE,所有的边界点都会被存储。但是真的需要这么多点吗?例如,当需要寻找的边界是一条直线时,用直线上所有的点来表示直线吗?不是的,只需要这条直线的两个端点而已。这就是 cv2.CHAIN_APPROX_SIMPLE 要做的。 它会将轮廓上的冗余点都去掉,压缩轮廓,从而节省内存开支。

我们用下图中的矩形来演示这个技术。在轮廓列表中的每一个坐标上画一个蓝色圆圈。第一个图显示使用 cv2.CHAIN_APPROX_NONE 的效果,共有很多个点。第二个图是使用 cv2.CHAIN_APPROX_SIMPLE 的结果,只有 4 个点。

二、轮廓特征(参考:https://jingyan.baidu.com/article/4f7d5712fe74cf1a20192782.html)

目标

• 查找轮廓的不同特征,例如面积,周长,重心,边界框等。

• 你会学到很多轮廓相关函数

1、矩

图像的矩可以帮助我们计算图像的质心,面积等 ,具体参考Image Moments。函数 cv2.moments() 会将计算得到的矩以一个字典的形式返回。例如:

import cv2
import numpy as np
img = cv2.imread('cat.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
image, 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'])print(cx)
print(cy)

运行结果:

{'m11': 403860.0, 'mu02': 3.0, 'm30': 40207125.0, 'm03': 163872926.0, 'm02': 645163.0, 'm01': 2540.0, 'mu30': 7.450580596923828e-09, 'mu12': 0.0, 'nu20': 0.2166666666665697, 'm20': 252831.66666666666, 'nu11': 0.0, 'mu11': 0.0, 'mu03': 0.0, 'm21': 64219243.333333336, 'nu21': 1.5645846789371704e-11, 'mu20': 21.666666666656965, 'nu03': 0.0, 'm12': 102580917.0, 'nu30': 2.3560804576936214e-11, 'm10': 1590.0, 'm00': 10.0, 'nu12': 0.0, 'mu21': 4.94765117764473e-09, 'nu02': 0.030000000000000006}
159
254

2、轮廓的面积与周长

轮廓的面积可以使用函数 cv2.contourArea() 计算得到,也可以使用矩(0 阶矩), M['m00']。轮廓的周长被称为弧长,可以使用函数 cv2.arcLength() 计算得到。这个函数的第二参数可以用来指定对象的形状是闭合的(True),还是打开的(一条曲线)。

import cv2
import numpy as np
img = cv2.imread('cat.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
image, contours,hierarchy = cv2.findContours(thresh, 1, 2)
cnt = contours[0]
# 轮廓的面积
area = cv2.contourArea(cnt)
print(area)
# 轮廓的周长
perimeter = cv2.arcLength(cnt,True)
print(perimeter)

运行结果:

10.0
13.656854152679443

3、轮廓的近似

       将轮廓形状近似到另外一种由更少点组成的轮廓形状,新轮廓的点的数目由我们设定的准确度来决定。使用的Douglas-Peucker算法,可以到维基百科获得更多此算法的细节。为了帮助理解,假设需要在一幅图像中查找一个矩形,但是由于图像的种种原因,我们不能得到一个完美的矩形,而是一个“坏形状”(如下图所示)。现在你就可以使用这个函数来近似这个形状()了。这个函数的第二个参数叫epsilon,它是从原始轮廓到近似轮廓的最大距离。它是一个准确度参数。选择一个好的 epsilon 对于得到满意结果非常重要。

import cv2
import numpy as np
img = cv2.imread('phi.jpg',0)
ret,thresh = cv2.threshold(img,127,255,0)
image, contours,hierarchy = cv2.findContours(thresh, 1, 2)
cnt = contours[0]epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)print(epsilon)
print(approx)

运行结果:

36.409040009975435
[[[186  75]][[ 91  55]][[ 52 109]]]

下边第二幅图中的绿线是当 epsilon = 10% 时得到的近似轮廓,第三幅图是当 epsilon = 1% 时得到的近似轮廓。第三个参数设定弧线是否闭合。

示例代码:

import cv2# 1.先找到轮廓
img = cv2.imread('phi.jpg', 0)
_, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
image, contours, hierarchy = cv2.findContours(thresh, 3, 2)
cnt = contours[0]# 2.进行多边形逼近,得到多边形的角点
approx = cv2.approxPolyDP(cnt, 3, True)# 3.画出多边形
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [approx], True, (0, 255, 0), 2)cv2.namedWindow("img")
cv2.imshow("img",image)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:


其中cv2.approxPolyDP()的参数2(epsilon)是一个距离值,表示多边形的轮廓接近实际轮廓的程度,值越小,越精确;参数3表示是否闭合。

4、凸包

凸包与轮廓近似相似,但不同,虽然有些情况下它们给出的结果是一样的。函数 cv2.convexHull() 可以用来检测一个曲线是否具有凸性缺陷,并能纠正缺陷。一般来说,凸性曲线总是凸出来的,至少是平的。如果有地方凹进去了就被叫做凸性缺陷。参考文章:https://www.jianshu.com/p/d53bdfb1051f,示例代码:

import cv2
im = cv2.imread('phi.jpg')
g = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,g = cv2.threshold(g,200,255,0)
a,b,c = cv2.findContours(g,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = b[0]
hull = cv2.convexHull(cnt)
print(hull) #轮廓的索引点
t = cv2.cvtColor(a, cv2.COLOR_GRAY2BGR)
i = cv2.polylines(im, [hull], True, (255, 0, 0), 2)cv2.namedWindow("img")
cv2.imshow("img",i)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:

[[[360 221]][[358 405]][[356 414]][[ 94 417]][[ 94 287]][[ 96  52]][[133  44]][[233  44]][[347  46]][[360 105]]]


参数说明:

hull = cv2.convexHull(points, hull, clockwise, returnPoints]

• points 我们要传入的轮廓

• hull 输出,通常不需要

• clockwise 方向标志。如果设置为 True,输出的凸包是顺时针方向的。否则为逆时针方向。

• returnPoints 默认值为 True。它会返回凸包上点的坐标。如果设置为 False,就会返回与凸包点对应的轮廓上的点。

要获得上图的凸包,下面的命令就可以:

hull = cv2.convexHull(cnt)

但如果想获得凸性缺陷,需要把 returnPoints 设置为 False。

5、凸性检测

函数 cv2.isContourConvex() 可以用来检测一个曲线是不是凸的,它只能返回 True 或 False。

k = cv2.isContourConvex(cnt)

6、边界矩形

(1)直边界矩形

一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。所以边界矩形的面积不是最小的。可以使用函数 cv2.boundingRect() 查找得到。(x, y)为矩形左上角的坐标,(w, h)是矩形的宽和高。

import cv2
im = cv2.imread('11.png')
g = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,g = cv2.threshold(g,200,255,0)
a,b,c = cv2.findContours(g,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = b[0]x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)cv2.namedWindow("img")
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果:

(2)旋转的边界矩形 
       这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为 cv2.minAreaRect()。返回的是一个 Box2D 结构,其中包含矩形左上角角点的坐标(x, y),矩形的宽和高(w, h),以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv2.boxPoints() 获得。
import cv2
import numpy as npim = cv2.imread('22.png')
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,180,255,cv2.THRESH_BINARY)
image, contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = contours[0]# 旋转的边界矩形
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(im, [box], 0, (0, 0, 255), 2)cv2.imshow('result',im)
cv2.waitKey(0)

运行结果:

7、最小外接圆
        函数 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)

8、椭圆拟合
      使用的函数为 cv2.ellipse(),返回值其实就是旋转边界矩形的内切圆。

ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(im,ellipse,(0,255,0),2)

9、直线拟合

      可以根据一组点拟合出一条直线,同样我们也可以为图像中的白色点拟合出一条直线。

rows,cols = img.shape[:2]
#cv2.fitLine(points, distType, param, reps, aeps[, line ]) → line
#points – Input vector of 2D or 3D points, stored in std::vector<> or Mat.
#line – Output line parameters. In case of 2D fitting, it should be a vector of
#4 elements (likeVec4f) - (vx, vy, x0, y0), where (vx, vy) is a normalized
#vector collinear to the line and (x0, y0) is a point on the line. In case of
#3D fitting, it should be a vector of 6 elements (like Vec6f) - (vx, vy, vz,
#x0, y0, z0), where (vx, vy, vz) is a normalized vector collinear to the line
#and (x0, y0, z0) is a point on the line.
#distType – Distance used by the M-estimator
#distType=CV_DIST_L2
#ρ(r) = r2 /2 (the simplest and the fastest least-squares method)
#param – Numerical parameter ( C ) for some types of distances. If it is 0, an optimal value
#is chosen.
#reps – Sufficient accuracy for the radius (distance between the coordinate origin and the
#line).
#aeps – Sufficient accuracy for the angle. 0.01 would be a good default value for reps and
#aeps.
[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)
综上所述的完整代码:
import cv2
import numpy as npim = cv2.imread('22.png')
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,180,255,cv2.THRESH_BINARY)
image, contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = contours[0]# 直边界矩形
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(im,(x,y),(x+w,y+h),(0,255,0),2)# 旋转的边界矩形
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(im, [box], 0, (0, 0, 255), 2)# 最小外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv2.circle(im,center,radius,(255,0,0),2)# 椭圆拟合
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(im,ellipse,(255,255,0),2)# 直线拟合
rows,cols = im.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)
im = cv2.line(im,(cols-1,righty),(0,lefty),(0,255,255),2)cv2.imshow('result',im)
cv2.waitKey(0)

运行结果:

三、轮廓的性质

本节将学习提取一些经常使用的对象特征,参考网址:Matlab regionprops documentation。

1、长宽比

边界矩形的宽高比:

x,y,w,h = cv2.boundingRect(cnt)
aspect_ratio = float(w)/h

2、Extent

       轮廓面积与边界矩形面积的比:

area = cv2.contourArea(cnt)
x,y,w,h = cv2.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area

3、Solidity

轮廓面积与凸包面积的比 :

area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area)/hull_area

4、Equivalent Diameter 

         与轮廓面积相等的圆形的直径:

area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)

5、方向

      对象的方向,下面的方法还会返回长轴和短轴的长度 :

(x,y),(MA,ma),angle = cv2.fitEllipse(cnt)

6、掩模和像素点

      有时需要构成对象的所有像素点,可以这样做:

mask = np.zeros(imgray.shape,np.uint8)
# 这里一定要使用参数-1, 绘制填充的的轮廓
cv2.drawContours(mask,[cnt],0,255,-1)
#Returns a tuple of arrays, one for each dimension of a,
#containing the indices of the non-zero elements in that dimension.
#The result of this is always a 2-D array, with a row for
#each non-zero element.
#To group the indices by element, rather than dimension, use:
#transpose(nonzero(a))
#>>> x = np.eye(3)
#>>> x
#array([[ 1., 0., 0.],
# [ 0., 1., 0.],
# [ 0., 0., 1.]])
#>>> np.nonzero(x)
#(array([0, 1, 2]), array([0, 1, 2]))
#>>> x[np.nonzero(x)]
#array([ 1., 1., 1.])
#>>> np.transpose(np.nonzero(x))
#array([[0, 0],
# [1, 1],
# [2, 2]])
pixelpoints = np.transpose(np.nonzero(mask))
#pixelpoints = cv2.findNonZero(mask)

这里共有两种方法,第一种方法使用了 Numpy 函数,第二种使用了 OpenCV 函数。结果相同,但还是有点不同。 Numpy 给出的坐标是(row,colum)形式的。而 OpenCV 给出的格式是(x, y)形式的。所以这两个结果基本是可以互换的。 row=x, colunm=y。

7、最大值和最小值及它们的位置

       可以使用掩模图像得到这些参数 :

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray,mask = mask)

8、平均颜色及平均灰度

可以使用相同的掩模求一个对象的平均颜色或平均灰度:

mean_val = cv2.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])

实际例子:

import cv2
import numpy as npim = cv2.imread('22.png')
imgray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,180,255,cv2.THRESH_BINARY)
image, contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = contours[0]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])cv2.drawMarker(im,leftmost,(0,255,0),2)
cv2.drawMarker(im,rightmost,(0,255,0),2)
cv2.drawMarker(im,topmost,(0,255,0),2)
cv2.drawMarker(im,bottommost,(0,255,0),2)cv2.imshow('result',im)
cv2.waitKey(0)

运行结果:

四、轮廓:更多函数

目标

• 凸缺陷,以及如何找凸缺陷

• 找某一点到一个多边形的最短距离

• 不同形状的匹配

1、凸缺陷

轮廓的凸包对象上的任何凹陷都被成为凸缺陷。OpenCV 中有一个函数 cv.convexityDefect() 可以找到凸缺陷。函数调用如下:

hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)

值得注意的是:如果要查找凸缺陷,在使用函数 cv2.convexHull 找凸包时,参数returnPoints 一定要是 False。 它会返回一个数组,其中每一行包含的值是 [起点,终点,最远的点,到最远点的近似距离]。我们可以在一张图上显示它,将起点和终点用一条绿线连接,在最远点画一个圆圈,要记住的是返回结果的前三个值是轮廓点的索引。因此我们还要到轮廓点中去找:

import cv2
import numpy as np
img = cv2.imread('22.png')
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(img_gray, 127, 255,0)
image, 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()

运行结果:

2、Point Polygon Test

       求解图像中的一个点到一个对象轮廓的最短距离。如果点在轮廓的外部,返回值为负。如果在轮廓上,返回值为 0。如果在轮廓内部,返回值为正。下面以点(50, 50)为例:

dist = cv2.pointPolygonTest(cnt,(50,50),True)

此函数的第三个参数是 measureDist。如果设置为 True,就会计算最短距离。如果是 False,只会判断这个点与轮廓之间的位置关系(返回值为+1, -1, 0)。 因此,如果不需要知道具体距离,建议将第三个参数设False,这样速度会提高 2 到 3 倍。

3、形状匹配

函数 cv2.matchShape() 可以帮我们比较两个形状或轮廓的相似度。如果返回值越小,匹配越好。它是根据 Hu 矩来计算的。文档中对不同的方法都有解释。

import cv2
import numpy as np
img1 = cv2.imread('22.png',0)
img2 = cv2.imread('11.png',0)
ret, thresh = cv2.threshold(img1, 127, 255,0)
ret2, thresh2 = cv2.threshold(img2, 127, 255,0)
image, contours,hierarchy = cv2.findContours(thresh,2,1)
cnt1 = contours[0]
image2, contours2,hierarchy2 = cv2.findContours(thresh2,2,1)
cnt2 = contours2[0]
ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
print(ret)

运行结果:

2.0089771107078076

及时发生了旋转对匹配的结果影响也不是非常大,Hu 矩是归一化中心矩的线性组合,之所以这样做是为了能够获取代表图像的某个特征的矩函数,这些矩函数对某些变化如缩放,旋转,镜像映射(除了 h1)具有不变形。

五、轮廓的层次结构

目标——学习轮廓的层次结构,比如轮廓之间的父子关系

使用函数 cv2.findContours 来查找轮廓,需传入一个参数:轮廓提取模式(Contour_Retrieval_Mode)。我们总是把它设置为 cv2.RETR_LIST 或者是 cv2.RETR_TREE,效果还可以,但是它们到底代表什么呢?

同时,得到的结果包含 3 个数组,第一个图像,第二个是轮廓,第三个是层次结构。但是我们从来没有用过层次结构,层次结构是用来干嘛的呢?层次结构与轮廓提取模式有什么关系呢?

1、什么是层次结构?

       通常使用函数 cv2.findContours 在图片中查找一个对象。有时对象可能位于不同的位置,还有些情况,一个形状在另外一个形状的内部,这种情况下我们称外部的形状为父,内部的形状为子。按照这种方式分类,一幅图像中的所有轮廓之间就建立父子关系。这样我们就可以确定一个轮廓与其他轮廓是怎样连接的,比如它是不是某个轮廓的子轮廓,或者是父轮廓。这种关系就成为组织结构 。下图就是一个简单的例子 :

在这幅图像中, 假设这几个形状编号为 0-5。 2 和 2a 分别代表最外边矩形的外轮廓和内轮廓。在这里边轮廓 0, 1, 2 在外部或最外边,我们可以称他们为(组织结构)0 级,简单来说就是他们属于同一级。

接下来轮廓 2a,我们把它当成轮廓 2 的子轮廓。它就成为(组织结构)第1 级,同样轮廓 3 是轮廓 2 的子轮廓,成为(组织结构)第 3 级。最后轮廓4,5 是轮廓 3a 的子轮廓,成为(组织结构)4 级(最后一级)。按照这种方式给这些形状编号,我们可以说轮廓 4 是轮廓 3a 的子轮廓(当然轮廓 5 也是)。

2、OpenCV 中层次结构

不管层次结构是什么样的,每一个轮廓都包含自己的信息:谁是父,谁是子等。 OpenCV 使用一个含有四个元素的数组表示。 [Next, Previous,First_Child, Parent]。

        Next 表示同一级组织结构中的下一个轮廓。

以上图中的轮廓 0 为例,轮廓 1 就是他的 Next。同样,轮廓 1 的 Next是 2, Next=2。那轮廓 2 呢?在同一级没有 Next。这时 Next=-1。而轮廓 4 的 Next为 5,所以它的 Next=5。

Previous 表示同一级结构中的前一个轮廓。

与前面一样,轮廓 1 的 Previous 为轮廓 0,轮廓 2 的 Previous 为轮廓 1。轮廓 0 没有 Previous,所以 Previous=-1。

First_Child 表示它的第一个子轮廓。

没有必要再解释了,轮廓 2 的子轮廓为 2a。所以它的First_Child 为2a。那轮廓 3a 呢?它有两个子轮廓。但是我们只要第一个子轮廓,所以是轮廓4(按照从上往下,从左往右的顺序排序)。

        Parent 表示它的父轮廓。
        与 First_Child 刚好相反。轮廓 4 和 5 的父轮廓是轮廓 3a。而轮廓 3a的父轮廓是 3。
        如果没有父或子,就为 -1。 现在了解 OpenCV 中的轮廓组织结构后,还是根据上边的图片再学习一下 OpenCV 中的轮廓检索模式:cv2.RETR_LIST,cv2.RETR_TREE,cv2.RETR_CCOMP,cv2.RETR_EXTERNAL

到底代表什么意思?

3、轮廓检索模式

RETR_LIST 从解释的角度来看,这中应是最简单的。它只是提取所有的轮廓,而不去创建任何父子关系。换句话说就是“人人平等”,它们属于同一级组织轮廓。

所以在这种情况下,组织结构数组的第三和第四个数都是 -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]]])

RETR_EXTERNAL 如果你选择这种模式的话,只会返回最外边的的轮廓,所有的子轮廓都会被忽略掉。所以在上图中使用这种模式的话只会返回最外边的轮廓(第 0 级):轮廓0, 1, 2。下面是我选择这种模式得到的结果:

>>> hierarchy
array([[[ 1, -1, -1, -1],
[ 2, 0, -1, -1],
[-1, 1, -1, -1]]])

当只想得到最外边的轮廓时,可以选择这种模式,这在有些情况下很有用。
        RETR_CCOMP 在这种模式下会返回所有的轮廓并将轮廓分为两级组织结构。例如,一个对象的外轮廓为第 1 级组织结构。而对象内部中空洞的轮廓为第 2 级组织结构,空洞中的任何对象的轮廓又是第 1 级组织结构。空洞的组织
结构为第 2 级。

想象一下一副黑底白字的图像,图像中是数字 0。 0 的外边界属于第一级组织结构, 0 的内部属于第 2 级组织结构。以下图为例简单介绍一下,其中红色数字为这些轮廓编号,并用绿色数字代表它们的组织结构。顺序与 OpenCV 检测轮廓的顺序一致。

现在我们考虑轮廓 0,它的组织结构为第 1 级。其中有两个空洞 1 和 2,它们属于第 2 级组织结构。所以对于轮廓 0 来说跟他属于同一级组织结构的下一个(Next)是轮廓 3,并且没有 Previous。它的 Fist_Child 为轮廓1,组织结构为 2。由于它是第 1 级,所以没有父轮廓。因此它的组织结构数组为[3, -1, 1, -1]。

轮廓 1,它是第 2 级。处于同一级的下一个轮廓为 2。没有 Previous,也没有 Child,(因为是第 2 级所以有父轮廓)父轮廓是 0。所以数组是[2, -1, -1, 0]。
       轮廓 2:它是第 2 级。在同一级的组织结构中没有 Next。 Previous 为轮廓 1。没有子,父轮廓为 0,所以数组是 [-1, 1, -1, 0]。
       轮廓 3:它是第 1 级。在同一级的组织结构中 Next 为 5。 Previous 为轮廓 0。子为 4,没有父轮廓,所以数组是 [5, 0, 4, -1]。
       轮廓 4:它是第 2 级。在同一级的组织结构中没有 Next。没有 Previous,没有子,父轮廓为 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]]])

RETR_TREE 终于到最后一个,也是最完美的一个。这种模式下会返回所有轮廓,并且创建一个完整的组织结构列表。它甚至会告诉你谁是爷爷,爸爸,儿子,孙子等。

还是以上图为例,使用这种模式,对 OpenCV 返回的结果重新排序并分析它,红色数字是边界的序号,绿色是组织结构 。

轮廓 0 的组织结构为 0,同一级中 Next 为 7,没有 Previous。子轮廓是 1,没有父轮廓。所以数组是 [7, -1, 1, -1]。
       轮廓 1 的组织结构为 1,同一级中没有其他,没有 Previous。子轮廓是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-Python 中文教程15——OpenCV 中的轮廓相关推荐

  1. python是人都能学会_人人都能学会的python编程教程15:高级特性2

    生成器 如果你想要一百万个数,而这些数里只有一百个数是你经常要用的,剩下的都几乎不怎么会用到,那么如果直接把这一百万个数全部放在list中是不明智的因为这会浪费较多存储空间,生成器就是为了解决这个问题 ...

  2. OpenCV演示代码以查找图像中的轮廓(附完整代码)

    OpenCV演示代码以查找图像中的轮廓 OpenCV演示代码以查找图像中的轮廓 OpenCV演示代码以查找图像中的轮廓 #include "opencv2/imgcodecs.hpp&quo ...

  3. opencv python安装 centos_在Ubuntu中安装OpenCV-Python

    和Fedora差不多,Ubuntu安装OpenCV基本上和它是一致的,所以在安装方法上基本上大体相同,有一些不一样的地方我会着重写出来,下面的步骤是在Ubuntu 16.04和18.04(64位)测试 ...

  4. opencv python 编译_编译opencv python

    1, 下载并且安装python2.7 + numpy 2,运行cmakegui打开opencv.应该要显示如下信息: Python 2: Interpreter: C:/Python27/python ...

  5. python opencv用法中文教程

    https://www.cnblogs.com/Undo-self-blog/p/8423851.html

  6. python 图像无缝拼接,OpenCV Python 系列教程3 - Core 组件

    基本知识 灰度图像的存储方式: image 多通道图像存储方式 image OpenCV 中的通道存储为 BGR 像素值的存储方式 RGB 模式,显示设备采用这种模式 HSV.HLS 将颜色分解成色调 ...

  7. python中文教程-中谷python中文视频教程(全38集)

    python在线教学视频教程共40节,中谷教育录制,主要介绍了python编程方面的知识. python在线教学-01-走进python python在线教学-02-开始编程吧 python在线教学- ...

  8. python环境安装opencv,Python环境搭建之OpenCV的步骤方法

    一.openCV介绍 Open Source Computer Vision Library.OpenCV于1999年由Intel建立,如今由Willow Garage提供支持.OpenCV是一个基于 ...

  9. opencv python是什么_Python+OpenCV 十几行代码模仿世界名画

    现在很多人都喜欢拍照(自拍).有限的滤镜和装饰玩多了也会腻,所以就有 APP 提供了模仿名画风格的功能,比如 prisma.versa 等,可以把你的照片变成梵高.毕加索.蒙克等大师的风格. 这种功能 ...

最新文章

  1. SAP实施项目中采购员在非生产性采购申请审批流中的角色安排
  2. ThinkPHP5整合LayUI编辑器图片上传
  3. AJAX俺也不会,是真的,不过,以后就会了
  4. 操作系统-命令解释程序(实验一)
  5. php网站标签加小图标,在htmltitle/title标签添加图标,网页title左边显示网页的logo图标...
  6. discuz viewthread.php,修改discuz论坛的标题header和footer
  7. 【CodeForces - 1042C】Array Product(思维,有坑细节)
  8. oracle key的含义,v$session SERIAL#字段的含义
  9. ubuntu磁盘分区问题
  10. 打开Hololens自动相机,和live stream
  11. Linux 执行 Shell脚本报错,“syntax error: unexpected end of file” 原因及处理
  12. HearthBuddy卡组
  13. 手机邮箱怎么弄_如何设置Android手机邮箱的详细教程
  14. 微信卡劵、微信卡包,必须是认证订阅号或认证服务号
  15. USACO3.2.4 Feed Ratios (ratios)
  16. 北京哪里可以买到含羞草啊?或者种子也行
  17. 今年来,知识分享与内容付费趋势已经暗潮汹涌
  18. 安装SQL2008时遇到未能加载文件或file:///d:microsoft..sql.chainer.packagedata.dll或它的某个依赖项
  19. [转载]我的老师唐圭璋先生(王兆鹏)
  20. webinspect安全扫描

热门文章

  1. ocr初始化失败怎么办_win10重置初始化失败怎么办_win10重置初始化失败的处理办法...
  2. 飞浆AI studio人工智能课程学习(2)-Prompt优化思路|十个技巧高效优化Prompt|迭代法|Trick法|通用法|工具辅助
  3. python数据分析的交叉分析和分组分析 -第三次笔记
  4. HuaWei ❀ IP源防护概述
  5. Auto.js Pro安卓免ROOT引流脚本开发系列教程23网易公开课(1)-前言
  6. Unreal Engine 4 —— GAS系统学习 (二十八) 创建Lazer招式与GameplayEffect
  7. 强光手电充电快才能持久使用(LDR6328S)
  8. NodeJS学习:环境变量
  9. mysql limit acs_Oracle Acs资深顾问罗敏 老罗技术核心感悟:牛! 11g的自动调优和
  10. Microchip最新推出的ATMEGA4809-XPRO开发板简介