OpenCV图像矩详解

1,185 阅读4分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

图像矩

在数学中,矩表示函数形状的特定定量测量。而在计算机数据领域,图像矩可以视为图像像素强度的加权平均值,其编码了图像的一些特性。因此,图像矩可用于描述检测到的轮廓的一些性质(例如,物体的质心,或物体的区域等)。 OpenCV 中 提供了 cv2.moments() 函数用于计算向量形状或栅格化形状的三阶矩,函数用法如下:

retval = cv.moments(array[, binaryImage])

因此,可以使用以下方式计算检测到的轮廓(例如,检测到的第一个轮廓)的矩:

M = cv2.moments(contours[0])

我们打印 M,以查看图像矩的信息:

{'m00': 203700.5, 'm10': 65138893.0, 'm01': 65184079.166666664, 'm20': 24157077178.583332, 'm11': 20844151188.958332, 'm02': 24186367618.25, 'm30': 9853254039349.8, 'm21': 7730082994775.5, 'm12': 7733632427205.399, 'm03': 9869218925404.75, 
'mu20': 3327106799.2006187, 'mu11': -268722.3380508423, 'mu02': 3327488151.551258, 'mu30': 487977833.58203125, 'mu21': -253389.3426513672, 'mu12': -458453806.3643799, 'mu03': 1109170.4453125, 
'nu20': 0.08018304628713532, 'nu11': -6.476189966458202e-06, 'nu02': 0.08019223685270128, 'nu30': 2.605672665422043e-05, 'nu21': -1.3530321224005687e-08, 'nu12': -2.448022162878717e-05, 'nu03': 5.9226770393023014e-08}

如上所示,有三种不同类型的矩,包括 m_jimu_jinu_ji

m_ji 表示空间矩,其计算公式如下:

mji=x,y(array(x,y)xjyi)m_{ji}=\sum_{x,y}(array(x,y)\cdot x^j \cdot y^i)

mu_ji 表示中心矩,其计算公式如下:

muji=x,y(array(x,y)(xxˉ)j(yyˉ)i)mu_{ji}=\sum_{x,y}(array(x,y)\cdot (x-\bar x)^j \cdot (y-\bar y)^i)

其中:

xˉ=m10m00,yˉ=m01m00\bar x= \frac {m_{10}}{m_{00}}, \bar y =\frac {m_{01}}{m_{00}}

通过定义,可知中心矩是具有平移不变性。因此,中心矩适合描述物体的形状。然而,空间矩和中心矩的缺点是它们依赖于对象的大小,它们不具尺度不变性。

nu_jl 表示归一化中心矩,其计算公式如下:

nuji=mujim00(i+j)2+1nu_{ji}=\frac{mu_{ji}}{m_{00}^{\frac{(i+j)}2+1}}

根据定义可知,归一化中心矩是具有平移和缩放不变量。

接下来,将计算基于矩的一些对象特征(例如,中心,偏心或轮廓的区域)。

一些基于矩的对象特征

我们已经知道了,矩是从轮廓计算的特征,虽然其没有直接理解表征其几何含义,但可以根据矩计算一些几何特性。

接下来,我们首先计算检测到的轮廓的矩,然后,据此计算一些对象特征:

M = cv2.moments(contours[0])
print("Contour area: '{}'".format(cv2.contourArea(contours[0])))
print("Contour area: '{}'".format(M['m00']))

m_00 给出了轮廓的区域,这等价于函数cv2.contourArea()。要计算轮廓的质心,需要使用以下方法:

print("center X : '{}'".format(round(M['m10'] / M['m00'])))
print("center Y : '{}'".format(round(M['m01'] / M['m00'])))

圆度 κκ 是测量轮廓接近完美圆轮廓的程度,轮廓圆度计算公式如下:

k=P24Aπk=\frac{P^2} {4 \cdot A\cdot \pi}

其中,P 是轮廓的周长,A 是轮廓的区域面积。如果轮廓为圆形,其圆度为 1kk 值越高,它将越不像圆:

def roundness(contour, moments):
    """计算轮廓圆度"""
    length = cv2.arcLength(contour, True)
    k = (length * length) / (moments['m00'] * 4 * np.pi)
    return k

偏心率(也称为伸长率)是一种衡量轮廓伸长的程度。偏心 ε 可以直接从对象的长半轴 a 和短半轴 b 计算得出:

ε=a2b2b2ε=\sqrt{\frac {a^2-b^2}{b^2}}

因此,计算轮廓的偏心度的一种方法是首先计算拟合轮廓的椭圆,然后从计算出的椭圆导出 ab;最后,利用上述公式计算 ε

def eccentricity_from_ellipse(contour):
    """利用拟合的椭圆计算偏心率"""
    # 拟合椭圆
    (x, y), (MA, ma), angle = cv2.fitEllipse(contour)
    a = ma / 2
    b = MA / 2

    ecc = np.sqrt(a ** 2 - b ** 2) / a
    return ecc

另一种方法是通过使用轮廓矩来计算偏心率:

ε=1mu20+mu0224mu112+(mu20+mu02)22mu20+mu022+4mu112+(mu20+mu02)22ε=\sqrt{1-\frac {\frac{mu20+mu02}2-\sqrt {\frac{4\cdot mu11^2+(mu20+mu02)^2}2}}{\frac{mu20+mu02}2+\sqrt {\frac{4\cdot mu11^2+(mu20+mu02)^2}2}}}

接下来利用轮廓矩计算偏心率:

def eccentricity_from_moments(moments):
    """利用轮廓矩计算偏心率"""

    a1 = (moments['mu20'] + moments['mu02']) / 2
    a2 = np.sqrt(4 * moments['mu11'] ** 2 + (moments['mu20'] - moments['mu02']) ** 2) / 2
    ecc = np.sqrt(1 - (a1 - a2) / (a1 + a2))
    return ecc

纵横比是轮廓边界矩形的宽度与高度的比率,可以基于 cv2.boundingRect() 计算的最小边界矩形的尺寸来计算纵横比:

def aspect_ratio(contour):
    """计算纵横比"""

    x, y, w, h = cv2.boundingRect(contour)
    res = float(w) / h
    return res

在下图中,通过可视化脚本中计算的所有对象属性来显示轮廓分析结果:

一些基于矩的对象特征

需要注意的是:在以上示例中,仅使用二阶矩计算简单对象特征。为了更精确的描述复杂对象,应该使用高阶矩或更复杂的矩,对象越复杂,为了最大限度地减少从矩重构对象的误差,应计算的矩阶越高。

相关链接

OpenCV轮廓检测详解