“This is the 28th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

The contour filter

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

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

# the canvas
image = np.ones((300.700.3), dtype='uint8')
Draw circles of different radii
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)
Copy the code

Next, examine the contour in the diagram:

gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Threshold processing
ret, thresh = cv2.threshold(gray_image, 50.255, cv2.THRESH_BINARY)
# Detect contour
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# Print the number of contours detected
print("detected contours: '{}' ".format(len(contours)))
Copy the code

Sort according to the size of each detected contour:

def sort_contours_size(cnts) :
    """ Sort contours by size """

    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)
Copy the code

Finally, visualize:

for i, (size, contour) in enumerate(zip(contour_sizes, contours)):
    # Calculate the moment of the contour
    M = cv2.moments(contour)
    # centroid
    cX = int(M['m10'] / M['m00'])
    cY = int(M['m01'] / M['m00'])
    The # get_position_to_draw() function is the same as above
    (x, y) = get_position_to_draw(str(i + 1), (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 2.5)

    # place the sorting result at the center of mass of the shape
    cv2.putText(image, str(i + 1), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (255.255.255), 5)
The # show_img_with_matplotlib() function is the same as above
show_img_with_matplotlib(image, 'image'.1)
show_img_with_matplotlib(image, "result".2)

plt.show()
Copy the code

The running result of the program is as follows:

Contour recognition

We have previously introduced cv2.approxPolydp (), which uses the Douglas Peucker algorithm to make a contour approximate to the 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 () to identify contours (for example, triangles, squares, rectangles, pentagons or hexagons) based on the number of vertices detected in the extracted contours. To make the perimeter count, we first count the perimeter edges of a given contour. Epsilon parameters are established based on edges. Epsilon parameters are calculated as follows:

epsilon = 0.03 * perimeter
Copy the code

If the constant becomes larger (for example, from 0.03 to 0.1), the Epsilon parameter will also become larger and the approximate accuracy will decrease, resulting in contours with fewer points and resulting in the loss of vertices. Recognition of the contours will 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), then the Epsilon parameter will also be smaller and, therefore, the approximation accuracy will increase, resulting in approximate contours with more points, which will also be mistaken because false vertices are obtained.

# Build test images
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)
# Contour detection
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

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

Draw the outline of all checks
draw_contour_outline(image_contours, contours, (255.255.255), 4)

def get_position_to_draw(text, point, font_face, font_scale, thickness) :
    """ Get the center point of the graph coordinates ""
    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) :
    """ Shape recognition """
    # Calculate the perimeter of the 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:
    # Calculate the moment of the contour
    M = cv2.moments(contour)
    # Calculate the center of mass of the contour
    cX = int(M['m10'] / M['m00'])
    cY = int(M['m01'] / M['m00'])
    # Recognize contour shapes
    shape, vertices = detect_shape(contour)
    # Draw outline
    draw_contour_points(image_contours, [vertices], (255.255.255))
    # place the name of the shape at its center of mass
    (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)

# visualization
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()
Copy the code

A link to the

OpenCV contour detection in detail

OpenCV image moments in detail

OpenCV Hu invariant moment in detail

OpenCV outline rendering details