laitimes

Don't miss it! The article allows you to learn OpenCV contour screening and identification, and the whole process is dry

author:Artificial Intelligence - Tyro

Profile filtering

In OpenCV Detailed Outline Drawing, we have learned how to draw contours. Next, if you want to calculate the size of the detected contours, you can use the image moment-based method or use the OpenCV function cv2.contourArea() to calculate the size of the detected contours, in this section, we will first sort each detected contour size, in practice, some small contours may be caused by noise and may need to be filtered on the profiles.

We first draw circles of different radii on the canvas for subsequent inspections:

# 画布
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)           

Next, detect the outline in the plot:

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)))           

Sort according to the size of each detected profile:

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)           

Finally visualize:

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()           

The program runs as follows:

Don't miss it! The article allows you to learn OpenCV contour screening and identification, and the whole process is dry

Contour recognition

We have already covered cv2.approxPolyDP(), which can use the Douglas Peucker algorithm to approximate a detected contour with fewer points. A key parameter in this function is epsilon, which is used to set the approximate precision. We use cv2.approxPolyDP() in order to identify a profile (e.g., triangle, square, rectangle, pentagon or hexagon) based on the number of vertices detected in the extracted profile. To reduce the number of points, given a certain profile, we first calculate the edges of the profile (perimeter). Based on the edge, the epsilon parameter is established, and the epsilon parameter is calculated as follows:

epsilon = 0.03 * perimeter           

If the constant becomes larger (for example, from 0.03 to 0.1), the epsilon parameter will also be larger and the approximate accuracy will be reduced, which will result in an outline with fewer points, and will result in the absence of vertices, and the recognition of the profile will also be incorrect, because it is based on the number of vertices detected; on the other hand, if the constant is smaller (for example, from 0.03 to 0.001), the epsilon parameter will also become smaller, so the approximate accuracy will increase, resulting in an approximate outline with more points, The identification of the profile also occurs incorrectly because false vertices are obtained.

# 构建测试图像
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()           
Don't miss it! The article allows you to learn OpenCV contour screening and identification, and the whole process is dry

Related Links:

OpenCV Contour Inspection Explained - Nuggets: https://juejin.cn/post/7062929275426439204

OpenCV Image Moments Explained - Nuggets: https://juejin.cn/post/7063399022249279525

OpenCV Hu Invariant Moments Explained - Nuggets: https://juejin.cn/post/7063654603480367112

OpenCV Contour Drawing Explained - Nuggets: https://juejin.cn/post/7064011386388480008

Author: Pan Xiaohui

Link: https://juejin.cn/post/7064397660291072014

Source: Rare Earth Nuggets