OpenCV-Python实战(10)——详解 OpenCV 轮廓检测

x33g5p2x  于2021-10-20 转载在 Python  
字(13.5k)|赞(0)|评价(0)|浏览(946)

0. 前言

在计算机视觉领域,轮廓通常指图像中对象边界的一系列点。因此,轮廓通常描述了对象边界的关键信息,包含了有关对象形状的主要信息,该信息可用于形状分析与对象检测和识别。在本文中,将首先通过简单示例了解轮廓的基本概念,然后通过实际示例来了解如何检测和压缩轮廓,最后介绍如何利用图像矩描述检测到的轮廓属性。

1. 轮廓介绍

轮廓视为对象边界曲线包含的所有点,通过对这些点的分析可以进行形状判断以及对象检测和识别等计算机视觉过程。OpenCV 提供了许多函数来检测和处理轮廓,在深入了解这些函数之前,我们首先通过函数模拟观察轮廓的基本结构:

def get_test_contour():
    cnts = [np.array(
        [[[600, 320]], [[460, 562]], [[180, 563]], [[40, 320]], 
         [[179, 78]], [[459, 77]]], dtype=np.int32)]
    return cnts

如上所示,轮廓是由 np.int32 类型的多个点组成的数组,调用此函数可以获取此阵列表示的轮廓,此阵列只有包含一个轮廓:

contours = get_test_contour()
print("contour shape: '{}'".format(contours[0].shape))
print("'detected' contours: '{}' ".format(len(contours)))

获得轮廓后,我们可以应用 OpenCV 提供与轮廓相关的所有函数。请注意,get_one_contour() 函数中仅包含简单轮廓,而在实际场景中,检测到的真实轮廓通常有数百个点,因此调试代码将十分耗时,此时设置一个简单轮廓(例如此处的 get_one_contour() 函数)以调试和测试与轮廓相关的函数将非常有用。
OpenCV 提供了cv2.drawContours() 用于在图像中绘制轮廓,我们可以调用此函数来查看轮廓外观:

def draw_contour_outline(img, cnts, color, thickness=1):
    for cnt in cnts:
        cv2.drawContours(img, [cnt], 0, color, thickness)

此外,我们可能还想要绘制图像中的轮廓点:

def draw_contour_points(img, cnts, color):
    for cnt in cnts:
        # 维度压缩
        squeeze = np.squeeze(cnt)
        # 遍历轮廓阵列的所有点
        for p in squeeze:
            # 为了绘制圆点,需要将列表转换为圆心元组
            p = array_to_tuple(p)
            # 绘制轮廓点
            cv2.circle(img, p, 10, color, -1)

    return img
    
def array_to_tuple(arr):
    """将列表转换为元组"""
    return tuple(arr.reshape(1, -1)[0])

最后,调用 draw_contour_outline()draw_contour_points() 函数绘制轮廓和轮廓点,并可视化:

# 绘制轮轮廓点
draw_contour_points(image_contour_points, contours, (255, 0, 255))

# 绘制轮廓
draw_contour_outline(image_contour_outline, contours, (0, 255, 255), 3)

# 同时绘制轮廓和轮廓点
draw_contour_outline(image_contour_points_outline, contours, (255, 0, 0), 3)
draw_contour_points(image_contour_points_outline, contours, (0, 0, 255))

# 可视化函数
def show_img_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(1, 3, pos)
    plt.imshow(img_RGB)
    plt.title(title, fontsize=8)
    plt.axis('off')
    
# 绘制图像
show_img_with_matplotlib(image_contour_points, "contour points", 1)
show_img_with_matplotlib(image_contour_outline, "contour outline", 2)
show_img_with_matplotlib(image_contour_points_outline, "contour outline and points", 3)

# 可视化
plt.show()

2. 轮廓检测

我们已经介绍了轮廓的相关概念,并通过实例了解了轮廓的绘制,接下来我们将介绍如何在 OpenCV 中检测轮廓。为此我们首先绘制一些预定义的形状,然后使用绘制的形状讲解如何进行轮廓检测:

def build_sample_image():
    """绘制一些基本形状"""
    img = np.ones((500, 500, 3), dtype="uint8") * 70
    cv2.rectangle(img, (50, 50), (250, 250), (255, 0, 255), -1)
    cv2.rectangle(img, (100, 100), (200, 200), (70, 70, 70), -1)
    cv2.circle(img, (350, 350), 100, (255, 255, 0), -1)
    cv2.circle(img, (350, 350), 50, (70, 70, 70), -1)
    return img
    
