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
相关产品推荐
相关产品推荐

