news 2026/10/21 2:51:58

YOLO26自动计数实战:从检测框到类别统计的工程方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLO26自动计数实战:从检测框到类别统计的工程方案

有时候用户问的问题越简单,动手做的时候坑越多。前阵子我在迭代一版检测方案,模型用到的就是队里一直沿用的YOLO26内部版本,功能上已经能稳定输出边界框和类别了。结果下游同学过来问了一句:你能不能直接告诉我,这批测试视频里一共有多少辆车、多少人、多少只鸟?我下意识觉得这不就是把框的数量加一下吗?等真去实现“YOLO26+自动计数+自动统计各个类别数量”的时候才发现,检测和计数之间隔着一个状态管理问题,远不是一行sum能解决的。

这篇把整个方案的思考过程、模块设计、核心代码、实验指标和踩坑记录完整写出来。如果你正在做检测类的项目,或者想给论文/报告里的“工作量”加一个有说服力的扩展模块,可以直接按这套思路复现。它不需要改模型结构,也不需要重新训练,属于把检测结果变成业务统计结果的“最后一公里”。

1. 检测和计数之间隔着一个“状态管理”

很多刚接触检测任务的读者会以为“模型能输出框,那数量自然就出来了”。这个直觉对单张静态图成立,放到视频或长序列里就立刻破功。

1.1 从一张图到一段视频,YOLO26原生的输出缺了什么

YOLO26单帧推理拿到的一般是[N, 6]这样的数组,每一行对应一个检测框,包括框的坐标、置信度、类别编号。它解决的是“这张图里有没有目标、目标在哪儿、目标属于哪一类”这个问题。它不解决“这段视频里一共有多少个独立目标”这个问题。

差别在哪?模型天生没有跨帧状态。视频第10帧出现一辆车,第11帧同一辆车还停在那里,模型会再次输出一个检测框。如果不做任何处理,按帧累加就会把同一辆车数两遍。更别说一辆车在画面里停留30秒,按25帧每秒算,它可能被数了750次。这就是典型的“框有价值,直接加框没价值”。

所以要做自动计数,必须把“检测结果”和“统计口径”分开看。你的业务到底要的是“某一帧画面里同时出现的最大数量”“一个视频里出现过多少独立目标”还是“固定区域里进出了多少人”,统计逻辑完全不同。

1.2 先把“计数”拆成三类:逐帧统计、去重总数、区域断面计数

我建议在写代码之前先确认需求,否则后面返工的概率非常大。结合实际场景,最常见的计数需求其实是下面三种:

计数类型统计口径典型场景最关键依赖
逐帧统计每一帧图片中各类别目标的数量考场人数统计、货架商品瞬时数量只需要检测结果
去重总数整段视频中互不重复的目标总数量停车场车辆统计、养殖场动物盘点需要跟踪ID或特征匹配
区域断面计数越过某条线/进入某块区域的目标数量出入口客流统计、高速卡口车流统计需要跟踪ID + 空间判断

逐帧统计最简单,拿检测结果按类别做聚合就行。去重总数就复杂了,你需要知道“第10帧的这辆车”和“第15帧的这辆车”是不是同一辆。区域断面计数还要在这个基础上加上轨迹方向判断。

我在这个项目里的需求比较复合:既要统计视频中各类别出现的去重总数,也要输出每一帧的分布情况。所以我最终做的是一个可插拔的计数模块,既不碰YOLO26的训练流程,也不改它的权重,所有逻辑都挂在推理结果的后面。

2. 自动计数模块的总体设计与选型

这个模块我起名叫 AutoCounter,设计原则很简单:把计数从模型里剥离出来,做成一个独立组件。好处是模型升级了计数逻辑不用重写,业务口径变了模型也不需要重新训练。

2.1 不修改模型结构也能挂接计数功能

有些同学一看到“改进YOLO”就以为必须往网络里加分支、加注意力、加损失函数。其实大部分计数需求完全不需要动模型。检测模型负责感知,计数模块负责记忆和汇总,两者解耦之后反而更容易排查问题。

我们当时考虑过三种做法:

第一种是在模型输出层加一个回归头,直接预测目标数量。这个方案对单一类别且画面目标数量固定的场景或许有效,但可解释性差,也不方便扩展到多类别,而且需要重新训练。

第二种是训练一个单独的计数网络,比如密度图估计模型。这类模型适合人群密度估计,但照样需要自己准备数据、训练、调参,周期很长。

第三种就是本文说的后处理计数方案。检测器直接复用现成的YOLO26能力,后面挂计数器,通过跟踪ID完成去重。这种方案实现最快,也最容易写清楚逻辑,非常适合作为工程落地或论文中的“附加模块”。