# 加载图像并转换为灰度图像
image = build_sample_image()
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 应用 cv2.threshold() 函数获取二值图像
ret, thresh = cv2.threshold(gray_image, 70, 255, cv2.THRESH_BINARY)

上述函数绘制了两个填充的矩形和两个填充的圆圈,此函数创建的图像具有两个外部轮廓和两个内部轮廓,并在加载图像后,将其转换为灰度图形,并获取二值图像,此二值图像将用于使用 cv2.findContours() 函数查找轮廓。
接下来,就可以调用 cv2.findContours() 检测到利用 build_sample_image() 函数创建的图形的轮廓,cv2.findContours() 函数用法如下:

cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> image, contours, hierarchy

其中,method 参数设置检索与每个检测到的轮廓相关的点时使用的近似方法,cv2.findContours() 返回检测到的二值图像中的轮廓(例如,经过阈值处理之后得到的图像),每个轮廓包含定义边界的所有轮廓点,检索到的轮廓可以以不同的模式( mode )输出:

输出模式说明
cv2.RETR_EXTERNAL仅输出外部轮廓
cv2.RETR_LIST输出没有分层关系的所有轮廓
cv2.RETR_TREE通过建立分层关系输出所有轮廓

输出矢量 hierarchy 包含有关分层关系的信息,为每个检测到的轮廓提供一个索引。对于第 i 个轮廓 contours[i]hierarchy[i][j] ( j 的取值范围为 [0,3] )包含以下内容:

索引说明
hierarchy[i][0]位于相同的层次级别的下一个轮廓的索引,当其为负值时,表示没有下一轮廓
hierarchy[i][1]位于相同的层次级别的前一个轮廓的索引,当其为负值时,表示没没有前一轮廓
hierarchy[i][2]第一个孩子轮廓的索引,当其为负值时,表示没有父轮廓
hierarchy[i][3]父轮廓的索引,当其为负值时,表示没有下一轮廓

调用 cv2.findContours() 函数查找测试图像中轮廓:

# 轮廓检测
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contours2, hierarchy2 = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
contours3, hierarchy3 = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

# 打印使用不同 mode 参数获得的轮廓数
print("detected contours (RETR_EXTERNAL): '{}' ".format(len(contours)))
print("detected contours (RETR_LIST): '{}' ".format(len(contours2)))
print("detected contours (RETR_TREE): '{}' ".format(len(contours3)))

image_contours = image.copy()
image_contours_2 = image.copy()

# 绘制检测到的轮廓
draw_contour_outline(image_contours, contours, (0, 0, 255), 5)
draw_contour_outline(image_contours_2, contours2, (255, 0, 0), 5)

# 可视化
show_img_with_matplotlib(image, "image", 1)
show_img_with_matplotlib(cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR), "threshold = 100", 2)
show_img_with_matplotlib(image_contours, "contours (RETR EXTERNAL)", 3)
show_img_with_matplotlib(image_contours_2, "contours (RETR LIST)", 4)

3. 轮廓压缩

当检测到的轮廓包含大量点时,可以使用轮廓压缩算法来减少轮廓点的数量,OpenCV 提供了减少轮廓点数量的方法,这就是 cv2.findContours() 函数中 method 参数的用武之地了:

可选值解释
cv2.CHAIN_APPROX_NONE禁用压缩,其存储所有边界点,不进行压缩
cv2.CHAIN_APPROX_SIMPLE压缩轮廓的水平,垂直和对角线,仅保留端点,例如,如果将其用于压缩矩形的轮廓,压缩后的结果仅由四个顶点组成
cv2.CHAIN_APPROX_TC89_L1基于非参数方法的Teh-Chin算法压缩轮廓
cv2.CHAIN_APPROX_TC89_KCOS基于非参数方法的Teh-Chin算法压缩轮廓

最后两个压缩算法均是基于非参数方法的Teh-Chin算法压缩轮廓,该算法的第一步根据每个点的局部属性确定其支持区域( region of support, ROS );接下来,该算法计算每个点的相对重要性度量。最后,通过非最大抑制检测优势点。区别在于它们使用不同的显著性度量,对应于离散曲率度量的不同精度。
接下来,我们使用不同的压缩算法来比较它们之间的区别:

# 阈值处理
ret, thresh = cv2.threshold(gray_image, 70, 255, cv2.THRESH_BINARY)

