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

OpenCV实时面粉盒计数求助:如何避免跨线重复计数?

面粉盒跨检测线重复计数问题解决方法

我已实现目标检测与轮廓绘制功能,希望统计面粉盒跨越检测线时的数量。当前采用物体y坐标大于检测线y坐标的规则计数,但出现了重复计数问题,求解决方法。

核心解决思路

重复计数的根本原因是同一物体在连续多帧中多次触发计数条件,因此需要引入物体跟踪机制,给每个面粉盒分配唯一ID,记录其是否已被计数,确保每个物体只被统计一次。

关键改进点:

  • 物体ID跟踪:用字典存储每个物体的ID、质心位置和计数状态(是否已跨越检测线)。
  • 轮廓匹配:通过计算IOU(交并比)匹配前后帧的同一物体,保持ID不变,避免把同一物体当成新物体重复计数。
  • 计数状态锁定:只有当物体首次满足跨线条件时才计数,之后标记为已计数,不再重复统计。

修改后的完整代码

import datetime
import math
import cv2
import numpy as np
from imutils.video import FPS
import imutils

# 全局变量
width = 0
height = 0
EntranceCounter = 0
ExitCounter = 0
MinCountourArea = 2000  # 根据实际场景调整
BinarizationThreshold = 70  # 根据实际场景调整
OffsetRefLines = 100  # 根据实际场景调整
tracked_objects = {}  # 存储跟踪的物体:{id: (centroid, counted)}
next_object_id = 0

# 计算两个轮廓的IOU(交并比),用于匹配同一物体
def calculate_iou(rect1, rect2):
    x1, y1, w1, h1 = rect1
    x2, y2, w2, h2 = rect2
    
    # 计算交集区域
    inter_x1 = max(x1, x2)
    inter_y1 = max(y1, y2)
    inter_x2 = min(x1 + w1, x2 + w2)
    inter_y2 = min(y1 + h1, y2 + h2)
    
    inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1)
    rect1_area = w1 * h1
    rect2_area = w2 * h2
    union_area = rect1_area + rect2_area - inter_area
    
    if union_area == 0:
        return 0.0
    return inter_area / union_area

# 检查物体是否进入监测区域
def CheckEntranceLineCrossing(y, CoorYEntranceLine, CoorYExitLine):
    AbsDistance = abs(y - CoorYEntranceLine)    
    if ((AbsDistance <= 2) and (y < CoorYExitLine)):
        return True
    else:
        return False

# 检查物体是否离开监测区域
def CheckExitLineCrossing(y, CoorYEntranceLine, CoorYExitLine):
    AbsDistance = abs(y - CoorYExitLine)
    if ((AbsDistance <= 2) and (y > CoorYExitLine)):
        return True
    else:
        return False

camera = cv2.VideoCapture("11.mp4")
fps = camera.get(cv2.CAP_PROP_FPS)
print("fps: ", fps)

ReferenceFrame = None

# 让摄像头适应环境光线,丢弃前20帧
for i in range(0,20):
    (grabbed, Frame) = camera.read()
Frame = cv2.resize(Frame, (860, 540))

