I'm trying detect various shapes by using a video source using opencv with python, however my code only detect "ghost circles", I don't know why happens this behavior. I'm using this video in my code.
here my code full commented:
import cv2
# Initialize counters for detected shapes
triangle_count = 0
quadrilateral_count = 0
pentagon_count = 0
hexagon_count = 0
circle_count = 0
# Horizontal reference line (middle of the frame)
line_y = 240 # Adjust according to the height of the video
# Read the video from file
video_path = 'video.mp4' # Video path
cap = cv2.VideoCapture(video_path)
# Check if the video is loaded correctly
if not cap.isOpened():
print("Error opening video.")
exit()
# Process the video frame by frame
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break # Exit when the video ends
# Create a copy of the original frame to use later
original = frame.copy()
# Convert the frame from BGR to HSV
hsv_image = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# Convert the HSV image to grayscale
gray_image = cv2.cvtColor(hsv_image, cv2.COLOR_BGR2GRAY)
# Apply Otsu thresholding to binarize the image
ret, otsu = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Apply the mask to the original image
image = cv2.bitwise_and(original, original, mask=otsu)
# Find contours in the binary image
contours, _ = cv2.findContours(otsu, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Draw the horizontal reference line
cv2.line(frame, (0, line_y), (frame.shape[1], line_y), (0, 255, 0), 2)
# Process each contour
for i, contour in enumerate(contours):
if i == 0: # Ignore the largest outer contour
continue
# Calculate the area of the contour
contour_area = cv2.contourArea(contour)
# Filter out small objects based on area
if contour_area < 300: # Adjust the minimum area value
continue
# Approximate the shape of the contour
epsilon = 0.01 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
# Calculate the center of the object (bounding box coordinates)
x, y, w, h = cv2.boundingRect(approx)
center_y = y + h // 2 # Y coordinate of the object's center
# Check if the object crosses the horizontal reference line
if line_y - 10 <= center_y <= line_y + 10:
# Classify the shape based on the number of vertices
if len(approx) == 3:
cv2.putText(frame, "Triangle", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
triangle_count += 1
elif len(approx) == 4:
cv2.putText(frame, "Quadrilateral", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
quadrilateral_count += 1
elif len(approx) == 5:
cv2.putText(frame, "Pentagon", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
pentagon_count += 1
elif len(approx) == 6:
cv2.putText(frame, "Hexagon", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
hexagon_count += 1
else:
cv2.putText(frame, "Circle", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
circle_count += 1
# Draw the detected contour
cv2.drawContours(frame, [approx], 0, (0, 0, 0), 2)
# Display the counters in the top left corner
cv2.putText(frame, f"Triangles: {triangle_count}", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.putText(frame, f"Quadrilaterals: {quadrilateral_count}", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
cv2.putText(frame, f"Pentagons: {pentagon_count}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.putText(frame, f"Hexagons: {hexagon_count}", (10, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
cv2.putText(frame, f"Circles: {circle_count}", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
# Show the processed frame
cv2.imshow("Shape Detection", frame)
# Exit with the 'q' key
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Release resources
cap.release()
cv2.destroyAllWindows()
as we can see, when the shape crosses the horizontal line should be classified. I will appreciate you guys if you can help me to fix this problem, above is the link of the video to test my code, only requieres installed opencv and python in your machine.
UPDATE - SECOND APPROACH.
seems that the countour area for this shapes is > 5000
code:
import math
import numpy as np
import cv2
# Initialize the camera or video
cap = cv2.VideoCapture("video.mp4")
print("Press 'q' to exit")
# Function to calculate the angle between three points
def angle(pt1, pt2, pt0):
dx1 = pt1[0][0] - pt0[0][0]
dy1 = pt1[0][1] - pt0[0][1]
dx2 = pt2[0][0] - pt0[0][0]
dy2 = pt2[0][1] - pt0[0][1]
return float((dx1 * dx2 + dy1 * dy2)) / math.sqrt(float((dx1 * dx1 + dy1 * dy1)) * (dx2 * dx2 + dy2 * dy2) + 1e-10)
# Initialize a dictionary to count the detected shapes
shape_counts = {
'TRI': 0,
'RECT': 0,
'PENTA': 0,
'HEXA': 0,
'CIRC': 0
}
# Main loop
while(cap.isOpened()):
# Capture frame by frame
ret, frame = cap.read()
if ret:
# Convert to grayscale
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Apply the Canny detector
canny = cv2.Canny(gray, 80, 240, 3)
# Find contours
contours, hierarchy = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Draw a horizontal line in the center of the image
line_y = int(frame.shape[0] / 2)
cv2.line(frame, (0, line_y), (frame.shape[1], line_y), (0, 255, 0), 2)
# Shape detection counter
for i in range(len(contours)):
# Approximate the contour with precision proportional to the perimeter of the contour
approx = cv2.approxPolyDP(contours[i], cv2.arcLength(contours[i], True) * 0.02, True)
#print(cv2.contourArea(contours[i]))
# Filter small or non-convex objects
if abs(cv2.contourArea(contours[i])) < 5000 or not cv2.isContourConvex(approx):
continue
# Classify the shapes based on the number of vertices
x, y, w, h = cv2.boundingRect(contours[i])
if y + h / 2 > line_y: # Only classify if the shape crosses the line
if len(approx) == 3:
# Triangle
shape_counts['TRI'] += 1
cv2.putText(frame, 'TRI', (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2, cv2.LINE_AA)
elif 4 <= len(approx) <= 6:
# Polygon classification
vtc = len(approx)
cos = []
# Calculate the angles between the vertices using the angle() function
for j in range(2, vtc + 1):
cos.append(angle(approx[j % vtc], approx[j - 2], approx[j - 1]))
# Sort the angles and determine the type of figure
cos.sort()
mincos = cos[0]
maxcos = cos[-1]
# Classify based on the number of vertices
if vtc == 4:
shape_counts['RECT'] += 1
cv2.putText(frame, 'RECT', (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2, cv2.LINE_AA)
elif vtc == 5:
shape_counts['PENTA'] += 1
cv2.putText(frame, 'PENTA', (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2, cv2.LINE_AA)
elif vtc == 6:
shape_counts['HEXA'] += 1
cv2.putText(frame, 'HEXA', (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2, cv2.LINE_AA)
else:
# Detect and label circle
area = cv2.contourArea(contours[i])
radius = w / 2
if abs(1 - (float(w) / h)) <= 2 and abs(1 - (area / (math.pi * radius * radius))) <= 0.2:
shape_counts['CIRC'] += 1
cv2.putText(frame, 'CIRC', (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2, cv2.LINE_AA)
# Display the number of each detected shape at the top of the image
offset_y = 30
for shape, count in shape_counts.items():
cv2.putText(frame, f'{shape}: {count}', (10, offset_y), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2, cv2.LINE_AA)
offset_y += 30
# Display the resulting frame
cv2.imshow('Frame', frame)
cv2.imshow('Canny', canny)
# Exit if 'q' is pressed
if cv2.waitKey(1) == ord('q'):
break
# Once finished, release the capture
cap.release()
cv2.destroyAllWindows()
however is detecting many rectangles and pentagons, but my video only has triangles and rectangles/squares. seems that I need to classify only shapes greater than 5000 and try to close his vertex because sometimes the contour is not complete.
thanks in advance.
I'm trying detect various shapes by using a video source using opencv with python, however my code only detect "ghost circles",
Utilizing Python 3.12.8 and 3.14.0a2 on Windows 10.
The problem can be fixed.
I have seen shadows (who was holding a video camera) of you behind the Geometrical Shapes along with shadows. The boxes and the triangles weren't the same images.
cv2.putText
command.epsilon = 0.09 * cv2.arcLength(contour, True)
#epsilon = 0.01
cv2.drawContours
was inside if\elses
condition block should
be below center_y = y + h // 2
# Y coordinate of the object's
centerSnippet modify the script:
import cv2
# Initialize counters for detected shapes
triangle_count = 0
quadrilateral_count = 0
pentagon_count = 0
hexagon_count = 0
circle_count = 0
# Horizontal reference line (middle of the frame)
line_y = 240 # Adjust according to the height of the video
# Read the video from file
video_path = 'V0.mp4' # Video path
cap = cv2.VideoCapture(video_path)
# Check if the video is loaded correctly
if not cap.isOpened():
print("Error opening video.")
exit()
# Process the video frame by frame
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break # Exit when the video ends
# Create a copy of the original frame to use later
original = frame.copy()
# Convert the frame from BGR to HSV
#hsv_image = cv2.cvtColor(dst, cv2.COLOR_BGR2HSV)
# Convert the HSV image to grayscale
gray_image = cv2.cvtColor(original, cv2.COLOR_BGR2GRAY)
# Apply Otsu thresholding to binarize the image
ret, otsu = cv2.threshold(gray_image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Apply the mask to the original image
# Find contours in the binary image
contours, _ = cv2.findContours(otsu, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Draw the horizontal reference line
cv2.line(frame, (0, line_y), (frame.shape[1], line_y), (0, 255, 0), 2)
# Process each contour
for i, contour in enumerate(contours):
if i == 0: # Ignore the largest outer contour
continue
# Calculate the area of the contour
contour_area = cv2.contourArea(contour)
# Filter out small objects based on area
if contour_area <= 300: # Adjust the minimum area value
continue
# Approximate the shape of the contour
epsilon = 0.09 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
# Calculate the center of the object (bounding box coordinates)
x, y, w, h = cv2.boundingRect(approx)
#cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
center_y = y + h // 2 # Y coordinate of the object's center
cv2.drawContours(frame, [approx], 0, (0, 255, 0), 2)
# Check if the object crosses the horizontal reference line
if line_y - 10 <= center_y <= line_y + 10:
# Classify the shape based on the number of vertices
if len(approx) == 3:
triangle_count += 1
#cv2.putText(frame, "Triangle", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
txt = "Triangle"
elif len(approx) == 4:
quadrilateral_count += 1
txt = "Quadrilateral"
#cv2.putText(frame, "Quadrilateral", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
elif len(approx) == 5:
pentagon_count += 1
txt = "Pentagon"
#cv2.putText(frame, "Pentagon", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
elif len(approx) == 6:
hexagon_count += 1
txt = "Hexagon"
#cv2.putText(frame, "Hexagon", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
elif len(approx) == 0:
circle_count += 1
txt = "Circle"
#cv2.putText(frame, "Circle", (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
cv2.putText(frame, f'{str(txt)}', (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# Display the counters in the top left corner
cv2.putText(frame, f'Triangles: {str(triangle_count)}', (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
cv2.putText(frame, f"Quadrilaterals: {quadrilateral_count}", (10, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2)
cv2.putText(frame, f"Pentagons: {pentagon_count}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
cv2.putText(frame, f"Hexagons: {hexagon_count}", (10, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
cv2.putText(frame, f"Circles: {circle_count}", (10, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
# Show the processed frame
cv2.imshow("Shape Detection", frame)
# Exit with the 'q' key
if cv2.waitKey(72) & 0xFF == ord('q'):
break
# Release resources
cap.release()
cv2.destroyAllWindows()
Screenshot: