You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:使用OpenCV检测视频中形状时出现漏检与大量误检问题

求助:使用OpenCV检测视频中形状时出现漏检与大量误检问题

我现在尝试用OpenCV结合Python从视频源中检测各种形状,但我的代码只能检测出“幽灵圆圈”,我不知道为什么会出现这种情况。

以下是我的完整带注释代码:

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

检测到的“幽灵圆圈”示例

如我们所见,当形状穿过水平线时应该被分类。如果你们能帮我解决这个问题,我会非常感激,测试只需安装OpenCV和Python环境即可运行代码。


UPDATE - 第二种方法

我发现这些形状的轮廓面积应该大于5000:

轮廓面积检测示例

对应的代码如下:

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

但现在还是检测到大量矩形和五边形,可我的视频里其实只有三角形和圆形。


备注:内容来源于stack exchange,提问作者FreddicMatters

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:38:01