OpenCV4-python 学习笔记 之 图像轮廓

1 查找并绘制图像轮廓

一个轮廓对应一系列的点,在OpenCV中提供函数 cv2.fingContours() 用于查找图像轮廓。
并可以根据参数返回特定的轮廓曲线,而函数 cv2.drawCountours() 可以将轮廓绘制到图像上。

1.1 函数介绍

1.1.1 cv2.findContours()

查找图像轮廓: cv2.findContours() 的语法格式如下 :

image,contours,hierarchy = cv2.findContours(image,moda,method)

返回值 :
(1). image : 与参数中的原始图像一致
(2). countours : 返回的轮廓
(3). hierarchy : 图像的拓扑信息(轮廓层次)

参数 :
(4). image : 原始图像(为8位单通道,所有非零值被处理为1所有零值保持不变,即将灰度图像先采用阈值处理为二值图像)
(5). mode : 轮廓检索模式
(6). method : 轮廓的近似方法

PS : cv4x及以上版本这个返回值是没有第一个原始图像的的,也就是说只写后两个即可

下面来细说下各个参数及返回值 :
(1). 返回值contours :
该值返回的是一组轮廓信息,是由若干点构成的 例如 : contourd[i][j] 表示的是第i个轮廓内的第j个点
其type属性为list类型其每个元素都是图像的一个轮廓用Numpy中的ndarray结构表示
轮廓的个数可由 len(contours) 表示
len(contours[i])表示第i个轮廓中包含的像素点数
也可用contours[i].shape来获取每个轮廓内点的shape属性
(2). 返回值hierarchy :
若一个轮廓区内包含着另一个轮廓则称这个轮廓为父轮廓,其内包的轮廓被称为子轮廓
而返回值hierarchy包含着这种层次关系
每个轮廓contours[i]对应4个元素来说明当前轮廓的层次关系,其形式为;
[Next,Previous,First_Child,Parent]
Next:后一个轮廓的索引编号
Previous:前一个轮廓的索引编号
First_Child:第一个子轮廓的索引编号
Parent:父轮廓的索引编号
若上述关系对应为空时该值被设为 -1
(3). 参数image :
必须为8位单通道的二值图像,一般情况下都是将图像处理为二值图像后再将其作为参数image使用
(4). 参数mode :
该参数决定了轮廓的提取方式,具体由4种
a.cv2.RETR_EXTERNAL : 只检测外轮廓
b.cv2.RETE_LIST : 对检测到的轮廓部建立等级关系
c.cv2.RETE_CCOMP : 检测所有轮廓并将其组织成两级结构上面一层为外边界,下面的则为内孔的边界
d.cv2.RETE_TREE : 建立一个等级树结构的轮廓
(5). 参数method :
决定了如何表达轮廓,可为如下值
cv2.CHAIN_APPROX_NONE 储存所有像素点,相邻两点间误差<=1
cv2.CHAIN_APPROX_SIMPLE 压缩水平,垂直对角线方向的元素只保留该方向的终点坐标,在极端情况下一个矩形只需要四个点

1.1.2 cv2.drawContours()

绘制图像轮廓: cv2.drawContours() 其语法格式如下 :

image = cv2.drawContours(image,contours,contourldx,color[,thickness[,lineType[,hierarchy[,maxLevel[,offset]]]]])

返回值 :
(1). 返回值为image表示目标图像即绘制了边缘的原始图像

参数 :
(2). image : 待绘制轮廓的图像(注意:函数会直接在原始图像上绘制,即此行语句后图像不再是原始图像)
(3). contours : 需要绘制的轮廓(与findContours()的输出contours相同)
(4). contourldx : 需要绘制的边缘索引(该值为负数(-1)时绘制全部轮廓)
(5). color : 要绘制的颜色
(6). thickness : 可选参数,对应轮廓的粗细
(7). lineType : 可选参数,绘制轮廓时所用的线型
(8). hierarchy : 对应函数findContours()所输出的层次信息
(9). maxLevel : 控制所绘制轮廓层次的深度(如果该值为0则表示只绘制第0层轮廓)
(10). offset : 偏移参数,该参数使轮廓偏移到不同的位置展现出来

