Python OpenCV如何准确统计唯一检测的目标实例总数
问题描述
基于Python+OpenCV开发YOLOv3棕榈树检测应用时,需要统计视频中出现的唯一棕榈树总数量。原有实现仅统计单帧检测到的边界框数量,未做跨帧目标匹配去重,导致计数每帧重置,控制台输出的棕榈树数值在0-6区间反复跳变,无法得到准确的累计结果。
原有控制台错误输出
Palm Trees: 0 Palm Trees: 3 Palm Trees: 3 Palm Trees: 4 Palm Trees: 4 Palm Trees: 6 Palm Trees: 4 Palm Trees: 4 Palm Trees: 4 Palm Trees: 4 Palm Trees: 4 Palm Trees: 5 Palm Trees: 3 Palm Trees: 3 Palm Trees: 3 Palm Trees: 3 Palm Trees: 3 Palm Trees: 2 Palm Trees: 2 Palm Trees: 3 Palm Trees: 3 Palm Trees: 2 Palm Trees: 3 Palm Trees: 3 Palm Trees: 3 Palm Trees: 5 Palm Trees: 4 Palm Trees: 3 Palm Trees: 3
原有问题代码
import cv2 import numpy as np import matplotlib.pyplot as plt net = cv2.dnn.readNet('yolov3_training_last.weights', 'yolov3_testing.cfg') classes = [] with open("classes.txt", "r") as f: classes = f.read().splitlines() cap = cv2.VideoCapture('driveday2_plamtrees.mp4') # cap = cv2.VideoCapture(4) font = cv2.FONT_HERSHEY_PLAIN colors = np.random.uniform(0, 255, size=(100, 3)) a=[] count_trees = 0; while True: _, img = cap.read() height, width, _ = img.shape blob = cv2.dnn.blobFromImage(img, 1/255, (416, 416), (0,0,0), swapRB=True, crop=False) net.setInput(blob) output_layers_names = net.getUnconnectedOutLayersNames() layerOutputs = net.forward(output_layers_names) boxes = [] confidences = [] class_ids = [] for output in layerOutputs: for detection in output: scores = detection[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.2: center_x = int(detection[0]*width) center_y = int(detection[1]*height) w = int(detection[2]*width) h = int(detection[3]*height) x = int(center_x - w/2) y = int(center_y - h/2) boxes.append([x, y, w, h]) a.append([center_x,center_y]) # print("Palm Trees: " + str(len(a))) confidences.append((float(confidence))) class_ids.append(class_id) indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.2, 0.4) if len(indexes)>0: for i in indexes.flatten(): x, y, w, h = boxes[i] label = str(classes[class_ids[i]]) confidence = str(round(confidences[i],2)) color = colors[i] cv2.rectangle(img, (x,y), (x+w, y+h), color, 2) cv2.putText(img, label + " " + confidence, (x, y+20), font, 2, (255,255,255), 1) count_trees = 1 + len(boxes) print("Palm Trees: " + str(count_trees)) cv2.imshow('Image', img) key = cv2.waitKey(1) if key==ord('q'): break cap.release() cv2.destroyAllWindows()
问题根因
- 计数逻辑完全错误:
count_trees = 1 + len(boxes)每帧都会重新赋值为当前帧检测框数量+1,没有任何累计逻辑 - 无跨帧目标匹配能力:每帧检测到的边界框没有和历史帧的目标做关联,无法区分当前帧的目标是新出现的,还是之前已经统计过的旧目标
- 冗余代码无效:全局列表
a无限制追加所有检测中心点,既不去重也不清理,完全没有参与计数逻辑
解决方案
要准确统计唯一目标总数,核心是增加轻量跨帧跟踪+去重计数逻辑。针对车载视角拍摄路边棕榈树的场景,用中心点距离匹配即可实现稳定计数,不需要引入复杂的多目标跟踪框架,实现逻辑如下:
- 维护全局已跟踪目标列表,每个目标记录最近一次出现的中心点坐标、连续丢失的帧计数
- 每帧完成NMS拿到有效检测框后,计算每个检测框中心点和所有已跟踪目标中心点的欧氏距离
- 若距离小于设定匹配阈值,判定为同一个已统计目标,更新该目标的中心点坐标,重置丢失帧计数
- 若当前检测框和所有已跟踪目标的距离都大于阈值,判定为新目标,总计数+1,将该目标加入已跟踪列表
- 每帧检测完成后,将连续丢失帧超过阈值的目标从跟踪列表移除,避免列表无限膨胀
修改后可直接运行的代码
import cv2 import numpy as np import math net = cv2.dnn.readNet('yolov3_training_last.weights', 'yolov3_testing.cfg') classes = [] with open("classes.txt", "r") as f: classes = f.read().splitlines() cap = cv2.VideoCapture('driveday2_plamtrees.mp4') font = cv2.FONT_HERSHEY_PLAIN colors = np.random.uniform(0, 255, size=(100, 3)) # 跟踪阈值参数,可根据实际视频效果调整 DISTANCE_THRESH = 30 # 判定为同一目标的最大中心点像素距离 MISS_FRAME_THRESH = 10 # 目标连续丢失多少帧后,从跟踪列表移除 tracked_trees = [] # 跟踪列表存储格式: [center_x, center_y, miss_count] total_count = 0 # 唯一棕榈树总计数 def cal_distance(p1, p2): return math.hypot(p1[0] - p2[0], p1[1] - p2[1]) while True: ret, img = cap.read() if not ret: break height, width, _ = img.shape blob = cv2.dnn.blobFromImage(img, 1/255, (416, 416), (0,0,0), swapRB=True, crop=False) net.setInput(blob) output_layers_names = net.getUnconnectedOutLayersNames() layerOutputs = net.forward(output_layers_names) boxes = [] confidences = [] class_ids = [] current_centers = [] for output in layerOutputs: for detection in output: scores = detection[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.2: center_x = int(detection[0]*width) center_y = int(detection[1]*height) w = int(detection[2]*width) h = int(detection[3]*height) x = int(center_x - w/2) y = int(center_y - h/2) boxes.append([x, y, w, h]) current_centers.append([center_x, center_y]) confidences.append(float(confidence)) class_ids.append(class_id) indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.2, 0.4) # 所有已跟踪目标的丢失计数先+1 for track in tracked_trees: track[2] += 1 if len(indexes) > 0: for i in indexes.flatten(): x, y, w, h = boxes[i] cx, cy = current_centers[i] label = str(classes[class_ids[i]]) confidence = str(round(confidences[i], 2)) color = colors[i] cv2.rectangle(img, (x,y), (x+w, y+h), color, 2) cv2.putText(img, f"{label} {confidence}", (x, y+20), font, 2, (255,255,255), 1) # 匹配已跟踪目标 is_new = True for track in tracked_trees: if cal_distance((cx, cy), (track[0], track[1])) < DISTANCE_THRESH: # 匹配到旧目标,更新位置,重置丢失计数 track[0] = cx track[1] = cy track[2] = 0 is_new = False break if is_new: total_count += 1 tracked_trees.append([cx, cy, 0]) # 清理丢失过久的无效跟踪目标 tracked_trees = [t for t in tracked_trees if t[2] < MISS_FRAME_THRESH] # 画面叠加显示总计数 cv2.putText(img, f"Total Palm Trees: {total_count}", (20, 40), font, 2, (0,255,0), 2) print(f"Total Palm Trees: {total_count}") cv2.imshow('Image', img) key = cv2.waitKey(1) if key == ord('q'): break cap.release() cv2.destroyAllWindows()
参数调优提示
如果视频分辨率高、目标移动速度快,可适当调大
DISTANCE_THRESH避免同一目标被重复计数;如果检测模型漏检较多,可适当调大MISS_FRAME_THRESH,避免目标短暂漏检后被识别为新目标。如果使用场景存在大量遮挡、目标交叉移动的情况,可替换为SORT/DeepSORT这类成熟多目标跟踪算法提升匹配准确率。
内容的提问来源于stack exchange,提问作者ironmantis7x
相关产品推荐
相关产品推荐

