OpenCV-Python快速入门(十一):图像矩

奋斗吧
奋斗吧
擅长邻域:未填写

标签: OpenCV-Python快速入门(十一):图像矩 博客 51CTO博客

2023-07-25 18:24:13 160浏览

OpenCV-Python快速入门(十一):图像矩,Python、OpenCV、图像矩、查找轮廓、绘制轮廓、矩特征、Hu矩、形状匹配


OpenCV-Python快速入门(十一):图像矩

  • 前言
  • 前提条件
  • 实验环境
  • 图像矩
  • 查找并绘制轮廓
  • 矩特征
  • Hu 矩
  • 验证 h_0=nu_{20}+nu_{02}=v_{20}+v_{02} h0=nu20+nu02=v20+v02
  • Hu矩的应用:形状匹配
  • 参考文献

前言

  • 本文是个人快速入门OpenCV-Python的电子笔记,由于水平有限,难免出现错漏,敬请批评改正。
  • 更多精彩内容,可点击进入
    OpenCV-Python快速入门
    专栏或我的个人主页查看

前提条件

实验环境

  • Python 3.x (面向对象的高级语言)
  • OpenCV 4.0(python第三方库)pip3 install opencv-python

图像矩

  • 在了解图像矩之前,先了解一下图像轮廓。
  • 边缘检测虽然能够检测出边缘,但边缘是不连续的,检测到的边缘并不是一个整体。
  • 图像轮廓是指将边缘连接起来形成的一个整体,用于后续的计算,比如,用于计算图像矩。
  • 图像轮廓是图像中非常重要的一个特征信息,比如图像的矩特征,通过对图像轮廓的操作,能够获取目标图像的大小、位置、方向等信息。
  • OpenCV 提供了查找图像轮廓的函数 cv2.findContours(),该函数能够查找图像内的轮廓信息,而函数 cv2.drawContours()能够将轮廓绘制出来。

查找并绘制轮廓

