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

OpenCV帧差法车辆检测:同一车辆多次标记的解决咨询

问题描述

我正在使用帧差法检测和跟踪车辆,但部分车辆被多次标记。试过用膨胀(dilate)函数解决,但会导致噪声问题严重,求实现每辆车仅用一个矩形标记,直到其离开画面的方法。

相关截图

  • 多帧截图:
    多帧截图
  • 背景与当前帧的差异图:
    背景与当前帧的差异图

现有代码

import cv2
import argparse
import numpy
import math
from tracker import *

from get_background import get_background


parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', help='path to the input video',
                    required=True)
parser.add_argument('-c', '--consecutive-frames', default=4, type=int,
                    dest='consecutive_frames', help='path to the input video')
args = vars(parser.parse_args())

cap = cv2.VideoCapture(args['input'])
# get the video frame height and width
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
save_name = f"outputs/{args['input'].split('/')[-1]}"
# define codec and create VideoWriter object
out = cv2.VideoWriter(
    save_name,
    cv2.VideoWriter_fourcc(*'mp4v'), 10,
    (frame_width, frame_height)
)

#object_detector = cv2.createBackgroundSubtractorMOG2(history=100, varThreshold=40)

# get the background model
background = get_background(args['input'])
# convert the background model to grayscale format
background = cv2.cvtColor(background, cv2.COLOR_BGR2GRAY)
frame_count = 0
consecutive_frame = args['consecutive_frames']

###########################################################################################

kernel_size = 3
gauss_img = cv2.bilateralFilter(background, 9, 300,300)
canny_img = cv2.Canny(gauss_img, 100, 200)
#canny_img = cv2.erode(canny_img, None, iterations=1)
lines = cv2.HoughLinesP(canny_img, rho=1, theta=math.pi / 180,
                        threshold=15,
                        minLineLength=50,
                        maxLineGap=5)
line_img = numpy.zeros((background.shape[0], background.shape[1], 3), dtype=numpy.uint8)

