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

轮廓查找:
• 为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理或者Canny 边界检测。
• 查找轮廓的函数会修改原始图像。如果你在找到轮廓之后还想使用原始图像的话,你应该将原始图像存储到其他变量中。
• 在OpenCV 中,查找轮廓就像在黑色背景中找白色物体。 你应该记住,要找的物体应该是白色而背景应该是黑色。

cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])

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

import cv2
img = cv2.imread('3.jpg',1)
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(imgray,127,255,0)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(255,255,0),3)
cv2.imshow("img",img)
print(hierarchy)
cv2.waitKey()

函数返回值:

contours:hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

返回值contours:定义为“vector<vector> contours”,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。 有多少轮廓,向量contours就有多少元素。

hierarchy:定义为“vector hierarchy”,先来看一下Vec4i的定义:

typedef Vec<int, 4> Vec4i;

Vec4i是Vec<int,4>的别名,定义了一个“向量内每一个元素包含了4个int型变量”的向量。所以从定义上看,hierarchy也是一个向量,向量内每个元素保存了一个包含4个int整型的数组。向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3],分别表示第 i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果当前轮廓没有对应的后一个 轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~hierarchy[i][3]的相应位被设置为默认值-1。

python 截取轮廓的图像 python提取轮廓_轮廓


实例2(二值化图像):

import cv2
import numpy as np

def contour(img):
    dst = cv2.GaussianBlur(img,(3,3),0)
    gray = cv2.cvtColor(dst,cv2.COLOR_BGR2GRAY)
    ret,binary = cv2.threshold(gray,0,255,cv2.THRESH_BINARY|cv2.THRESH_OTSU)
    cv2.imshow("binary",binary)
    #contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    contours, hierarchy = cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    for i,contour in enumerate(contours):
        cv2.drawContours(img,contours,-1,(0,0,255),2)
        print(i)
    cv2.imshow('img', img)

img = cv2.imread('4.jpg',1)
cv2.namedWindow('img', 0)
cv2.namedWindow('imgsrc', 0)
cv2.namedWindow('binary', 0)
cv2.resizeWindow('img', 400, 480)
cv2.resizeWindow('imgsrc', 400, 480)
cv2.resizeWindow('binary', 400, 480)
cv2.imshow('imgsrc', img)
contour(img)
cv2.waitKey(0)
cv2.destroyAllWindows()

python 截取轮廓的图像 python提取轮廓_轮廓_02

cv2.drawContours(img,contours,-1,(0,0,255),-1)  #最后一位为-1是为闭区间

python 截取轮廓的图像 python提取轮廓_轮廓特性_03


实例3(Canny 边界检测后图像):

import cv2
import numpy as np
def edge_demo(image):
    blurred = cv2.GaussianBlur(image, (3, 3), 0)
    gray = cv2.cvtColor(blurred, cv2.COLOR_RGB2GRAY)
    # xgrad = cv.Sobel(gray, cv.CV_16SC1, 1, 0) #x方向梯度
    # ygrad = cv.Sobel(gray, cv.CV_16SC1, 0, 1) #y方向梯度
    # edge_output = cv.Canny(xgrad, ygrad, 50, 150)
    edge_output = cv2.Canny(gray, 220, 250)
    cv2.imshow("Canny Edge", edge_output)
    return edge_output

def contour(img):
    binary = edge_demo(img)
    #contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    contours, hierarchy = cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    for i,contour in enumerate(contours):
       # cv2.drawContours(img,contours,-1,(0,0,255),2)
        cv2.drawContours(img, contours, -1, (0, 0, 255),2)
        print(i)
    cv2.imshow('img', img)


img = cv2.imread('7.jpg',1)
cv2.namedWindow('img', 0)
cv2.namedWindow('imgsrc', 0)
cv2.namedWindow('Canny Edge', 0)
cv2.resizeWindow('img', 400, 480)
cv2.resizeWindow('imgsrc', 400, 480)
cv2.resizeWindow('Canny Edge', 400, 480)
cv2.imshow('imgsrc', img)
#edge_demo(img)
contour(img)
cv2.waitKey(0)
cv2.destroyAllWindows()

python 截取轮廓的图像 python提取轮廓_组织结构_04


总结:根据不同的图像选择不同的方法进行求取轮廓!

重心求取:

cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])

零阶矩、一阶矩、二阶矩、三阶矩代表的含义:
数学上,“矩”是一组点组成的模型的特定的数量测度。
在力学和统计学中都有用到“矩”。
如果这些点代表“质量”,那么:
零阶矩表示所有点的 质量;
一阶矩表示 质心;
二阶矩表示 转动惯量。

如果这些点代表“概率密度”,那么:
零阶矩表示这些点的 总概率(也就是1);
一阶矩表示 期望;
二阶(中心)矩表示 方差;
三阶(中心)矩表示 偏斜度;
四阶(中心)矩表示 峰度;

求面积:

area = cv2.contourArea(cnt)

python 截取轮廓的图像 python提取轮廓_组织结构_05


求轮廓周长:

这个函数的第二参数可以用来指定对象的形状是闭合的(True),还是打开的(一条曲线)。

perimeter = cv2.arcLength(cnt,True)

python 截取轮廓的图像 python提取轮廓_组织结构_06


求轮廓近似:

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

epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)

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

python 截取轮廓的图像 python提取轮廓_python 截取轮廓的图像_07


求凸包://利用图像做差 来查找凸包缺陷

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

python 截取轮廓的图像 python提取轮廓_python 截取轮廓的图像_08


python 截取轮廓的图像 python提取轮廓_轮廓_09


python 截取轮廓的图像 python提取轮廓_OpenCV_10


上面一段是参考的,与自己使用结果不一致,留下记录。

