Python版本是Python3.7.3,OpenCV版本OpenCV3.4.1,开发环境为PyCharm
OpenCV提供了函数cv2.moments()来获取图像的moments特征。通常情况下,我们将使用函数cv2.moments()获取的轮廓特征称为“轮廓矩”。轮廓矩描述了一个轮廓的重要特征,使用轮廓矩可以方便地比较两个轮廓。
函数cv2.moments()的语法格式为:
retval = cv2.moments( array[, binaryImage] )
式中有两个参数:
● array:可以是点集,也可以是灰度图像或者二值图像。当array是点集时,函数会把这些点集当成轮廓中的顶点,把整个点集作为一条轮廓,而不是把它们当成独立的点来看待。
● 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”的含义比较直观,它表示一个轮廓的面积。
矩特征函数cv2.moments()所返回的特征值,能够用来比较两个轮廓是否相似。例如,有两个轮廓,不管它们出现在图像的哪个位置,我们都可以通过函数cv2.moments()的m00矩判断其面积是否一致。
在位置发生变化时,虽然轮廓的面积、周长等特征不变,但是更高阶的特征会随着位置的变化而发生变化。在很多情况下,我们希望比较不同位置的两个对象的一致性。解决这一问题的方法是引入中心矩。中心矩通过减去均值而获取平移不变性,因而能够比较不同位置的两个对象是否一致。很明显,中心矩具有的平移不变性,使它能够忽略两个对象的位置关系,帮助我们比较不同位置上两个对象的一致性。
除了考虑平移不变性外,我们还会考虑经过缩放后大小不一致的对象的一致性。也就是说,我们希望图像在缩放前后能够拥有一个稳定的特征值。也就是说,让图像在缩放前后具有同样的特征值。显然,中心矩不具有这个属性。例如,两个形状一致、大小不一的对象,其中心矩是有差异的。
归一化中心矩通过除以物体总尺寸而获得缩放不变性。它通过上述计算提取对象的归一化中心矩属性值,该属性值不仅具有平移不变性,还具有缩放不变性。
在OpenCV中,函数cv2.moments()会同时计算上述空间矩、中心矩和归一化中心距。
eg1:使用函数cv2.moments()提取一幅图像的特征。
根据题目的要求及分析,编写代码如下:
import cv2
import numpy as np
o = cv2.imread('moments.bmp')
cv2.imshow("original",o)
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
image,contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
contoursImg=[]
for i in range(n):
temp=np.zeros(image.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()
本例中,首先使用函数cv2.moments()提取各个轮廓的特征;接下来,通过语句cv2.moments(contours[i])[‘m00’])提取各个轮廓矩的面积信息。
运行上述程序,会显示如下图所示的图像。其中:
● ( a )图是原始图像。
● ( b )图是原始图像的第0个轮廓。
● ( c )图是原始图像的第1个轮廓。
● ( d )图是原始图像的第2个轮廓。
同时,程序会显示如下输出结果:
轮廓0的矩:
{'m00': 14900.0, 'm10': 1996600.0, 'm01': 7800150.0, 'm20': 279961066.6666666, 'm11': 1045220100.0, 'm02': 4110944766.6666665, 'm30': 40842449600.0, 'm21': 146559618400.0, 'm12': 550866598733.3334, 'm03': 2180941440375.0, 'mu20': 12416666.666666627, 'mu11': 0.0, 'mu02': 27566241.666666508, 'mu30': 1.52587890625e-05, 'mu21': 2.09808349609375e-05, 'mu12': 6.198883056640625e-05, 'mu03': 0.000244140625, 'nu20': 0.05592841163310942, 'nu11': 0.0, 'nu02': 0.12416666666666591, 'nu30': 5.630596400372416e-16, 'nu21': 7.742070050512072e-16, 'nu12': 2.2874297876512943e-15, 'nu03': 9.008954240595866e-15}
轮廓1的矩:
{'m00': 34314.0, 'm10': 13313832.0, 'm01': 9728019.0, 'm20': 5356106574.0, 'm11': 3774471372.0, 'm02': 2808475082.0, 'm30': 2225873002920.0, 'm21': 1518456213729.0, 'm12': 1089688331816.0, 'm03': 824882507095.5, 'mu20': 190339758.0, 'mu11': 0.0, 'mu02': 50581695.5, 'mu30': 0.0, 'mu21': 0.0, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.16165413533834588, 'nu11': 0.0, 'nu02': 0.042958656330749356, 'nu30': 0.0, 'nu21': 0.0, 'nu12': 0.0, 'nu03': 0.0}
轮廓2的矩:
{'m00': 3900.0, 'm10': 2696850.0, 'm01': 273000.0, 'm20': 1866699900.0, 'm11': 188779500.0, 'm02': 19988800.0, 'm30': 1293351277725.0, 'm21': 130668993000.0, 'm12': 13822255200.0, 'm03': 1522248000.0, 'mu20': 1828125.0, 'mu11': 0.0, 'mu02': 878800.0, 'mu30': 0.0, 'mu21': 0.0, 'mu12': 0.0, 'mu03': 0.0, 'nu20': 0.1201923076923077, 'nu11': 0.0, 'nu02': 0.05777777777777778, 'nu30': 0.0, 'nu21': 0.0, 'nu12': 0.0, 'nu03': 0.0}
观察各个轮廓的面积:
轮廓0的面积:14900
轮廓1的面积:34314
轮廓2的面积:3900
上述输出结果显示了每个轮廓的矩特征和面积。
函数cv2.contourArea()用于计算轮廓的面积。该函数的语法格式为:
retval =cv2.contourArea(contour [, oriented] ))
式中的返回值retval是面积值。
式中有两个参数:
● contour是轮廓。
● oriented是布尔型值。当它为True 时,返回的值包含正/负号,用来表示轮廓是顺时针还是逆时针的。该参数的默认值是False,表示返回的retval是一个绝对值。
eg2:使用函数cv2.contourArea()计算各个轮廓的面积。
代码如下:
import cv2
import numpy as np
o = cv2.imread('contours.bmp')
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
image,contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
cv2.CHAIN_APPROX_SIMPLE)
cv2.imshow("original",o)
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()
本例通过函数cv2.contourArea()计算各个轮廓的面积。运行上述程序,会显示各个轮廓的面积值:
contours[0]面积= 13108.0
contours[1]面积= 19535.0
contours[2]面积= 12058.0
同时,还会显示如下图所示的图像。其中:
● ( a )图是原始图像。
● ( b )图是原始图像的第0个轮廓。
● ( c )图是原始图像的第1个轮廓。
● ( d )图是原始图像的第2个轮廓。
eg3:在例2的基础上,将面积大于15000的轮廓筛选出来。
分析:我们在例2中已经计算出图像contours.bmp内三个轮廓的面积分别是13108.0、19535.0、12058.0,使用if函数将面积大于15000的值筛选出来即可。
代码如下:
#筛选出大于特定大小的轮廓
import cv2
import numpy as np
o = cv2.imread('contours.bmp')
cv2.imshow("original",o)
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
image,contours, hierarchy = cv2.findContours(binary,
cv2.RETR_LIST,
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),3)
if cv2.contourArea(contours[i])>15000:
cv2.imshow("contours[" + str(i)+"]",contoursImg[i])
cv2.waitKey()
cv2.destroyAllWindows()
本例中,通过语句“if cv2.contourArea(contours[i])>15000:”实现对面积值的筛选,然后对面积值大于15000的轮廓使用语句“cv2.imshow(“contours[” + str(i)+"]", contoursImg[i])”显示出来。
运行上述程序,显示的图像如下图所示。其中:
● 左图是原始图像。
● 右图是原始图像内面积大于15000的唯一轮廓。
函数cv2.arcLength()用于计算轮廓的长度,其语法格式为:
retval = cv2.arcLength( curve, closed )
式中返回值retval是轮廓的长度(周长)。
上式中有两个参数:
● curve是轮廓。
● closed是布尔型值,用来表示轮廓是否是封闭的。该值为True时,表示轮廓是封闭的。
eg4:将一幅图像内长度大于平均值的轮廓显示出来。
分析:首先,使用函数cv2.arcLength()计算各个轮廓的长度,接下来计算各个轮廓的长度之和以及长度的平均值,最后使用判断语句将长度大于平均值的轮廓显示出来。
代码如下:
#筛选出大于特定大小的轮廓
import cv2
import numpy as np
#--------------读取及显示原始图像--------------------
o = cv2.imread('contours0.bmp')
cv2.imshow("original",o)
#--------------获取轮廓--------------------
gray = cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
image,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()
本例先通过函数cv2.arcLength()计算各个轮廓的长度,并据此计算总长度和平均长度,然后使用判断结构语句“if cv2.arcLength(contours[i], True)>cntLenAvr:”对各个轮廓的长度进行判断,并根据判断结果决定是否显示对应的轮廓。
运行上述程序,会显示各个轮廓的长度、总长度与平均长度。
第0个轮廓的长度:145
第1个轮廓的长度:147
第2个轮廓的长度:398
第3个轮廓的长度:681
第4个轮廓的长度:1004
第5个轮廓的长度:398
第6个轮廓的长度:681
第7个轮廓的长度:1004
第8个轮廓的长度:2225
第9个轮廓的长度:2794
各个轮廓的总长度为:9480
各个轮廓的平均长度为:948
同时,程序还会显示如下图所示的图像,其中显示了原始图像和长度大于平均轮廓长度的4条轮廓:
● ( a )图是原始图像。
● ( b )图是原始图像的第4个轮廓。
● ( c )图是原始图像的第7个轮廓。
● ( d )图是原始图像的第8个轮廓。
● ( e )图是原始图像的第9个轮廓(最外层,即整体图像的外轮廓)。