从结果看,第三种确实是最划算的。它把检测精度和计数精度解耦:检测不准,锅归检测;计数逻辑有Bug,单独修计数逻辑就行。实验时还能分别报mAP和计数指标,论文的工作量也丰富不少。

2.2 类别字典、跟踪ID和ROI规则三者如何协作

自动统计各个类别数量,前提是有一个“类别字典”。这个字典直接来自训练用的类别配置文件,不要在代码里硬编码["cat", "dog"]这种列表。因为数据集一旦增加类别,硬编码列表就会漏统计,而且排查起来非常隐蔽。

跟踪ID是整个模块最核心的字段。它解决的是“同一目标出现多次,如何只算一次”的问题。如果推理时没有跟踪ID,也可以先用交并比匹配相邻两帧的框,自己维护一套简易ID。不过自己写匹配逻辑容易在目标密集或遮挡时把ID弄丢,更省心的方式是直接开启检测器配套的跟踪链路。

ROI规则属于可选项。如果业务不需要按区域计数,完全可以不接。它起的作用是空间过滤:只统计画面中某个多边形区域内部的目标。比如只统计货架区域内的人,忽略走廊上走动的人。

这三样东西协作起来后,整个计数流程才真正闭环:先过滤出ROI内的检测框,再按类别聚合,最后用跟踪ID去重。

3. 核心代码:把检测结果变成可复用的统计流

下面这段代码是我实际项目里简化后的版本,把复杂的工程细节去掉,保留核心逻辑。它分为三部分:检测结果包装、计数器主体、调用示例。

3.1 推理结果先转成统一的检测对象

YOLO26推理返回的原始数组直接用起来不方便,我习惯先包一层TrackedDet对象,把类别、坐标、置信度、跟踪ID都放在一个结构里,后续所有模块都用这个结构传数据。

class TrackedDet: __slots__ = ("cls", "score", "bbox", "track_id") def __init__(self, cls, score, bbox, track_id=None): self.cls = cls self.score = score self.bbox = bbox # [x1, y1, x2, y2] self.track_id = track_id @property def center(self): return ( (self.bbox[0] + self.bbox[2]) / 2, (self.bbox[1] + self.bbox[3]) / 2 )

包装这一步的价值在于:后面无论换什么检测器,只需要把新模型的输出适配成TrackedDet,计数器代码完全不用改。以后YOLO26再升级,或者换成另一套检测框架,迁移成本都极低。

3.2 按类别累计与去重逻辑

计数器主体维护两个状态:total是当前累计的各类别总数,_seen是已经计过数的跟踪ID集合。每次新进来一批检测框,如果带跟踪ID,就先去重;如果不带跟踪ID,就按逐帧累加处理。

class AutoCounter: def __init__(self, class_names): self.class_names = class_names self.total = {name: 0 for name in class_names} self._seen = set() self.frame_idx = 0 self.frame_stats = [] def reset(self): self.total = {name: 0 for name in self.class_names} self._seen.clear() self.frame_idx = 0 self.frame_stats = [] def update(self, dets, dedup=True): self.frame_idx += 1 per_frame = {name: 0 for name in self.class_names} for det in dets: if det.cls not in self.total: continue per_frame[det.cls] += 1 if not dedup: self.total[det.cls] += 1 continue if det.track_id is None: self.total[det.cls] += 1 continue if det.track_id in self._seen: continue self._seen.add(det.track_id) self.total[det.cls] += 1 self.frame_stats.append({ "frame": self.frame_idx, "per_frame": per_frame }) return self.total.copy()

这里有个细节值得展开:当dedup=True时,ID去重后的总数只会在目标第一次出现时加一次,之后目标被持续检测到,total不再增加。但per_frame反映的是当前帧的实际可见数量,两个口径用途不同。比如统计停车场总车辆数要用去重总数,监控画面里同时排队的人数要用逐帧统计,千万别混用。

3.3 实时调用和周期输出

实际跑视频时,只需要循环读帧、推理、更新计数器即可。下面这段是调用端的示例:

counter = AutoCounter(["cat", "dog", "bird", "car"]) for frame in video_stream: raw_results = yolo26_infer(frame) dets = [TrackedDet( cls=raw["class_name"], score=raw["score"], bbox=raw["bbox"], track_id=raw.get("track_id") ) for raw in raw_results] counter.update(dets, dedup=True) if counter.frame_idx % 100 == 0: print(f"第 {counter.frame_idx} 帧: {counter.total}")