import cv2
img = cv2.imread('9.jpg')
cv2.imshow("origin",img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
'''
image, contours, hierarchy = cv2.findContours( image, mode, method)
参数说明:
    image:与函数参数中的原始图像 image 一致。
    contours:返回的轮廓。
    hierarchy:图像的拓扑信息(轮廓层次)。
    式中的参数为:
    image:原始图像。8 位单通道图像,所有非零值被处理为 1,所有零值保持不变。
                    灰度图像会被自动处理为二值图像。
                    在实际操作时,可以根据需要,预先使用阈值处理等函数将待查找轮廓的图像处理为二值图像。
    mode:轮廓检索模式。
    method:轮廓的近似方法。
                轮廓的提取方式,具体有如下 4 种:
                    (1)cv2.RETR_EXTERNAL:只检测外轮廓。
                    (2)cv2.RETR_LIST:对检测到的轮廓不建立等级关系。
                    (3)cv2.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。
                                        上面的一层为外边界,下面的一层为内孔的边界。
                                        如果内孔内还有一个连通物体,那么这个物体的边界仍然位于顶层。
                    (4)cv2.RETR_TREE:建立一个等级树结构的轮廓。
返回值:
    contours:该返回值返回的是一组轮廓信息,每个轮廓都是由若干个点所构成的。
                例如,contours[i]是第 i 个轮廓(下标从 0 开始),contours[i][j]是第 i 个轮廓内的第 j 个点。
    hierarchy:图像内的轮廓可能位于不同的位置。
                比如,一个轮廓在另一个轮廓的内部。外部的轮廓称为父轮廓,内部的轮廓称为子轮廓。一幅图像中所有轮廓之间就建立了父子关系。
            根据轮廓之间的关系,就能够确定一个轮廓与其他轮廓是如何连接的。
                比如,确定一个轮廓是某个轮廓的子轮廓,或者是某个轮廓的父轮廓。称为层次(组织结构),返回值 hierarchy 就包含上述层次关系。 
            每个轮廓 contours[i]对应 4 个元素来说明当前轮廓的层次关系。其形式为:[Next,Previous,First_Child,Parent]
                其中,Next:后一个轮廓的索引编号。
                    Previous:前一个轮廓的索引编号。
                    First_Child:第 1 个子轮廓的索引编号。
                    Parent:父轮廓的索引编号。
'''
image,contours, hierarchy = cv2.findContours(binary,
                                            cv2.RETR_EXTERNAL,
                                            cv2.CHAIN_APPROX_SIMPLE)
'''
cv2.drawContours(image, # 待绘制轮廓的图像。
                contours, # 需要绘制的轮廓。
                contourIdx, # 需要绘制的边缘索引,一个整数或者为零,则表示绘制对应索引号的轮廓;
                                如果该值为-1,则表示绘制全部轮廓。
                color[, # 绘制的颜色。
                thickness[, # 画笔粗细。-1表示要绘制实心轮廓。
                lineType[, # 绘制轮廓时所用的线型。
                hierarchy[, # 对应函数 cv2.findContours()所输出的层次信息。
                maxLevel[, # 控制所绘制的轮廓层次的深度。如果值为 0,表示仅仅绘制第 0 层的轮廓;
                                如果值为其他的非零正数,表示绘制最高层及以下的相同数量层级的轮廓。
                offset]]]]] # 偏移参数。该参数使轮廓偏移到不同的位置展示出来。
                )
'''
img=cv2.drawContours(img,contours,-1,(0,0,255),5)
cv2.imshow("cv2.drawContours",img)
cv2.waitKey(0)
cv2.destroyAllWindows()

OpenCV-Python快速入门(十一):图像矩_opencv

矩特征

  • 空间矩:
    OpenCV-Python快速入门(十一):图像矩_python_02
    其中,OpenCV-Python快速入门(十一):图像矩_OpenCV_03表示图像第x行第y列的像素值
  • 零阶矩:m00
  • 一阶矩:m10, m01
  • 二阶矩:m20, m11, m02
  • 三阶矩:m30, m21, m12, m03
  • 中心矩
    OpenCV-Python快速入门(十一):图像矩_python_04
    其中,OpenCV-Python快速入门(十一):图像矩_OpenCV_03表示图像第x行第y列的像素值,OpenCV-Python快速入门(十一):图像矩_计算机视觉_06
  • 二阶中心矩:mu20, mu11, mu02
  • 三阶中心矩:mu30, mu21, mu12, mu03
  • 归一化中心矩
    OpenCV-Python快速入门(十一):图像矩_opencv_07
  • 二阶 Hu 矩:nu20, nu11, nu02
  • 三阶 Hu 矩:nu30, nu21, nu12, nu03
  • 空间矩、中心矩和归一化中心矩都是根据公式计算得到的,有兴趣的,可自行去深入了解,在此不做过多赘述,大多数矩比较抽象。
  • 如果两个轮廓的矩一致,那么这两个轮廓就是一致的。虽然大多数矩都是通过数学公式计算得到的抽象特征,但是零阶矩m00的含义比较直观,表示一个轮廓的面积。
  • 矩特征函数 cv2.moments()所返回的特征值,能够用来比较两个轮廓是否相似。
  • 例如,有两个轮廓,不管它们出现在图像的哪个位置,我们都可以通过函数 cv2.moments()的 m00 矩判断其面积是否一致。
  • 在位置发生变化时,虽然轮廓的面积、周长等特征不变,但是更高阶的特征会随着位置的变化而发生变化。在很多情况下,我们希望比较不同位置的两个对象的一致性。
  • 解决这一问题的方法是引入中心矩。中心矩通过减去均值而获取平移不变性,因而能够比较不同位置的两个对象是否一致。中心矩具有的平移不变性,能够忽略两个对象的位置关系,比较不同位置上两个对象的一致性。
  • 除了考虑平移不变性外,还会考虑经过缩放后大小不一致的对象的一致性。图像在缩放前后能够拥有一个稳定的特征值。也就是说,让图像在缩放前后具有同样的特征值。显然,中心矩不具有这个属性。
  • 例如,两个形状一致、大小不一的对象,其中心矩是有差异的。
  • 归一化中心矩通过除以物体总尺寸而获得缩放不变性。它通过上述计算提取对象的归一化中心矩属性值,该属性值不仅具有平移不变性,还具有缩放不变性。
import cv2
import numpy as np
img = cv2.imread('10.jpg')
cv2.imshow("origin",img)
gray = cv2.cvtColor(img,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):")
'''
retval = cv2.moments( array[, binaryImage] )
参数说明:
    array:可以是点集,也可以是灰度图像或者二值图像。
            当 array 是点集时,函数会把这些点集当成轮廓中的顶点,把整个点集作为一条轮廓,而不是把它们当成独立的点来看待。
    binaryImage:该参数为 True 时,array 内所有的非零值都被处理为 1。该参数仅在参数
    array 为图像时有效。
返回值:
    retval是矩特征,
'''
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()

OpenCV-Python快速入门(十一):图像矩_python_08

观察各个轮廓的矩(moments):
轮廓0的矩:
 {'m00': 11529.5, 'm10': 1105743.3333333333, 'm01': 2847787.833333333, 'm20': 120176862.41666666, 'm11': 270349796.125, 'm02': 712630365.9166666, 'm30': 14235866112.7, 'm21': 29152670573.233334, 'm12': 66977238524.066666, 'm03': 180577623654.95, 'mu20': 14129911.619918242, 'mu11': -2768935.0824339986, 'mu02': 9226441.749845982, 'mu30': -39658.30255317688, 'mu21': 84283.91559934616, 'mu12': -132304.25695943832, 'mu03': -23497.28594970703, 'nu20': 0.10629638576745037, 'nu11': -0.020830122622460843, 'nu02': 0.06940860197030822, 'nu30': -2.778484238845095e-06, 'nu21': 5.904981202030144e-06, 'nu12': -9.269314847779921e-06, 'nu03': -1.6462338139499466e-06}
观察各个轮廓的面积:
轮廓1的矩:
 {'m00': 8953.5, 'm10': 2733993.6666666665, 'm01': 2274016.833333333, 'm20': 839022387.75, 'm11': 694381324.375, 'm02': 588967940.9166666, 'm30': 258754950659.80002, 'm21': 213146723971.6, 'm12': 179843992765.1, 'm03': 155290097928.05002, 'mu20': 4184461.869233489, 'mu11': -495.0505565404892, 'mu02': 11411392.272744179, 'mu30': -3134.562957763672, 'mu21': 51473359.34176171, 'mu12': 91628.19532775879, 'mu03': -92982228.10702515, 'nu20': 0.05219800910754708, 'nu11': -6.1753827054780176e-06, 'nu02': 0.14234852088438427, 'nu30': -4.132329106022059e-07, 'nu21': 0.006785790040230899, 'nu12': 1.2079446595493111e-05, 'nu03': -0.012257950238254916}
观察各个轮廓的面积:
轮廓2的矩:
 {'m00': 9842.5, 'm10': 3005239.333333333, 'm01': 771158.8333333333, 'm20': 925324247.9166666, 'm11': 235467933.04166666, 'm02': 68113586.08333333, 'm30': 287249593438.7, 'm21': 72503694924.4, 'm12': 20798735853.566666, 'm03': 6542331746.450001, 'mu20': 7725726.137263536, 'mu11': 7749.33013227582, 'mu02': 7693373.106139764, 'mu30': -204687.6685180664, 'mu21': -94220.82750475407, 'mu12': 200932.48085737228, 'mu03': 89548.22490978241, 'nu20': 0.07974959059351447, 'nu11': 7.9993245222886e-05, 'nu02': 0.07941562315268903, 'nu30': -2.129747516905527e-05, 'nu21': -9.803549714150096e-06, 'nu12': 2.0906752970020027e-05, 'nu03': 9.317371731559569e-06}
观察各个轮廓的面积:
轮廓3的矩:
 {'m00': 14160.5, 'm10': 1550604.3333333333, 'm01': 1111569.6666666665, 'm20': 186503823.5833333, 'm11': 121720951.125, 'm02': 103965306.25, 'm30': 24081987687.2, 'm21': 14640475200.516666, 'm12': 11384761698.816666, 'm03': 10784367997.800001, 'mu20': 16709409.646529496, 'mu11': 1750.395137220621, 'mu02': 16709409.646529496, 'mu30': -1157.9857597351074, 'mu21': -68661.15165162086, 'mu12': 68661.1516456604, 'mu03': 1157.985761642456, 'nu20': 0.08333048885405045, 'nu11': 8.72928999634898e-06, 'nu02': 0.08333048885405045, 'nu30': -4.852960404034913e-08, 'nu21': -2.8774952322123127e-06, 'nu12': 2.8774952319625176e-06, 'nu03': 4.852960412028351e-08}
观察各个轮廓的面积:
轮廓0的面积:11529
轮廓1的面积:8953
轮廓2的面积:9842
轮廓3的面积:14160
import cv2
import numpy as np
img = cv2.imread('10.jpg')
gray = cv2.cvtColor(img,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("origin",img)
n=len(contours)
contoursImg=[]
'''
retval =cv2.contourArea(contour [, oriented] ))
参数说明:
    contour 是轮廓。
    oriented 是布尔型值。当它为 True 时,返回的值包含正/负号,用来表示轮廓是顺时针还是逆时针的。
                        该参数的默认值是 False,表示返回的 retval 是一个绝对值。

retval = cv2.arcLength( curve, closed )
参数说明:
    curve 是轮廓。
    closed 是布尔型值,用来表示轮廓是否是封闭的。该值为 True 时,表示轮廓是封闭的。
'''
for i in range(n):
    print("contours["+str(i)+"]面积=",cv2.contourArea(contours[i]))
    print("contours["+str(i)+"]长度=",cv2.arcLength(contours[i],True))
    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()
cv2.destroyAllWindows()

OpenCV-Python快速入门(十一):图像矩_计算机视觉_09

contours[0]面积= 11529.5
contours[0]长度= 455.4385987520218
contours[1]面积= 8953.5
contours[1]长度= 413.2964633703232
contours[2]面积= 9842.5
contours[2]长度= 371.2619733810425
contours[3]面积= 14160.5
contours[3]长度= 475.41421353816986

Hu 矩

  • Hu 矩是归一化中心矩的线性组合。Hu 矩在图像旋转、缩放、平移等操作后,仍能保持矩的不变性,所以经常会使用 Hu 距来识别图像的特征。
  • 在 OpenCV 中,使用函数 cv2.HuMoments()可以得到 Hu 距。该函数使用 cv2.moments()函数的返回值作为参数,返回 7 个 Hu 矩值。
  • Hu 矩是归一化中心矩的线性组合,每一个矩都是通过归一化中心矩的组合运算得到的。
  • 函数 cv2.moments()返回的归一化中心矩中包含:
  • 二阶 Hu 矩:nu20, nu11, nu02
  • 三阶 Hu 矩:nu30, nu21, nu12, nu03
  • 为了表述上的方便,将上述字母“nu”表示为字母“v”,则归一化中心矩为:
  • 二阶 Hu 矩:v20, v11, v02
  • 三阶 Hu 矩:v30, v21, v12, v03
  • 上述 7 个 Hu 矩的计算公式为:

验证OpenCV-Python快速入门(十一):图像矩_opencv_10

import cv2
img = cv2.imread('10.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
'''
hu = cv2.HuMoments( m )
参数:
    m,是由函数 cv2.moments()计算得到矩特征值。
返回值:
    hu,表示返回的 Hu 矩值。
'''
HuM1=cv2.HuMoments(cv2.moments(gray)).reshape(-1,)
print("cv2.moments(gray)=\n",cv2.moments(gray))
print("\nHuM1=\n",HuM1)
print("\ncv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']=%f+%f=%f\n"
        %(cv2.moments(gray)['nu20'],cv2.moments(gray)['nu02'],cv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']))
print("HuM1[0]=",HuM1[0])
print("\nHu[0]-(nu02+nu20)=",
        HuM1[0]-(cv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']))
cv2.moments(gray)=
 {'m00': 10984711.0, 'm10': 2073419806.0, 'm01': 1727047846.0, 'm20': 511135534408.0, 'm11': 331060890119.0, 'm02': 363947136528.0, 'm30': 144117116068876.0, 'm21': 83257440040473.0, 'm12': 70594257720405.0, 'm03': 87457550994094.0, 'mu20': 119767050347.46558, 'mu11': 5071866847.833557, 'mu02': 92415708675.84805, 'mu30': 2424422543738.1875, 'mu21': 980560651950.082, 'mu12': 302566419319.2676, 'mu03': 1177008938055.9062, 'nu20': 0.0009925675733482376, 'nu11': 4.203301788676019e-05, 'nu02': 0.0007658937531944091, 'nu30': 6.062290242573147e-06, 'nu21': 2.4519006754501144e-06, 'nu12': 7.565700361543787e-07, 'nu03': 2.9431213709124163e-06}

HuM1=
 [ 1.75846133e-03  5.84481191e-08  3.38545321e-11  7.56031184e-11    
  1.57210638e-21  1.01272182e-14 -3.48686093e-21]

cv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']=0.000993+0.000766=0.001758

HuM1[0]= 0.0017584613265426467

Hu[0]-(nu02+nu20)= 0.0

Hu矩的应用:形状匹配

  • 通过 Hu 矩来可以判断两个对象的一致性。为了更直观方便地比较 Hu 矩值,OpenCV 提供了函数 cv2.matchShapes(),对两个对象的 Hu 矩进行比较。
  • 函数 cv2.matchShapes()允许我们提供两个对象,对二者的 Hu 矩进行比较。这两个对象可以是轮廓,也可以是灰度图。
import cv2
img1 = cv2.imread('11.jpg')
img2 = cv2.imread('12.jpg')
img3 = cv2.imread('13.jpg')
gray1 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
gray3 = cv2.cvtColor(img3,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)
image,contours1, hierarchy = cv2.findContours(binary1,
                                                cv2.RETR_LIST,
                                                cv2.CHAIN_APPROX_SIMPLE)
image,contours2, hierarchy = cv2.findContours(binary2,
                                                cv2.RETR_LIST,
                                                cv2.CHAIN_APPROX_SIMPLE)
image,contours3, hierarchy = 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("相同图像的 matchShape=",ret0)
print("相似图像的 matchShape=",ret1)
print("不相似图像的 matchShape=",ret2)
cv2.imshow("img1",img1)
cv2.imshow("img2",img2)
cv2.imshow("img3",img3)
cv2.waitKey()
cv2.destroyAllWindows()

OpenCV-Python快速入门(十一):图像矩_python_11

相同图像的 matchShape= 0.0
相似图像的 matchShape= 0.0006734369574569188
不相似图像的 matchShape= 0.12010013449803147
  • 从结果上看:
  • 同一幅图像的 Hu 矩是不变的,二者差值为 0。
  • 相似的图像即使发生了平移、旋转和缩放后,函数 cv2.matchShapes()的返回值仍然比较接近。
  • 例如,图像 img1 和图像 img2,img2 是对 img1 经过缩放、旋转和平移后得到的,但是对二者应用 cv2.matchShapes()函数后,返回值的差较小。
  • 不相似图像 cv2.matchShapes()函数返回值的差较大。
  • 例如,图像 img1 和图像 img3 的差别较大,因此对二者应用 cv2.matchShapes()函数后,返回值的差也较大。

参考文献

[1] https://opencv.org/
[2] 李立宗. OpenCV轻松入门:面向Python. 北京: 电子工业出版社,2019

  • 更多精彩内容,可点击进入 OpenCV-Python快速入门专栏或我的个人主页查看


好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695