Issue
I'm having problem with my code, it's supposed to get a two line input, the first line is how many shapes are in the image, and the second line is the image path.
The output should be the number of types of shapes, example input: there is a test1.jpg
image that has 10 circles in it, the output should be: 1
my codes write here :
import cv2
import numpy as np
num_shapes = int(input("Enter the number of shapes in the image: "))
image_path = input("Enter the path to the image: ")
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
filtered_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > 100]
num_detected_shapes = len(filtered_contours)
print("Detected shapes:", num_detected_shapes)
But for this image:
The output it gives is 1.
why?
Solution
Your image has very bad contrast and the background has very high values. One way to emphasize the difference between the background and the shapes is to use OTSU thresholding instead of binary:
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_OTSU)
After that, you need to invert the binary mask, since again, the highest values in your case are the background and not the shapes.
inv_binary = cv2.bitwise_not(binary)
After that, it's good to introduce some contrast by means of checking and thresholding:
kernel = 255*np.ones((10, 10), np.uint8) # dilate the binary mask, here and next line
dilated_binary = cv2.dilate(gray, kernel, iterations=1)
mask = cv2.dilate((dilated_binary < 245).astype(np.uint8)*255, kernel, iterations=1) # create another binary mask where the values are lower than 245, here I did some manual checking
contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # find the contour on the mask
filtered_contours = [cnt for cnt in contours if cv2.contourArea(cnt) > 100] # filter the contours
num_detected_shapes = len(filtered_contours)
print("Detected shapes:", num_detected_shapes)
Results -> Detected shapes: 8
Here is also an image of the contouring:
V2.0: with added shape counting
To identify the type of contour, you can approximate the shape by using cv2.approxPolyDP
. The idea would be to count the vertices afterwards:
circles = 0
triangles = 0
rectangles = 0
for contour in filtered_contours:
M = cv2.moments(contour)
if M["m00"] != 0:
cx = int(M["m10"] / M["m00"])
cy = int(M["m01"] / M["m00"])
# approximate the contour
epsilon = 0.04 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
# number of sides
num_vertices = len(approx)
# see how many sides and decide which shape
if num_vertices == 3:
shape = "Triangle"
triangles += 1
elif num_vertices == 4:
shape = "Rectangle"
rectangles += 1
else: # in this case I just put the default as a circle, since there are currently no other options.
shape = "Circle"
circles += 1
plt.text(cx, cy, shape)
After that, here is the summary:
print("Summary:")
print("Rects or squares: "+str(rectangles))
print("Triangles: "+str(triangles))
print("Circles: "+str(circles))
This will give the following:
Summary:
Rects or squares: 3
Triangles: 3
Circles: 2
Answered By - Tino D
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.