OpenCV轮廓检测详解

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

前言

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

轮廓介绍

轮廓视为对象边界曲线包含的所有点,通过对这些点的分析可以进行形状判断以及对象检测和识别等计算机视觉过程。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() 函数绘制轮廓和轮廓点,并可视化:

# 创建画布并复制,用于显示不同检测效果
canvas = np.zeros((640, 640, 3), dtype="uint8")
image_contour_points = canvas.copy()
image_contour_outline = canvas.copy()
image_contour_points_outline = canvas.copy()
# 绘制轮轮廓点
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()
复制代码

轮廓介绍

轮廓检测

我们已经介绍了轮廓的相关概念,并通过实例了解了轮廓的绘制,接下来我们将介绍如何在 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)
复制代码

轮廓探测

猜你喜欢

转载自juejin.im/post/7062929275426439204