一个轮廓对应一系列的点,在OpenCV中提供函数 cv2.fingContours() 用于查找图像轮廓。
并可以根据参数返回特定的轮廓曲线,而函数 cv2.drawCountours() 可以将轮廓绘制到图像上。
查找图像轮廓: 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 压缩水平,垂直对角线方向的元素只保留该方向的终点坐标,在极端情况下一个矩形只需要四个点
绘制图像轮廓: 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: 该函数可以写作无返回值的形式(在原图像上操作)
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()
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()
将 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()
OpenCV提供函数 cv2.moments() 函数来获取图像的矩特征
所谓矩特征,即包括了对应对象不同类型的几何特征,如位置大小角度形状等
通常情况下,我们将通过函数 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 ’ 为一轮廓的面积
retval = cv2.contourArea(contour[,oriented])
返回值 :
(1). retval : 为所求面积值
参数 :
(1). contour : 为轮廓
(2). orientde : 为一bool值,默认为false表示返回值为面积绝对值,若为True则返回值包含正负号,用来表示轮廓是顺时针还是逆时针
retval = cv2.arcLength(curve,closed)
返回值 :
(1). retval : 为轮廓周长
参数 :
(1). curve : 为轮廓
(2). closed为bool类型,表示轮廓是否封闭,为True时表示轮廓封闭
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
'''
(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()
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()
效果 :
即找到一个接近轮廓的近似多边形
retval = cv2.boundingRect(array)
返回值 :
(1). retval : 返回的矩形边界的左上角顶点的坐标及矩形的宽度及高度
也可以写成四个返回值的形式 : x,y,w,h = cv2.boundingRect(array) 对应上述解释(一个坐标,宽和高)
参数 :
(1). array : 为灰度图像或轮廓
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()的返回值类型的值
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)
'''
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()
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()
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]]
'''