methods = [cv2.CHAIN_APPROX_NONE, cv2.CHAIN_APPROX_SIMPLE, cv2.CHAIN_APPROX_TC89_L1, cv2.CHAIN_APPROX_TC89_KCOS]
# 循环使用每一压缩算法来比较它们之间的区别
for index in range(len(methods)):
    method = methods[index]
    image_approx = image.copy()
    contours ,hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, method)
    # 可视化
    draw_contour_points(image_approx, contours, (255, 255, 255))
    show_img_with_matplotlib(image_approx, "contours ({})".format(method), 3 + index)

show_img_with_matplotlib(image, "image", 1)
show_img_with_matplotlib(cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR), "threshold = 100", 2)

plt.show()

4. 图像矩

在数学中,矩表示函数形状的特定定量测量。而在计算机数据领域,图像矩可以视为图像像素强度的加权平均值,其编码了图像的一些特性。因此,图像矩可用于描述检测到的轮廓的一些性质(例如,物体的质心,或物体的区域等)。
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 表示空间矩,其计算公式如下:
m j i = ∑ x , y ( a r r a y ( x , y ) ⋅ x j ⋅ y i ) m_{ji}=\sum_{x,y}(array(x,y)\cdot x^j \cdot y^i)mji​=x,y∑​(array(x,y)⋅xj⋅yi)
mu_ji 表示中心矩,其计算公式如下:
m u j i = ∑ x , y ( a r r a y ( x , y ) ⋅ ( x − x ˉ ) j ⋅ ( y − y ˉ ) i ) mu_{ji}=\sum_{x,y}(array(x,y)\cdot (x-\bar x)^j \cdot (y-\bar y)^i)muji​=x,y∑​(array(x,y)⋅(x−xˉ)j⋅(y−yˉ​)i)
其中:
x ˉ = m 10 m 00 , y ˉ = m 01 m 00 \bar x= \frac {m_{10}}{m_{00}}, \bar y =\frac {m_{01}}{m_{00}}xˉ=m00​m10​​,yˉ​=m00​m01​​
通过定义,可知中心矩是具有平移不变性。因此,中心矩适合描述物体的形状。然而,空间矩和中心矩的缺点是它们依赖于对象的大小,它们不具尺度不变性。
nu_jl 表示归一化中心矩,其计算公式如下:
n u j i = m u j i m 00 ( i + j ) 2 + 1 nu_{ji}=\frac{mu_{ji}}{m_{00}^{\frac{(i+j)}2+1}}nuji​=m002(i+j)​+1​muji​​
根据定义可知,归一化中心矩是具有平移和缩放不变量。
接下来,将计算基于矩的一些对象特征(例如,中心,偏心或轮廓的区域)。

4. 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 = P 2 4 ⋅ A ⋅ π k=\frac{P^2} {4 \cdot A\cdot \pi}k=4⋅A⋅πP2​
其中,P 是轮廓的周长,A 是轮廓的区域面积。如果轮廓为圆形,其圆度为 1;k kk 值越高,它将越不像圆:

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

偏心率(也称为伸长率)是一种衡量轮廓伸长的程度。偏心 ε 可以直接从对象的长半轴 a 和短半轴 b 计算得出:
ε = a 2 − b 2 b 2 ε=\sqrt{\frac {a^2-b^2}{b^2}}ε=b2a2−b2​​
因此,计算轮廓的偏心度的一种方法是首先计算拟合轮廓的椭圆,然后从计算出的椭圆导出 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

另一种方法是通过使用轮廓矩来计算偏心率:
ε = 1 − m u 20 + m u 02 2 − 4 ⋅ m u 1 1 2 + ( m u 20 + m u 02 ) 2 2 m u 20 + m u 02 2 + 4 ⋅ m u 1 1 2 + ( m u 20 + m u 02 ) 2 2 ε=\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}}}ε=1−2mu20+mu02​+24⋅mu112+(mu20+mu02)2​​2mu20+mu02​−24⋅mu112+(mu20+mu02)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

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

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

4.2 Hu 不变矩

Hu 不变矩可以保持平移、缩放和旋转不变,同时,所有的矩(第 7 个矩除外)对于反射都是不变的。第 7 个矩因反射而改变,从而使其能够区分镜像图片。 OpenCV 提供 cv2.HuMoments() 来计算 7 个Hu 不变矩,使用方法如下:

cv2.HuMoments(m[, hu]) → hu

