ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

9100张图自建安防异常行为数据集:YOLO标注到训练全指南

9100张图自建安防异常行为数据集:YOLO标注到训练全指南 去年做智慧工地项目的时候客户那边的安全主管给我看了段监控录像几个工人蹲在塔吊阴影下抽烟旁边就是材料堆放区。画面里并没有发生什么大事但主管说如果当时系统能识别出人员聚集抽烟这个组合动作就能提前干预避免上个月差点发生的火灾。问题在于市面上现成的异常行为数据集要么太实验室要么只覆盖商店偷盗场景拿到工地、校园、园区这类真实安防场景里误报率高到没法用。所以后来我干脆自己做了一套面向安防监控场景的异常行为检测数据集总共9100张图像全部用YOLO格式标注完成。这篇博文就把我构建这套数据集的全过程、踩过的坑、以及基于它训练YOLO模型的心得完整记录下来希望能给正在做安防监控、行为识别、或者准备自己造数据的同学一些参考。1. 为什么我会自己动手做一套异常行为数据集1.1 公开数据集的三个尴尬先说公开数据集。UCF-Crime、ShanghaiTech这些学术数据集在论文里很漂亮但拿到真实安防项目里直接训练基本会撞上三堵墙第一堵墙是场景错位。学术数据集里的异常多是从电影、新闻、或者固定机位的实验室场景中截取的视角往往偏高或偏正而真实安防摄像头大多是屋檐下斜俯视、画面里有树影摇晃、有车辆穿行、有光线忽明忽暗。用这些数据训练出来的模型放到新场景里第一个月你会发现它把风吹树叶大幅摆动都当成异常来报警。第二堵墙是标注格式各搞一套。有的数据集只给视频级标签整个视频是不是异常有的给帧级标签有的给边界框但用的是类似COCO的JSON格式。做工程项目时模型大多需要落到YOLO系v5/v8还得手动写一堆转换脚本转完还得抽检标注质量一来一回两周时间就没了。第三堵墙是负样本太少。学术数据集里正常行为的占比往往很低因为它们的研究目的是做异常检测不需要太多正常帧。但真实项目里我们需要模型既能识别异常又能在大量正常行为中不误报。正负样本比例失衡直接导致的后果就是模型上线后全天误报几百次安保人员直接把系统关了。1.2 9100张这个规模是怎么定下来的定9100张这个数量绝不是拍脑袋。我当时的逻辑是这样推的目标场景有工地、校园、园区周界、养老院公共区四类每类场景至少要保证有足够的异常类别激活。计划覆盖15类异常行为加1类正常行为后续会讲类别细节。如果要让YOLO系模型学到同一类行为在不同角度、光照、遮挡情况下的泛化特征每个类别至少要300张以上的有效标注图。16个类别合计就需要大约4800~5000张。但考虑到部分图像里会同时出现多个目标、多个异常行为比如一群人打架且有人在破坏公物实际每张图能提供2~3个有效标注所以9100张图的规模可以产出大约1.5万~1.6万个目标框能满足一个中等规模工程项目的前期数据需求。另外9100张这个量级对标注成本和训练效率也是友好的——人工精标大概需要8到10人日单张V100上训练YOLOv8m大约3到4小时一轮迭代试错成本完全可控。再往上加比如到3万张性能提升可能有限但标注和算力成本会翻倍项目前期完全没必要。2. 数据集的组成与标注规范9100张图到底覆盖了什么2.1 151的类别体系做安防异常行为检测类别设计不能太算法竞赛——不能搞一堆学术上很酷但实际项目用不上的行为得先问一句客户在现场到底关心什么我最终定下来的是15类异常行为加1类正常行为表格如下:编号类别名称典型场景举例标注难度大概样本数0person_normal正常行走、站立、坐着、打电话低28001fight两人及以上互相推搡、击打中5002robbery抢夺物品、强行拉扯高2803fallen_person跌倒、倒地不起中4204crowd_aggregation人群异常聚集、围观中3605running非运动场合的快速奔跑低4806smoke_visible可见烟雾含抽烟产生的烟雾高5407fire_visible明火高2608vandalism破坏公物、踢打门窗/器材高3109fence_climbing翻越围栏、围墙中29010vehicle_illegal_park违规停车、堵塞通道中32011intrusion闯入警戒区域周界中45012weapon_carried携带刀具等疑似危险物品高24013throwing_object高空抛物、投掷物品高18014climbing_height攀爬高处、塔吊、窗台中22015slouching_sleep在工作/值守岗位睡觉或长时间姿态异常低260这里有个细节想提醒大家把正常行为单独作为一类放进数据集看起来占了类别体系的一个坑位但在实际项目里极其重要。因为安防场景中正常行为是绝大多数模型如果只学异常正样本推理时会把所有没见过的情况都判为异常误报爆炸。加入person_normal且数量给到2800张本质上是在给模型建立正常基线。2.2 YOLO格式的标注细节整个数据集全部采用YOLO格式的TXT标注文件每张图对应一个同名的.txt文件文件里的每一行代表一个目标框格式是class_id x_center y_center width height注意这里全部是归一化坐标取值范围0到1。举个例子一张分辨率为1920x1080的图假设一个人跌倒后的边界框左上角是(350, 420)、右下角是(580, 900)那么对应的归一化结果是x_center (350 580) / 2 / 1920 ≈ 0.242y_center (420 900) / 2 / 1080 ≈ 0.611width (580 - 350) / 1920 ≈ 0.120height (900 - 420) / 1080 ≈ 0.444对应标注行就是3 0.242 0.611 0.120 0.444类别的编号必须严格跟数据集根目录下的配置文件对齐。我是用YOLOv8的命名规范做的比如# dataset.yaml path: ./anomaly_dataset train: images/train val: images/val test: images/test names: 0: person_normal 1: fight 2: robbery 3: fallen_person 4: crowd_aggregation 5: running 6: smoke_visible 7: fire_visible 8: vandalism 9: fence_climbing 10: vehicle_illegal_park 11: intrusion 12: weapon_carried 13: throwing_object 14: climbing_height 15: slouching_sleep很多新手喜欢在标注软件里直接导出然后路径不对、类名对不上、索引偏移训练时报错class 12 out of bounds还不知道怎么回事。所以我在做数据集的第一天就写死了这套映射后面任何人都不能随便改类别顺序。2.3 图像来源与场景分布9100张图像的构成按来源主要分三块公开可用的监控类开源视频片段占比约45%从一些开放数据集中抽取关键帧覆盖白天、黑夜、黄昏、雨雾等不同光照条件自采集场景模拟占比约40%在允许的场地内请同事配合演示打架、跌倒、攀爬、抛掷物品等动作用手机和摄像头按监控视角拍摄后抽帧网络公开的安防新闻/案例图像占比约15%从正规新闻媒体、官方通报中采集的异常事件现场图仅作补充场景多样性说实话自采集这部分是最耗体力的。为了模拟真实监控视角我不会站着平拍而是把设备架高、略带俯角有时甚至隔着树叶、栏杆去拍故意制造遮挡。因为真实安防摄像头就是这样——它不可能给你一个完美的正视角。场景分布上我也做了刻意控制避免某一类异常行为只在单一背景出现。比如fight打架这个类别我要求必须覆盖工地泥地、校园操场、园区广场、室内走廊4种子场景否则模型很容易学到背景打架换个地砖颜色就失灵。3. 数据质量把控标注流程、审核机制与踩坑记录3.1 标注团队的搭建和培训很多个人开发者习惯自己一张张画框但9100张图、1.5万个框一个人标到崩溃不说标准也很难前后一致。我的做法是找了两名有标注经验的兼职加上我自己组成三人小团队先花一上午统一标准。统一标准最关键的不是怎么画框而是边界情况怎么处理。我整理了一份标注问答清单挑几条最典型的目标被大面积遮挡只露出30%以下的身体部位是否标注答不标。两个人扭打在一起边界框重叠超过70%怎么处理答各标各的允许重叠但框要贴合各自身体主体不能合并成一个大框。远处小目标比如画面里50x20像素的人要不要标答标但要保证框中心大致准确。因为安防场景大量目标就是小目标不标会削弱模型的小目标检测能力。同一画面里既有正常走路的人又有一个人在打架。正常走路的人要不要标答要标标为person_normal。模型需要学会区分同一场景下的正常和异常目标。这些问题看似琐碎其实直接决定数据集的可用性。如果标注标准不统一模型学到的边界就是模糊的后期调参怎么调都别扭。3.2 交叉审核与抽检机制标注完成之后不能直接开训。我设计了两层检查第一层是逐张快速筛查。每张标注完的图用标注可视化工具把框画出来按目录快速浏览一遍。主要看有没有漏标、框有没有严重偏离目标、类别有没有明显标错。这一层通常能筛掉大概5%的低质量样本。第二层是抽检统计。对每个类别随机抽取20%的图片统计标注框面积分布。这里有个很容易踩的坑如果某个类别的框面积普遍偏大比如占图70%以上说明标注员可能把整片打斗区域画成了一个框而不是打斗的两个人各自一个框。这种框会让YOLO学到一个巨大的、语义不准确的框推理时根本没法用。发现问题后我会把该类别全部返工重新画。3.3 我踩过的几个标注坑这里必须吐槽一下我自己的失误给后来的同学提个醒。第一个坑是同源帧泄漏。我从开源视频里抽关键帧时图方便连续抽了同一段视频的50帧。结果训练集和验证集都被分到了这50帧里的不同帧模型在验证集上性能虚高得离谱。训练mAP 0.85验证mAP居然也有0.84我还开心了一阵子。后来发现同源帧泄漏后把验证集里所有同源视频的帧全部剔除重新评估mAP直接掉到0.68。这件事给我的教训是划分训练/验证集时必须按视频源分而不是按单帧分。不然你测出来的指标根本代表不了真实场景。第二个坑是类别相似度造成的标注歧义。violent行为里fight和robbery看起来很像——都是两个人纠缠在一起。区别在于robbery要有抢夺物品的语义而fight重点是击打对方身体。同一段素材我标成fight标注员可能标成robbery。这种错标会直接抑制模型对该类别的召回率。后来我在标注说明里强行加了一条规则除非能明确看到手部与物品的争夺动作否则一律按fight标注。歧义必须在标注规则里消解不能靠标注员自由发挥。第三个坑是细小但致命的格式错误。有一次训练的时候YOLO报错说某一行坐标值超出0-1范围查了半天发现是某张图是从视频抽帧的图像被旋转过但标注数据还是按旋转前的坐标写的导致归一化后出现了负数。后来我在所有标注脚本里强制加入数据合法性校验——解析每一行如果x_center、y_center、width、height有任何一项小于0或大于1直接打印文件名并中断不允许带着脏数据进入训练环节。4. 基于YOLO的训练实战配置、调参与效果验证4.1 数据划分与目录结构9100张图我按7:2:1划分成训练集、验证集、测试集注意是按视频源划分不是按帧随机划分前面说过了目录结构如下anomaly_dataset/ ├── images/ │ ├── train/ # 约6370张 │ ├── val/ # 约1820张 │ └── test/ # 约910张 ├── labels/ │ ├── train/ # 约6370个TXT │ ├── val/ # 约1820个TXT │ └── test/ # 约910个TXT └── dataset.yamltrain、val、test的图片和TXT同名同前缀保证一一对应。YOLO的标签文件不需要单独的JSON或XML一个TXT就搞定省事。4.2 训练配置与超参数我推荐从YOLOv8m起步不建议一上来就上YOLOv8x——安防场景对推理速度有硬性要求后面会细说。在单张V100上YOLOv8m训练9100张图大约3个多小时多卡并行还能更快完全在可接受范围内。训练命令大概是这样的yolo detect train \ data./anomaly_dataset/dataset.yaml \ modelyolov8m.pt \ epochs100 \ imgsz1280 \ batch16 \ lr00.01 \ lrf0.01 \ augmentTrue \ patience20 \ seed42 \ project./runs \ nameanomaly_v8m_1280关键参数说明输入分辨率imgsz1280。安防场景里大量目标是小目标用默认的640会让小目标特征严重损失。实测提升到1280后小目标小于32x32像素的召回率大约能提升8~10个百分点。代价是训练和推理速度变慢但对安防场景来说这是值得的。batch16V100 16G显存能扛住1280分辨率下的batch16再大就会OOM。如果显存不够优先降batch图像分辨率别降。lr00.01用预训练权重微调时这个初始学习率比较稳。如果是从零开始训练不用预训练权重需要降到0.005以下。patience20早停耐心值如果20个epoch验证集mAP没有提升就自动停止。实际我观察到大约在第70~85个epoch收敛。数据增强方面YOLOv8默认开启马赛克增强对安防场景特别有用——因为它能模拟出多目标互相遮挡、拥挤的复杂场景这和真实监控画面的特性很贴合。我额外关闭了hsv_h的增强幅度因为安防监控画面经常是固定的白平衡和色彩风格色彩扰动太猛反而会让模型学到不真实的颜色关联。4.3 训练过程观察与结果训练过程中的loss曲线我重点看两个点第一个点是验证集box_loss和cls_loss是否持续走低且没有明显反弹。如果遇到epoch 30之后验证集loss开始回升基本就是过拟合了应该立刻停止训练并增加数据增强或者缩小模型。第二个点是我关注各类别单独的AP平均精度输出而不是只看整体的mAP。因为整体mAP会被person_normal这类样本数量大、易学类别拉高掩盖掉fight、weapon_carried这些关键小类的性能问题。YOLO训练结束后会输出每个类别的AP我建议你一把它们记下来。我跑出来的基线数据大概是这样的具体数字因数据版本可能会有差异但量级可以给个参考类别AP0.5说明person_normal0.92正常行为类容易学fight0.74互相遮挡严重AP偏低robbery0.68样本量少且与fight存在语义混淆fallen_person0.81姿态明确效果尚可crowd_aggregation0.77人群密集目标多存在漏检running0.86运动特征明显smoke_visible0.71透明、半透明目标框不紧fence_climbing0.79姿态模式和翻越动作相关weapon_carried0.62小目标遮挡最差类别训练结束后还有一个容易被忽略的操作用同样的超参数再训练一次但把随机种子改掉。因为YOLO训练有随机性单次结果可能因为运气好/差产生几个点的偏差。跑两次取稳定的那个结果才算摸到底。5. 这些指标才是硬道理正确评估异常行为检测模型5.1 为什么mAP不是唯一标准如果你把安防异常行为检测当成一个纯学术任务可能看mAP就够了。但做工程项目客户更关心的是两个数字误报率和漏报率。这两个指标和mAP有关但不完全等价。举个例子mAP很高不代表误报少。因为mAP是框级别的检测指标它计算的是一张图里所有类别预测框和真实框的匹配度。而安防场景里客户真正在意的是这个模型一天到晚乱报警几次。哪怕模型把一辆正常的白色货车识别成intrusion框还画得特别准对mAP的影响可能只有0.01但对用户体验的伤害是100%。所以我在评估时额外做了两件事第一件事是给每个类别设定置信度阈值。比如person_normal的阈值设为0.35而fight的阈值设为0.55。这样做的原因是不同类别的误报成本不同——把正常行人标成打架比漏掉打架更让客户崩溃。YOLO的detect命令里可以传conf参数但逐类别阈值需要自己写推理脚本处理。第二件事是计算每千帧误报率。我取了一段连续的真实监控视频约2000帧统计模型在这段视频上产生虚假报警的次数。理想目标是小于每千帧2次。如果在测试集上mAP很漂亮但真实监控视频上每千帧误报10次这个模型就是不能上线。5.2 按场景拆分评估我强烈建议在评估阶段就把测试集按子场景再拆开工地、校园、园区、室内。分别跑一遍mAP和误报率。原因很简单安防项目通常不是全场景通吃的。同一个模型可能在校园场景下表现很好但到工地因为光照太强、尘土干扰误报翻倍。如果你只报一个总指标甲方和算法团队都没法定位问题。我当时拆分后发现园区周界场景的误报率比其他场景高出3倍查了半天发现是围栏外面有一条马路车辆经过时被识别成vehicle_illegal_park。后来针对这个问题增加了一批马路上正常行驶车辆的负样本重新微调后误报率降到正常水平。这就是按场景评估的价值——帮你精准定位数据缺口。5.3 半监督和自训练的补充思路9100张标注数据训出来的模型能保底但如果想再往上冲我试过一条性价比很高的路径用训练好的模型去跑大量未标注的监控视频拿到高置信度的预测结果比如置信度0.85作为伪标签加入训练集再训一轮。这叫自训练也叫模型蒸馏到自身。我这里提醒一句伪标签的置信度阈值一定要拉高宁缺毋滥。低置信度伪标签的噪声会直接污染模型反而不如不加。此外伪标签只适合补充正样本多样性不适合创造新类别——如果你想让模型学一个原来类别体系里没有的行为还得靠人工标注。6. 真实场景落地的几个关键挑战6.1 单模型vs多模型联动直接用单个YOLO模型跑全部15类异常推理端看起来省事但实际坑很大。不同异常行为的时空特征差异巨大——打架需要连续几帧的动作变化而fire_visible单帧就能判断。YOLO是单帧检测器天然缺少时序信息所以我会建议在工程上做多模型联动第一级用YOLO做单帧目标检测输出行为类别和位置第二级对fight、falling_person、running这些需要时序判断的类别接一个轻量级的时序分类器比如TSM或简单的LSTM输入连续10~15帧的检测结果过滤单帧误检我在项目里落地这个方案后打架检测的误报率下降了大约40%。第一级YOLO负责找到可疑目标第二级时序模型负责确认是否真的异常。这条思路比单靠调大YOLO阈值有效得多。6.2 实时性与卡顿问题安防监控通常要求多路视频流同时分析。如果在NVR旁边放一台服务器16路1080p视频每路要求至少10FPS的分析速度那么总吞吐是160FPS。YOLOv8m在1280分辨率下用TensorRT加速的FP16推理大约能跑到60~80FPS距离160FPS还差一倍。我常用的三个优化手段把检测分辨率拆成两路——全局用640分辨率做目标发现对检测到人的区域用1280分辨率做二次精细分类。这样大部分没有目标的画面帧只需跑一次低成本检测整体吞吐能提高50%以上。跳帧处理对行为变化缓慢的类别如smoke_visible每2帧分析一次对fight这类快速变化的行为保持每帧分析。这种按类别动态调帧率的策略在工程上很实用。用NVIDIA DeepStream或自研的流水线框架把解码、缩放、推理、后处理放到不同线程/硬件单元上并行。CPU解码和GPU推理如果串行做性能损耗非常大。6.3 告警之后的事可解释性与证据留存安防项目里模型输出一个打架告警客户不可能直接冲过去处理——他们需要看到证据什么时间、在哪个摄像头、画面截图、甚至一段5秒的短视频。所以在做落地时光有检测框不够还需要保存每次报警对应的原始帧和时序片段。我建议在推理链路里加一个报警缓冲池当某类异常置信度超过阈值时自动从视频流缓冲区取前2秒和后3秒的帧序列连同检测框标注一起落盘存档。后续客户申诉误报时这套证据链能帮你快速定位是模型问题还是场景问题。7. 训练与部署中的常见问题排查7.1 模型训练不收敛怎么办如果你训练时发现loss一直不降先检查三件事标签文件是不是真的和图片对齐了——建议写个脚本随机抽10张图把TXT标签画回图上肉眼看一遍。类别映射是不是正确——YOLO训练时如果names数量和你TXT里的class_id最大值不匹配会大概率出现梯度异常。数据增强是不是太猛——如果开了马赛克增强、随机透视、旋转90度部分类别如smoke_visible、fire_visible的语义会被完全破坏模型学到的东西就不对了。7.2 小目标漏检率高安防场景的小目标问题无解之一就是远距离人物只有几十个像素。除了前面提到的imgsz1280还可以尝试在标注阶段强制保留那些远处小目标框而不是顺手删掉。我遇到过很多标注员习惯性把小目标标得特别粗糙甚至漏标。YOLO对漏标的小目标不仅不会学还会把它当成背景负样本反而抑制对小目标的检测。所以数据集的标注规范里必须明确规定只要人能看出画面里有对象不管多小都要标。7.3 置信度阈值到底怎么调这个问题没有标准答案但有一条经验法则先看测试集上各类别AP从高到低排序AP最高的类别阈值可以设低一点0.3~0.4因为这类目标漏检损失比误报损失更可惜AP最低的类别阈值必须调高0.55~0.7因为低AP类别往往意味着模型对它的预测不可靠宁可少报也不能乱报。比如weapon_carried这类AP只有0.62如果阈值设0.25每天可能会误报几十次发现刀具。我最后把它的阈值拉到0.6误报降到几乎为零代价是漏掉一些真实但很小很模糊的刀具目标。在安防场景里误报的后果安保人员疲劳比一定程度的漏检更严重这个取舍方向是符合客户利益的。8. 这套数据集的后续扩展思路9100张、151类别只是一个起点。我接下来的想法有两条主线一条是继续加数据。目前fight、robbery、weapon_carried这几个类别的样本量和AP明显偏低下一步重点是补充夜间红外、雨雾天气、不同摄像头型号拍的素材。安防行业的数据积累是持续性的模型上线后每天都能从真实场景中采集大量难例hard examples这些难例经过人工确认后回填到数据集里迭代效果会非常明显。另一条是引入更丰富的行为建模维度。YOLO能提供的单帧检测信息是一个地基但如果想识别更复杂的异常比如长时间逗留、徘徊、尾随需要把YOLO检测结果送到时序模型或轨迹分析模块。我在数据集设计时特意保留了person_normal的连续轨迹信息就是为了给后续这些时序模型留好扩展口子。如果你也想自建一套类似的数据集我的建议是别等数据完美了才开始训练先用最小可行版本比如先标3000张、先跑一个模型把流程整个捋顺然后边训边补。数据标注是一个越标越准的过程前期标准没定好就开干后面返工成本远高于一开始多花两天定标准。最后分享一个我自己印象最深的操作细节第一次在暗光环境下测试模型时发现smoke_visible的置信度普遍掉到0.4以下后来排查发现是因为训练集中的烟雾多半是白天明亮背景下拍的。补了一批黄昏和夜间的烟雾帧之后暗光下的检测率立刻回到正常水平。数据分布覆盖是决定模型真实效果的第一因素这比调任何参数都重要。
返回列表