ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于YOLOv5/v8/v10的教室人员检测与计数系统实战

基于YOLOv5/v8/v10的教室人员检测与计数系统实战

如果你也负责过教室或会议室里的视频分析任务,大概能理解那种尴尬:监控画面里明明坐满了人,却没人能说清楚到底来了多少学生。我最近把基于YOLOv5/v8/v10的教室人员检测与计数系统完整做了一轮,从数据标注到三套模型的对比训练,再到跟踪计数和边缘设备部署,整个过程走下来,最大的感触是:模型本身并不是最难的部分,真正麻烦的是数据、计数逻辑和长期的稳定性。这篇文章把我踩过的坑、试过的参数和最终的效果全部写出来,希望能给同样要做教室、会议室、自习室人员统计的同行一个可以照做的参考。

这套系统的定位很明确:输入一路或几路教室摄像头画面,输出实时在线人数、累计进入人数和累计离开人数,同时支持通过API或Web页面查看结果。它解决的典型问题有三个——课堂到课率统计、自习室占座检测、教室占用率评估。下面我按实际开发顺序展开,从需求分析一直讲到部署落地。

1. 教室人员检测的难点与方案选型逻辑

1.1 教室场景和通用检测场景的本质差异

很多人一上来就想直接用现成的目标检测模型去跑,结果发现效果远不如公开榜单上那么漂亮。原因在于教室场景有几个特殊性,这些特殊性直接决定了数据怎么采、模型怎么选、后处理怎么做。

第一是密集与遮挡。教室座位紧挨着,前后排身体互相遮挡是常态,学生低头看书、趴在桌上、侧身拿东西都会让目标不完整。前后排两个人同时出现在画面里,前排的头部会挡住后排人的胸口,这种情况下如果只标注全身,很容易把后排人漏掉。第二是固定视角下的背景变化。摄像头装在教室前方或后方墙角,角度基本不动,但投影幕布亮起时会有人影,讲台上放个大白板会反光,窗户透进来的阳光会在某个时间段把半边座位打亮。第三是上下课瞬间的拥堵。下课铃一响,走廊和门口瞬间涌出一堆人,很多人只有半截身子出现在画面里,检测器很容易把两个人并成一个框。

教室还有一个特殊要求:检测必须稳定且可解释。刷卡数据能精确到人,视频检测如果今天说40人、明天说38人,教务那边就会对系统的可信度产生怀疑。这就对计数逻辑提出了比单纯mAP更高的要求——不能只输出检测框,还要能对同一人持续跟踪,避免重复计数和漏计数。

1.2 为什么最终选择了YOLO系列而不是传统方案

在动手之前,我对比了几类常见方案,这里把选型时的完整考虑写出来。

传统计算机视觉方案包括背景建模(MOG2、帧差法)和人工特征检测(HOG+SVM)。背景建模在空教室场景下很好用,但问题很多:学生只有在动的时才会被识别为前景,坐定不动几分钟整个人就融进背景里了;窗帘晃动、光线变化都会触发误报。HOG+SVM能做静态人形检测,但密集场景下的处理速度非常慢,而且对遮挡几乎没有抵抗力。这两种方案我都试过,最后放弃了,原因很直接:检测对象的形态太不稳定,靠图像分布规律去猜人,不如学出来的特征可靠。

两阶段检测器(Faster R-CNN系列)精度确实高,尤其在COCO上的基线表现不错,但推理速度很难跑到视频流的实时要求。教室系统往往要同时处理八路摄像头,一路30fps的话,单帧推理时间必须控制在40ms以内,Faster R-CNN很难做到。

最终选择落在YOLO系列上,原因有三点:速度足够快,轻量版本在普通显卡上轻松跑满30fps以上;生态成熟,训练、导出、部署工具链完整,这是工程落地最看重的;版本选择多,从老设备到新硬件都能找到合适档位。在实际项目中,我同时试了YOLOv5、v8和v10三个版本,后面会专门讲三者的差异和各自的适用场景。

1.3 系统整体架构与数据流