PS: 该函数可以写作无返回值的形式(在原图像上操作)

1.2 实例

1.2.1 绘制一幅图片中所有的轮廓

from cv2 import cv2 as cv

img = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
cv.imshow("img",img)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)

# contourldx = -1 表示绘制所有轮廓 (255,191,0)表示轮廓为浅蓝色  最后的thickness = 5 表示轮廓的宽度为5像素
img = cv.drawContours(img,contours,-1,(255,191,0),5)
cv.imshow("res",img)
cv.waitKey()
cv.destroyAllWindows()

效果 :
OpenCV4-python 学习笔记 之 图像轮廓_第1张图片

1.2.2 逐个显示一幅图像中的边缘信息

cv2.drawContours()中参数contourldx设置为具体的索引值并通过循环语句逐一绘制轮廓

from cv2 import cv2 as cv
import numpy as np 

img = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
cv.imshow("img",img)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_NONE)

n = len(contours)
# 生成列表储存各个轮廓
contoursimg = []
for i in range(n):
    temp = np.zeros(img.shape,np.uint8)
    contoursimg.append(temp)
    contoursimg[i] = cv.drawContours(contoursimg[i],contours,i,(255,255,255),2)
    cv.imshow("contours[" + str(i) + "]",contoursimg[i])

cv.waitKey()
cv.destroyAllWindows()

效果 :
OpenCV4-python 学习笔记 之 图像轮廓_第2张图片

1.2.3 使用轮廓绘制功能提取前景图像(轮廓内的图像)

将 cv2.drawcontours() 中参数 thickness 的值设置为 -1 即可提取前景图像的实心轮廓
再将该图像(的实心轮廓)与原图像按位与即可将前景图像从原图像中提取出来

from cv2 import cv2 as cv
import numpy as np 

img = cv.imread(r"D:\anaconda\vscode-python\pic\timg.jpg")
cv.imshow("img",img)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)

# 创建掩码图像
mask = np.zeros(img.shape,np.uint8)
mask = cv.drawContours(mask,contours,-1,(255,255,255),-1)
cv.imshow("mask",mask)
loc = cv.bitwise_and(img,mask)
cv.imshow("loc",loc)
cv.waitKey()
cv.destroyAllWindows()

效果 :
OpenCV4-python 学习笔记 之 图像轮廓_第3张图片

2 矩特征

OpenCV提供函数 cv2.moments() 函数来获取图像的矩特征
所谓矩特征,即包括了对应对象不同类型的几何特征,如位置大小角度形状等

2.1 函数介绍

2.1.1 获取轮廓特征

通常情况下,我们将通过函数 cv2.moments() 获取的轮廓特征称为 ’ 轮廓矩 ’ , 其语法格式如下 :

retval = cv2.moments(array[,binaryImage])

参数 :
(1). array : 可以为点集也可以为二值图像(不过无论什么都会被当作轮廓处理)
(2). binaryImage : 该参数为True时,array内所有非零值均被处理为1(该参数只在array为图像时有效)

返回值 :
返回值retval为矩特征,主要包括 :
(1). 空间矩 : 零阶矩: m00 , 一阶矩: m10,m01 , 二阶矩: m20,m11,m02 , 三阶矩: m30,m21,m12,m03
(2). 中心矩 : 二阶中心矩: mu20,mu11,mu02 , 三阶中心矩: mu30,mu21,mu12,mu03
(3). 归一化中心矩 : 二阶Hu矩: nu20,nu11,nu02 , 三阶Hu矩: nu30,nu21,nu12,nu03

上述矩都是根据公式计算得到的, 大都较为抽象, 但是很明显,如果两个轮廓的矩特征一致, 那么这两个轮廓就是一致的, 另外就是零阶矩’ m00 ’ 为一轮廓的面积

