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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:07:04