Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save edward1986/dbbe55e93f0dec988c46477f98c32bf6 to your computer and use it in GitHub Desktop.
Save edward1986/dbbe55e93f0dec988c46477f98c32bf6 to your computer and use it in GitHub Desktop.
"""
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