计算机系统应用教程网站

网站首页 > 技术文章 正文

不可错过!一文让你学会OpenCV轮廓筛选与识别,全程干货

btikc 2024-10-12 10:21:10 技术文章 14 ℃ 0 评论

轮廓筛选

《OpenCV轮廓绘制详解》中我们已经学习了如何绘制轮廓。接下来,如果想要计算检测到的轮廓的大小,可以使用基于图像矩的方法或使用 OpenCV 函数 cv2.contourArea() 来计算检测到的轮廓的大小,本节中,我们将首先根据每个检测到的轮廓大小对其进行排序,在实践中,某些小的轮廓可能是噪声导致的,可能需要对轮廓进行筛选。

我们首先在画布上绘制不同半径的圆,用于后续检测:

# 画布
image = np.ones((300,700,3), dtype='uint8')
# 绘制不同半径的圆
cv2.circle(image, (20, 20), 8, (64, 128, 0), -1)
cv2.circle(image, (60, 80), 25, (128, 255, 64), -1)
cv2.circle(image, (100, 180), 50, (64, 255, 64), -1)
cv2.circle(image, (200, 250), 45, (255, 128, 64), -1)
cv2.circle(image, (300, 250), 30, (35, 128, 35), -1)
cv2.circle(image, (380, 100), 15, (125, 255, 125), -1)
cv2.circle(image, (600, 210), 55, (125, 125, 255), -1)
cv2.circle(image, (450, 150), 60, (0, 255, 125), -1)
cv2.circle(image, (330, 180), 20, (255, 125, 0), -1)
cv2.circle(image, (500, 60), 35, (125, 255, 0), -1)
cv2.circle(image, (200, 80), 65, (125, 64, 125), -1)
cv2.circle(image, (620, 80), 48, (255, 200, 128), -1)
cv2.circle(image, (400, 260), 28, (255, 255, 0), -1)

接下来,检测图中的轮廓:

gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 阈值处理
ret, thresh = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY)
# 检测轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 打印检测到的轮廓数
print("detected contours: '{}' ".format(len(contours)))

根据每个检测到的轮廓大小进行排序:

def sort_contours_size(cnts):
    """根据大小对轮廓进行排序"""

    cnts_sizes = [cv2.contourArea(contour) for contour in cnts]
    (cnts_sizes, cnts) = zip(*sorted(zip(cnts_sizes, cnts)))
    return cnts_sizes, cnts
    
(contour_sizes, contours) = sort_contours_size(contours)

最后进行可视化:

for i, (size, contour) in enumerate(zip(contour_sizes, contours)):
    # 计算轮廓的矩
    M = cv2.moments(contour)
    # 质心
    cX = int(M['m10'] / M['m00'])
    cY = int(M['m01'] / M['m00'])
    # get_position_to_draw() 函数与上例相同
    (x, y) = get_position_to_draw(str(i + 1), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 2, 5)

    # 将排序结果置于形状的质心
    cv2.putText(image, str(i + 1), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 255, 255), 5)
# show_img_with_matplotlib() 函数与上例相同
show_img_with_matplotlib(image, 'image', 1)
show_img_with_matplotlib(image, "result", 2)

plt.show()

程序运行结果如下所示:

轮廓识别

我们之前已经介绍过了 cv2.approxPolyDP(),它可以使用 Douglas Peucker 算法用较少的点来使一个轮廓逼近检测的轮廓。此函数中的一个关键参数是 epsilon,其用于设置近似精度。我们使用 cv2.approxPolyDP(),以便根据被抽取的轮廓中的检测到顶点的数量识别轮廓(例如,三角形,方形,矩形,五角形或六角形)。为了减少点数,给定某个轮廓,我们首先计算轮廓的边( perimeter )。基于边,建立 epsilon 参数, epsilon 参数计算如下:

epsilon = 0.03 * perimeter

