图像轮廓(一)【OpenCV&Python】

    技术2022-07-11  74

    图像轮廓(一)

    0 引言1 查找并绘制轮廓1.1 查找图像轮廓:findContours函数1、返回值image2、返回值contours3、返回值hierarchy4、参数image5、参数mode6、参数method 1.2 绘制图像轮廓:drawContours函数1.3 轮廓实例 2 矩特征2.1 矩的计算:moments函数2.2 计算轮廓的面积:coutourArea函数2.3 计算轮廓的长度:arcLength函数 3 Hu矩3.1 Hu矩函数3.2 形状匹配

    0 引言

    🍔小小搬运工,一边搬运一边思考 参考书籍 《OpenCV轻松入门——面向Python李立宗著,电子工业出版社出版

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

    OpenCV提供了查找轮廓的函数cv2.findContours(),该函数能够查找图像轮廓内的信息,而函数cv2.drawContours()函数能够将轮廓绘制出来。

    图像轮廓是图像中非常重要的一个特征信息,通过对图像轮廓的操作,我们能够获取目标图像的大小、位置、方向等信息。

    1 查找并绘制轮廓

    1.1 查找图像轮廓:findContours函数

    函数findContours用于查找图像的轮廓,并能够根据参数返回特定方式的轮廓。其语法格式为:

    image,contours,hierarchy = cv2.fingContours(image,mode,method) # 返回值image:与函数参数中的原始图像image一致 # 返回值contours:返回的轮廓 # 返回值hierarchy:图像的拓普信息(轮廓层次) # 参数image:原始图像。8位单通道图像,所有非零值被处理为1,所有零值保持不变。也就是说灰度图像会被自动处理成二值图像。在实际操作中,可以根据需要,预先使用阈值处理等函数,将待查找轮廓的图像处理为二值图像。 # mode:轮廓检索方式 # method:轮廓的近似方法

    1、返回值image

    在OpenCV 4.X中,函数cv2.findContours()仅有两个返回值,其语法格式为:

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

    2、返回值contours

    该返回值返回的是一组轮廓信息,每个轮廓都是由若干个点所构成的。例如,contour是[i]是第i个轮廓(下标从0开始),contours[i][j]是第i个轮廓内的第j个点。 (1)type属性

    print(type(contours)) # 获取轮廓contours的类型 print(type(contours[0])) # 获取轮廓contours中每个元素的类型

    (2)轮廓的个数

    print(len(contours)) # 获取轮廓的个数

    (3)每个轮廓的点数

    print(len(contours[0])) # 打印第0个轮廓的长度(点的个数) print(contours[0].shape) # 获取轮廓每个轮廓内点的shape属性

    (4)轮廓内的点

    print (contours[0]) # 打印第0个轮廓中的像素点

    3、返回值hierarchy

    图像内的轮廓位于不同的位置,将外部的轮廓称为父轮廓,内部的轮廓称为子轮廓。这样,一幅图像的轮廓就建立了父子关系。上述关系就被称为层次(组织结构)。

    print(hierarchy) # 查看hierarchy的值,轮廓的层次结构由参数mode决定 [Next,Previous,First_Child,Parant] # 四个元素说明当前轮廓的层次关系 # Next:后一个轮廓的索引编号 # Previous:前一个轮廓的索引编号 # First_Child:第1个子轮廓的索引编号 # Parant:父轮廓的索引编号

    如果上述参数所对应的关系为空时,则将该参数所对应的值设为"1"。

    4、参数image

    原始图像。8位单通道图像,所有非零值被处理为1,所有零值保持不变。也就是说灰度图像会被自动处理成二值图像。在实际操作中,可以根据需要,预先使用阈值处理等函数,将待查找轮廓的图像处理为二值图像。

    5、参数mode

    参数mode决定了轮廓的提取方式,具体有如下4种: (1)cv2.RETR_EXTERNAL:只检测外轮廓 (2)cv2.RETR_LIST:对检测到的轮廓不建立等级关系 (3)cv2.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构 (4)cv2.RETR_TREE:建立一个等级树的结构

    6、参数method

    参数method决定了如何表达轮廓,具体如下: (1)cv2.CHAIN_APPROX_NONE:存储所有的轮廓点,相邻两个像素位置差不超过1。 (2)cv2.CHAIN_APPROX_SIMPLE:压缩水平方向、垂直方向、对角线方向的元素,只保留该方向的终点坐标。例如,在极端的情况下,一个矩形只需要用4个点来保存轮廓信息。 (3)cv2.CHAIN_APPROX_TC89_L1:使用teh-Chinl chain近似算法的一种风格。 (4)cv2.CHAIN_APPROX_TC89_KCOS:使用teh-Chinl chain近似算法的一种风格。 注意: 待处理的源图像必须是灰度二值图。因此,在通常情况下,都要预先对图像进行阈值分割或者边缘检测,得到满意的二值图像后再将其作为参数使用。 在OpenCV中,都是从黑色背景中查找白色对象。因此,对象必须是白色的,背景必须是黑色的。

    1.2 绘制图像轮廓:drawContours函数

    在OpenCV中,可以使用函数cv2.drawContours()绘制图像轮廓。该函数的语法格式是:

    image = cv2.drawContours( image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) # 返回值image表示目标图像,即绘制了边缘的原始图像。 # 参数image:待绘制轮廓的图像。函数cv2.drawContours会在图像image上直接绘制轮廓,若有其他用途,需要预先复制一份,将该副本图像传递给函数cv2.drawContours()使用。 # 参数contours:需要绘制的轮廓。该参数的类型与函数cv2.findContours()的输出contours相同,都是list类型。 # 参数contourIdx:需要绘制的边缘索引,告诉函数cv2.drawContours()要绘制某一条轮廓还是全部轮廓。如果该参数是一个整数或者为0,则表示绘制对应索引号的轮廓;如果该值为负值(-1),则表示绘制全部轮廓。 # 参数color:绘制的颜色,用BGR格式表示。 # thickness:可选参数,表示绘制轮廓时所用画笔的粗细,如将该值设置为"-1",则表示要绘制实心轮廓。 # lineType:可选参数,表示绘制轮廓时所用的线型。 # hierarchy:对应函数cv2.findContours()所输出的层次信息。 # maxLevel:控制所控制的轮廓层次的深度。如果值为0,表示仅仅绘制第0层的轮廓;如果值为其他的非零正数,表示绘制最高层及以下的相同数量层级的轮廓。 # offset: 偏移参数。该参数使轮廓偏移到不同的位置展示出来。

    1.3 轮廓实例

    例1:绘制一幅图像内的所有轮廓。 将cv2.drawContours()的参数contourIdx的值设置为“-1”。

    import cv2 o = cv2.imread("C:\\Users\\Kris\\Desktop\\timg.jpg") cv2.imshow("original",o) gray = cv2.cvtColor(o,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) # 根据版本确定是否返回image o = cv2.drawContours(o,contours,-1,(0,0,255),5) # -1代表轮廓索引,BGR(0,255,255)代表红色,5代表参数thickness的轮廓粗细 cv2.imshow("result",o) cv2.waitKey() cv2.destroyAllWindows()

    运行程序,结果显示如下。

    例2:逐个显示一幅图像内的边缘信息。 将cv2.drawContours()的参数contourIdx的值设置为具体的索引值。

    import cv2 import numpy as np o = cv2.imread("C:\\Users\\Kris\\Desktop\\timg.jpg") cv2.imshow("original",o) gray = cv2.cvtColor(o,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(o.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() cv2.destroyAllWindows()

    运行程序,结果显示如下。 例3:使用轮廓绘制功能,提取前景图像。 将函数cv2.drawContours()的参数thickness的值设置为“-1”,可以绘制前景图像的实心轮廓,将该实心轮廓与原始图像进行“按位与”操作,即可将前景图像从原始图像中提取出来。 根据题目要求,编写代码图如下:

    import cv2 import numpy as np o = cv2.imread("C:\\Users\\Kris\\Desktop\\33.png") cv2.imshow("original",o) # 显示原始图像 gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY) ret,binary = cv2.threshold(gray,0,200,cv2.THRESH_BINARY) contours,hierarchy = cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) mask = np.zeros(o.shape,np.uint8) mask = cv2.drawContours(mask,contours,-1,(255,255.255)) cv2.imshow("mask",mask) # 显示mask,即从原始图像中得到的实心轮廓 loc = cv2.bitwise_and(o,mask) # 按位与操作 cv2.imshow("location",loc) # 显示提取的前景图像 cv2.waitKey() cv2.destroyAllWindows()

    运行程序,结果显示如下。从右往左依次为:原始图像、实心轮廓、前景对象。

    2 矩特征

    2.1 矩的计算:moments函数

    OpenCV提供了函数cv2.moments()来获取图像的moments特征。通常情况下,我们将使用函数cv2.moments()获取的轮廓特征称为“轮廓矩”。轮廓矩描述了一个轮廓的重要特征,使用轮廓矩可以方便地两个轮廓。

    retval = cv2.moments(array[,binaryImage]) # 参数array:可以是点集,也可以是灰度图像或者二值图像。当array是点集时,函数会把这些点集当成轮廓中的顶点,把整个点集作为一条轮廓,而不是把它们当成独立的点来看待。 # 参数binaryImage:该参数为Ture时,array内所有的非零值都被处理为1.该参数在参数array为图像时有效。 # 返回值retval是矩特征,主要包括: # (1)空间矩 # 零阶矩m00,一阶矩m10,m01,二阶矩m20,m11,m02,三阶矩m30,m21,m12,m03 # (2)中心矩 # 二阶中心矩mu20,mu11,mu02,三阶中心矩mu30,mu21,mu12,mu20 # (3)归一化中心矩 # 二阶HU矩nu20,nu11,nu02,三阶HU矩nu30,nu21,nu12,nu03 import cv2 import numpy as np o = cv2.imread("C:\\Users\\Kris\\Desktop\\111.jpg") cv2.imshow("original",o) gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY) cv2.imshow("gray",gray) 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(gray.shape,np.uint8) contoursImg.append(temp) contoursImg[i] = cv2.drawContours(contoursImg[i],contours,i,255,3) cv2.imshow("contours["+str[i]+"]",contoursImg[i]) print("观察各个轮廓的矩(moments):") for i in range(n): print("轮廓"+str(i)+"的矩:\n",cv2.moments(contours[i])) print("观察各个轮廓的面积:") for i in range(n): print("轮廓"+str(i)+"的面积:%d"%cv2.moments(contours[i])['m00']) cv2.waitKey() cv2.destroyAllWindows()

    运行程序,结果显示如下。从右往左依次为原始图像、灰度图、最后一个轮廓图。 同时,还显示如下结果。从结果中可以看出每个轮廓矩特征和面积的不同之处。

    2.2 计算轮廓的面积:coutourArea函数

    函数cv2.contourArea()用于轮廓的面积。该函数的语法格式为:

    retval = cv2.contourArea(contour[,oriented]) # oriented是布尔型值。当它为Ture时,返回的值包含正负号,用来表示轮廓是顺时针还是逆时针的。该参数的默认值为False,表示返回的retval是一个绝对值。

    例5:使用函数cv2.contourArea()计算各个轮廓的面积。

    import cv2 import numpy as np o = cv2.imread("C:\\Users\\Kris\\Desktop\\111.jpg") gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY) # 灰度图 ret,binary = cv2.threshold(gray,220,255,cv2.THRESH_BINARY) # 设置阈值 contours,hierarchy = cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓 cv2.imshow("original",o) # 显示原始图像 cv2.imshow("gray",gray) # 显示灰度图像 n = len(contours) contoursImg = [] for i in range(n): print("contours["+str(i)+"]面积=",cv2.contourArea(contours[i])) temp = np.zeros(o.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() cv2.destroyAllWindows()

    运行程序,结果如下(显示检测到的所有轮廓)。 同时,会显示各个轮廓的面积值。 例6:在例5的基础上,将面积大于20000的筛选出来。 根据题目要求,编写代码如下。

    import cv2 import numpy as np o = cv2.imread("C:\\Users\\Kris\\Desktop\\111.jpg") gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY) # 灰度图 ret,binary = cv2.threshold(gray,220,255,cv2.THRESH_BINARY) # 设置阈值 contours,hierarchy = cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓 cv2.imshow("original",o) # 显示原始图像 cv2.imshow("gray",gray) # 显示灰度图像 n = len(contours) contoursImg = [] for i in range(n): print("contours["+str(i)+"]面积=",cv2.contourArea(contours[i])) temp = np.zeros(o.shape,np.uint8) contoursImg.append(temp) contoursImg[i] = cv2.drawContours(contoursImg[i],contours,i,(255,255,255),3) # if语句筛选面积,对面积符合要求的进行显示 if cv2.contourArea(contours[i])>20000: cv2.imshow("contours["+str(i)+"]",contoursImg[i]) cv2.waitKey() cv2.destroyAllWindows()

    运行程序,结果显示如下。

    2.3 计算轮廓的长度:arcLength函数

    函数 cv2.arcLength()用于计算轮廓的长度,其语法格式为:

    retval = cv2.arcLength(curve,closed) # 式中返回值retval是轮廓的长度(周长) # 参数curve是轮廓,参数closed是布尔型值,用来表示轮廓是否是封闭的。该值为Ture时,表示轮廓是封闭的。

    例7:将一幅图像内长度大于平均值的轮廓显示出来。 根据题目要求,编写代码如下。

    import cv2 import numpy as np # -----------------读取及显示原始图像---------------------- o = cv2.imread("C:\\Users\\Kris\\Desktop\\0.jpg") cv2.imshow("original",o) # 显示原始图像 # -----------------获取轮廓---------------------- gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY) # 灰度图 ret,binary = cv2.threshold(gray,220,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) # 各轮廓的长度之和 cntLenAvr = cntLenSum/n # 轮廓长度的平均值 print("轮廓的总长度为:%d"%cntLenSum) print("轮廓的平均长度为:%d"%cntLenAvr) # -----------------显示长度超过平均值的轮廓---------------------- contoursImg = [] for i in range(n): temp = np.zeros(o.shape,np.uint8) contoursImg.append(temp) contoursImg[i] = cv2.drawContours(contoursImg[i],contours,i,(255,255,255),3) if cv2.arcLength(contours[i],True)>cntLenAvr: cv2.imshow("contours["+str(i)+"]",contoursImg[i]) cv2.waitKey() cv2.destroyAllWindows()

    运行程序,会显示如下结果,同时将大于平均长度的轮廓显示出来。

    3 Hu矩

    Hu矩是归一化中心矩的线性组合,具有平移不变性。

    3.1 Hu矩函数

    函数cv2.HuMoments()可以得到Hu矩。该函数使用cv2.moment函数的返回值作为参数,返回7个Hu值。 该函数的语法格式为:

    import cv2 #---------------------------计算图像o1的Hu矩--------------------------- o1 = cv2.imread("C:\\Users\\Kris\\Desktop\\0.jpg") gray1 = cv2.cvtColor(o1,cv2.COLOR_BGR2GRAY) HuM1 = cv2.HuMoments(cv2.moments(gray1)).flatten() #---------------------------计算图像o2的Hu矩--------------------------- o2 = cv2.imread("C:\\Users\\Kris\\Desktop\\1.jpg") gray2 = cv2.cvtColor(o2,cv2.COLOR_BGR2GRAY) HuM2 = cv2.HuMoments(cv2.moments(gray2)).flatten() #---------------------------计算图像o3的Hu矩--------------------------- o3 = cv2.imread("C:\\Users\\Kris\\Desktop\\2.jpg") gray3 = cv2.cvtColor(o3,cv2.COLOR_BGR2GRAY) HuM3 = cv2.HuMoments(cv2.moments(gray3)).flatten() #---------------------------计算图像o4的Hu矩--------------------------- o4 = cv2.imread("C:\\Users\\Kris\\Desktop\\3.jpg") gray4 = cv2.cvtColor(o4,cv2.COLOR_BGR2GRAY) HuM4 = cv2.HuMoments(cv2.moments(gray4)).flatten() #---------------------------打印图像o1、o2、o3的特征值------------------- print("o1.shape=",o1.shape) print("o2.shape=",o2.shape) print("o3.shape=",o3.shape) print("o4.shape=",o4.shape) print("cv2.moments(gray1)=\n",cv2.moments(gray1)) print("cv2.moments(gray2)=\n",cv2.moments(gray2)) print("cv2.moments(gray3)=\n",cv2.moments(gray3)) print("cv2.moments(gray4)=\n",cv2.moments(gray4)) print("\nHuM1=\n",HuM1) print("\nHuM2=\n",HuM2) print("\nHuM3=\n",HuM3) print("\nHuM4=\n",HuM4) #--------------------计算图像O1与o2、o3、o4的Hu矩之差---------------------- print("\nHuM1-HuM2=",HuM1-HuM2) print("\nHuM1-HuM3=",HuM1-HuM3) print("\nHuM1-HuM4=",HuM1-HuM4) print("\nHuM3-HuM4=",HuM3-HuM4) #--------------------------显示图像---------------------------------------- cv2.imshow("original",o1) cv2.imshow("one",o2) cv2.imshow("two",o3) cv2.imshow("three",o4) cv2.waitKey() cv2.destroyAllWindows()

    同时还会显示各个图像的shape属性、moments属性、HuMoments属性,以及不同图像的Hu矩之差。

    3.2 形状匹配

    函数cv2.matchShapes()通过Hu矩来判断两个对象的一致性。其语法格式为:

    retval = cv2.matchShapes(contour1,contour2,method,parameter) # contour1第一个轮廓或者灰度图 # contour2第二个轮廓或者灰度图 # method 比较两个对象的Hu矩的方法

    例10:使用函数cv2.matchShapes()计算三幅不同图像的匹配度。

    import cv2 #------------------读取三幅不同的图像------------------------ o1 = cv2.imread("C:\\Users\\Kris\\Desktop\\1.jpg") o1 = cv2.imread("C:\\Users\\Kris\\Desktop\\2.jpg") o1 = cv2.imread("C:\\Users\\Kris\\Desktop\\3.jpg") #--------------------灰度图---------------------------------- gray1 = cv2.cvtColor(o1,cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(o2,cv2.COLOR_BGR2GRAY) gray3 = cv2.cvtColor(o3,cv2.COLOR_BGR2GRAY) #----------------------阈值------------------------------ ret,binary1 = cv2.threshold(gray1,127,255,cv2.THRESH_BINARY) ret,binary2 = cv2.threshold(gray2,127,255,cv2.THRESH_BINARY) ret,binary3 = cv2.threshold(gray3,127,255,cv2.THRESH_BINARY) #---------------------查找轮廓----------------------------- contour1,hierarchy = cv2.findContours(binary1,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) contour2,hierarchy = cv2.findContours(binary2,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) contour3,hierarchy = cv2.findContours(binary3,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE) #---------------------------------------------------------- cnt1 = contour1[0] cnt2 = contour2[0] cnt3 = contour3[0] #------------------------------------------------------------- ret1 = cv2.matchShapes(cnt2,cnt1,1,0.0) ret2 = cv2.matchShapes(cnt2,cnt2,1,0.0) ret3 = cv2.matchShapes(cnt2,cnt3,1,0.0) #----------------------------------------------------------------- print("不相同图像的matchShape=",ret1) print("相同图像的matchShape=",ret2) print("相似图像的matchShape=",ret2)

    运行程序,显示如下的结果。 (1)同一幅图像的Hu矩是不变的,二者差值为0 (2)相似的图像即使发生了平移、旋转和缩放后,函数的返回值仍然比较接近。 (3)不相似图像的返回值的差较大。

    Processed: 0.015, SQL: 9