2.1.2 计算轮廓的面积–contourArea函数

retval = cv2.contourArea(contour[,oriented])

返回值 :
(1). retval : 为所求面积值

参数 :
(1). contour : 为轮廓
(2). orientde : 为一bool值,默认为false表示返回值为面积绝对值,若为True则返回值包含正负号,用来表示轮廓是顺时针还是逆时针

2.1.3 计算轮廓长度–arcLength()函数

retval = cv2.arcLength(curve,closed)

返回值 :
(1). retval : 为轮廓周长

参数 :
(1). curve : 为轮廓
(2). closed为bool类型,表示轮廓是否封闭,为True时表示轮廓封闭

2.2 实例

2.2.1 使用函数 cv2.moments()提取一幅图像的特征

from cv2 import cv2 as cv 
import numpy as np

o = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
cv.imshow("original",o)
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)

n = len(contours)
contoursImg = []
for i in range(n):
    temp = np.zeros(o.shape,np.uint8)
    contoursImg.append(temp)
    contoursImg[i] = cv.drawContours(contoursImg[i],contours,i,255,3)
    cv.imshow("contours[" + str(i) + "]",contoursImg[i])
    
print("观察各个轮廓的矩: \n")
for i in range(n):
    print("轮廓" + str(i) + "的矩 : \n",cv.moments(contours[i]))
    
print("观察各个矩的面积 : \n")
for i in range(n):
    print("轮廓" + str(i) + "的面积: %d" %cv.moments(contours[i])['m00'])
    
cv.waitKey()
cv.destroyAllWindows()



#输出:
'''
观察各个轮廓的矩: 

轮廓0的矩 :
 {'m00': 226631.5, 'm10': 312977968.6666666, 'm01': 116261853.16666666, 'm20': 437567547260.4166, 'm11': 160557522830.4583, 'm02': 63069609247.916664, 'm30': 619043840858645.4, 'm21': 224471863420618.75, 'm12': 87099060422400.27, 'm03': 35871120534976.555, 'mu20': 5345155974.505615, 'mu11': -28249.270629882812, 'mu02': 3427333122.366783, 'mu30': 23477.5, 'mu21': -4997012.293457031, 'mu12': -3998866.2392578125, 'mu03': 15048.609375, 'nu20': 0.10406862971287169, 'nu11': -5.500050697981531e-07, 'nu02': 0.06672917746749925, 'nu30': 9.601767068276657e-10, 'nu21': -2.0436651295533736e-07, 'nu12': -1.6354459446938125e-07, 'nu03': 6.154541238216775e-10}
轮廓1的矩 :
 {'m00': 95617.0, 'm10': 46419747.33333333, 'm01': 49809957.166666664, 'm20': 23635312518.833332, 'm11': 24179311775.583332, 'm02': 26792247787.333332, 'm30': 12542231245959.8, 'm21': 12310495387383.15, 'm12': 13004625772653.684, 'm03': 14835467145002.451, 'mu20': 1099644776.794712, 'mu11': -2221072.768131256, 'mu02': 844646074.8223, 'mu30': 155039228.0078125, 'mu21': 260796144.5908203, 'mu12': -50275550.953063965, 'mu03': -1479049728.3671875, 'nu20': 0.12027688963260327, 'nu11': -0.0002429363825809138, 'nu02': 0.09238565477129439, 'nu30': 5.484080403206228e-05, 'nu21': 9.224936450987655e-05, 'nu12': -1.7783574343404297e-05, 'nu03': -0.0005231726018590121}
 观察各个矩的面积 :

轮廓0的面积: 226631
轮廓1的面积: 95617
'''

2.2.2 使用函数contourArea()计算各个轮廓的面积

(1). 单纯看轮廓

from cv2 import cv2 as cv 
import numpy as np
o = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)