hull = cv2.convexHull(cnt, True, False)      #
cv2.drawContours(img,hull,-1,(255,255,0),3)

python 截取轮廓的图像 python提取轮廓_轮廓_11

k = cv2.isContourConvex(cnt)
print("是否凸性:")
print(k)

python 截取轮廓的图像 python提取轮廓_python 截取轮廓的图像_12

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

x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

旋转的边界矩形这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为cv2.minAreaRect()。返回的是一个Box2D 结构,其中包含矩形左上角角点的坐标(x,y),矩形的宽和高(w,h),以及旋转角度。但是要绘制这个矩形需要矩形的4 个角点,可以通过函数cv2.boxPoints() 获得。

x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)

python 截取轮廓的图像 python提取轮廓_OpenCV_13


最小外接圆:

x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
x,y,w = cv2.minAreaRect(cnt)
print(x)
print(y)
print(w)
img = cv2.rectangle(img,(int(x[0]),int(x[1])),(int(x[0]+y[1]),int(x[1]+y[0])),(255,255,0),2)
**椭圆拟合:**
ellipse = cv2.fitEllipse(cnt)
im = cv2.ellipse(im,ellipse,(0,255,0),2)

python 截取轮廓的图像 python提取轮廓_python 截取轮廓的图像_14


直线拟合:

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

[vx,vy,x,y] = cv2.fitLine(cnt, cv2.DIST_L2,0,0.01,0.01)
print([vx,vy,x,y])
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)

python 截取轮廓的图像 python提取轮廓_组织结构_15


长宽比:

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

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

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

轮廓面积与凸包面积的比(Solidity):

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

与轮廓面积相等的圆形的直径(Equivalent Diameter):

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

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

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

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

mask = np.zeros(imgray.shape,np.uint8)
# 这里一定要使用参数-1, 绘制填充的的轮廓
cv2.drawContours(mask,[cnt],0,255,-1)
pixelpoints = np.transpose(np.nonzero(mask))

python 截取轮廓的图像 python提取轮廓_轮廓特性_16


最大值和最小值及它们的位置:

我们可以使用掩模图像得到这些参数。

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

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

mean_val = cv2.mean(im,mask = mask)

极点:
一个对象最上面,最下面,最左边,最右边的点。

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])

python 截取轮廓的图像 python提取轮廓_轮廓_17

#1.先找到轮廓
img = cv2.imread('convex.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.寻找凸包,得到凸包的角点
hull = cv2.convexHull(cnt)

#3.绘制凸包
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.polylines(image, [hull], True, (0, 255, 0), 2)

python 截取轮廓的图像 python提取轮廓_组织结构_18


其中函数cv2.convexHull()有个可选参数returnPoints,默认是True,代表返回角点的x/y坐标;如果为False的话,表示返回轮廓中是凸包角点的索引,比如说:

print(hull[0])  # [[362 184]](坐标)
hull2 = cv2.convexHull(cnt, returnPoints=False)
print(hull2[0])  # [510](cnt中的索引)
print(cnt[510])  # [[362 184]]

点到轮廓距离:
其中参数3为True时表示计算距离值:点在轮廓外面值为负,点在轮廓上值为0,点在轮廓里面值为正;参数3为False时,只返回-1/0/1表示点相对轮廓的位置,不计算距离。以点(100,100)为例:

dist = cv2.pointPolygonTest(cnt, (100, 100), True)  # -3.53

形状匹配:
函数cv2.matchShape() 可以帮我们比较两个形状或轮廓的相似度。如果返回值越小,匹配越好。它是根据Hu 矩来计算的。发生了旋转对匹配的结果影响也不是非常大。

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)
contours,hierarchy = cv2.findContours(thresh,2,1)
cnt1 = contours[0]
contours,hierarchy = cv2.findContours(thresh2,2,1)
cnt2 = contours[0]
ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
print(ret)

python 截取轮廓的图像 python提取轮廓_组织结构_19


我得到的结果是:

. A 与自己匹配0.0

. A 与B 匹配0.001946

. A 与C 匹配0.326911

python 截取轮廓的图像 python提取轮廓_OpenCV_20

轮廓的层次结构

python 截取轮廓的图像 python提取轮廓_轮廓特性_21


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

python 截取轮廓的图像 python提取轮廓_轮廓_22


在这幅图像中,我给这几个形状编号为0-5。2 和2a 分别代表最外边矩形的外轮廓和内轮廓。在这里边轮廓0,1,2 在外部或最外边。我们可以称他们为(组织结构)0 级,简单来说就是他们属于同一级。接下来轮廓2a。我们把它当成轮廓2 的子轮廓。它就成为(组织结构)第1 级。同样轮廓3 是轮廓2 的子轮廓,成为(组织结构)第3 级。最后轮廓4,5 是轮廓3a 的子轮廓,成为(组织结构)4 级(最后一级)。按照这种方式给这些形状编号,我们可以说轮廓4 是轮廓3a 的子轮廓(当然轮廓5 也是)。

不管层次结构是什么样的,每一个轮廓都包含自己的信息:谁是父,谁是子等。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。

轮廓检索模式:

RETR_LIST 从解释的角度来看,这中应是最简单。它只是提取所有的轮

廓,而不去创建任何父子关系。换句话说就是“人人平等”,它们属于同一级组织轮廓。

RETR_EXTERNAL 如果你选择这种模式的话,只会返回最外边的的轮廓,

所有的子轮廓都会被忽略掉。

RETR_TREE 终于到最后一个了,也是最完美的一个。这种模式下会返回

所有轮廓,并且创建一个完整的组织结构列表。

RETR_CCOMP 在这种模式下会返回所有的轮廓并将轮廓分为两级组织结

构。

python 截取轮廓的图像 python提取轮廓_组织结构_23


现在我们考虑轮廓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]