整个系统我拆成了四层,每一层的职责尽量单一,方便后面单独替换或优化:

  • 采集层:通过RTSP协议从网络摄像头拉取视频流,采用独立线程持续读帧,并把最新一帧放入带锁的循环队列。
  • 检测层:从队列取帧,送入YOLO模型做推理,输出每个人体目标框、置信度和类别。
  • 跟踪与计数层:将检测框送入跟踪器(我用的是ByteTrack),为每个目标分配全局ID,然后根据中心点与门线位置判断进出方向,更新计数。
  • 服务层:维护当前在线人数、进出累计值,提供HTTP接口和Web展示页,同时把每分钟的数据写入数据库,方便出报表。

这一层架构在实际运行中很稳定,后期换模型版本只需要改动检测层的封装,其它层基本不动。文章后续的每一段,都会对应这一架构里的一层来展开。

2. 训练数据从哪来:采集、标注与增强的实操记录

2.1 公共数据集能帮多少忙

一开始我也图省事,直接拿公共数据集预训练模型然后迁移到教室场景。我用了COCO数据集的预训练权重作为起点,这部分没有问题,COCO里包含了person类,模型已经学会了"人"的基本特征。真正需要思考的是:COCO里的人大多是在自然场景下完整出现的,教室场景里那种俯视、密集、半遮挡的形态,COCO并没有覆盖充分。

我也尝试过补充CrowdHuman和WiderPerson这两个密集行人数据集来微调。它们在密集场景上确实比COCO更有参考价值,CrowdHuman里大量存在互相遮挡的行人;WiderPerson覆盖了不同拍摄角度。但它们有一个共同问题:图像大多来自街拍或开放场景,和教室的固定俯视角差异很大。模型在微调后仍然会在教室的讲台、角落产生较多误检。

结论很明确:公共数据集适合做预训练起点,但要做成稳定可用的教室检测系统,必须采集目标教室的实拍数据。没有任何替代方案。

2.2 数据采集与标注规范

我实际做了两间教室的采集,一间是普通多媒体教室,另一间是阶梯教室,摄像头分别装在黑板正上方和教室后墙顶部,俯视角度大约25度到40度。采集时段覆盖了早晨、中午、下午、晚上四个时间段,目标是让光照分布尽量全面。后来又特意补采了拉上窗帘看投影、夕阳直射半间教室、阴天雾天这些边界场景。

采集的两个关键教训:第一,采集视频要连续录长片段,不要录几秒就停。因为后续做计数测试需要看到同一个人从进门到入座的完整过程,短片段没法用来验证跟踪逻辑。第二,每个教室至少采集不同人数的多个片段——3人、5人、10人、20人、40人混着来,只录空教室或只录满员教室都会让模型在某个密度区间内失效。

标注上,我只标注了一个类别person,没有单独标注head。做过两个方案的对比:单独标注头部可以帮助模型在严重遮挡情况下找到人,但也会引入一个问题——模型可能在看到后排人头顶时给出一个框,但看不到身体时又不敢确认,导致检测结果在座位区域闪烁。最终我把person定义成可见部分占整体面积25%以上就标,如果只露出头部或者一只手臂,就不单独标。同时,对严重遮挡的目标使用忽略区域而不是硬画框,这样训练时梯度不会去强行拟合一个不完整的目标。

标注工具用的是图形界面的标注软件,直接导出YOLO格式的txt文件。总共完成了约15000张有效标注图,其中训练集12000张、验证集2000张、测试集1000张。这个量级对单一类别的检测任务来说是足够的。

2.3 数据增强策略

YOLO训练自带Mosaic(拼图)、MixUp、HSV扰动、随机翻转等增强策略,默认参数在大多数场景下都可用。但教室场景有两个调整需要特别注意。

一是关闭Mosaic的剧烈缩放。Mosaic会把四张图拼成一张并随机缩放,这本身能提升对小目标的鲁棒性,但教室里的目标尺度相对固定——前排人很大,后排人很小。缩放范围太大会让模型学到不真实的尺度分布,反而干扰它对教室空间的判断。所以我把Mosaic的缩放区间限制在0.5到1.5倍之间。二是不要做90度旋转增强。教室的座位排列是固定方向的,摄像头也不会旋转,模型需要保持"上大下小"的空间感知,旋转会破坏这种一致性。