n = len(contours)
# contoursImg = []
for i in range(n):
    print("contours[" + str(i) + "]面积 = ",cv.contourArea(contours[i]))



#输出:
'''
contours[0]面积 =  226631.5
contours[1]面积 =  95617.0
'''

(2). 将面积大于150000 的轮廓筛选出来

from cv2 import cv2 as cv 
import numpy as np

o = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
cv.imshow("original",o)
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)

n = len(contours)
contoursImg = []
for i in range(n):
    # print("contours[" + str(i) + "]面积 = ",cv.contourArea(contours[i]))
    temp = np.zeros(o.shape,np.uint8)
    contoursImg.append(temp)
    contoursImg[i] = cv.drawContours(contoursImg[i],contours,i,(255,255,255),3)
    # cv.imshow("contours[" + str(i) + "]",contoursImg[i])
    if cv.contourArea(contours[i]) > 150000:
        cv.imshow("contours[" + str(i) + "]",contoursImg[i]) 

cv.waitKey()
cv.destroyAllWindows()

效果 :
OpenCV4-python 学习笔记 之 图像轮廓_第4张图片

2.2.3 计算平均轮廓,并将最大的轮廓显示出来

这次用个复杂点的图OpenCV4-python 学习笔记 之 图像轮廓_第5张图片

from cv2 import cv2 as cv 
import numpy as np

# 导入原始图像并绘制
o = cv.imread(r"D:\anaconda\vscode-python\pic\timg.jpg")
cv.imshow("original",o)
# 获取轮廓
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)

# 计算各轮廓长度与平均长度
n = len(contours) # 获取轮廓个数
cntLen = []  # 储存各个轮廓长度
contoursImg = []
for i in range(n):
    cntLen.append(cv.arcLength(contours[i],True))
    print("第" + str(i) + "个轮廓的长度: %d" %cntLen[i])
    # print("contours[" + str(i) + "]面积 = ",cv.contourArea(contours[i]))
    temp = np.zeros(o.shape,np.uint8)
    contoursImg.append(temp)
    contoursImg[i] = cv.drawContours(contoursImg[i],contours,i,(255,255,255),3)
    # cv.imshow("contours[" + str(i) + "]",contoursImg[i])

cntLen_sum = np.sum(cntLen)  # 长度总和
cntLen_avr = cntLen_sum / n  # 长度平均值
print("轮廓总长度为 : %d" %cntLen_sum)
print("平均轮廓长度为 : %d" %cntLen_avr)

# 计算超过平均轮廓长度的轮廓的个数
j = 0  # 计数
max = 0 # 最大值索引
k = 0 # 保存最大长度
for i in range(n):
    if cntLen[i] > cntLen_avr:
        j = j+1
    if cntLen[i] > k:
        max = i
        k = cntLen[i]

print("超过平均轮廓的长度的轮廓个数为 : %d" %j)

# 显示最大轮廓
cv.imshow("contours[" + str(max) + "]",contoursImg[max])

cv.waitKey()
cv.destroyAllWindows()

效果 :

3 轮廓拟合

即找到一个接近轮廓的近似多边形

3.1 函数介绍

3.1.1 cv2.boundingRect() (绘制轮廓的矩形边界)

retval = cv2.boundingRect(array)

返回值 :
(1). retval : 返回的矩形边界的左上角顶点的坐标及矩形的宽度及高度
也可以写成四个返回值的形式 : x,y,w,h = cv2.boundingRect(array) 对应上述解释(一个坐标,宽和高)

参数 :
(1). array : 为灰度图像或轮廓

3.1.2 cv2.minAreaRect() (绘制最小的矩形包围框) 及轮廓格式转换 cv2.boxPoint

retval = cv.minAreaRect(pionts)

返回值 :
(1). retval : 为矩形的特征信息,其结构为: (最小外接矩形中心(x,y),(w,h),旋转角度)

参数 :
(1). points : 轮廓信息

