Created
March 9, 2021 00:10
-
-
Save edward1986/dbbe55e93f0dec988c46477f98c32bf6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Contours shape recognition mainly based on cv2.approxPolyDP() function | |
""" | |
# Import required packages: | |
import numpy as np | |
import cv2 | |
from matplotlib import pyplot as plt | |
def get_position_to_draw(text, point, font_face, font_scale, thickness): | |
"""Gives the coordinates to draw centered""" | |
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): | |
"""Returns the shape (e.g. 'triangle', 'square') from the contour""" | |
detected_shape = '-----' | |
# Calculate perimeter of the contour: | |
perimeter = cv2.arcLength(contour, True) | |
# Get a contour approximation: | |
contour_approx = cv2.approxPolyDP(contour, 0.03 * perimeter, True) | |
# Check if the number of vertices is 3. In this case, the contour is a triangle | |
if len(contour_approx) == 3: | |
detected_shape = 'triangle' | |
# Check if the number of vertices is 4. In this case, the contour is a square/rectangle | |
elif len(contour_approx) == 4: | |
# We calculate the aspect ration from the bounding rect: | |
x, y, width, height = cv2.boundingRect(contour_approx) | |
aspect_ratio = float(width) / height | |
# A square has an aspect ratio close to 1 (comparison chaining is used): | |
if 0.90 < aspect_ratio < 1.10: | |
detected_shape = "square" | |
else: | |
detected_shape = "rectangle" | |
# Check if the number of vertices is 5. In this case, the contour is a pentagon | |
elif len(contour_approx) == 5: | |
detected_shape = "pentagon" | |
# Check if the number of vertices is 6. In this case, the contour is a hexagon | |
elif len(contour_approx) == 6: | |
detected_shape = "hexagon" | |
# The shape as more than 6 vertices. In this example, we assume that is a circle | |
else: | |
detected_shape = "circle" | |
# return the name of the shape and the found vertices | |
return detected_shape, contour_approx | |
def array_to_tuple(arr): | |
"""Converts array to tuple""" | |
return tuple(arr.reshape(1, -1)[0]) | |
def draw_contour_points(img, cnts, color): | |
"""Draw all points from a list of contours""" | |
for cnt in cnts: | |
print(cnt.shape) | |
squeeze = np.squeeze(cnt) | |
print(squeeze.shape) | |
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): | |
"""Draws contours outlines of each contour""" | |
for cnt in cnts: | |
cv2.drawContours(img, [cnt], 0, color, thickness) | |
def show_img_with_matplotlib(color_img, title, pos): | |
"""Shows an image using matplotlib capabilities""" | |
# Convert BGR image to RGB | |
img_RGB = color_img[:, :, ::-1] | |
ax = plt.subplot(2, 2, pos) | |
plt.imshow(img_RGB) | |
plt.title(title) | |
plt.axis('off') | |
# Create the dimensions of the figure and set title: | |
fig = plt.figure(figsize=(12, 9)) | |
plt.suptitle("Shape recognition based on cv2.approxPolyDP()", fontsize=14, fontweight='bold') | |
fig.patch.set_facecolor('silver') | |
# Load the image and convert it to grayscale: | |
# image = build_sample_image_2() | |
image = cv2.imread("match_shapes.png") | |
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
# Apply cv2.threshold() to get a binary image: | |
ret, thresh = cv2.threshold(gray_image, 50, 255, cv2.THRESH_BINARY) | |
# Find contours using the thresholded image: | |
# Note: cv2.findContours() has been changed to return only the contours and the hierarchy | |
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) | |
# Show the number of detected contours: | |
print("detected contours: '{}' ".format(len(contours))) | |
# Make a copy to draw the results: | |
image_contours = image.copy() | |
image_recognition_shapes = image.copy() | |
# Draw the outline of all detected contours: | |
draw_contour_outline(image_contours, contours, (255, 255, 255), 4) | |
for contour in contours: | |
# Compute the moments of the current contour: | |
M = cv2.moments(contour) | |
# Calculate the centroid of the contour from the moments: | |
cX = int(M['m10'] / M['m00']) | |
cY = int(M['m01'] / M['m00']) | |
# Detect shape of the current contour: | |
shape, vertices = detect_shape(contour) | |
# Draw the detected vertices: | |
draw_contour_points(image_contours, [vertices], (255, 255, 255)) | |
# Get the position to draw: | |
(x, y) = get_position_to_draw(shape, (cX, cY), cv2.FONT_HERSHEY_SIMPLEX, 1.6, 3) | |
# Write the name of shape on the center of shapes | |
cv2.putText(image_recognition_shapes, shape, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 1.6, (255, 255, 255), 3) | |
# Plot the images | |
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) | |
# Show the Figure: | |
plt.show() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment