1. 项目概述:从“数爪子”到智能计数
最近在GitHub上看到一个挺有意思的项目,叫johnkozan/clawcounting。光看名字,你可能会有点摸不着头脑——“数爪子”?这听起来像是个什么生物学的趣味小工具。但点进去一看,才发现它其实是一个关于计算机视觉和目标检测在特定场景下应用的实战项目。简单来说,这个项目的核心目标,是利用深度学习模型,自动识别并统计图像或视频中特定物体的数量,比如流水线上的零件、仓库里的货箱,或者,正如其名所暗示的,某些需要“数爪子”的特定场景(例如统计鸡、鸭等禽类的数量,虽然爪子可能是个比喻或特定用例)。
在实际的工业、农业乃至科研领域,自动计数是一个高频且繁琐的需求。传统的人工计数不仅效率低下、容易疲劳出错,而且在处理大批量、高速移动的物体时几乎不可能完成。clawcounting项目正是瞄准了这一痛点,它不是一个泛泛而谈的教程,而是一个提供了完整代码、模型和数据处理流程的“开箱即用”型解决方案。对于想入门计算机视觉应用,特别是目标检测和计数方向的朋友来说,这是一个非常好的学习范本和实操起点。它把那些在论文里看起来高大上的模型,拉到了解决实际问题的地面上。
接下来,我会带你一起拆解这个项目。我们不仅会看懂它做了什么,更要弄明白它为什么这么做,以及如果你要把它用在自己的项目里(比如数螺丝、数水果、数车辆),需要关注哪些关键环节,避开哪些常见的“坑”。整个旅程会围绕数据、模型、训练、部署和应用这几个核心模块展开。
2. 核心思路与方案选型解析
2.1 为什么选择目标检测而非其他方法?
面对“计数”这个问题,技术路径有好几种。最简单的可能是图像处理+轮廓查找:把图片转成灰度图,二值化,然后找连通域,数有多少个轮廓。这种方法在背景干净、物体形状规则且互不粘连时效果很好,速度快,不需要训练。但它的致命弱点就是鲁棒性差。光照变化、物体重叠、背景复杂、物体形状多变,任何一个因素都能让它“数瞎”。而clawcounting项目要应对的显然是更真实的场景。
另一种思路是密度图估计:不直接定位每个物体,而是学习一个映射,将输入图像映射为一个密度图,图上每个点的值代表该位置的人群(或物体)密度,积分得到总数量。这种方法对于极度拥挤、严重遮挡的场景(比如大型集会人流量统计)有优势。但它也有缺点:无法给出每个物体的具体位置框,对于需要知道物体分布的应用不友好;并且,密度图的质量高度依赖于标注的准确性(每个点需要标注精确位置)。
clawcounting项目选择了基于深度学习的目标检测路线。这是目前工业界在需要同时获取“位置”和“数量”信息时的主流选择。它的核心优势在于:
- 精准定位:不仅能数出个数,还能知道每个物体在画面中的具体位置(用边界框表示),这对于后续的跟踪、分类、质量检查等下游任务至关重要。
- 强鲁棒性:深度学习模型,特别是卷积神经网络(CNN),能够从数据中自动学习层次化的特征,对光照变化、部分遮挡、视角变化、背景干扰等有很强的适应能力。
- 灵活性高:同一个检测框架(如YOLO、SSD、Faster R-CNN)可以通过更换训练数据,轻松应用到不同物体(爪子、零件、水果)的计数上,复用性极强。
项目很可能选择了YOLO系列模型作为其检测器。原因在于YOLO在速度和精度之间取得了很好的平衡,非常适合需要实时或准实时计数的应用场景。相比于两阶段的Faster R-CNN,单阶段的YOLO速度更快;相比于同为单阶段的SSD,YOLO(特别是v5、v8版本)在社区支持、易用性和精度上表现更综合。
2.2 项目整体架构猜想
虽然没看到全部源码,但根据项目名称和常见模式,我们可以推断出clawcounting的核心工作流程:
- 数据准备与标注:收集大量包含待计数物体(“爪子”)的图像。使用标注工具(如LabelImg、CVAT)手工为每个物体画上边界框(Bounding Box),并打上统一的标签(例如“claw”)。这构成了训练集的原始素材。
- 数据预处理与增强:原始数据往往不够。需要对图像进行缩放、归一化,并应用一系列数据增强技术,如随机裁剪、翻转、旋转、色彩抖动、添加噪声等。目的是增加数据的多样性,模拟各种真实拍摄条件,让模型更具泛化能力,防止过拟合。
- 模型选择与搭建:选定一个目标检测模型骨架(如YOLOv5s, YOLOv8n)。这些模型通常由骨干网络(Backbone,用于特征提取)、颈部网络(Neck,用于特征融合)和检测头(Head,用于预测框的位置和类别)组成。项目可能会直接使用开源预训练模型(在COCO等大型数据集上训练过的),在此基础上进行微调(Fine-tuning),这比从零训练快得多,效果也通常更好。
- 模型训练:将准备好的数据集(通常按比例分为训练集、验证集)输入模型。通过定义损失函数(如YOLO用的CIoU Loss + 分类损失)来衡量模型预测框和真实框之间的差距。使用优化器(如SGD或Adam)反向传播误差,迭代更新模型参数,使得预测越来越准。这个过程需要在GPU上进行,以节省时间。
- 模型评估与验证:训练过程中和训练结束后,使用验证集评估模型性能。关键指标包括:精度(Precision)、召回率(Recall)、平均精度均值(mAP)。一个好的计数模型,不仅mAP要高,精度和召回率也要平衡,避免漏数或多数。
- 推理与计数:训练好的模型可以保存下来(通常是
.pt或.onnx格式)。在新的图片或视频流上运行模型推理,模型会输出一系列边界框和置信度。通过设定一个置信度阈值(如0.5),过滤掉不可信的检测结果,剩下的边界框数量,就是计数值。 - 部署与应用:将训练好的模型集成到实际应用中。这可能是一个简单的Python脚本,读取摄像头视频流,实时显示计数结果;也可能封装成API服务,供其他系统调用;或者部署到边缘设备(如Jetson Nano)上,在资源受限的环境下运行。
注意:数据标注是项目中最耗时、最枯燥,但也最重要的环节。标注质量直接决定模型性能的天花板。一个常见的经验是,宁愿在数据清洗和标注上多花一倍时间,也不要为了赶进度而使用有噪声的标注数据去训练,后者会导致事倍功半,模型行为难以捉摸。
3. 关键技术与实操要点拆解
3.1 数据工程:模型性能的基石
在clawcounting这类项目中,数据工作至少占据60%的精力。我们详细拆解每一步。
数据收集:来源可以是公开数据集、网络爬虫(注意版权)、或者自己拍摄。对于工业计数,可能需要与生产线同步,架设工业相机拍摄。关键原则是:多样性。要涵盖不同的光照条件(白天、夜晚、阴天、强光)、不同的拍摄角度(俯视、侧视)、不同的物体状态(完整、部分遮挡、重叠)、不同的背景。如果数据分布太单一,模型就学不会泛化。
数据标注:使用工具给每个待计数的物体画框。这里有三个核心细节:
- 框的紧密度:边界框应该紧贴物体边缘,既不要留太多空隙,也不要切掉物体部分。松松垮垮的框会让模型学习到的位置信息不精确。
- 遮挡处理:对于部分被遮挡的物体,只要可见部分超过一定比例(例如50%),并且你能确信它是一个独立物体,就应该标注。对于严重遮挡、无法判断的,则不标。需要制定统一的标注规则。
- 小目标处理:如果要数的物体很小(比如图像中的爪子可能只占几十个像素),标注会非常困难且容易出错。这时可以考虑适当放大图像后再标注,或者在训练时专门针对小目标进行数据增强(如随机拼接小图到背景上,即“马赛克增强”)。
数据增强策略:这是提升模型鲁棒性的“魔法”。除了常规的翻转、旋转,在计数项目中特别有用的增强包括:
- 随机裁剪与缩放:模拟物体在不同距离下的成像大小。
- 色彩空间变换:调整亮度、对比度、饱和度、色调,模拟不同光照和相机参数。
- 混合(MixUp)与拼接(Mosaic):将多张图片混合成一张进行训练,强迫模型学习在复杂背景和多个目标共存下的检测能力,这对提升小目标检测效果尤其有效。
- 添加噪声与模糊:模拟图像传输中的噪声或相机失焦。
一个常见的坑是:过度增强。增强是为了模拟真实世界的变化,但如果增强得过于“离谱”(比如把物体颜色变得完全脱离现实,或者旋转角度违反物理规律),反而会干扰模型学习到本质特征。增强参数需要根据实际场景谨慎调整。
3.2 模型训练中的核心参数与调优
假设项目使用YOLOv5/v8,训练过程中有几个关键参数需要理解:
- 学习率(Learning Rate):这是最重要的超参数之一。它控制模型参数每次更新的步长。太大容易震荡甚至无法收敛,太小则收敛缓慢。通常的做法是使用“热身(Warmup)”策略:训练初期使用较小的学习率,让模型“稳起步”,然后逐步提升到预设值。YOLO官方代码通常已经内置了良好的学习率调度策略。
- 批次大小(Batch Size):一次训练输入多少张图片。受限于GPU显存。更大的批次大小通常能使训练更稳定,梯度估计更准确,但可能会降低模型的泛化能力。一般设置为当前GPU能承受的最大值(不爆显存)。
- 迭代次数(Epochs):整个训练集遍历多少次。太少欠拟合,太多过拟合。需要观察训练损失和验证集指标(如mAP)的变化曲线。当验证集指标不再提升甚至开始下降时,就应该提前停止(Early Stopping)。
- 损失函数权重:YOLO的损失一般由边界框损失(如CIoU)、目标置信度损失和分类损失三部分组成。有时需要调整它们的权重,以应对特定场景。例如,如果你的场景中物体非常密集,IoU计算容易出问题,可能需要关注框损失的改进;如果更关心是否漏检,可以适当提高目标置信度损失的权重。
调优经验:
- 不要一上来就调参:首先使用默认参数在你的数据上跑一个基线模型。这个基线性能是你所有调优工作的起点。
- 一次只改变一个变量:如果你想调整学习率和批次大小,应该先固定批次大小,调整学习率找到较优值;然后固定这个学习率,再去调整批次大小。否则你无法知道性能变化是哪个参数引起的。
- 善用验证集:训练时一定要留出独立的验证集(通常20%)。它是你判断模型是否过拟合、是否需要停止训练的“裁判”。绝对不能把验证集当测试集用,更不能在调参时用测试集来评估,那会导致对模型性能的乐观估计。
3.3 后处理与计数逻辑
模型推理的直接输出是大量的预测框,每个框带有坐标(x1, y1, x2, y2)、置信度(confidence)和类别概率。直接数框的数量就是计数吗?没那么简单。
- 置信度阈值过滤:首先,需要设定一个置信度阈值(
conf_thres,如0.5)。只有置信度高于这个阈值的预测框才被认为是有效的检测。这个阈值需要根据验证集结果来调整:调高阈值,检测结果更可靠(精度高),但可能漏掉一些模糊目标(召回率低);调低阈值,能抓到更多目标(召回率高),但会引入更多误检(精度低)。需要在精度和召回率之间根据应用需求权衡。 - 非极大值抑制:这是关键的一步。由于模型可能会对同一个物体产生多个重叠的、置信度不同的预测框,NMS的作用就是去除这些冗余框。其原理是:首先选出置信度最高的框,然后计算它与剩余所有框的交并比(IoU)。如果IoU超过设定的阈值(
iou_thres,如0.5),就认为它们检测的是同一个物体,将那些置信度较低的框抑制掉。重复这个过程直到处理完所有框。NMS的IoU阈值设置很关键:对于密集物体,阈值要设小一些(如0.3),避免把两个紧挨着的不同物体当成一个给抑制了;对于稀疏物体,可以设大一些(如0.5)。 - 计数输出:经过过滤和NMS后,剩下的唯一预测框的数量,就是最终的计数值。可以将这个数字叠加显示在图像或视频帧上。
一个进阶技巧:对于视频流计数,为了避免在相邻帧中对同一物体重复计数,可以引入简单的跟踪算法,如基于IoU的跟踪(计算当前帧检测框和上一帧跟踪框的IoU,进行关联)。这样可以得到更稳定的、跨帧的计数,而不是每一帧独立计数导致的数字跳动。
4. 从零开始复现:核心环节实现指南
这里我们以YOLOv8为例,勾勒一个类似clawcounting项目的实现路径。请注意,以下代码和步骤是基于常见实践的逻辑补全。
4.1 环境搭建与数据准备
首先,准备一个Python环境(建议3.8+),并安装核心库。
# 创建虚拟环境(可选但推荐) python -m venv claw_env source claw_env/bin/activate # Linux/Mac # claw_env\Scripts\activate # Windows # 安装PyTorch (请根据你的CUDA版本到PyTorch官网选择对应命令) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 示例CUDA 11.8 # 安装Ultralytics YOLOv8 pip install ultralytics # 安装其他辅助库 pip install opencv-python pillow matplotlib pandas数据组织遵循YOLO格式。假设你的项目目录结构如下:
claw_counting_project/ ├── data/ │ ├── images/ │ │ ├── train/ # 训练图片 │ │ └── val/ # 验证图片 │ └── labels/ │ ├── train/ # 训练标签 (.txt文件,与图片同名) │ └── val/ # 验证标签 ├── dataset.yaml # 数据集配置文件 └── train.py # 训练脚本dataset.yaml文件内容示例:
# dataset.yaml path: /path/to/claw_counting_project/data # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 # 类别信息 names: 0: claw # 只有一个类别,索引为0,名为'claw' # 可选:自动下载数据集的链接(本例不需要) # download: ...标签文件(如image_001.txt)的格式是每行一个物体,内容为:<class_id> <x_center> <y_center> <width> <height>。坐标和宽高都是相对于图片宽度和高度的归一化值(范围0-1)。
4.2 模型训练与验证
使用Ultralytics的API,训练变得非常简单。创建一个train.py脚本:
# train.py from ultralytics import YOLO def main(): # 加载一个预训练模型(推荐从YOLOv8n开始,速度快) model = YOLO('yolov8n.pt') # 会自动下载预训练权重 # 开始训练 results = model.train( data='data/dataset.yaml', # 数据集配置文件路径 epochs=100, # 训练轮数 imgsz=640, # 输入图像尺寸 batch=16, # 批次大小(根据GPU调整) workers=4, # 数据加载线程数 device='0', # 使用GPU 0,如果是CPU则设为'cpu' project='claw_counting_runs', # 结果保存目录 name='exp1', # 实验名称 save=True, save_period=10, # 每10个epoch保存一次检查点 pretrained=True, # 使用预训练权重 optimizer='SGD', # 优化器 lr0=0.01, # 初始学习率 lrf=0.01, # 最终学习率因子 (lr0 * lrf) warmup_epochs=3, # 学习率热身轮数 box=7.5, # 框损失权重 cls=0.5, # 分类损失权重 dfl=1.5, # DFL损失权重(v8特有) ) # 在验证集上评估最佳模型 model.val( data='data/dataset.yaml', save_json=True, # 保存JSON格式的评估结果 ) if __name__ == '__main__': main()运行python train.py,训练就开始了。控制台会输出损失曲线和评估指标。训练结束后,最佳模型会保存在claw_counting_runs/exp1/weights/best.pt。
关键参数解释:
imgsz=640: YOLOv8的默认输入尺寸。如果你的目标物体非常小,可以尝试增大到1280(小目标检测模式),但会显著增加计算量和内存消耗。box,cls,dfl: 损失函数权重。除非你非常清楚自己在做什么,否则建议先用默认值。如果你发现模型定位不准(框漂移),可以尝试微调box权重。project和name: 所有训练日志、模型、指标图都会保存在{project}/{name}目录下,非常清晰。
4.3 模型推理与计数
训练好模型后,使用以下脚本进行单张图片或视频的推理和计数:
# infer_and_count.py from ultralytics import YOLO import cv2 import argparse def count_objects(model_path, source, conf_thres=0.5, iou_thres=0.5): """ 使用训练好的模型进行推理并计数。 参数: model_path: 模型文件路径 (.pt) source: 图片/视频路径,或摄像头索引 (0) conf_thres: 置信度阈值 iou_thres: NMS的IoU阈值 """ # 加载训练好的模型 model = YOLO(model_path) # 进行推理 results = model(source, stream=False, conf=conf_thres, iou=iou_thres, verbose=False) for result in results: # 获取原始图像 orig_img = result.orig_img # 获取检测到的边界框信息 boxes = result.boxes if boxes is not None: # 计数:检测到的框的数量 count = len(boxes) # 在图像上绘制框和标签 result_img = result.plot() # Ultralytics内置的绘图方法,很方便 # 在图像左上角添加计数文本 label = f'Count: {count}' font = cv2.FONT_HERSHEY_SIMPLEX font_scale = 1.2 thickness = 2 (text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, thickness) cv2.rectangle(result_img, (10, 10), (10 + text_width, 10 + text_height + baseline), (0, 0, 0), -1) # 背景框 cv2.putText(result_img, label, (10, 10 + text_height), font, font_scale, (0, 255, 0), thickness) # 显示结果 cv2.imshow('Detection & Counting', result_img) print(f'Detected objects: {count}') # 按'q'退出显示 if cv2.waitKey(1) & 0xFF == ord('q'): break else: print('No objects detected.') cv2.imshow('Detection & Counting', orig_img) if cv2.waitKey(1) & 0xFF == ord('q'): break cv2.destroyAllWindows() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--model', type=str, default='claw_counting_runs/exp1/weights/best.pt', help='模型路径') parser.add_argument('--source', type=str, default='test_video.mp4', help='测试源,可以是图片、视频或摄像头索引') parser.add_argument('--conf', type=float, default=0.5, help='置信度阈值') parser.add_argument('--iou', type=float, default=0.5, help='NMS IoU阈值') args = parser.parse_args() count_objects(args.model, args.source, args.conf, args.iou)这个脚本封装了加载模型、推理、绘制结果和显示计数的完整流程。你可以通过命令行参数指定不同的模型、数据源和阈值进行测试。
5. 常见问题与排查技巧实录
在实际操作中,你几乎一定会遇到下面这些问题。这里是我踩过坑后总结的排查思路。
5.1 问题一:模型训练损失不下降,或者波动很大
可能原因及排查:
- 学习率设置不当:这是最常见的原因。学习率太大,损失会震荡甚至爆炸(变成NaN);学习率太小,损失下降极其缓慢。
- 排查:观察训练日志开头的几个epoch。如果损失一开始就变成NaN,肯定是学习率太大。如果损失几乎不变,尝试将学习率(
lr0)提高一个数量级(如从0.001调到0.01)试试。使用Ultralytics默认的带热身的调度器通常比较稳健。
- 排查:观察训练日志开头的几个epoch。如果损失一开始就变成NaN,肯定是学习率太大。如果损失几乎不变,尝试将学习率(
- 数据有问题:
- 标签错误:检查标注文件。确保标签文件中的类别ID正确(从0开始),坐标值在0-1之间。可以用一个脚本可视化一下,看框是否画在了正确位置。
- 图片损坏:极少数情况下,图片文件可能损坏导致无法读取。训练日志通常会报错。
- 数据路径错误:检查
dataset.yaml中的path、train、val路径是否正确。一个快速验证方法是,在Python里用cv2.imread尝试读取os.path.join(path, train, ‘某图片名’)看是否能成功。
- 批次大小太小:在GPU显存允许的情况下,尽量使用较大的批次大小(如16, 32)。太小的批次(如2, 4)可能导致梯度估计噪声太大,损失曲线波动剧烈。
- 模型复杂度与数据量不匹配:如果你数据量很少(比如只有几百张图),却使用了一个非常大的模型(如YOLOv8x),模型很容易过拟合,表现为训练损失很快下降但验证损失不降反升。这时应该换用小模型(如YOLOv8n),或者大力增加数据增强。
5.2 问题二:模型推理时漏检(召回率低)或误检(精度低)严重
可能原因及排查:
- 置信度阈值不合适:
- 漏检多:尝试降低
conf_thres(如从0.5降到0.3)。模型可能对部分模糊、小目标预测的置信度不高,过高的阈值把它们过滤掉了。 - 误检多:尝试提高
conf_thres(如从0.5升到0.7)。模型可能把一些背景噪声也当成了目标。 - 最佳实践:在验证集上,以不同的置信度阈值运行评估,绘制“精度-召回率曲线(PR Curve)”。选择曲线拐点附近、能平衡精度和召回率的阈值。
- 漏检多:尝试降低
- NMS的IoU阈值不合适:
- 对于密集物体:如果两个物体靠得很近,较高的IoU阈值(如0.5)可能会把其中一个当成冗余框抑制掉,导致漏数。尝试降低
iou_thres(如0.3或0.4)。 - 对于大物体:如果一个大物体被模型预测成多个重叠的小框,较高的IoU阈值可能无法有效去重,导致一个物体被数多次。这时可能需要结合其他后处理,或者检查标注是否准确(一个物体是否只标了一个框?)。
- 对于密集物体:如果两个物体靠得很近,较高的IoU阈值(如0.5)可能会把其中一个当成冗余框抑制掉,导致漏数。尝试降低
- 训练数据不具代表性:模型在没见过的场景(光照、角度、背景)下表现差。检查你的测试场景是否被训练集覆盖。如果没有,需要补充相应场景的数据进行重新训练或微调。
- 锚框(Anchor)不匹配:YOLOv8已经使用了自适应锚框计算,通常不需要手动调整。但如果你要检测的物体尺寸非常极端(比如所有目标都特别特别小),可以尝试在训练前让模型在你的数据上重新计算锚框(YOLOv8训练时会自动做这件事)。
5.3 问题三:小目标检测效果差
这是计数项目中的经典难题。物体在图像中占比太小(比如小于32x32像素),特征信息少,很难检测。
解决思路:
- 数据层面:
- 提高输入分辨率:将训练和推理的
imgsz从640提高到1280甚至更高。这是最直接有效的方法,但计算成本呈平方增长。 - 针对性数据增强:使用“马赛克(Mosaic)”增强,将四张小图拼成一张大图训练,强迫模型学习在“大画面”中找小目标。YOLO默认已启用。
- 生成更多小目标样本:可以人工复制-粘贴一些小目标到图像的不同位置,并确保标注正确,以增加小目标在训练数据中的密度和多样性。
- 提高输入分辨率:将训练和推理的
- 模型层面:
- 使用更密集的检测头:YOLOv8的检测头本身已经考虑了多尺度特征融合。确保你在训练时没有关闭相关功能。
- 关注特征金字塔网络(FPN):模型通过FPN将深层语义特征和浅层细节特征融合,这对小目标检测至关重要。选择具有良好FPN设计的模型架构。
- 损失函数:可以尝试使用更关注小目标的损失函数变体,例如为小目标分配更高的损失权重,但实现起来较复杂,通常不是首选。
5.4 问题四:计数结果在视频中跳动(同一物体被重复计数)
这是视频流处理中的常见问题,因为每一帧都是独立检测的。
解决方案:
- 简单滤波:对连续N帧(比如5帧)的计数结果取中位数或平均值作为当前帧的输出,可以平滑掉偶然的跳动。但这种方法有延迟,不适合实时性要求极高的场景。
- 基于IoU的跟踪:这是更优雅的解决方案。基本思路是:
- 保存上一帧中所有检测框的位置。
- 对于当前帧的每个新检测框,计算它与上一帧所有框的IoU。
- 如果最大IoU超过一个阈值(如0.3),则认为这是同一个物体,继承其ID,不增加总计数。
- 如果所有IoU都低于阈值,则认为这是一个新出现的物体,分配新ID,总计数加1。
- 同时,需要处理物体消失的情况(连续几帧没匹配上则移除ID)。
- 使用专用跟踪器:集成一个简单的跟踪算法,如SORT或DeepSORT。Ultralytics YOLOv8其实内置了跟踪功能(
model.track(...)),它基于BoT-SORT算法,可以很方便地实现带ID的跟踪,从而进行跨帧的稳定计数。
集成跟踪的计数示例片段:
from ultralytics import YOLO import cv2 model = YOLO('best.pt') cap = cv2.VideoCapture('test.mp4') object_id_set = set() # 用于记录当前帧出现的物体ID total_count = 0 # 累计总计数(可根据业务逻辑调整) while cap.isOpened(): ret, frame = cap.read() if not ret: break # 使用track模式进行推理,persist=True允许在帧间保持ID results = model.track(frame, persist=True, conf=0.5, iou=0.5, verbose=False) if results[0].boxes.id is not None: # 获取当前帧所有物体的ID current_ids = results[0].boxes.id.int().cpu().tolist() # 找出新出现的ID(可选逻辑:比如只计数第一次出现的) new_ids = [id for id in current_ids if id not in object_id_set] if new_ids: total_count += len(new_ids) # 根据你的计数逻辑调整 object_id_set.update(new_ids) # 更新集合为当前帧的ID(如果只想统计历史出现过的所有不同物体) # object_id_set = set(current_ids) # total_count = len(object_id_set) # 在画面上显示累计计数 annotated_frame = results[0].plot() cv2.putText(annotated_frame, f'Total Count: {total_count}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow('Tracking Count', annotated_frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()这段代码展示了如何利用YOLOv8的跟踪功能实现更稳定的视频计数。关键参数persist=True让跟踪器在帧之间维持物体ID的连续性。