[Python] why my code can't detect any triangle, square or another shape using opencv

Discussão em 'Python' iniciado por Stack, Dezembro 18, 2024.

  1. Stack

    Stack Membro Participativo

    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()


    [​IMG]

    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

    [​IMG]

    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, cv2.arcLength(contours, True) * 0.02, True)
    #print(cv2.contourArea(contours))
    # Filter small or non-convex objects
    if abs(cv2.contourArea(contours)) < 5000 or not cv2.isContourConvex(approx):
    continue

    # Classify the shapes based on the number of vertices
    x, y, w, h = cv2.boundingRect(contours)
    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)
    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.

    Continue reading...

Compartilhe esta Página