实际训练中,我单独验证过增强策略的影响:默认增强训练出来的模型在空教室和多人教室的表现差异很大,限制缩放范围后,各密度区间下的精度波动明显更小。如果你只想快速出一个可用模型,可以直接沿用默认增强;但如果追求各个时段都稳定,建议花一点时间调增强参数。

3. YOLOv5/v8/v10三轮对比:结构差异与选型结论

3.1 三个版本各自要解决的问题

YOLOv5是必须提的,它是过去几年工程界用得最广泛的检测器之一。它采用anchor-based的方法,网络结构以C3模块为主,配套的训练和部署工具链非常完善,对老硬件、老CUDA环境兼容性最好。在很多工厂项目里仍然能看到YOLOv5s的身影,原因不是它最强,而是它最稳。

YOLOv8把检测头换成了anchor-free结构,用C2f模块替代了C3,并引入了解耦头设计。训练收敛速度明显更快,在同样数据量和GPU条件下,v8从零开始训练能够比v5更快看到效果。它的一个显著优势是Anchor-Free机制绕开了聚类预设anchor尺寸的麻烦,换数据集时不再需要重新计算先验框。

YOLOv10是我这次重点实验的新版本。它的核心设计是端到端无NMS推理,通过一对一的匹配策略让训练和推理时的标签分配保持一致,省掉了推理时最耗时的NMS(非极大值抑制)后处理。在相同模型体积下,v10的推理速度比v8更快,特别适合计算资源有限的设备。

3.2 关键结构差异对照

下面这个表格是我在项目中整理的,直接对照了三个版本在核心机制上的差异,方便看清各自的侧重点:

对比项YOLOv5YOLOv8YOLOv10
锚框机制Anchor-basedAnchor-freeAnchor-free(一对多 + 一对一)
主干模块C3C2f类似C2f的改进版本
检测头耦合头解耦头解耦头 + 轻量化分类头
NMS依赖需要需要不需要
训练收敛速度中等较快较快
推理速度(同体积)中等中等最快
老硬件兼容性最好较好一般
典型适用设备老显卡、CPU环境主流GPU、边缘设备边缘设备、低算力设备

需要注意,这个表格里的"推理速度"是指在相同输入尺寸、相同模型尺度下的大致排序。实际影响速度的因素还包括TensorRT的支持程度。我在实测中发现,v10导出到TensorRT后,吞吐量优势会更明显,因为省掉的NMS在TensorRT推理流程里原本就比较难优化。

3.3 我在教室项目里最终怎么选

我的选择策略可以归纳成一句话:按硬件的下限来做决定。

如果部署设备是几年前的显卡,显存不超过8GB,或者需要跑CPU推理,我会选择YOLOv5s加上FP16推理,虽然精度略低,但兼容性最好,不会在部署环节卡壳。

如果部署设备是新一点的GPU,显存在8GB以上,优先选YOLOv8s。它在精度和速度之间最均衡,生态也最活跃,遇到问题能找到很多参考资料。我的主要测试环境就是这套组合。

如果部署平台是边缘设备,比如带GPU的开发板,重点考虑YOLOv10n或YOLOv10s。无NMS带来的延迟收益在低算力设备上非常明显,而且v10小模型在教室这种单类别场景下的精度损失比想象中要小。

三个版本不是替代关系,而是针对不同设备的并列选择。项目如果只允许带一套模型,我建议取YOLOv8s作为基准,然后在边缘设备上用v10n做备份模型,两者共用同一套数据和后处理模块,切换成本很低。

4. 完整训练过程与调参经验

4.1 环境配置与训练参数

我用的是PyTorch 2.x配合CUDA 12.1,显卡显存16GB,训练中batch size设为32,输入图像分辨率640×640。初始学习率0.01,优化器用SGD,配合warmup和余弦退火。整体训练300个epoch,大约花了9个小时。如果你的显存是8GB,batch size降到16即可,梯度累积也能缓解显存压力;如果是24GB显存,可以尝试batch size 64,收敛会更顺滑。