while True:    
    (grabbed, Frame) = camera.read()
    if not grabbed:
        break
    
    Frame = cv2.resize(Frame, (860, 540))
    Frame = Frame[0:540, 300:860]
    height = np.size(Frame, 0)
    width = int(np.size(Frame, 1)/2)

    # 灰度转换与高斯模糊
    GrayFrame = cv2.cvtColor(Frame, cv2.COLOR_BGR2GRAY)
    GrayFrame = cv2.GaussianBlur(GrayFrame, (21, 21), 0)
    
    if ReferenceFrame is None:
        ReferenceFrame = GrayFrame
        continue

    # 背景减除与二值化
    FrameDelta = cv2.absdiff(ReferenceFrame, GrayFrame)
    FrameThresh = cv2.threshold(FrameDelta, BinarizationThreshold, 255, cv2.THRESH_BINARY)[1]
    
    # 膨胀图像并查找轮廓
    FrameThresh = cv2.dilate(FrameThresh, None, iterations=2)
    cnts, _ = cv2.findContours(FrameThresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    QttyOfContours = 0
    current_contours = []

    # 绘制参考线(入口和出口线)
    CoorYEntranceLine = int((height / 2) - OffsetRefLines)
    CoorYExitLine = int((height / 2) + OffsetRefLines)
    cv2.line(Frame, (0, CoorYEntranceLine), (width, CoorYEntranceLine), (255, 0, 0), 2)
    cv2.line(Frame, (0, CoorYExitLine), (width, CoorYExitLine), (0, 0, 255), 2)

    # 收集当前帧的有效轮廓(面积符合要求)
    for c in cnts:
        if cv2.contourArea(c) > MinCountourArea:
            (x, y, w, h) = cv2.boundingRect(c)
            current_contours.append((x, y, w, h))
            QttyOfContours += 1

    # 匹配当前轮廓与已跟踪物体
    new_tracked_objects = {}
    for rect in current_contours:
        x, y, w, h = rect
        centroid = (int((x+x+w)/2), int((y+y+h)/2))
        matched_id = None
        max_iou = 0.3  # 匹配阈值,可调整

        # 遍历已跟踪物体,寻找匹配的
        for obj_id, (prev_centroid, counted) in tracked_objects.items():
            prev_rect = (prev_centroid[0] - w//2, prev_centroid[1] - h//2, w, h)
            iou = calculate_iou(rect, prev_rect)
            if iou > max_iou:
                max_iou = iou
                matched_id = obj_id

        if matched_id is not None:
            # 匹配到已有物体,更新质心,保留计数状态
            new_tracked_objects[matched_id] = (centroid, tracked_objects[matched_id][1])
        else:
            # 新物体,分配新ID,未计数
            new_tracked_objects[next_object_id] = (centroid, False)
            next_object_id += 1

    # 更新跟踪字典
    tracked_objects = new_tracked_objects

    # 处理每个跟踪物体的计数逻辑
    for obj_id, (centroid, counted) in tracked_objects.items():
        CoordXCentroid, CoordYCentroid = centroid
        # 从当前轮廓中匹配对应的矩形信息
        for (x, y, w, h) in current_contours:
            if abs(x + w//2 - CoordXCentroid) < 5 and abs(y + h//2 - CoordYCentroid) <5:
                # 绘制矩形和质心
                cv2.rectangle(Frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
                cv2.circle(Frame, centroid, 1, (0, 0, 0), 2)
                
                if not counted:
                    # 检查入口线
                    if CheckEntranceLineCrossing(CoordYCentroid, CoorYEntranceLine, CoorYExitLine):
                        EntranceCounter += 1
                        tracked_objects[obj_id] = (centroid, True)
                    # 检查出口线
                    elif CheckExitLineCrossing(CoordYCentroid, CoorYEntranceLine, CoorYExitLine):
                        ExitCounter += 1
                        tracked_objects[obj_id] = (centroid, True)
                break

    # 在画面上显示计数
    cv2.putText(Frame, "Entrances: {}".format(str(EntranceCounter)), (10, 50),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (250, 0, 1), 2)
    cv2.putText(Frame, "Exits: {}".format(str(ExitCounter)), (10, 70),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
    cv2.imshow("Original Frame", Frame)

    if cv2.waitKey(int(fps)) & 0xFF == ord('q'):
          break

# 清理资源
camera.release()
cv2.destroyAllWindows()

可调整参数说明:

  • MinCountourArea:过滤过小的轮廓,避免噪音干扰。
  • max_iou:轮廓匹配的IOU阈值,值越大匹配越严格,根据实际场景调整。
  • OffsetRefLines:检测线的位置偏移,根据视频画面调整。

内容的提问来源于stack exchange,提问作者Behruz Inomov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 07:38:09