这里,m 对应于用 cv2.moments() 计算的矩,输出 hu 对应于 7 个 Hu 不变矩。
7 个 Hu 不变矩定义如下:
h u [ 0 ] = n u 20 + n u 02 h u [ 1 ] = ( n u 20 − n u 02 ) 2 + 4 ⋅ n u 11 2 h u [ 2 ] = ( n u 30 − 3 ⋅ n u 12 ) 2 + ( 3 ⋅ n u 21 − n u 03 ) 2 h u [ 3 ] = ( n u 30 + n u 12 ) 2 + ( n u 21 + n u 03 ) 2 h u [ 4 ] = ( n u 30 − 3 ⋅ n u 12 ) ( n u 30 + n u 12 ) [ ( n u 30 + n u 12 ) 2 − 3 ⋅ ( n u 21 + n u 03 ) 2 ] + ( 3 ⋅ n u 21 − n u 03 ) ( n u 21 + n u 03 ) [ 3 ⋅ ( n u 30 + n u 12 ) 2 − ( n u 21 + n u 03 ) 2 ] h u [ 5 ] = ( n u 20 − n u 02 ) [ ( n u 30 + n u 12 ) 2 − ( n u 21 + n u 03 ) 2 ] + 4 ⋅ ( n u 30 + n u 12 ) ( n u 21 + n u 03 ) h u [ 6 ] = ( 3 ⋅ n u 21 − n u 03 ) ( n u 21 + n u 03 ) [ 3 ⋅ ( n u 30 + n u 12 ) 2 − ( n u 21 + n u 03 ) 2 ] − ( n u 30 − 3 ⋅ n u 12 ) ( n u 21 + n u 03 ) [ 3 ⋅ ( n u 30 + n u 12 ) 2 − ( n u 21 + n u 03 ) 2 ] \begin{aligned} hu[0]&=nu_{20}+nu_{02} \ hu[1]&=(nu_{20}-nu_{02})^2+4\cdot nu_{11}^2 \ hu[2]&=(nu_{30}-3\cdot nu_{12})^2+(3\cdot nu_{21}- nu_{03})^2 \ hu[3]&=(nu_{30}+nu_{12})^2+(nu_{21}+ nu_{03})^2 \ hu[4]&=(nu_{30}-3\cdot nu_{12})(nu_{30}+nu_{12})[(nu_{30}+nu_{12})^2-3\cdot(nu_{21}+nu_{03})^2]+(3\cdot nu_{21}- nu_{03})(nu_{21}+ nu_{03})[3\cdot (nu_{30}+nu_{12})^2-(nu_{21}+ nu_{03})^2]\ hu[5]&=(nu_{20}-nu_{02})[(nu_{30}+nu_{12})^2-(nu_{21}+nu_{03})^2]+4\cdot(nu_{30}+nu_{12})(nu_{21}+ nu_{03})\ hu[6]&=(3\cdot nu_{21}- nu_{03})(nu_{21}+ nu_{03})[3\cdot (nu_{30}+nu_{12})^2-(nu_{21}+nu_{03})^2]-(nu_{30}-3\cdot nu_{12})(nu_{21}+ nu_{03})[3\cdot (nu_{30}+nu_{12})^2-(nu_{21}+ nu_{03})^2] \end{aligned}hu[0]hu[1]hu[2]hu[3]hu[4]hu[5]hu[6]​=nu20​+nu02​=(nu20​−nu02​)2+4⋅nu112​=(nu30​−3⋅nu12​)2+(3⋅nu21​−nu03​)2=(nu30​+nu12​)2+(nu21​+nu03​)2=(nu30​−3⋅nu12​)(nu30​+nu12​)[(nu30​+nu12​)2−3⋅(nu21​+nu03​)2]+(3⋅nu21​−nu03​)(nu21​+nu03​)[3⋅(nu30​+nu12​)2−(nu21​+nu03​)2]=(nu20​−nu02​)[(nu30​+nu12​)2−(nu21​+nu03​)2]+4⋅(nu30​+nu12​)(nu21​+nu03​)=(3⋅nu21​−nu03​)(nu21​+nu03​)[3⋅(nu30​+nu12​)2−(nu21​+nu03​)2]−(nu30​−3⋅nu12​)(nu21​+nu03​)[3⋅(nu30​+nu12​)2−(nu21​+nu03​)2]​

接下来编写程序计算 7 个 Hu 不变矩,为了计算不变矩,必须首先使用 cv2.moments() 计算矩。计算图像矩时,可以使用矢量形状或图像,如果 binaryImage 参数为真(仅用于图像),则输入图像中的所有非零像素将被视为1。计算使用矢量形状和图像的图像矩后,根据计算的矩,计算 Hu 不变矩。