每一百帧打印一次累计结果,方便人工核对。最后一帧处理完后,counter.total就是整段视频的去重后类别数量。如果你还要统计ROI区域,只需要在counter.update之前过滤一次:

def is_inside(bbox, roi): cx = (bbox[0] + bbox[2]) / 2 cy = (bbox[1] + bbox[3]) / 2 return roi.contains(cx, cy) def filter_inside(dets, roi): return [det for det in dets if is_inside(det.bbox, roi)]

中心点落在ROI多边形内就保留,否则丢掉。要注意的是,这个过滤逻辑只能做区域内部目标统计,不能据此判断目标是否“进入区域”。想统计进出流量,还得结合跟踪ID和相邻帧中心点的位移方向。

4. 用实验设计和图表把“工作量”做实

很多做检测改进的同学会把精力全放在mAP上,做完发现论文里只有一张结果表,显得很单薄。挂上计数模块之后,实验维度一下子变多了,而且都是真实可复现的指标,不是凭空造数据。

4.1 计数类任务建议增加的指标

检测模型能力之外,计数模块本身可以单独出指标。比较常用的是三个:

指标计算公式含义
MAEmean(预测计数 - 真实计数
MAPEmean(预测计数 - 真实计数
计数准确率1 - MAE / 真实计数计数任务中最直观的准确率

计算方式不复杂,但需要说明的是“真实计数”怎么定义。如果统计的是去重总数,那人工标注也要按同一个口径来。否则会出现模型数了8辆车、人工数了3次进出同一辆车然后标了3辆车的尴尬情况,指标无意义。

我当时的做法是单独标了一组测试序列,每个序列给一个“期望正确总数”标签,再跑计数模块,统计 MAE 和 MAPE。这个指标可以和原始YOLO26的检测指标放在一张大表里,展示改进模块不会牺牲检测精度的同时,还额外输出了业务价值。

4.2 可视化方案:时间线、堆叠柱状图、热力分布

除了数字指标,一张好的图能让人一眼看清计数模块在干什么。

第一张图建议画“每帧各类别数量折线图”。横轴是帧序号,纵轴是目标数量,每条曲线对应一个类别。它能反映一天中或一段视频中各类别出现的波动趋势。

第二张图是“类别堆叠柱状图”。把每个时间段内的去重总数按类别堆起来,适合做整段时间的类别结构分析。

第三张图是“ROI热力图”。把检测框中心点落在画面中的位置统计成密度热力图,能直观看出哪些区域目标最集中。

这三张图不用刻意美化,用常见的绘图库就能画得很好看。对论文来说,“框体可视化”只是展示模型看到了什么,而“统计图表”展示的是模型输出如何被转化成了决策信息。两者配合,工作量看起来完整得多。

4.3 论文怎么写这部分才不突兀

计数模块在论文里的正确位置是“方法”章节里作为后处理分支出现,或者在实验章节作为扩展分析。不要把它包装成检测器主干创新,那会很牵强,审稿人一眼就能看穿。

一个比较自然的结构是:先描述YOLO26作为基础检测器,再提出“现有检测结果无法直接满足业务统计需求”的问题,然后给出一个后处理计数分支,包含跟踪ID去重和类别累计逻辑。实验部分增加计数指标,结论部分强调“不仅提精度,还输出结构化统计结果,对下游决策更友好”。

相比无中生有地加一堆模块,这种“检测+计数”的组合是真实需求驱动的,每一步都有明确的目的,论文也更好写。

5. 踩坑复盘:重复计数、类别失衡与阈值设置

这个模块写起来不难,真正让我花时间的是各种隐藏场景下的错误计数。下面几个坑是我实际踩过并且反复确认过的,直接写出来帮你省时间。

5.1 重复计数比漏计更可怕

漏计往往是因为目标被挡住或检测置信度低,相对容易被发现。重复计数则隐蔽得多,尤其当跟踪ID频繁切换时。目标被短暂遮挡后重新出现,跟踪模块可能认为它是个新目标,分配一个新ID,计数就会从1变成2。

我在项目中遇到过最夸张的情况:一辆车因为树荫遮挡导致ID切换了三次,总数直接多了2。解决思路有两个。一是提高跟踪模块的关联稳定性,允许目标在遮挡期间保留ID更久,不过这会引入计算开销。二是在统计层做“ID合并”补偿,如果两个ID的框位置高度重合且类别相同,就视为同一个目标。

实际操作中我选择的方案是前者:在进入计数模块之前,先对短时间中断的目标做轨迹插值。也就是目标消失了三五帧,就延续它原来的ID继续保持。只有超过阈值才认为它真正离开。

5.2 类别不均衡会造成计数虚高

如果数据集里某个类别数量极少,模型对该类别的置信度往往不稳定。比如“施工现场机械”只有几十个标注框,模型很容易把其他类别误检成机械。计数模块是把所有超过阈值的框都算进去的,一旦误检,错误就会被累计到总数里。

这时候不要急着调计数逻辑,先回头看看检测器在这个类别上的precision和recall。分类别统计的precision低,误检框自然多,计数自然虚高。可以按类别单独设置置信度阈值,比如普通类别用0.25,稀有类别提高到0.45,牺牲一点召回率换取计数可信度。

5.3 置信度阈值和IOU阈值之间的平衡

阈值这件事没有一个通用值,关键看你的业务容忍度。如果计数是为了做总量统计,稍微漏检几个目标影响不大,那么置信度阈值可以适当降低,把更多目标框进来。要是计数是为了精确核对库存或人员,漏检一个都会被追责,那就要把阈值调高,再配合跟踪模块保障重框目标只计一次。

检测的IOU阈值影响的是NMS之后保留的框数量。阈值设太低,同一目标会被拆成两个框;阈值设太高,密集场景下两个紧挨的目标会被合并成一个框。这两个方向都会让计数失真。我的经验是先按常见值 0.45 跑一遍,再看具体场景里的计数误差曲线,误差突然变大的帧数往往就是阈值敏感区间。

6. 一点个人体会:计数模块还能往哪延伸

从“YOLO26自动计数”这个点延伸出去,我后来还做了两个比较有用的变体。一个是时间片统计,按小时或按天聚合计数结果,得到一段趋势曲线,能够辅助判断高峰期。另一个是ROI密度预警,把ROI内目标数量超过阈值的时刻标记出来,做成异常告警。

这两个变体都没有重新训练模型,纯粹是在计数模块之外再加一层规则。过程让我意识到,模型性能之外的“封装能力”往往被严重低估。把每一帧的检测结果变成结构化统计,再把统计结果变成业务语言,很多问题不需要重新训练也能解决。以后你再听到“自动计数”这个需求,先别急着往模型里塞新模块,仔细问清楚统计口径,后面能少走不少弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 2:51:13

微信小程序+Django实现旧衣回收系统:全栈开发实战

前阵子接手了一个挺有意思的项目,内部代号叫 htgs84d4:基于微信小程序的旧衣回收商品系统。需求方是一家做本地环保回收的小团队,他们之前的回收业务全靠在微信群里手动登记,用户约时间、师傅上门称重、积分记录、兑换商品&#x…

作者头像 李华
网站建设 2026/10/21 2:50:51

知网AIGC检测原理与降AI工具实测:从标红到通过的完整攻略

去年有一阵子,实验室里的几位同学轮流来找我,说同一个问题:初稿明明是自己熬夜搭出来的框架,再用AI润了一轮,结果往知网AIGC检测系统一放,直接标红一片,相似度干到百分之四五十,要求…

作者头像 李华
网站建设 2026/10/21 2:50:09

Redis分布式锁从原理到落地:SETNX、Lua脚本与生产排坑实践

去年我负责的一个订单服务扩大了部署规模,从单机变成三节点集群之后,测试环境一切正常,结果一上生产就出现了两个不同节点同时给同一笔订单发放优惠券的情况。查了半天日志,问题指向同一个时间点,两台机器的处理线程都…

作者头像 李华
网站建设 2026/10/21 2:50:08

独立游戏开发工程化指南:从版本控制到发布交付的核心流程

“看5分钟让你超越90%以上独立游戏开发者”这个标题确实有些夸张,但如果你真的把本文读透,会发现独立游戏开发中最容易拉开差距的,不是某个炫酷的技能设计,也不是某个高级算法,而是你对项目流程、代码组织、资源管理、…

作者头像 李华
网站建设 2026/10/21 2:49:51

从模型加载到微调部署:Hugging Face生态实战指南

这年头只要跟大模型沾边的工作,你基本上绕不开 Hugging Face 这个生态。从早期的 transformers 库一枝独秀,到后来 Model Hub、Datasets、Space 越铺越大,它已经不只是“一个很好用的模型仓库”,而是把模型、数据、微调、部署全部…

作者头像 李华
网站建设 2026/10/21 2:49:48

FP32与FP16详解:从浮点数原理到云端GPU选卡与混合精度训练

1. 先理解单精度/半精度,再谈选卡上云1.1 你为什么会看到两个算力数字很多朋友第一次打开GPU规格表或者云端算力平台的选卡页面时,都会遇到同一个困惑:同一张卡,为什么有的地方写“FP32算力多少T”,有的地方又写“FP16…

作者头像 李华