边缘检测虽然能够检测出边缘,但是边缘不是连续的,检测到的边缘不是一个整体。图像轮廓是指将边缘连接起来形成一个整体
#查找图像轮廓
cv2.findContours()
#绘制轮廓图像
cv2.drawContours()
查找图像轮廓
image,contours,hierarchy=cv2.findContours(image,mode,method)
image:前后两个image一致,灰度图像会被自动处理为二值图像
contours:轮廓组,每个轮廓由若干个点构成,且contours[i]表示第i个轮廓,contours[i][j]表示第i个轮廓的第j个点,contours的type是list,list的每个元素都是图像的一个轮廓,是Numpy的ndarray结构
print(contours[0])
[[[79 270]]
[[79 383]]
[[195 383]]
[[195 270]]]
- hierarchy:返回父子轮廓的层次关系,mode决定其参数
[Next , Previous , First_Child , Parent]
[后一个轮廓的索引编号,前一个轮廓的索引编号,第一个子轮廓的索引编号,父轮廓的索引编号]
上述参数没有对应关系时,设置为" -1 "
- mode:轮廓检索模式
cv2.RETR_LIST:对检测到的轮廓不建立等级关系
cv2.RETR_EXTERNAL:只检测外轮廓
cv2.RETE_CCOMP:检测所有轮廓并组织成两级层次结构,上面一层为外边界,下面一层为内控的边界
(注:当含有多个层次结构时,其也只会得到两层的层次结构,而TREE会得到多层)
cv2.RETR_TREE:建立一个等级树结构的轮廓
- method:轮廓近似方法
如何表达轮廓
cv2.CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点元素
cv2.CHAIN_APPROX_NONE:存储所有轮廓点
注意点:
- image必须是灰度二值图像
- 在opencv中都是从黑色背景中查找白色对象,因此对象必须是白色,背景必须是黑色
- opencv 4.x中,第一个返回的image忽略,只有两个返回值
绘制图像轮廓
image=cv2.drawContours(image,contours,contourIdx,RGB,画笔粗细,...)
返回值image:绘制了轮廓的图像
参数:
image:待绘制轮廓的图像,会在image上直接绘制,后续如果image原始图像有用,需要先复制备份
contours:需要绘制的轮廓
contourIdx:一个整数或者0,表示对应的索引号;-1表示绘制全部轮廓
thickness:-1,表示绘制实心轮廓
绘制一副图像中的所有轮廓
import cv2
img=cv2.imread('D:/part12/part12_2.bmp')
cv2.imshow("img",img)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
img=cv2.drawContours(img,contours,-1,(0,0,255),5)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyWindow()
逐个显示一幅图像中的边缘信息
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_2.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
contoursImg=[]
for i in range(n):
temp=np.zeros(img.shape,np.uint8)
contoursImg.append(temp)
contoursImg[i]=cv2.drawContours(contoursImg[i],contours,i,(255,255,255),5)
cv2.imshow("contours["+str(i)+"]",contoursImg[i])
cv2.waitKey(0)
cv2.destroyAllWindows()
使用轮廓绘制功能,提取前景图像
import cv2
import numpy as np
img=cv2.imread("D:/hjbzwy.jpg")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
mask=np.ones(img.shape,np.uint8)
mask=cv2.drawContours(mask,contours,-1,(255,255,255),-1)
cv2.imshow("mask",mask)
loc=cv2.bitwise_and(img,mask)
cv2.imshow("loc",loc)
cv2.waitKey(0)
cv2.destroyWindow()
矩特征
比较两个轮廓最简单的方法是比较二者的轮廓矩。轮廓矩代表了一个轮廓,一幅图像,一组点集的全部特征,矩信息包含了对应对象的不同类型的几何特征,例如大小,位置,角度,形状等。如果两个轮廓的矩一致,那么这两个轮廓就是一致的。
矩特征被广泛应用在模式识别和图像识别方面
retval=cv2.moments(array,binaryImage)
空间矩(m00表示轮廓的面积)
中心矩(比较不同位置上两个对象的一致性)
归一化中心矩(不仅具有平移不变性,还具有缩放不变性)
array:可以是点集,灰度图像,二值图像
binaryImage:该参数为True时,arry内所有的非零值被处理为1,也就是二值化,仅在array为图像时有效
由m00观察每个轮廓的面积
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_4.bmp")
cv2.imshow("img",img)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
contourImg=[]
for i in range(n):
temp=np.zeros(img.shape,np.uint8)
contourImg.append(temp)
contourImg[i]=cv2.drawContours(contourImg[i],contours,i,255,3)
for i in range(n):
print(cv2.moments(contours[i]))
print(cv2.moments(contours[i])['m00'])
cv2.waitKey(0)
cv2.destroyAllWindows()
{'m00': 13764.0, 'm10': 1348872.0, 'm01': 4081026.0, 'm20': 149825728.0, 'm11': 399940548.0, 'm02': 1224156396.0, 'm30': 18139630656.0, 'm21': 44423328352.0, 'm12': 119967326808.0, 'm03': 371342758305.0, 'mu20': 17636272.0, 'mu11': 0.0, 'mu02': 14132187.0, 'mu30': 0.0, 'mu21': 0.0, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.09309309309309309, 'nu11': 0.0, 'nu02': 0.07459677419354839, 'nu30': 0.0, 'nu21': 0.0, 'nu12': 0.0, 'nu03': 0.0}
13764.0
{'m00': 30381.0, 'm10': 8582632.5, 'm01': 4055863.5, 'm20': 2579053217.0, 'm11': 1145781438.75, 'm02': 579760623.0, 'm30': 815852171501.25, 'm21': 344303604469.5, 'm12': 163782375997.5, 'm03': 87624902985.75, 'mu20': 154459535.75, 'mu11': 0.0, 'mu02': 38302845.75, 'mu30': 0.0, 'mu21': 0.0, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.16734417344173438, 'nu11': 0.0, 'nu02': 0.04149797570850201, 'nu30': 0.0, 'nu21': 0.0, 'nu12': 0.0, 'nu03': 0.0}
30381.0
{'m00': 3348.0, 'm10': 1638846.0, 'm01': 117180.0, 'm20': 804628188.0, 'm11': 57359610.0, 'm02': 4462884.0, 'm30': 396227894535.0, 'm21': 28161986580.0, 'm12': 2184581718.0, 'm03': 181511820.0, 'mu20': 2413071.000000119, 'mu11': 0.0, 'mu02': 361584.0, 'mu30': -6.103515625e-05, 'mu21': 3.4570693969726562e-06, 'mu12': 2.9802322387695312e-08, 'mu03': 0.0, 'nu20': 0.2152777777777884, 'nu11': 0.0, 'nu02': 0.03225806451612903, 'nu30': -9.410581005043431e-14, 'nu21': 5.330211897387881e-15, 'nu12': 4.595010256368863e-17, 'nu03': 0.0}
3348.0
计算轮廓的面积
retval=cv2.contourArea(contour,oriented)
oriented:
True,返回值包含正负号,用来表示轮廓是顺时针还是逆时针
False,表示返回的retval是一个绝对值
计算轮廓的长度
retval=cv2.arcLength(curve,closed)
closed:
True,轮廓封闭
False,轮廓不封闭
轮廓面积实验
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_2.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
contoursImg=[]
for i in range(n):
print("contours["+str(i)+"]面积=",cv2.contourArea(contours[i]))
temp=np.zeros(img.shape,np.uint8)
contoursImg.append(temp)
contoursImg[i]=cv2.drawContours(contoursImg[i],contours,i,(255,255,255),3)
cv2.imshow("contours["+str(i)+"]",contoursImg[i])
cv2.waitKey(0)
cv2.destroyAllWindows()
contours[0]面积= 11220.0
contours[1]面积= 11035.5
contours[2]面积= 10126.0
轮廓长度实验
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_5.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
cntlen=[]
for i in range(n):
cntlen.append(cv2.arcLength(contours[i],True))
print("第"+str(i)+"个轮廓的长度是:%d" % cntlen[i])
cntlensum=np.sum(cntlen)
cntlenavg=cntlensum/n
print("轮廓的总长度是:%d" % cntlensum)
print("轮廓的平均长度是:%d" % cntlenavg)
#显示超过平均值的轮廓
contourImg=[]
for i in range(n):
temp=np.zeros(img.shape,np.uint8)
contourImg.append(temp)
contourImg[i]=cv2.drawContours(contourImg[i],contours,i,(255,255,255),3)
if cv2.arcLength(contours[i],True)>cntlenavg:
cv2.imshow("contours["+str(i)+"]",contourImg[i])
cv2.waitKey(0)
cv2.destroyAllWindows()
第0个轮廓的长度是:205
第1个轮廓的长度是:199
第2个轮廓的长度是:700
第3个轮廓的长度是:881
第4个轮廓的长度是:1106
第5个轮廓的长度是:546
第6个轮廓的长度是:787
第7个轮廓的长度是:956
轮廓的总长度是:5382
轮廓的平均长度是:672
Hu矩
Hu矩是归一化中心矩的线性组合。Hu矩在图像旋转,平移,缩放等操作后,仍能保持矩的不变性,所以经常拿Hu矩作为识别图像的特征
hu=cv2.HuMoments(m)
其中,hu是表示返回的Hu矩值,m是由参数cv2.moments()计算得到的矩特征值
import cv2
img1=cv2.imread("D:/part12/part12_6.bmp")
img2=cv2.imread("D:/part12/part12_9.bmp")
img3=cv2.imread("D:/part12/part12_7.bmp")
gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
gray3=cv2.cvtColor(img3,cv2.COLOR_BGR2GRAY)
hum1=cv2.HuMoments(cv2.moments(gray1)).flatten()
hum2=cv2.HuMoments(cv2.moments(gray2)).flatten()
hum3=cv2.HuMoments(cv2.moments(gray3)).flatten()
print("hum1-hum2 = " ,hum1-hum2)
print("hum2-hum3 = " ,hum2-hum3)
print("hum1-hum2 = " ,hum1-hum3)
由于Hu矩的值本身就非常的小,因此这里没有发现两个对象的Hu矩差值的特殊意义
hum1-hum2 = [ 1.10252089e-04 1.58852755e-07 -1.06979452e-11 1.70011798e-12
3.94358178e-23 1.31166167e-15 -1.46520812e-23]
hum2-hum3 = [-2.31106854e-04 -4.04824074e-07 -1.72068592e-10 -4.52145391e-11
-3.85337150e-21 -1.80608902e-14 -2.58397648e-21]
hum1-hum2 = [-1.20854765e-04 -2.45971319e-07 -1.82766537e-10 -4.35144211e-11
-3.81393569e-21 -1.67492286e-14 -2.59862856e-21]
形状匹配
通过Hu矩来判断两个对象的一致性
retval=cv2.matchShapes(contour1,contour2,cv2.CONTOURS_MATCH_I1,0)
import cv2
img1=cv2.imread("D:/part12/part12_7.bmp")
img2=cv2.imread("D:/part12/part12_9.bmp")
img3=cv2.imread("D:/part12/part12_1.bmp")
gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2=cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
gray3=cv2.cvtColor(img3,cv2.COLOR_BGR2GRAY)
ret1,binary1=cv2.threshold(gray1,127,255,cv2.THRESH_BINARY)
ret2,binary2=cv2.threshold(gray2,127,255,cv2.THRESH_BINARY)
ret3,binary3=cv2.threshold(gray3,127,255,cv2.THRESH_BINARY)
contours1,hierarchy1=cv2.findContours(binary1,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
contours2,hierarchy2=cv2.findContours(binary2,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
contours3,hierarchy3=cv2.findContours(binary3,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
cnt1=contours1[0]
cnt2=contours2[0]
cnt3=contours3[0]
ret0=cv2.matchShapes(cnt1,cnt1,1,0.0)
ret1=cv2.matchShapes(cnt1,cnt2,1,0.0)
ret2=cv2.matchShapes(cnt1,cnt3,1,0.0)
print("相同图像的matchShapes=",ret0)
print("相似的图像的matchShapes=",ret1)
print("不同图像的matchShapes=",ret2)
相同图像的matchShapes= 0.0
相似的图像的matchShapes= 0.6671988353714976
不同图像的matchShapes= 0.8752263821862232
轮廓拟合
1. 矩形包围框
retval=cv2.boundingRect(轮廓/灰度图)
其中 retval返回左上角顶点的x,y以及宽和高(x,y,w,h)
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_1.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
x,y,w,h=cv2.boundingRect(contours[0])
brcnt=np.array([[[x,y]],[[x+w,y]],[[x+w,y+h]],[[x,y+h]]])
#注意画笔的顺序方向
img=cv2.drawContours(img,[brcnt],-1,(255,255,255),2)
cv2.imshow("img",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
采用cv2.boundingRect()和cv2.rectangle()绘制矩形包围框
import cv2
img=cv2.imread("D:/part12/part12_1.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
x,y,w,h=cv2.boundingRect(contours[0])
cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,255),2)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 最小包围矩形框
box = cv2.minAreaRect(points)
此处返回的[最小外接矩形中心,(宽,高),旋转角度],必须经过下面函数
points = cv2.boxPoints(box)
此处的points值再经过np.int0()取整,可以应用于cv2.drawContours()
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_1.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
points=cv2.minAreaRect(contours[0])
points=cv2.boxPoints(points)
points=np.int0(points)
image=cv2.drawContours(img,[points],0,(255,255,255),2)
cv2.imshow("image",image)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 最小包围圆形
center,radius=cv2.minEnclosingCircle(points)
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_1.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
(x,y),radius=cv2.minEnclosingCircle(contours[0])
center=(int(x),int(y))
radius=int(radius)
cv2.circle(img,center,radius,(255,255,255),2)
cv2.imshow("image",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
4. 最优拟合椭圆
retval=cv2.fitEllipse(points)
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_1.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
ellipse=cv2.fitEllipse(contours[0])
cv2.ellipse(img,ellipse,(0,255,0),3)
cv2.imshow("image",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
5. 最优拟合直线
cv2.fitLine(points,distType,0,0.01,0.01)
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_1.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
rows,cols=img.shape[:2]
[vx,vy,x,y]=cv2.fitLine(contours[0],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)
cv2.imshow("image",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
6. 最小外包三角形
retval,triangle=cv2.minEnclosingTraingle(points)
retval:最小外包三角形的面积
triangle:最小外包三角形的三个顶点集
注意:三个顶点集的坐标必须转化为整数
import cv2
import numpy as np
img=cv2.imread("D:/part12/part12_1.bmp")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
area,trgl=cv2.minEnclosingTriangle(contours[0])
print("area:",area)
print("trgl",trgl)
for i in range(0,3):
cv2.line(img,tuple(np.int0(trgl[i][0])),tuple(np.int0(trgl[(i+1)%3][0])),(255,255,255),2)
cv2.imshow("result",img)
cv2.waitKey(0)
cv2.destroyAllWindows()
7. 逼近多边形
approxCurve = cv2.approxPolyDP(curve,epsilon,closed)
返回值:逼近多边形的点集
参数:轮廓,原始轮廓边界点与逼近多边形边界之间的最大距离,True:封闭;False不封闭
DP算法:该算法首先从轮廓中找到距离最远的两个点,并且将两点相连。接下来在轮廓上找到一个距离当前直线最远的点,并且将该点与原有直线连成一个封闭多边形,上述过程不断迭代,将新的找到的距离当前多边形最远的点加入到结果中。当轮廓上所有的点到当前多边形的距离都小于函数cv2.approxPolyDP()的参数epsilon的值时,就停止迭代
构造不同精度的逼近多边形
import cv2
img=cv2.imread("D:/part12/part12_1.bmp")
cv2.imshow("img",img)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours,hierarchy=cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
#------------------------------0.1周长
adp=img.copy()
epsilon=0.1*cv2.arcLength(contours[0],True)
approx=cv2.approxPolyDP(contours[0],epsilon,True)
adp=cv2.drawContours(adp,[approx],0,(0,0,255),2)
cv2.imshow("result 0.1",adp)
#-------------------------------0.09周长
adp=img.copy()
epsilon=0.09*cv2.arcLength(contours[0],True)
approx=cv2.approxPolyDP(contours[0],epsilon,True)
cv2.drawContours(adp,[approx],0,(0,0,255),2)
cv2.imshow("result 0.09",adp)
#--------------------------------0.055周长
adp=img.copy()
epsilon=0.055*cv2.arcLength(contours[0],True)
approx=cv2.approxPolyDP(contours[0],epsilon,True)
cv2.drawContours(adp,[approx],0,(0,0,255),2)
cv2.imshow("result 0.055",adp)
#---------------------------------0.02周长
adp=img.copy()
epsilon=0.02*cv2.arcLength(contours[0],True)
approx=cv2.approxPolyDP(contours[0],epsilon,True)
cv2.drawContours(adp,[approx],0,(0,0,255),2)
cv2.imshow("result 0.02",adp)
cv2.waitKey()
cv2.destroyAllWindows()