for points in lines:
    # Extracted points nested in the list
    x1, y1, x2, y2 = points[0]

    # Draw the lines joing the points
    # On the original image
    cv2.line(line_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
    # Maintain a simples lookup list for points
    #lines_list.append([(x1, y1), (x2, y2)])

cv2.imshow("lines", line_img)
#cv2.imshow("canny", canny_img)
if cv2.waitKey(100) & 0xFF == ord('q'):
    pass

###########################################################################################
# 24.08.2022 object tracking
tracker = EuclideanDistTracker() ###
detections = []

while (cap.isOpened()):
    ret, frame = cap.read()
    if ret == True:
        frame_count += 1
        orig_frames = []
        orig_frame = frame.copy()
        orig_frames.append(orig_frame)
        # IMPORTANT STEP: convert the frame to grayscale first
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        if frame_count % consecutive_frame == 0 or frame_count == 1:
            frame_diff_list = []
        # find the difference between current frame and base frame
        frame_diff = cv2.absdiff(gray, background)
        # thresholding to convert the frame to binary
        #ret, thres = cv2.threshold(frame_diff, 50, 255, cv2.THRESH_BINARY)

        #frame_diff = cv2.GaussianBlur(frame_diff, (3, 3), 0)
        ret, thres = cv2.threshold(frame_diff, 50, 255, cv2.THRESH_OTSU)

        cv2.imshow('frame_diff', frame_diff)
        out.write(frame_diff)
        if cv2.waitKey(100) & 0xFF == ord('q'):
            break

        # ... makes the detection of contours a bit easier
        kernel = numpy.ones((9,9), numpy.uint8) # bu gerekli mi
        erode_frame = cv2.erode(thres, kernel, iterations=1) # (thres, None, iterations=1)
        # append the final result into the `frame_diff_list`
        frame_diff_list.append(erode_frame)

        #     cv2.imshow("lines", line_img)
        #     #cv2.imshow("canny", canny_img)
        #     if cv2.waitKey(100) & 0xFF == ord('q'):
        #         break

        # if we have reached `consecutive_frame` number of frames
        if len(frame_diff_list) == consecutive_frame:
            # add all the frames in the `frame_diff_list`
            sum_frames = sum(frame_diff_list)
            # find the contours around the white segmented areas
            contours, hierarchy = cv2.findContours(sum_frames, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
            # draw the contours, not strictly necessary
            for i, cnt in enumerate(contours):
                cv2.drawContours(frame, contours, i, (0, 0, 255), 3)
            for contour in contours:
                # continue through the loop if contour area is less than 500...
                # ... helps in removing noise detection
                if cv2.contourArea(contour) < 300:
                    continue
                # get the xmin, ymin, width, and height coordinates from the contours
                (x, y, w, h) = cv2.boundingRect(contour)
                # draw the bounding boxes
                cv2.rectangle(orig_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
                detections.append([x, y, w, h]) ###

            cv2.imshow('Detected Objects', orig_frame)
            out.write(orig_frame)
            if cv2.waitKey(100) & 0xFF == ord('q'):
                break
    else:
        break
cap.release()
cv2.destroyAllWindows()
解决方案

以下几种方法可解决同一车辆被多次标记的问题,同时避免膨胀操作带来的噪声:

1. 轮廓合并(IOU匹配)

提取轮廓生成bounding box后,计算框之间的交并比(IOU),合并重叠度高的框:

def merge_boxes(boxes, iou_threshold=0.3):
    if not boxes:
        return []
    # 按x坐标排序
    boxes = sorted(boxes, key=lambda x: x[0])
    merged = [boxes[0]]
    for current in boxes[1:]:
        last = merged[-1]
        # 计算IOU
        x1 = max(last[0], current[0])
        y1 = max(last[1], current[1])
        x2 = min(last[0]+last[2], current[0]+current[2])
        y2 = min(last[1]+last[3], current[1]+current[3])
        if x2 > x1 and y2 > y1:
            area_inter = (x2-x1)*(y2-y1)
            area_last = last[2]*last[3]
            area_current = current[2]*current[3]
            iou = area_inter / (area_last + area_current - area_inter)
            if iou >= iou_threshold:
                # 合并框:取最小x、y,最大宽高
                new_x = min(last[0], current[0])
                new_y = min(last[1], current[1])
                new_w = max(last[0]+last[2], current[0]+current[2]) - new_x
                new_h = max(last[1]+last[3], current[1]+current[3]) - new_y
                merged[-1] = [new_x, new_y, new_w, new_h]
            else:
                merged.append(current)
        else:
            merged.append(current)
    return merged

在代码中替换原检测框绘制逻辑:

# 替换原contour遍历部分
temp_boxes = []
for contour in contours:
    if cv2.contourArea(contour) < 300:
        continue
    (x, y, w, h) = cv2.boundingRect(contour)
    temp_boxes.append([x, y, w, h])
# 合并重叠框
merged_boxes = merge_boxes(temp_boxes)
# 绘制合并后的框并更新detections
for box in merged_boxes:
    x, y, w, h = box
    cv2.rectangle(orig_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
    detections.append([x, y, w, h])

2. 改进背景差分预处理

  • 对帧差图先做高斯模糊再阈值化,减少噪声干扰:
# 替换原阈值化代码
frame_diff = cv2.GaussianBlur(frame_diff, (5,5), 0)
ret, thres = cv2.threshold(frame_diff, 50, 255, cv2.THRESH_OTSU)
  • 使用形态学开运算(先腐蚀后膨胀)替代单纯腐蚀,既能去除小噪声,又能避免轮廓过度破碎:
kernel = numpy.ones((5,5), numpy.uint8)
opening_frame = cv2.morphologyEx(thres, cv2.MORPH_OPEN, kernel, iterations=1)
frame_diff_list.append(opening_frame)

3. 正确启用跟踪器

你已初始化EuclideanDistTracker,但未真正用它关联前后帧的检测框。修改循环逻辑,让跟踪器维护车辆ID和框:

# 在获得merged_boxes后添加:
tracked_objects = tracker.update(merged_boxes)
for obj_id, box in tracked_objects.items():
    x, y, w, h = box
    cv2.rectangle(orig_frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
    cv2.putText(orig_frame, f"ID: {obj_id}", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2)

跟踪器会基于欧氏距离匹配前后帧的框,同一车辆会被分配固定ID,不会重复标记。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:45:43