训练v8时有一个参数需要注意:标签分配策略的选择。v8同时支持基于框分配的loss和基于点分配的loss,教室这类目标重叠严重的场景,我推荐默认的框分配方式,点分配在遮挡严重时容易出现单头两框的现象。

训练v10时需要留意的是推理图片尺寸。v10在无NMS的架构下对输入尺寸更加敏感,官方训练默认是640,但如果教室画面里后排目标很小,可以把推理尺寸提升到1280,精度提升立竿见影,代价是推理时间翻倍。我建议先跑640基线版本,如果后排漏检明显,再单独对后排区域裁切放大做二次检测,比全局用1280更划算。

4.2 预训练权重还是从零训练

我的结论:一定要用COCO预训练权重做迁移学习,除非你有几万张同分布标注数据。教室场景虽然特殊,但"人"这个类别的基本视觉特征已经充分包含在COCO的person类里了。从零训练时,前几十个epoch基本都在重新学习边缘、纹理和人体结构这些通用特征,纯浪费算力。

迁移训练的具体做法我调整了几轮,比较稳定的是:先冻结主干前10层训练50个epoch,让检测头先适配教室数据的尺度分布;然后解冻全部层,按正常学习率微调250个epoch。这样做的原因是防止训练初期检测头梯度太强,把预训练主干学到的特征冲掉。如果直接全量微调,模型在后期精度上往往不如分阶段微调。

4.3 训练过程中出现的三个典型问题

第一个问题是误检投影幕布上的人影。阶梯教室播放PPT时,屏幕上的讲课人影像被识别为真人,高峰期一帧能多出两三个假框。解决办法不是删除这些帧,而是把它们单独作为负样本加入训练集。我先跑了一轮模型,把所有置信度大于0.3的误检帧截图,挑出约300张,只标注真实学生,屏幕区域用忽略框跳过,然后重新训练。第二轮误检率大幅下降,从每帧平均0.8个误检降到0.1个左右。

第二个问题是后排小目标漏检。640分辨率下,最后一排学生的检测框往往只有10×20像素,模型对这种小目标的召回率偏低。我做了个实验:把训练分辨率提高到1280,后排召回率提升了接近6个百分点,但训练时间翻倍、推理时间也翻倍。最终采用的是混合策略——训练阶段用mosaic增强时加入一张1280分辨率的裁剪,与640图像混合训练,让模型同时适应不同尺度,推理时保持640,性价比最高。

第三个问题是上下课瞬间的人群粘连。下课铃响后门口区域十几个人挤压在一起,检测框经常把两人框成一人。这个问题的根源不只是模型,跟踪器也参与了误判。模型层的应对是调整置信度阈值,从0.25提高到0.35,减少低置信度融合框的干扰;跟踪器层的应对是用ByteTrack的保留低分检测框功能,让被遮挡目标的轨迹尽量延续,后面会详细讲。

4.4 评测指标与实测结果

我在测试集上的指标如下,这是针对单类别person的训练结果:

模型配置mAP50mAP50-95推理耗时/帧
YOLOv5s(640)0.9280.68512ms
YOLOv8s(640)0.9410.71314ms
YOLOv10s(640)0.9350.70211ms

mAP50在90%以上并不代表计数就准,因为mAP衡量的是检测框和真值框的匹配程度,而计数关心的是目标数量是否有漏检和重复。我做了一个人工对比实验:挑了一段40分钟的课间录像,逐帧人工清点每个时间点的实际人数,与系统计数对比。结果显示,模型检测层的漏检率大约在3%左右,但经过跟踪和计数逻辑修正后,最终在线人数的误差可以控制到1到2人以内。这说明计数逻辑对整个系统的价值非常大。

5. 计数为什么依赖跟踪:从单帧检测到跨帧ID

5.1 单帧检测不能直接用于计数

这是很多初学者最容易踩的一个坑。如果你只是对每一帧统计数据里检测框的数量,然后求平均,得到的结果会非常不稳定。因为:一旦某个学生低头趴在桌上,模型漏检了,这一帧人数立刻少一个;两个相邻座位的同学脸靠得很近时,框偶尔合并,人数又跳变;一个人走过摄像头画面边缘,半身框反复出现又消失,也会被算成新的人。

