1

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:

but for this image

The output it gives is 1.

why?

5
  • 2
    "why?" -- because the input image is too bright compared to your threshold. (when you inspect the result of your thresholding operation, you'll find only 18 pixels remain black in the entire image).
    – Dan Mašek
    Commented Dec 28, 2023 at 12:50
  • still doesn't work though
    – Ilia Kh
    Commented Dec 28, 2023 at 13:06
  • You may try automatic threshold with OTSU and invert the result with : ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU). Blurring the gray image before may also help. Finally, note that your input number is not used by your code.
    – SeB
    Commented Dec 28, 2023 at 13:16
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking.
    – Community Bot
    Commented Dec 28, 2023 at 13:41
  • Use cv2.inRange() to threshold on the background color, then invert.
    – fmw42
    Commented Dec 28, 2023 at 16:55

1 Answer 1

2

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:

example

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

Labelled

4
  • Thanks but one small problem you used kernel and caught how many shapes are there in the picture but Im not sure how to use that to find out about *how many types of shapes are there in the image *
    – Ilia Kh
    Commented Dec 28, 2023 at 13:28
  • For that, you can use the cv2 approxPolyDP approach on every contour. Your question was aimed at identifying the contours, this is done. If you have further questions, add it in another post.
    – Tino D
    Commented Dec 28, 2023 at 13:34
  • Im not really good at it and for asking another question I need to wait 30 mins..
    – Ilia Kh
    Commented Dec 28, 2023 at 13:45
  • I edited the code. Please consider marking it as an answer
    – Tino D
    Commented Dec 28, 2023 at 13:52

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.