PS : 返回值retval不符合函数cv2.drawControus()的参数结构, 故而需要用函数 cv2.boxPoint() 加以转换, 其结构如下 :

points = cv2.boxPoint(box)

返回值 :
(1). points : 为可用于cv2.drawControus()的轮廓点

参数 :
(1). box : 为cv2.minAreaRect()的返回值类型的值

3.2 实例

3.2.1 显示cv2.boundingRect()的不同返回值

from cv2 import cv2 as cv 

# 导入图像
o = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
# 提取图像轮廓
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)
# 1.返回顶点与边长
x,y,w,h = cv.boundingRect(contours[0])
print("轮廓左上顶点及长宽 : ")
print("x = ",x)
print("y = ",y)
print("w = ",w)
print("h = ",h)
# 2.只有一个返回值
rect = cv.boundingRect(contours[0])
print("\n 顶点及长宽的元组(tuple)形式 : ")
print("rect = ",rect)



#输出:
'''
轮廓左上顶点及长宽 : 
x =  1115
y =  300
w =  533
h =  427

 顶点及长宽的元组(tuple)形式 :
rect =  (1115, 300, 533, 427)
'''

3.2.2 使用 cv2.drawContours() 绘制矩形包围框

from cv2 import cv2 as cv 
import numpy as np

# 导入图像并显示
o = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
cv.imshow("img",o)
# 提取图像轮廓
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)
# 构造矩形边界
x,y,w,h = cv.boundingRect(contours[1])
brcnt = np.array([[x,y],[x+w,y],[x+w,y+h],[x,y+h]])
cv.drawContours(o,[brcnt],-1,(255,255,255),2)
# 显示矩形边界
cv.imshow("org",o)
# print(o.shape)
cv.waitKey()
cv.destroyAllWindows()

效果 :
OpenCV4-python 学习笔记 之 图像轮廓_第6张图片

3.2.3 使用函数 cv2.boundingRect() 与 cv2.rectangle 函数绘制矩形外包框

from cv2 import cv2 as cv 

# 导入图像
o = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
# 提取图像轮廓
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)
# 构造矩形边界
x,y,w,h = cv.boundingRect(contours[1])
# 此时不需要再用numpy把边界做成框,而可以直接使用rectangle在原图片上画边界
# 最后的20是轮廓宽度 hhh
cv.rectangle(o,(x,y),(x+w,y+h),(0,0,255),20)

# 显示矩形边界
cv.imshow("org",o)
cv.waitKey()
cv.destroyAllWindows()

效果(其实和前面那个一样,就是’略粗’了点 hhh)
OpenCV4-python 学习笔记 之 图像轮廓_第7张图片

3.2.4 使用函数cv2.minAreaRect()计算的最小外包矩形框

from cv2 import cv2 as cv 
import numpy as np

# 导入图像
o = cv.imread(r"D:\anaconda\vscode-python\pic\lunkuo.png")
# 提取图像轮廓
gray = cv.cvtColor(o,cv.COLOR_BGR2GRAY)
ret,binary = cv.threshold(gray,127,255,cv.THRESH_BINARY)
contours,hierarchy = cv.findContours(binary,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)

rect = cv.minAreaRect(contours[1])
print("返回值rect: \n",rect)
points = cv.boxPoints(rect)
print("转换后的points: \n",points)
points = np.int0(points)    # 取整
image = cv.drawContours(o,[points],-1,(255,255,255),2)
cv.imshow("result",o)
cv.waitKey()
cv.destroyAllWindows() 



#输出 :
'''
返回值rect:
 ((507.351806640625, 535.9856567382812), (553.6937255859375, 502.299560546875), -33.13561248779297)
转换后的points:
 [[412.81015 897.62396]
 [138.24191 477.00885]
 [601.89343 174.34735]
 [876.4617  594.96246]]
'''

效果 :
OpenCV4-python 学习笔记 之 图像轮廓_第8张图片

你可能感兴趣的:(openCV-python)