更关键的麻烦是无法区分每一个人。统计"进入教室人数"时,模型必须知道这一帧里的某个人和上一帧里的某个人是同一个人,否则同一个学生从前门走进来会被算成十个不同的人。目标跟踪要做的事,就是给每个检测框分配一个全局ID,跟踪器通过这些ID来维持目标的连续性。

5.2 我为教室场景选择的跟踪器

我比较了DeepSORT和ByteTrack。DeepSORT有出色的外观重识别模块,通过提取行人外观特征来做跨帧匹配,对遮挡引起的ID切换恢复能力强。但它需要额外训练一个ReID模型,推理时还要额外跑一次特征提取,整体速度会下降不少。ByteTrack的核心思路是利用低置信度检测框,先把高置信度框做匹配,再拿低置信度框去补漏,这个特性用在教室的场景简直恰到好处——因为遮挡严重时,一个人可能只有0.2的置信度,但它仍然是真实的人。

实际测试中,DeepSORT在教室门口拥堵区域的ID切换次数明显高于ByteTrack,因为拥堵时每个人的可见面积都很小,外观特征难以区分,ReID模型的作用大打折扣。最终我使用ByteTrack作为默认跟踪器,IOU阈值设在0.3,匹配阈值设在0.8。这几个值也是我调了几轮才确定的,太严格的IOU会导致遮挡期间大量ID丢失,太宽松会导致邻近不同目标互相串ID。

5.3 进出判定与在线人数统计

真正让计数变得可用的是进出判定逻辑。我在画面里预设了一条虚拟门线,它会根据摄像头画面标注在教室门口的地板位置。每个目标跟踪框维护一个历史轨迹点序列,我用最新5帧的中心点计算运动方向。

当目标的中心点从门线的门外一侧移动到门内一侧时,判定为一次"进入";反方向则判定为一次"离开"。这样有一个问题,就是目标在门线上来回徘徊时很容易误判。所以我给门线越过加了一个确认机制:目标必须在门线另一侧连续停留至少1秒,才真正算一次进出。这一条规则把门口徘徊造成的误计次数降低了90%以上。

在线人数的计算方式很简单:每个教室维护一个计数器,初始为0,每确认一次进入加1,每确认一次离开减1。教室人数 = 初始人数 + 累计进入 - 累计离开。上下课时段因为进出同时大量发生,计数波动会比较剧烈,我采用5分钟滑动平均作为展示值,避免前端页面数字疯狂跳变。

5.4 计数逻辑在代码中的实现要点

这里给出一段核心逻辑的简化示意,便于理解整体流程:

# 每处理完一帧,从跟踪器拿到目标列表 for track in tracks: # 只保留确认被跟踪的目标,跳过未匹配和新建的 if not track.is_confirmed(): continue cx = track.center_x() cy = track.center_y() prev_cx, prev_cy = track.last_valid_center() # 判断目标是否越过预置门线 if line_state_changed(cx, cy, prev_cx, prev_cy): pending[track.id] = (time.time(), line_side(cx, cy)) else: # 连续停留确认 if track.id in pending: enter_time, side = pending[track.id] if time.time() - enter_time > 1.0: if side == INSIDE and current_side == OUTSIDE: enter_count += 1 elif side == OUTSIDE and current_side == INSIDE: leave_count += 1 del pending[track.id]

这个逻辑本身不复杂,但如果把跟踪和计数放在同一个线程里就会出现问题——检测耗时波动时,计数跳动会变严重。我把视频拉流、目标检测、跟踪计数拆成了三个线程,检测线程每处理完一帧就往跟踪线程发送一个检测结果,跟踪线程阻塞等待,这样每个模块的耗时波动不会相互影响。

6. 部署落地:模型加速、视频流处理与长期稳定运行

6.1 模型导出与推理加速

训练好的模型我不会直接用PyTorch的权重文件来部署,因为推理速度不够快、占内存也多。标准做法是先导出ONNX,再从ONNX转TensorRT,中间做FP16量化,必要时再压到INT8。我给一下实际测试数据:

