图像轮廓(1)

边缘检测虽然能够检测出边缘,但是边缘不是连续的,检测到的边缘不是一个整体。图像轮廓是指将边缘连接起来形成一个整体

#查找图像轮廓
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()

你可能感兴趣的:(图像轮廓(1))