如果该常数变大(例如,从 0.03 变为 0.1 ),则 epsilon 参数也会更大,近似精度将减小,这导致具有较少点的轮廓,并且导致顶点的缺失,对轮廓的识别也将不正确,因为它基于检测到的顶点的数量;另一方面,如果该常数较小(例如,从0.03 变为 0.001),则 epsilon 参数也将变小,因此,近似精度将增加,将产生具有更多点的近似轮廓,对轮廓的识别同样会出现错误,因为获得了虚假顶点。

# 构建测试图像
image = np.ones((300,700,3), dtype='uint8')
cv2.circle(image, (100, 80), 65, (64, 128, 0), -1)
pts = np.array([[300, 10], [400, 150], [200, 150]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (64, 255, 64))
cv2.rectangle(image, (450, 20),(650, 150),(125, 125, 255),-1)
cv2.rectangle(image, (50, 180),(150, 280),(255, 125, 0),-1)
pts = np.array([[365, 220], [320, 282], [247, 258], [247, 182], [320, 158]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (125, 64, 125))
pts = np.array([[645, 220], [613, 276], [548, 276], [515, 220], [547, 164],[612, 164]], np.int32)
pts = pts.reshape((-1, 1, 2))
cv2.fillPoly(image, [pts], (255, 255, 0))

gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

ret, thresh = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY)
# 轮廓检测
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

image_contours = image.copy()
image_recognition_shapes = image.copy()

# 绘制所有检测的轮廓
draw_contour_outline(image_contours, contours, (255, 255, 255), 4)

def get_position_to_draw(text, point, font_face, font_scale, thickness):
    """获取图形坐标中心点"""
    text_size = cv2.getTextSize(text, font_face, font_scale, thickness)[0]
    text_x = point[0] - text_size[0] / 2
    text_y = point[1] + text_size[1] / 2
    return round(text_x), round(text_y)

def detect_shape(contour):
    """形状识别"""
    # 计算轮廓的周长
    perimeter = cv2.arcLength(contour, True)
    contour_approx = cv2.approxPolyDP(contour, 0.03 * perimeter, True)
    if len(contour_approx) == 3:
        detected_shape = 'triangle'
    elif len(contour_approx) == 4:
        x, y, width, height = cv2.boundingRect(contour_approx)
        aspect_ratio = float(width) / height
        if 0.90 < aspect_ratio < 1.10:
            detected_shape = "square"
        else:
            detected_shape = "rectangle"
    elif len(contour_approx) == 5:
        detected_shape = "pentagon"
    elif len(contour_approx) == 6:
        detected_shape = "hexagon"
    else:
        detected_shape = "circle"
    return detected_shape, contour_approx

for contour in contours:
    # 计算轮廓的矩
    M = cv2.moments(contour)
    # 计算轮廓的质心
    cX = int(M['m10'] / M['m00'])
    cY = int(M['m01'] / M['m00'])
    # 识别轮廓形状
    shape, vertices = detect_shape(contour)
    # 绘制轮廓
    draw_contour_points(image_contours, [vertices], (255, 255, 255))
    # 将形状的名称置于形状的质心
    (x, y) = get_position_to_draw(shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.6, 3)
    cv2.putText(image_recognition_shapes, shape, (x+35, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 3)

# 可视化
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 outline (after approximation)", 3)
show_img_with_matplotlib(image_recognition_shapes, "contours recognition", 4)
plt.show()

相关链接:

OpenCV轮廓检测详解 - 掘金:https://juejin.cn/post/7062929275426439204

OpenCV图像矩详解 - 掘金:https://juejin.cn/post/7063399022249279525

OpenCV Hu不变矩详解 - 掘金:https://juejin.cn/post/7063654603480367112

OpenCV轮廓绘制详解 - 掘金:https://juejin.cn/post/7064011386388480008

作者:盼小辉丶
链接:https://juejin.cn/post/7064397660291072014
来源:稀土掘金

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表