部署格式输入尺寸平均帧耗时相对PyTorch加速
PyTorch权重640×64014ms1倍
ONNX(CPU)640×64035ms0.4倍
TensorRT FP16640×6406ms2.3倍
TensorRT INT8640×6404ms3.5倍

INT8在教室场景下的精度损失不算大,单类别检测目标不复杂,mAP50大概掉1到2个百分点,在可接受范围内。但如果同时跑计数,我还是更推荐FP16,因为尝试过INT8在极端光照下偶尔会把后排同学的框丢掉,计数就会出现1个人左右的误差。精度和速度的取舍,我宁愿多保留一点精度,换取计数结果的稳定。

6.2 RTSP视频流处理架构

教室摄像头一般通过RTSP协议输出H.264编码流。在Python中接收RTSP流,最流行的方法是OpenCV的VideoCapture,但它有缺陷:丢包时重连不够智能,长时间运行会出现画面冻结但进程还活着的情况,而且它在某些摄像头上是阻塞式的,拉流线程卡住会导致检测线程空转。

我的做法是用FFmpeg命令行从RTSP拉流,输出到管道,再用OpenCV从管道读帧。FFmpeg本身的重连机制更健壮,也能附加超时设置。整个视频流处理模块包含以下组件:

  • 拉流线程:每次从管道读取并解码一帧,放入循环缓冲队列。队列满时丢弃最旧帧,保证始终处理最新画面。
  • 检测线程:从队列取帧,缩放后送入TensorRT推理,返回检测框。
  • 跟踪计数线程:接收检测框,执行ByteTrack更新和门线计数。
  • 心跳监控:每30秒检查一次拉流线程的时间戳,如果画面超过5秒未更新,自动重启拉流进程。

6.3 多路并行与管理

在教室内安装多个摄像头时,可以采用每路独立线程组的方式来处理。例如四路摄像头,每路分配一个拉流线程、一个检测线程和一个跟踪计数线程,四路共享同一个神经网络引擎。需要注意NVIDIA显卡上并发推理会争抢显存和计算核心,四路同时推理时每路都可能掉到十帧以下。为了避免这种问题,我在检测层加了一个互斥锁,保证同一时刻只允许一路进行推理,其他路等待。经过测试,单张GPU处理四路640×640的FP16推理,整体稳定在每路12到18帧,对于教室监控场景完全够用。如果帧率要求更高,可以用两张GPU或改用更小的n型号模型。

6.4 无人值守的稳定性经验

这类系统一旦部署,通常要在学期内不间断运行,稳定性比功能本身更考验人。我遇到的几个真实问题值得分享。

第一,设备重启后要能自恢复。部署视频分析系统的边缘设备经常被拔电、重启、断网,如果系统启动后依赖手动拉流,很容易长期离线。我在启动脚本里加入了开机自启和健康检查,检测进程如果崩溃或被停止,会在10秒内自动拉起。摄像头掉线也做了自动重连,重连逻辑放在拉流线程里,断流时每5秒重试一次。

第二,文件和内存泄漏要盯牢。长时间运行的Python进程经常遭遇内存缓慢增长,视频帧队列若处理不及时就会堆积。我用了持锁队列加固定长度,超限时主动丢帧;另外每24小时重启一次检测进程,释放占用的显存和内存。这个重启策略看着粗暴,但对长时间无人值守的系统非常有效。

第三,写日志必须有时效策略。系统每1分钟向数据库写入一条计数记录,一个学期积累下来数据量不小。我按天分表,保留最近180天,超过时间的自动清空。日志文件则按大小轮转,避免单个文件一直膨胀。

7. 实测效果与最具代表性的踩坑复盘

7.1 计数准确度的综合实测

系统部署后我连续观察了两周,挑选了30节课进行人工对比。人工清点方式是在课中拍摄一张全景照片,两名同学分别数人数后取平均。三天内的统计结果如下:

场景人工清点人数系统统计人数绝对误差
40人大课38371
20人小课19201
课间自由进出静态15人/动态约10人次静态15人/动态约9人次1人次
投影幕布开启32331

