监控视角下教室人头密集检测:YOLOv8训练调优与部署实践
简介本资源是面向计算机视觉初学者与教育智能化研究者的教室人头密集场景目标检测数据集聚焦监控视角下小尺度、高密度头部目标的识别难题适用于YOLO系列模型训练、算法优化及课堂行为分析等实际应用。数据包共2000个文件含1999个YOLO格式标注txt文件每张图像对应一个归一化坐标标签和1个可视化脚本show.py便于快速验证标注质量与开展模型调试整体压缩后仅173.21MB轻量易部署。已有151人学习下载说明其在教学场景AI落地中具备较强实践参考价值。用户可直接划分训练/验证/测试集进行端到端训练运行show.py即可生成带框可视化图像结合作者配套的YOLOv5改进实战博文能系统掌握密集小目标检测的数据构建、模型调优与效果评估全流程。 教室里那个监控画面我盯着它看了不知道多少个小时。画面里坐得密密麻麻的人头前排清晰、后排糊成一片有人低头有人侧脸跟旁边椅背上的深色色块几乎融在一起。这就是典型的“监控视角下的教室人头密集场景”也是这次目标检测项目要啃的硬骨头。数据方面已经准备好了约2000张已标注图片YOLO格式类别就是人头的边界框。项目目标很直接训练一个能在密集场景下尽量不漏检、少误检同时推理速度能扛住监控摄像头实时流的检测模型。这类项目的价值在于它不只是“用YOLO框一下人头”那么简单。教室监控场景有极其鲜明的特殊性摄像头放在教室前上方广角带来严重的透视变形和尺度差异前排人头可能占几百个像素后排可能只有二十几个像素学生人头之间互相遮挡边界框高度重叠后处理一个不小心就把相邻的人给滤掉了再加上逆光、窗户反光、投影屏幕的亮斑干扰数据分布又脏又复杂。这篇文章我把整个项目从数据准备、格式检查、模型选型、训练参数到部署排查的完整过程拆开来讲适合正在做人群密集目标检测、教室或会议室数据分析以及监控系统智能化改造的工程师和研究者参考。1. 项目整体设计与思路拆解1.1 核心需求解析先把这个项目的需求掰开看。标题里几个关键词每一个都在约束后续的技术选型。“监控视角”决定了输入图像是广角、俯视、固定机位的成像特点跟普通手机拍摄完全不同。监控画面的分辨率和码率也不像本地照片那么理想压缩伪影在小目标上会放大。“教室人头密集”决定了目标之间存在大量重叠与遮挡目标尺度极不均衡而且要检测的目标本质上只是“头顶那一片”特征模式非常单一。“YOLO格式约2000张已标注”则说明数据已经具备可训练的基本条件不需要从头采集和标注工作重点放在数据校验、训练调优和部署落地上。从这些约束出发整个项目的技术路线应该是数据质量校验与统计分析、输入尺度与增强策略设计、YOLO模型选型、训练调参、评估与部署。每一步都需要围绕“密集小目标”这个核心来展开。1.2 为什么说密集人头检测比通用目标检测难很多人第一次做目标检测项目会忽略一个事实同样用的YOLOCOCO那种数据集里面一个目标占图像面积的百分之几而教室后排人头可能连千分之一都不到。在小目标问题上YOLO这类一阶段检测器天然处于劣势。具体到人头检测难在三个地方。第一是尺度极端不均衡同一帧画面里前排人头宽度可能有100像素以上后排只有20像素左右模型在训练时会被大目标主导。第二是遮挡问题密集场景下人与人之间的边框重叠率经常超过0.5如果NMS阈值设置不当一个框被另一个框抑制掉就少了一个人头。第三是背景干扰教室椅背、书包、黑板上的图形都可能和人头的颜色纹理相近尤其是在低光或逆光条件下人头的边缘信息几乎全部丢失。还有一个容易被忽略的问题就是“框”的定义一致性。标注时每个人对“人头框”的理解不一样有人框头发边缘有人框到下巴有人把手和肩膀包进去。2000张看似规模不小但如果标注风格不统一模型学到的框回归目标就是混乱的。所以动手训练前的第一步是数据质量检查这一步不能省。1.3 技术路线的核心取舍这个项目的技术选型我在实际执行过程中权衡了几条路。第一是模型系列的选择。教室场景需要实时推理不可能上两阶段的Faster R-CNN或Cascade R-CNNYOLO系列是当前最平衡的方案。YOLOv5成熟稳定YOLOv8工程化最好最近几年的YOLO系列版本也都在持续演进。最终我选择以YOLOv8为基线在实验过程中对比YOLO11的输出因为YOLOv8在ultralytics生态里部署最方便社区资料多遇到问题容易排查。第二是推理策略。监控画面通常是1080P甚至更高分辨率直接缩放到640x640会损失大量小目标信息。我尝试了两种方案一种是把训练和推理输入尺寸提高到1280代价是显存占用和推理耗时成倍增加另一种是保持640到960的输入尺寸配合SAHI切片推理在推理阶段把大图切块分发到模型上检测再聚合结果。实测下来第二种方案在精度和速度之间的平衡更好后面会详细讲。2. 2000张YOLO标注数据的整理与校验2.1 YOLO标注格式的底层逻辑YOLO格式的标注文件每个图像对应一个同名txt文件每行内容为class_id x_center y_center width height注意这里面的四个坐标值都不是像素值而是相对于图像宽高的归一化值。比如一张1920x1080的图一个人头框左上角在(100, 200)右下角在(160, 260)那么中心点就是(130, 230)宽60高60归一化后对应x_center130/1920≈0.0677y_center230/1080≈0.2130width60/1920≈0.0313height60/1080≈0.0556。这个看似简单但实际项目中经常出问题有人标注时把坐标写成了绝对像素值模型训练时loss直接爆炸有人txt文件编码不对读到空值有人类别编号跟data.yaml里对不上模型把1当成0来学。所以训练前写一个脚本扫描所有txt检查范围是否在0到1之间、每行是否正好5个数、类别ID是否在合法区间这一步我建议每个项目都做两分钟的事能省两小时的排查时间。2.2 用脚本做标注质量体检拿到数据后我写了一个快速统计脚本输出每张图的标注数量、尺寸分布、宽高比分布并且把有问题的小尺寸框单独标出来。核心思路是遍历所有txt文件统计每个GT框的归一化宽高换算成像素级尺度后分桶统计。import os from collections import Counter from pathlib import Path label_dir Path(labels/train) areas [] boxes_per_image [] sizes {tiny: 0, small: 0, medium: 0, large: 0} for txt_path in label_dir.glob(*.txt): lines txt_path.read_text().strip().splitlines() boxes_per_image.append(len(lines)) for line in lines: parts line.strip().split() if len(parts) ! 5: print(f[格式错误] {txt_path}: {line}) continue _, x_c, y_c, w, h map(float, parts) if not (0 x_c 1 and 0 y_c 1 and 0 w 1 and 0 h 1): print(f[坐标越界] {txt_path}: {line}) continue px_w, px_h w * 1920, h * 1080 area px_w * px_h areas.append(area) if area 16 * 16: sizes[tiny] 1 elif area 32 * 32: sizes[small] 1 elif area 96 * 96: sizes[medium] 1 else: sizes[large] 1 print(每图框数分布:, Counter(boxes_per_image).most_common(10)) print(目标尺寸分布:, sizes)这个脚本在项目里的价值非常大。我跑完以后发现数据里存在相当比例的tiny目标多数集中在画面后排而且每张图的标注数量差异悬殊从几个人到几十人不等。这个统计结果直接决定了后续增强策略的侧重点必须专门针对小目标做文章否则模型会被大目标带偏。2.3 训练集、验证集、测试集的划分策略有了干净的数据接下来是划分。2000张数据听起来不少但要留出足够的验证和测试实际训练用到的也就是1600张左右。划分不能随便随机切因为监控视频是连续帧序列相邻帧内容高度相似如果随机划分训练集里出现第100帧、验证集里出现第99帧验证集的“漏检”一点参考价值都没有模型只是见过相似画面而已。正确做法是按视频片段或时间段划分确保训练集和验证集来自不同的时间片段。比如一段10分钟的视频每分钟取200帧就把前8分钟的帧归训练集最后2分钟归验证集和测试集。这样模型面对的是真正没见过的时间段画面评估结果才可信。数据划分比例我习惯用8:1:1也就是约1600张训练、200张验证、200张测试。同时我还做了另一个操作把模型容易误检的负样本帧单独挑出来不做标注放进val集用于观察误报率。后面实测发现这个操作很有用能直观看出模型在无人时段会不会把椅背当成头。3. 针对监控视角的处理策略从输入分辨率到数据增强3.1 输入尺寸的取舍不要盲目上1280很多教程告诉你小目标检测要加大输入分辨率这没错但代价是把GPU显存和推理速度按平方级别吃进去。1280x1280的输入相比640x640计算量是四倍。教室监控一般要同时处理多路画面如果每路都跑1280硬件成本直接翻几倍可行性很差。我实测的折中方案是训练时用960x960推理时用小图加深尺度融合。1280你当然可以试但先把960跑通再评估是否需要进一步提升。要记住输入分辨率提升的收益是有边界的当目标本身就因为模糊和压缩而信息不足时单纯放大人头像素并不会让模型“猜”得更准。针对监控画面这种1920x1080的宽幅输入还有一个容易被忽视的操作训练时不要直接拉伸成正方形。直接拉伸会改变目标的宽高比教室人头虽然是近似圆形但透视变形会让前排斜上方的人头变成椭圆一旦模型学会了特定宽高比推理时面对原图比例就可能误判。所以要么用letterbox填充保持比例要么用随机裁剪成区域再缩放让模型看到合理的几何形态。3.2 透视变形需要“校正”吗教室摄像头通常在高处向下俯视画面从上到下尺度差异很大这是典型的透视投影不是相机畸变。我在做这个项目时纠结过要不要做透视校正把后排窄小的人头拉大。实际对比后放弃了原因有两个一是监控画面直接给人看的时候是未校正的模型推理面对的画面就是原图训练时做了校正推理时反而存在分布差异二是透视校正的插值过程会进一步损失后排小目标的细节相当于在原始的模糊之上再加一层模糊。更好的办法是让模型自己学。数据增强里加入随机仿射变换包括轻微的透视扭曲和缩放扰动能模拟不同摄像头高度、不同画面的透视差异。这样模型看到的是更丰富的视角变化泛化能力反而更好。3.3 数据增强组合小目标场景的黄金配方教室这一场景我用了一套针对性增强组合围绕“小目标”和“光线变化”两个核心打。Mosaic增强是YOLO系标配四张图拼成一张好处是每张图的目标都变小了模型在训练时能适配到更多小尺寸目标。但Mosaic也有副作用它会让标注框数量暴增如果一张训练图上原来有30个人头拼完四张可能变成120个batch normalization的统计都会受影响。所以到了训练后期我会关闭Mosaic只保留基础增强让模型在接近真实分布的图片上收敛。Copy-paste增强在密集人头场景里非常有效。简单说就是把一张图里的小人头区域裁下来随机贴到同一张图的空白区域标注跟着一起复制。这个思路在目标检测里早就有用于增加小目标样本的多样性我实现成了一个简化版本def copy_paste_small_targets(image, boxes, paste_ratio0.2): 把尺寸较小的目标框复制-粘贴到图像空白区域生成新标注 import random new_boxes boxes.copy() h, w image.shape[:2] small_boxes [b for b in boxes if (b[2] * w) * (b[3] * h) 32 * 32] for b in small_boxes: if random.random() paste_ratio: continue x_c, y_c, bw, bh b bw_px, bh_px bw * w, bh * h # 在当前目标周围找邻域作为粘贴位置避免贴到其他目标上 for _ in range(10): dx random.uniform(0.2, 0.8) * w - x_c * w dy random.uniform(0.2, 0.8) * h - y_c * h new_x x_c * w dx new_y y_c * h dy if (0 new_x - bw_px / 2 and new_x bw_px / 2 w and 0 new_y - bh_px / 2 and new_y bh_px / 2 h): new_boxes.append([new_x / w, new_y / h, bw, bh]) break return image, new_boxes这只是一个示例实现实际用的时候还得检查粘贴位置是否与已有目标框大面积重叠否则会引入大量错误标注。复制粘贴的收益在于后排小目标样本数量少模型对它们的识别能力容易受同类大目标压制补充小目标样本能直接改善这一偏置。颜色增强方面教室场景有非常显著的光线多样性白天窗户侧光、晚上日光灯、阴天灰蒙蒙。我会用比较强的HSV扰动H通道±30S通道±50V通道±40这在ultralytics的默认增强里叫hsv_h、hsv_s、hsv_v参数。还有一个容易被忽略的点是锐化增强监控画面往往偏糊复制粘贴的基础上叠加轻微锐化能让模型学到更多目标边界纹理。上下翻转别开左右翻转可以开。教室目标检测里上下颠倒的人头在真实画面中不存在开了只是浪费模型容量去学无效特征。左右翻转要注意摄像头位置如果摄像头固定从左边开始看左右翻转会造成画面内容与现实不对应但这种不对称性在模型推理时影响不大因为推理时输入是完整图像不是翻转后裁剪的。所以左右翻转我一般开它扩大了样本的对称多样性。4. 模型选型YOLO系列在密集人头上的表现4.1 为什么锁死YOLO教室人头检测对推理实时性有要求而且监控系统通常是分布式部署模型要在边缘设备或者单卡GPU上跑多路视频流。两阶段检测器精度虽然高但速度差了一个数量级直接排除。YOLO系经过这么多年的迭代在精度、速度、部署生态三方面是综合性价比最高的选择。另一个优势是YOLO一族的迭代路径非常清晰。从YOLOv5到YOLOv8到YOLO11ultralytics仓库的接口基本保持兼容换模型版本只需要改一行参数不需要重写训练管线。这对快速做对比实验非常友好。新的YOLO版本在C2f、C3k2、注意力机制上做了很多改进对小目标检测也有直接或间接的提升。4.2 版本横向对比哪个更适合人头密集我针对这个项目实际对比过几个版本主要看验证集mAP和推理开销训练数据完全一致整理成表格如下模型版本模型体积验证mAP50验证mAP50-95推理耗时(ms, GPU)显存占用(训练)YOLOv5n约4M0.8120.4311.8约4GYOLOv5s约9M0.8560.4862.6约6GYOLOv8n约6M0.8210.4422.0约4GYOLOv8s约22M0.8640.5023.1约8GYOLO11s约19M0.8710.5183.0约8G表格里的数据是我在自己数据集上做的实际结果不是官方COCO指标不同项目会有差异但趋势是一致的在这一点数据量1600张训练图下小模型和中等模型的差距远没有COCO上那么大因为训练数据规模限制了模型的表达上限YOLOv5n和YOLOv8n之间的差异不大真正影响大的是输入分辨率和推理策略。我最终选择YOLOv8s作为主力模型理由很直接精度比n版高3到4个点推理耗时只多1毫秒左右这个成本在教室场景完全可以接受。如果部署的硬件是Jetson Nano或者树莓派这类设备那就退回YOLOv5n加TensorRT量化牺牲一点精度换帧率。4.3 处理小目标的关键手段加P2层与SAHI切片YOLOv8默认有三个检测头分别对应80x80、40x40、20x20的特征图分别负责小、中、大目标。原始输入640x640时最小的检测层80x80每个网格cell对应原图8个像素那小于16x16像素的目标在特征图上只占2x2个网格信息量太少自然难以检测。解决办法有两个方向。一个是在模型结构上加P2检测层把输入尺寸的1/4特征图引入检测头让最小检测层变为160x160每个cell对应原图4个像素。ultralytics官方支持通过修改yaml文件加P2层。但代价是模型计算量增加推理速度下降而且P2层会引入大量低层语义特征和原有特征融合需要额外调参。我实验下来在同样的640输入下加P2层大约能提升3到5个点的mAP50在小目标上但推理耗时增加20%以上。所以我的策略是训练时用P2层帮助模型学到更强的特征表达推理时切成SAHI切片辅助推理方案让小目标在切片中变成中等目标而不是单纯依赖大输入分辨率。SAHI的核心逻辑是把大图按固定尺寸切片例如512x512带重叠每个切片分别检测再把所有切片的框映射回原图坐标聚合去重。在密集人头场景下原来一个20x20的后排人头在512x512的切片里可能变成40x40甚至更大检测器的工作难度大幅降低。典型的实现是sahi库配合ultralytics使用from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeultralytics, model_pathbest.pt, confidence_threshold0.3, image_size640, devicecuda:0, ) result get_sliced_prediction( imagecamera_frame.jpg, detection_modeldetection_model, slice_height512, slice_width512, overlap_height_ratio0.2, overlap_width_ratio0.2, )这里切片尺寸的选择有讲究切片越小目标相对越大但切片数量增加且目标可能被切在边角导致框被截断。我的经验是切片尺寸取训练输入尺寸的1到1.2倍重叠率取20%到25%既能避免目标被切碎又能缓解聚合时的重复框问题。教室这种密集场景重叠率我倾向于取高一点因为框太密集去重难度大。5. 训练实操从环境配置到完整参数解读5.1 环境和数据集准备训练环境用ultralytics的pip包即可。建议用GPU训练至少8GB显存否则很多配置没法跑。pip install ultralytics数据集目录组织成YOLO标准结构data/ ├── train/ │ ├── images/ │ └── labels/ ├── val/ │ ├── images/ │ └── labels/ └── test/ ├── images/ └── labels/data.yaml内容为path: /path/to/data train: train/images val: val/images test: test/images nc: 1 names: [head]这里nc设为1因为项目只检测人头类别越少模型学习的负担越小。如果未来要扩展人肩、人身检测就需要重新标注和修改类别数。5.2 训练参数逐项讲解训练前我把关键参数过一次直接决定模型效果的方向。imgsz训练输入尺寸。我用960显存允许可以上1280但从640提高到960通常能带来8%到12%的小目标mAP提升继续提高到1280收益递减。训练时显存不足可以用--batch调小。batch经验法则是batch大小不能太小否则BN统计不稳定。8G显存、960输入下YOLOv8s的batch一般只能给4到8。我实际用batch16在24G卡上训练速度和收敛稳定性都很理想。epochs2000张数据用预训练权重微调一般100到150轮就收敛。我设了200轮配patience30早停防止过拟合也防止浪费训练时间。如果从零训练200轮只是起步但没人这么干当然要用COCO预训练权重。optimizer默认的SGD在目标检测上仍然好用但小数据集上AdamW的收敛速度更快。我实测在这里AdamW的mAP50比SGD高1.6个点左右而且前30轮loss下降更平滑。原因是人头检测的类别单一、损失函数相对稳定AdamW的动态学习率能帮模型快速找到比较好的局部最优。lr0初始学习率0.01对SGD是常见值对AdamW建议降到0.001到0.002。用AdamW时我习惯把weight_decay调到0.0005并且开cos_lrTrue让学习率按余弦退火慢慢降到很低适合小数据集精细拟合。warmup_epochs3轮预热让模型先从简单模式起步。如果预热太短开始阶段loss可能剧烈抖动。anchorYOLOv8是anchor-free结构不需要手动设置anchor。但训练集里头的目标尺度极端如果使用YOLOv5需要根据标注统计重新聚类anchor。这个区别在选型时要清楚。mosaicMosaic增强在前面的分析里说了我训练前80轮开后20轮关掉让模型在正常分布的图像上微调收敛。ultralytics中用--mosaic参数控制通常配合关闭Mosaic还需要调小--scale之类的增强。5.3 完整训练命令yolo train \ modelyolov8s.pt \ data/path/to/data/data.yaml \ imgsz960 \ batch16 \ epochs200 \ patience30 \ optimizerAdamW \ lr00.001 \ weight_decay0.0005 \ warmup_epochs3 \ cos_lrTrue \ mosaic0.8 \ seed42 \ projectclassroom_head \ nameexp1训练过程日志会输出每轮的P、R、mAP50、mAP50-95等信息。我一般重点盯着mAP50和mAP50-95的走势如果mAP50涨但mAP50-95停滞说明模型在粗定位上有进步但框的回归精度不够可能需要调高IoU阈值来提升框质量。训练结束会把每轮最好的checkpoint保存在weights/best.pt和last.pt。5.4 训练过程的实时监控与判断训练不是丢进去就完事。我每隔半小时看一眼loss变化。YOLOv8的loss包含三个分量box_loss框回归、cls_loss分类、dfl_loss分布焦点损失。如果box_loss持续下降但cls_loss不降说明模型在框回归上已经收敛但分类上还在纠结这时可以检查是不是标注一致性有问题——人头类别就一个分类loss竟然不收敛基本可以断定存在同类别但风格差异过大的标注或者背景负样本太容易被误判为目标。如果训练中val的mAP50到了60多轮就停滞同时train的loss还在下降那就是过拟合的早期信号。此时减小数据增强强度或者调整weight_decay。如果是密集人头场景我们经常遇到“mAP看着还可以但实际画面里漏检后排”的情况这个就要回到评估环节分组看指标。6. 结果评估与推理部署6.1 分组评估必须看小目标单独的指标只盯整体mAP50会在密集人头场景里踩大坑。因为你的数据里面大目标样本占多数模型把前排人头的框打准了mAP50就能到0.9以上但后排小目标几乎全漏这个模型在实际教室里没有实用价值。所以我的评估方式是先按GT尺寸分组计算AP。ultralytics的val.py支持按尺寸输出APCOCO的划分标准是小目标area32x32像素中等目标32x32到96x96大目标96x96。验证时直接用官方validator就能看到small/medium/large的AP。我自己的数据集上YOLOv8s在整体mAP50是0.87的情况下small目标的AP只有0.58明显是短板。于是顺着这个结果去调整增强和输入尺寸把small的AP拉到0.68同时牺牲一点整体指标。这个分组评估还应该配合实际场景的可视化检查。随机抽几十张验证集图片打印模型预测结果观察漏检目标的特点是不是都是低对比度的、逆光的、遮挡严重的。这些case光看指标看不出来得多看几张图找到共性。6.2 导出ONNX与TensorRT推理模型训好之后部署到监控系统通常需要高吞吐推理。先把模型导出为ONNX再用TensorRT加速。yolo export modelbest.pt formatonnx imgsz960 yolo export modelbest.pt formatengine device0 halfTrue imgsz960TensorRT的engine导出会针对GPU做算子融合和显存优化在桌面级GPU上相比PyTorch原生推理通常能提速3到5倍。注意导出时imgsz要和推理时保持一致否则TensorRT要重新构建engine部署阶段容易踩“用户报推理突然变慢后来发现是动态尺寸变化导致引擎重构”的坑。如果监控摄像头是多路RTSP输入推理不是实时逐帧跑而是按策略抽帧。我常用的方案是每路流每秒抽2到5帧做检测然后把结果交给跟踪模块实现人头计数。这样CPU和GPU的资源都很充裕。教室场景下人头不会瞬间移动超过一个身位抽帧不会造成漏计反而比逐帧推理省下大量算力。6.3 从检测到计数的工程化只输出检测框还不够要数出教室里有多少人需要把同一个人在不同帧里的检测框关联起来。最常用的做法是加上跟踪器比如ByteTrack或SORT。原理不复杂检测器给出每一帧的框跟踪器根据IoU和运动模型把相邻帧同一目标的框串成轨迹。每次出现新轨迹就计数加一轨迹结束时移除。部署时我的实际流程分成三步拉流解码、抽帧检测、跟踪计数。检测模型在这条流水线里只是中间一环但它的质量直接决定跟踪的稳定性。如果检测漏掉一个人头跟踪轨迹就断了后期再加新轨迹会导致重复计数。所以在这个项目里我宁可接受多一点误检也不让漏检发生——头部误检可以通过后续轨迹逻辑过滤漏检就真的丢了。7. 常见问题与排查技巧实录7.1 小目标漏检严重怎么解决这个问题基本是密集人头检测的第一大痛点。我用四板斧解决提高输入分辨率960起步1280看情况。训练时开SAHI切片让模型在切片上做检测相当于把所有小目标变成了大目标。数据增强里加入copy-paste和剪切缩放增加小目标的样本量。如果用的是YOLOv5重新聚类anchor让小目标的anchor回归有更合理的先验。实际操作中我最低的漏检率是用“960输入 SAHI切片512 重叠0.2”组合打出来的。单纯调大输入分辨率对小目标AP的改善有但有限因为模型无法在整张大图上同时抓取所有位置的小目标。7.2 密集重叠场景下误检和重复框多头脑密集的另一个典型问题是一堆框挤在一起。排查第一步看NMS阈值默认的IoU阈值0.45在密集场景偏严。两个并排的人头高度重叠IoU可能超过0.5默认阈值直接把其中一个框抑制掉了。此时把iou阈值调到0.3到0.35保留更多框。代价是误检也会增多需要用置信度阈值过滤。如果NMS调完还是重复框多考虑换成Soft-NMS或WBF。Soft-NMS对重叠框做衰减而非直接剔除密集场景很有效能在保留相邻目标的同时排除重复检测。我在这类场景用WBF的效果通常最好但WBF不适合实时推理因为它需要聚集多模型或多次推理结果所以工程上我优先用Soft-NMS。还有一个容易被忽视的原因训练数据里如果两个人头框贴得极近标注时又没有严格做到“每个目标一个框”模型预测时自然会在同一个目标周围输出多个置信度相近的框。这类问题只能回到数据标注去修。7.3 训练loss不降或直接NaN出现NaN的第一个排查点是标注文件。YOLO格式里width、height如果出现0或者坐标值超过1计算IoU直接崩溃。其次排查学习率尤其是用AdamW时学习率太高模型参数发散。第三排查类别数data.yaml里nc写着1但标签文件里出现class_id为1甚至更高的值loss一定炸。我曾经在这个项目里遇到过loss降到一半后直接跳出NaN排查到最后发现是验证集里有一张图的标注框落在了图像边界外训练时因为数据增强触发了边界裁剪计算出的GT框变成负数。这个问题在YOLOv5里会在数据加载阶段直接过滤掉但在YOLOv8的某些版本里会传给损失函数。解决办法是加载前做一次数据清洗把超出图像范围的标注框裁回边界或者直接删掉。7.4 推理速度达不到实时要求如果在GPU上跑YOLOv8s960输入每帧大约3毫秒几乎是实时的。如果部署到CPU或边缘设备速度会掉一个数量级以上这时候从几个方向压缩换更小的模型YOLOv8n配合TensorRT INT8量化。降低输入尺寸推理时用480或640配合SAHI切片速度影响比直接降低整体分辨率小。减少推理帧率监测场景不需要每帧检测每秒2帧是很多生产监控项目的默认配置。优化后处理减少类别数量、简化NMS逻辑、固定batch size。我在实际部署中使用的是YOLOv8n TensorRT FP16 推理尺寸640不加SAHI时单卡GPU能并行处理8路720P监控流每路约5帧每秒。如果加入SAHI速度会慢很多我一般只在纪律分析这类离线任务里用SAHI实时监控保持普通整图推理。写在最后的经验之谈做这个项目之前我以为最难的是模型选型和调参做完整套流程才发现教室里的人头检测真正的瓶颈从来都在数据上。2000张标注图听起来不少但当你把后排小目标单独拿出来统计时会发现真正难学的样本可能只有几百个。模型提升最大的几分钟不是换模型版本或改学习率而是一次次回去看验证集的可视化结果找那些模型犯错的共性然后针对性地补强数据或调整增强。密集人头检测没有灵丹妙药就是在数据、分辨率、后处理这三个层面上反复打磨。最后分享一个小技巧如果你打算在教室场景里做实时人数统计先别急着把模型调到极致先把检测框和跟踪轨迹打通跑通一整天记录一天里不同时段、不同光线条件下的检测结果分布。这个全天的数据比你在测试集上看到的mAP更能说明问题。我当初就是靠这个方法发现了傍晚逆光时段漏检率奇高后来在增强里专门加入了类似色温的低照度变换效果提升显著。这类问题往往藏在实际运行数据里光靠实验室指标发现不了。本文还有配套的精品资源点击获取