
去年下半年我们接了一个园区安防监控升级的项目需求写了整整三页核心其实就一句在现有摄像头基础上加一套异常行为检测能力。打架斗殴、人员突然倒地、翻越围墙、多人聚集狂奔这些情况出现的时候要能自动告警而且要快。我们一开始想得很乐观——团队对YOLO系列已经很熟了直接拉个COCO预训练模型跑不就行了结果测试当天就被现实教育了COCO那80个类别里全是person、car、bicycle这类静态目标压根没有“打架”“倒地”“翻越”这些概念模型对着监控画面输出的全是person框完全判断不了这些人在干什么。问题根本不是检测器不行而是缺一套面向异常行为的数据集。这篇文章就围绕我们后来整理并反复验证过的这套9100张YOLO安防监控数据集展开内容包括数据集的构成和标注规范、用YOLOv8在它上面从零训练的全过程、实际训练中踩过的坑以及从单帧检测走向行为判断的部署经验。如果你正在做安防算法、园区智能化、社区监控相关的事或者已经在用YOLO训练自己的自定义数据集这篇应该对你有用。1. 异常行为为什么是安防场景里最难啃的硬骨头1.1 COCO预训练模型在监控场景失效的根因先聊一个很反直觉的现象很多人觉得YOLO已经是“什么都能检测”的模型了怎么到了安防场景就拉胯其实不是模型不行而是任务形态变了。目标检测解决的是“这里有什么”的问题输出的是静态物体的类别和位置。但异常行为检测要回答的是“这些人在干什么”的问题它是一个动词、一个事件、一段过程。打架斗殴和两个人正常拉扯单看某一帧画面可能几乎一样倒地这个行为在老人摔倒和工人弯腰捡东西之间视觉特征也严重重叠。用COCO预训练权重直接做监控视频推理唯一能用的信息就是人形框而人形框本身不携带行为语义。我用一个很土的类比来解释这件事COCO预训练模型就像一个认识全世界物品的博物学家你让他看监控画面他能告诉你这里有一个人那里有一个人但你要问他这两个人是不是在打架他根本没有这个知识维度。你需要喂给他的是“打架”的样本他才可能学会判断。1.2 行为定义的主观性标注一致性决定算法上限做异常行为检测数据集第一个绕不开的问题是这个行为到底怎么算“倒地”在医院走廊和商场中庭完全是两回事“翻越围墙”有从中间翻、从角落翻、拿着东西翻“奔跑追逐”在早高峰地铁站里几乎所有人都在跑。如果标注员的判断标准不统一数据集本身就会带噪声模型的收敛上限就被卡死了。我们做这套9100张数据集的时候给每类行为写了两页纸的判定规则。比如“人员倒地”必须满足三点人体躯干与地面夹角小于等于45度、该状态维持超过0.5秒、人体未发生明显移动。再比如“打架斗殴”要求至少两人身体接触且伴随快速挥舞动作。这些规则听起来繁琐但实际训练时你就能感受到巨大差异——标准清晰的数据集训练曲线很快收敛标准混乱的数据集验证集的loss像过山车一样怎么调都降不下来。1.3 监控环境的复杂度是数据集的隐形门槛安防监控和普通的互联网图片标注还有一个明显区别环境复杂度。我们统计过这套9100张数据集的场景属性发现真正难处理的不是那些光线充足、正对镜头的中景画面而是三类极端场景一是夜间低照度摄像头自动增益导致噪点严重二是大俯视视角人在画面里的投影面积很小长宽比变形三是密集人群遮挡一个人倒地后马上被别人挡住检测框中断。如果数据集里只有“好看”的样本训练出来的模型在实验环境里表现很好一上真实监控就露馅。所以这套数据集里大约有30%是夜间样本、25%是小目标样本目的就是逼着模型在更贴近实际的条件下工作。这也是我要强调的第一条方法论别贪图精确率好看先把最难的环境样本铺进去。2. 9100张数据集里到底有什么类别、标注与划分细节2.1 类别设计与样本分布这套数据集的类别设计逻辑很简单只保留告警价值高、视觉特征相对可定义的行为不追求大而全。我们最终沉淀了6个类别分布如下类别ID类别名称中文含义样本占比0person_fight打架斗殴18%1person_fall人员倒地15%2person_climb翻越围墙12%3person_run奔跑追逐20%4person_throw抛掷物品10%5abnormal_aggregation异常聚集25%有人可能会问为什么“异常聚集”占那么大比例这是实际项目逼出来的。疫情防控、安保巡逻、广场舞扰民这类场景里“人群突然聚集”往往是后续更严重事件的先兆而且它比打架更容易被检测到——密集的人形框堆叠在特征图上非常醒目。代价是这类样本的边界比较模糊三个人站在一起算不算聚集八个人正常排队算不算所以我们在标注规则里加了一条当检测框重叠区域的面积超过画面20%且有持续移动趋势时才标记为异常聚集。2.2 图像来源与分辨率构成数据集的9100张图片不是一次性采完的我们经历了三轮收集和清洗。第一轮从已有的公开监控片段中抽帧第二轮自己布置摄像头在模拟场景中拍摄第三轮从合作伙伴那边购买了一批脱敏后的园区素材。分辨率覆盖720p到1080p其中1080p占比约70%。这是一个很现实的选择目前大部分园区监控头都是200万像素的1080p再高的4K在普通NVR存储方案里应用还不算广。训练时我们的默认分辨率用的是640x640但后面会提到针对小目标漏检问题还要用960甚至1280分辨率做二次训练。还有一个细节图片全部要求是“自然监控视角”也就是斜俯视或者平视坚决不要航拍或者普通游客视角。模型非常擅长记忆训练分布如果你在数据集里混入大量与真实部署不一致的视角样本部署时就会吃大亏。2.3 YOLO格式标注到底长什么样这套数据集统一采用YOLO格式的txt标注每张图片对应一个同名txt文件每一行代表一个目标格式是类别ID 中心点x 中心点y 目标宽度 目标高度其中中心点坐标和宽高都是相对于图片宽高的归一化数值范围0到1。比如一张1080p的图片一个人物的检测框中心在像素坐标(540, 360)框的宽高为(200, 400)那么对应的一行就是0 0.500000 0.333333 0.185185 0.370370标注工具我们试过几款最后留下来的是X-AnyLabeling因为它支持半自动辅助标注能先用YOLO预训练模型自动打人形框标注员只需要调整边界和选择行为类别效率比纯手动LabelImg高出一截。但这里必须提醒半自动标注的框适合当初始结果行为类别的判定绝对不能交给模型自动打必须人工确认否则等于把模型的偏见又喂回给它。2.4 划分策略同源避让是数据泄露的大敌数据划分我们采用train/val/test 8:1:1。但比这个比例更重要的是“同源避让”原则——同一个视频片段里抽出来的连续帧必须全部划分到同一侧不能一部分进训练集一部分进验证集。这个坑我见过太多次了。视频抽帧做数据集时第1帧和第10帧在视觉上几乎是连续的如果第1帧在训练集、第10帧在验证集模型等于提前看到了“考试答案”验证集指标会虚高得离谱。等你上真实监控视频时才发现模型根本不是学会了检测行为而是记住了特定画面。所以我们的做法是先按视频片段分桶再在桶级别上做随机划分。这样验证集里看到的画面一定是模型完全没有见过的不同时段、不同人物、不同动作过程。3. 用YOLOv8在9100张图上从零跑通训练3.1 环境准备不需要太豪华先说环境。YOLO训练其实不挑机器真正吃显卡的是推理部署但训练阶段还是建议至少有一张显存8GB以上的显卡。我自己日常用的是单张RTX 3060 12GB配合32GB内存跑这套数据完全够用。依赖安装很简单装ultralytics这个包就够了pip install ultralytics如果你之前装过老版本yolo建议先升级一下不然API参数对不上会报一堆莫名其妙的错pip install -U ultralytics还需要一个能正常工作的PyTorch环境官方源安装就行pip install torch torchvision3.2 目录组织与data.yaml配置工程上我习惯把数据、配置、训练输出彻底分开避免每次跑实验都是复制粘贴混乱。目录结构大概是这样的anomaly_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── anomaly.yamlimages目录里放.jpg或.png图片labels目录里放对应同名txt这个结构是YOLO训练最省心的标准组织方式。anomaly.yaml是数据集的配置文件内容如下# 数据集路径用相对路径方便换机器 path: anomaly_dataset train: images/train val: images/val test: images/test # 类别名称与序号一一对应 names: 0: person_fight 1: person_fall 2: person_climb 3: person_run 4: person_throw 5: abnormal_aggregation这里有个容易踩坑的细节names字典的顺序必须和labels里txt文件的类别ID完全一致顺序乱了模型会把person_fight识别成person_fall结果整个训练就是废的。3.3 训练命令与关键参数解读训练命令比很多人想象中简单yolo detect train \ dataanomaly.yaml \ modelyolov8m.pt \ epochs200 \ imgsz640 \ batch16 \ patience30 \ projectruns/anomaly \ nameexp01几个参数我必须展开说说背后的逻辑。modelyolov8m.pt我选的是m版本不是最小的s也不是最大的x。原因是在9400张中等规模数据集上s的容量不足以学习行为类别的细微差异x虽然精度更高但训练时间和部署成本都上去了。m是性价比最稳的起点。你可以先用m跑通全流程再回头试s和x对比。epochs200加上patience30不是非要跑满200轮patience表示如果连续30轮验证集指标没有提升训练会提前停止。实际我们的训练大概在第140轮左右就收敛了。imgsz640这个参数和检测精度的关系很直接后面小目标漏检那一节会细说。训练过程中你可以随时打开runs/anomaly/exp01目录下的results.png监控损失曲线和指标曲线。如果一切正常train/box_loss和val/box_loss应该是平滑下降且没有明显反弹。3.4 训练结果应该看哪些指标训练结束后不要只盯着mAP看。我建议按这个顺序看结果先看混淆矩阵。这套数据集最容易混淆的两组类别是person_fall和abnormal_aggregation——一个人倒地后身边围了一圈人模型经常同时预测出两类。这时候不是急着加层而是要想清楚业务上怎么处理多框共存。再看mAP50和mAP50-95。前者是宽松阈值下的平均精度后者是严格阈值下的。安防异常行为检测我个人更看重mAP50因为告警系统对框的精确定位要求没那么变态框稍微偏一点没关系关键是别漏。最后可以抽几十张val集图片做可视化预测用肉眼看模型在哪类样本上表现差。数据统计告诉你是哪个类不对可视化告诉你它为什么不对这种针对性分析在调模型时的效率远高于盲目试参数。4. 实测踩坑类别不平衡、小目标漏检与误报调优4.1 类别不平衡人员倒地的样本就是学不好第一次训练结束我们做分类别指标统计发现person_fall这个类别的recall明显低于平均水平。查了训练集分布脸色当时就有点难看person_fall只占15%样本里的一部分而且其中不少是小目标、夜间场景难度本来就高。处理不平衡有几种不同思路我把它们串起来做了组合实验。第一步是数据重采样对person_fall和person_climb两个少数类做重复采样多训练了20轮。效果有一点但也没想象中好说明单纯重复样本会带来过拟合风险。第二步是增强策略针对这两个类别加重点增强随机旋转、透视变换、随机遮挡。因为人员倒地的姿态多变我们特别加强了几何变换的幅度让模型多看些“躺着的各种角度”。第三步是修改损失权重在YOLOv8里可以调整cls_loss的权重参数让模型更重视分类错误的惩罚。具体做法是传一个损失权重配置但我们实验发现权重设太高会让模型变得过度敏感误报率反而上来了。最后落地的是重采样增强的组合方案对少数类样本做1.5倍重采样同时对这两个类别的训练图片做Lighting和Rotation增强。分跑两次实验对比person_fall的recall从71%提高到了83%。4.2 小目标漏检一个真实案例引出的三级跳这是整个项目里让我们印象最深的一个坑。初版模型上线测试时部署在一个商场中庭的球型摄像头下人站在远处时检测框经常消失。我们截图分析后发现远处的人目标在640x640分辨率下只有20x40像素左右。YOLO的特征图金字塔设计对小目标是天然弱势的。解决思路是一级一级试上来的。第一级把推理和训练的imgsz从640提高到960。代价是训练速度明显下降推理耗时变长但小目标召回率确实提升不少。实测在夜间场景下mAP50提高了5个百分点。第二级针对监控大图场景引入切片推理工具SAHI。部署时把1080p的输入先切成有重叠的子图分别送进模型再做结果合并。这个方案对小目标特别管用但它增加了推理耗时需要用工程手段优化。第三级使用带P2目标检测头的改进结构。YOLOv8本身没有P2但社区有很多高效add-on头部的实现专门处理微小目标。我们的最终方案是在训练的最后一轮用960分辨率微调10个epoch部署时用TensorRT加速梯度实测在条带机上能有约2倍的速度余量。这套组合拳把远处小目标漏检率压到了可接受范围。4.3 误报与漏报的权衡宁可牺牲一点框精度安防告警场景有一个很特殊的矛盾漏报比误报更可怕但误报太多又会让值班人员麻木。我们的经验是把告警和检测分开看。检测层面我们选择在mAP50下优化不追求框特别紧因为行为检测的难点在于行为判定框稍微大一点不影响“这里有异常”的判断。告警层面引入置信度阈值和帧间确认机制。一个行为只有连续3帧以上都预测出来才对外发出告警。单帧的一次噪声预测不会触发真正异常事件的持续时间通常远超过3帧所以不会丢掉。另外提醒一个机制NMS非极大值抑制阈值。默认NMS IoU阈值是0.5监控场景里密集人群互相重叠如果阈值太低很多真实的目标会被抑制掉。我们通过实验把NMS阈值调到了0.45漏检有所减少但同一个人被检测出多个框的情况增加然后用帧间跟踪去过滤重复告警。调优过程中有一个非常典型的case夜间灯光明暗交替区域巡逻保安的走动被识别成了奔跑追逐。我们通过加入该场景的负样本抽帧重新训练后才解决了这类光线导致的误报。数据集的持续迭代能力在异常行为检测里不是bonus是必需品。5. 从单帧检测到行为落地部署阶段必须想清楚的三件事5.1 推理硬件与帧率匹配别上来就整X模型训练只是第一步部署才是见真章的地方。实时监控场景我们一般用两个标准衡量推理方案一是单路1080p视频的处理耗时二是硬件成本。实测下来在RTX 3060上用YOLOv8m推理单帧耗时在12到18毫秒之间处理一路25fps的视频绰绰有余。但如果要并行处理8路、16路视频就需要用TensorRT做模型转换把FP32量化成FP16再走DeepStream这种管线框架。我个人的建议是先跑通单路再考虑并发。很多项目在实验室里看起来很快一上多路视频就崩溃问题常常出在视频解码、预处理和结果回传这些工程环节而不是检测模型本身。5.2 单帧检测远远不够时序信息是行为判断的关键这是我特别想强调的一点。异常行为本质上是时序事件单帧检测只能给出“这一帧有什么”要判断“这段时间发生了什么”必须把时序信息引进来。我们最初做的做法可以叫“滑窗投票目标跟踪”用BYTEtrack对检测出的人形框做跨帧ID关联然后维护一个时间窗口。当同一个人被连续判定为“倒地”超过一定帧数或者一群人在一个窗口内都被判定为“打架”才触发行为告警。这个方案工程上最简单但有个致命问题跨帧ID断掉怎么办。在遮挡严重的场景人的ID会丢失重连行为事件就被硬生生截断了。后来我们做了改进把行为检测的结果做一层时间平滑——不要求ID完全连续只要在固定时间窗口内重复触发两次以上就确认这个事件成立。实测下来这个策略在单ID短暂断掉的情况下告警连续性大大改善。再进阶一点的就是把序列信息直接放进网络比如用类似slowfast的动作识别思路。但这类方案数据要求高、训练成本大我们当时没有完全落地仅仅作为后续演进方向。5.3 数据飞轮上线后才是数据集的真正考验模型上线不是终点而是数据迭代的起点。安防场景上线后每天会产生海量真实监控数据其中有很多badcase包括但不限于雨天玻璃反光被检测成可疑人形、宣传片里的人物互动被当成打架、猫狗快速跑过被识别成奔跑。我们的做法是建了一个“误报警情回流”机制每次现场误报或漏报的片段自动截取到本地每周做一轮人工筛选把有代表性的样本清洗后增量进入训练集然后做一轮增量训练或全量重训。这个流程跑三个月以后模型在真实场景的表现会有肉眼可见的提升。所以这里我想说一个反套路的结论数据集的价值不在首次训练而在迭代。9100张的初始规模已经足够启动项目但一个可持续运转的标注回流机制才是异常行为检测系统能不能在真实环境里活下来的关键。最后再分享一点我对异常行为检测和YOLO选择的具体心得在数据侧投入精力的回报远高于在模型结构上追新的回报。我们试过把各种改进注意力机制塞进YOLO有的确实涨点但幅度都不如把数据集里的类别不平衡重新平衡一遍来得大。先清洗好数据再考虑换模型顺序反了很容易白忙一场。这套9100张YOLO安防监控数据集我们用得很顺手后续如果积累了更多夜间和遮挡样本应该还能把顽固的person_fall误报再压一压。