整体的绝对误差基本控制在2人以内,教室人数统计这个需求是可以被满足的。系统的计数抖动也控制住了,页面上显示的人数不再随帧数来回乱跳。模型的漏检和跟踪的ID丢失,在计数汇总时会被逻辑修正一部分,因此最终误差远小于单纯检测的误差。

7.2 三个版本在同一段视频上的表现对比

我在同一段包含密集、遮挡、投影开启、教室门口潮汐进出的实测视频上,用v5s、v8s、v10s分别跑了一遍完整链路,记录到的差异如下:

版本漏检率ID切换次数(10分钟)计数误差综合表现
v5s4.1%72-3人稳定可用,速度够
v8s2.8%51-2人最推荐,精度高且稳
v10s3.2%41-2人适合边缘设备

v8s在密集遮挡下召回率最好,v10s凭借无NMS在ID追踪上表现意外地好,因为检测框中心点更稳定,跟踪器在匹配时不会因为边界框抖动而产生额外ID切换。如果你的部署环境是低算力设备,那么v10s搭配ByteTrack会是计数器精度最高的组合。

7.3 最有价值的三个踩坑记录

第一个坑是摄像头的自动曝光。为了解决这个问题我甚至怀疑过模型。教室窗户朝向不同,阳光强烈时摄像机会自动调低曝光,后排学生几乎变成一团黑影,检测基本失效。解决方式是在摄像头后台关闭自动曝光和自动白平衡,把曝光固定在一个中间偏亮的值。固定曝光后,模型检测框的抖动也明显减少。

第二个坑是检测框闪烁对跟踪的间接伤害。即使使用FP16推理,每帧的检测框坐标仍会有几个像素的随机波动,这种波动在半遮挡目标和边缘目标上会被放大,导致ByteTrack频繁发生匹配失败。最后我在检测结果送入跟踪器前加了一个轻微的位置平滑,把同一ID连续帧的检测框坐标做指数移动平均,稳定性和计数准确性同时提升。

第三个坑是边缘设备的散热降频。长时间运行推理任务时,边缘GPU外壳温度超过75度后会主动降频,推理耗时从6ms涨到15ms。刚开始我没在意,后来连续一周发现高峰期计数丢失,才排查到是设备过热导致。解决方式是在外壳上加装小风扇并将设备竖放,温度稳定在65度以下后,推理耗时回归正常。这类硬件问题在实验室里很难模拟,必须真机长时间测试才能暴露。

8. 一些后续扩展思路与个人体会

做到这一步,基础的人员检测与计数已经可以稳定交付了。如果继续往下做,我认为有几个方向是顺理成章的。一是把在线人数按座位区域映射成热力图,用来分析教室哪个区域更受欢迎、学生更爱坐前排还是后排;二是在跟踪ID的基础上做行为分析,比如统计低头看手机时长、长时间趴桌人数,教室场景下行为分析征得同意后是可行的;三是跨摄像头的人员重识别,把同一人进入教室前后的轨迹串联起来,这能覆盖走廊到教室的完整动线。

我个人的体会是:这套系统的价值大头不在深度学习模型本身,而在于数据工程和计数逻辑。模型精度从88%提升到94%,如果没有配套的跟踪、门线判定、平滑滤波和稳定性设计,最终计数误差依然会很大。反过来说,即使模型只有85%的mAP,但跟踪逻辑够健壮、容错设计足够,最终用户体验也可以做到基本可用。很多项目死在不稳定上,而稳定恰恰是最难在论文里学到的部分。

另一个想说的是版本焦虑没有必要。YOLOv5、v8、v10谁更强,脱离部署环境讨论没有意义。我建议先跑通一个版本的完整链路,把数据、跟踪、部署、监控这一套工程框架搭好,再花一两天换个别版本对比。模型替换在这个框架里其实就是换一个权重文件的事,真正有价值的是整个系统能持续稳定收集数据,帮你不断找到并修正新的边界问题。教室场景看起来简单,但光照、遮挡、设备、网络、温度都在参与影响结果,每排除一个问题,系统就可靠一分。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进