# 加载图像并将其转化为灰度图像
image = cv2.imread("example.png")
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 获取二值图像
ret, thresh = cv2.threshold(gray_image, 70, 255, cv2.THRESH_BINARY)
# 计算图像矩,传递参数为图像
M = cv2.moments(thresh, True)
print("moments: '{}'".format(M))
def centroid(moments):
    """根据图像矩计算质心"""
    x_centroid = round(moments['m10'] / moments['m00'])
    y_centroid = round(moments['m01'] / moments['m00'])
    return x_centroid, y_centroid
# 计算质心
x, y = centroid(M)
# 计算 Hu 矩并打印
HuM = cv2.HuMoments(M)
print("Hu moments: '{}'".format(HuM))

# 计算图像矩时传递轮廓,重复以上计算过程
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
M2 = cv2.moments(contours[0])
print("moments: '{}'".format(M2))
x2, y2 = centroid(M2)
# 绘制轮廓
draw_contour_outline(image, contours, (255, 0, 0), 10)
# 绘制质心
cv2.circle(image, (x, y), 25, (255, 255, 0), -1)
cv2.circle(image, (x2, y2), 25, (0, 0, 255), -1)
# 打印质心
print("('x','y'): ('{}','{}')".format(x, y))
print("('x2','y2'): ('{}','{}')".format(x2, y2))
# 可视化,show_img_with_matplotlib()函数与前述示例类似,不再赘述
def show_thresh_with_matplotlib(thresh, title, pos):
    ax = plt.subplot(1, 2, pos)
    plt.imshow(thresh, cmap='gray')
    plt.title(title, fontsize=8)
    plt.axis('off')
show_img_with_matplotlib(image, "detected contour and centroid", 1)
show_thresh_with_matplotlib(thresh_1, 'thresh', 2)
plt.show()

观察所计算的矩,Hu 不变矩,以及质心,可以发现使用矢量形状和图像的结果是相似的,但稍有不同,例如,获得的质心:

('x','y'): ('1124','1713')
('x2','y2'): ('1157','1636')

其坐标相差一些像素,原因是栅格化的图像分辨率有限。对于轮廓估计的矩与针对栅格化后的轮廓计算的矩稍有不同。上图中可以看到程序的输出,其中使用不同颜色显示了两个质心,以便观察它们之前的差异。
接下来,为了对比 Hu 不变矩,我们使用三个图像。第一个是原始图像,第二个将原始图像旋转180度,第三个将原始图像水平翻转,计算上述图像的 Hu 不变矩。
程序的第一步是使用 cv2.imread() 加载图像,并通过使用 cv2.cvtColor() 将它们转换为灰度图像。第二步是应用 cv2.threshold() 获取二进制图像。最后,使用 cv2.humoments() 计算Hu不变矩:

# 加载图像并进行转换
images = [image_1, image_2, image_3]
des = ['original', 'rotation', 'reflection']
for i in range(len(images)):
    image = images[i]
    # 转换为灰度图像
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # 图像二值化
    ret, thresh = cv2.threshold(gray_image, 100, 255, cv2.THRESH_BINARY)
    # 计算 Hu 不变矩
    HuM_1 = cv2.HuMoments(cv2.moments(thresh, True)).flatten()
    # 打印 Hu 不变矩
    print("Hu moments ({}): '{}'".format(des[i], HuM_1))
    # 可视化
    show_img_with_matplotlib(image, "original", 1+i )

plt.show()

查看计算的 Hu 不变矩结果:

Hu moments (original): '[ 3.01270761e-01 2.85277848e-02 6.91011783e-03 3.83970453e-04 -3.46840290e-07 -3.85059443e-05 5.20465006e-07]'
Hu moments (rotation): '[ 3.01270761e-01 2.85277848e-02 6.91011783e-03 3.83970453e-04 -3.46840290e-07 -3.85059443e-05 5.20465006e-07]'
Hu moments (reflection): '[ 3.01270761e-01 2.85277848e-02 6.91011783e-03 3.83970453e-04 -3.46840290e-07 -3.85059443e-05 -5.20465006e-07]'

可以看到,除了第七个矩外,计算的 Hu 不变矩在这三种情况下是相同的。

小结

在本文中,首先介绍了轮廓的相关概念,然后了解利用 cv2.findContours() 检测轮廓、cv2.drawContours() 绘制轮廓,在获取轮廓后,我们可以利用图像矩来计算轮廓的几何特征。

相关文章