OpenCV轮廓绘制详解

4,013 阅读4分钟

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

前言

在计算机视觉领域,轮廓通常指图像中对象边界的一系列点。因此,轮廓通常描述了对象边界的关键信息,包含了有关对象形状的主要信息,该信息可用于形状分析与对象检测和识别。我们已经在《OpenCV轮廓检测详解》中介绍了如何检测和绘制轮廓,在本文中,我们将继续学习如何利用获取到的轮廓,进行形状分析以及对象检测和识别。

轮廓绘制

《OpenCV图像矩详解》中,我们介绍了如何从图像矩计算获取轮廓属性(例如,质心,面积,圆度或偏心等)。除此之外,OpenCV 还提供了一些其他用于进一步描述轮廓的函数。

cv2.boundingRect() 返回包含轮廓所有点的最小边界矩形:

x, y, w, h = cv2.boundingRect(contours[0])

cv2.minAreaRect() 返回包含轮廓所有点的最小旋转(如有必要)矩形:

rotated_rect = cv2.minAreaRect(contours[0])

为了提取旋转矩形的四个点,可以使用 cv2.boxPoints() 函数,返回旋转矩形的四个顶点:

box = cv2.boxPoints(rotated_rect)

cv2.minEnclosingCircle() 返回包含轮廓所有点的最小圆(该函数返回圆心和半径):

(x, y), radius = cv2.minEnclosingCircle(contours[0])

cv2.fitEllipse() 返回包含轮廓所有点的椭圆(具有最小平方误差):

ellipse = cv2.fitEllipse(contours[0])

cv2.approxPolyDP() 基于给定精度返回给定轮廓的轮廓近似,此函数使用 Douglas-Peucker 算法:

approx = cv2.approxPolyDP(contours[0], epsilon, True)

其中,epsilon 参数用于确定精度,确定原始曲线之间的最大距离及其近似。因此,由此产生的轮廓是类似于给定的轮廓的压缩轮廓。

接下来,我们使用与轮廓相关的 OpenCV 函数计算给定轮廓的外端点,在具体讲解代码时,首先看下结果图像,以更好的理解上述函数:

轮廓绘制

首先编写 extreme_points() 用于计算定义给定轮廓的四个外端点:

def extreme_points(contour):
    """检测轮廓的极值点"""

    extreme_left = tuple(contour[contour[:, :, 0].argmin()][0])
    extreme_right = tuple(contour[contour[:, :, 0].argmax()][0])
    extreme_top = tuple(contour[contour[:, :, 1].argmin()][0])
    extreme_bottom = tuple(contour[contour[:, :, 1].argmax()][0])

    return extreme_left, extreme_right, extreme_top, extreme_bottom

np.argmin() 沿轴返回最小值的索引,在多个出现最小值的情况下返回第一次出现的索引;而 np.argmax() 返回最大值的索引。一旦索引 index 计算完毕,就可以利用索引获取阵列的相应元素(例如,contour[index]——[[40 320]] ),如果要访问第一个元素,则使用 contour[index][0]——[40 320];最后,我们将其转换为元组:tuple(contour[index][0]——(40,320),用以绘制轮廓点。

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

def draw_contour_points(img, cnts, color):
    """绘制所有检测到的轮廓点"""
    for cnt in cnts:
        squeeze = np.squeeze(cnt)
        for p in squeeze:
            pp = array_to_tuple(p)
            cv2.circle(img, pp, 10, color, -1)
    return img

def draw_contour_outline(img, cnts, color, thickness=1):
    """绘制所有轮廓"""
    for cnt in cnts:
        cv2.drawContours(img, [cnt], 0, color, thickness)

def show_img_with_matplotlib(color_img, title, pos):
    """图像可视化"""
    img_RGB = color_img[:, :, ::-1]

    ax = plt.subplot(2, 3, pos)
    plt.imshow(img_RGB)
    plt.title(title, fontsize=8)
    plt.axis('off')

# 加载图像并转换为灰度图像
image = cv2.imread("example.png")
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 阈值处理转化为二值图像
ret, thresh = cv2.threshold(gray_image, 60, 255, cv2.THRESH_BINARY)

# 利用二值图像检测图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

# 显示检测到的轮廓数
print("detected contours: '{}' ".format(len(contours)))
# 创建原始图像的副本以执行可视化
boundingRect_image = image.copy()
minAreaRect_image = image.copy()
fitEllipse_image = image.copy()
minEnclosingCircle_image = image.copy()
approxPolyDP_image = image.copy()

# 1. cv2.boundingRect()
x, y, w, h = cv2.boundingRect(contours[0])
cv2.rectangle(boundingRect_image, (x, y), (x + w, y + h), (0, 255, 0), 5)

# 2. cv2.minAreaRect()
rotated_rect = cv2.minAreaRect(contours[0])
box = cv2.boxPoints(rotated_rect)
box = np.int0(box)
cv2.polylines(minAreaRect_image, [box], True, (0, 0, 255), 5)

# 3. cv2.minEnclosingCircle()
(x, y), radius = cv2.minEnclosingCircle(contours[0])
center = (int(x), int(y))
radius = int(radius)
cv2.circle(minEnclosingCircle_image, center, radius, (255, 0, 0), 5)

# 4. cv2.fitEllipse()
ellipse = cv2.fitEllipse(contours[0])
cv2.ellipse(fitEllipse_image, ellipse, (0, 255, 255), 5)

# 5. cv2.approxPolyDP()
epsilon = 0.01 * cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)
draw_contour_outline(approxPolyDP_image, [approx], (255, 255, 0), 5)
draw_contour_points(approxPolyDP_image, [approx], (255, 0, 255))

# 检测轮廓的极值点
left, right, top, bottom = extreme_points(contours[0])
cv2.circle(image, left, 20, (255, 0, 0), -1)
cv2.circle(image, right, 20, (0, 255, 0), -1)
cv2.circle(image, top, 20, (0, 255, 255), -1)
cv2.circle(image, bottom, 20, (0, 0, 255), -1)

# 可视化
show_img_with_matplotlib(image, "image and extreme points", 1)
show_img_with_matplotlib(boundingRect_image, "cv2.boundingRect()", 2)
show_img_with_matplotlib(minAreaRect_image, "cv2.minAreaRect()", 3)
show_img_with_matplotlib(minEnclosingCircle_image, "cv2.minEnclosingCircle()", 4)
show_img_with_matplotlib(fitEllipse_image, "cv2.ellipse()", 5)
show_img_with_matplotlib(approxPolyDP_image, "cv2.approxPolyDP()", 6)
plt.show()

我们还可以测试在其他图像上的效果:

轮廓绘制

相关链接

OpenCV轮廓检测详解

OpenCV图像矩详解

OpenCV Hu不变矩详解