做安防监控算法这几年,我接过不少门店防损的项目。刚开始大家都想用目标检测直接“抓小偷”,真做起来才发现,偷窃检测根本不是“检测一个人”那么简单,它得在视频流里把“掏东西塞进包”“把货架商品拆开藏身上”“顺手牵羊放进购物车然后绕开收银台”这类动作识别出来。这里最大的卡点不是模型,而是数据——公开数据集里人倒是很多,但“偷窃关键时刻”的素材少得可怜。
后来我花了不少功夫整理和标注了一批超市店铺偷窃行为检测数据集,总共4000张高质量标注图片,格式直接对标YOLO系列常用格式。这篇文章就把我从数据整理、标注规范到调模型、踩坑排查的过程完整写下来,给做安防检测、门店智能防损的同行们一个能直接参考的方案。
1. 为什么偷窃检测特别依赖“场景化”数据
1.1 通用行人检测和偷窃检测之间的缺口
用通用模型很容易遇到一个情况:模型能把所有人都框出来,但框出来后不知道这些人“在干什么”。偷窃检测真正的输出不应该是“这里有人”,而是“正在发生疑似偷窃动作”。这就需要模型在目标检测的基础上,把人的姿态、手部位置、与货架或商品的关系一起学进特征里去。
通用数据集里,行人的姿态大多是走路、站立、交谈,很少标注“手伸进包/抓起商品/快速藏匿”这类关键帧。即使模型训练得很好,遇到真实的偷窃瞬间,漏检率也会很高。我见过不少方案把通用检测模型直接扔到超市场景里,效果惨不忍睹,并不是模型不行,而是数据分布压根不匹配。
1.2 为什么是4000张“图片”而不是视频帧序列
有朋友问,偷窃是个过程,为什么不用视频序列做动作识别?这个问题很现实。视频动作识别模型(如基于时序的网络)部署成本和标注成本都高得多,普通门店的NVR和边缘盒子根本扛不住。而且大部分安防摄像头的帧率在15到25帧,偷窃动作往往集中在几秒内,真正有效的关键帧也就那么几十张。
4000张高质量的静态图片,配合YOLO这种单阶段目标检测器,其实是在“成本”和“效果”之间取的平衡点。模型从单帧里学会识别“人-手-商品”的空间关系,再用前后帧的检测结果做简单的逻辑判断(比如商品从货架区域移动到包附近),就能覆盖绝大部分偷窃场景。
1.3 面向的检测目标和核心价值
这批数据集选择的目标不是动作本身(“偷窃”是一个抽象概念),而是把偷窃过程中最直观、可标注的几个视觉特征固化下来:
- 顾客把手伸向商品后直接塞入口袋或随身包;
- 打开饮料、食品包装后进行部分食用或藏匿;
- 将未结账的商品快速转移到购物车的夹层、伞层或被其他物品遮挡的位置;
- 多人配合遮挡下,其中一人将商品放入同伴的包中。
对应的检测标签围绕“关键交互区域”展开,比如“手部持物”“商品遮挡物”“包口附近的商品区域”。用目标边界框把“危险交互”的位置标出来,而不是简单标人。这样一来,YOLO的训练目标非常明确——模型学的是“有什么东西在哪个位置发生了从货架到私人物品的转移”。
2. 数据集构成与标注体系解析
2.1 图片来源与场景覆盖
这批4000张图片的采集逻辑是“覆盖不同门店环境”,而不是集中在一个固定摄像头角度。图片里包含了:
- 超市货架区(饮料区、零食区、日化区、生鲜区);
- 角落货架与视线盲区(摄像头容易拍不到完整全身的区域);
- 收银台周边(未结账区域内靠近出口的地方);
- 不同光照条件:正常日光灯、黄昏自然光、夜间灯光昏暗的角落;
- 不同距离范围:近景、中景、远景,其中远景图考验小目标漏检问题。
所有图片都已经做了人工筛选,剔除了模糊、拖影过于严重、多人重叠导致无法判断动作的样本。严格说,这批数据更看重“关键时刻的清晰度”,保证每一张图里至少有一个明确的偷窃行为交互区域。
2.2 标注类别与边界框设计
我采用的是COCO和YOLO通用的矩形边界框格式,每个框包含类别名称和归一化坐标(x_center, y_center, width, height)。这里的核心设计是类别要“可操作”,不要让标注员去猜“这是不是偷”。我最终把类别压缩为以下五个:
| 类别ID | 类别名称 | 标注对象 |
|---|---|---|
| 0 | hand_with_item | 手明显持有商品(拿起来、准备转移) |
| 1 | concealment | 商品正在被塞入包/口袋/衣物 |
| 2 | tampering | 打开包装、撕扯标签、破坏商品原状态 |
| 3 | hiding_obj | 商品被遮挡、藏在购物车底层或与其他物品混放 |
| 4 | suspicious_zone | 人与货架商品发生非正常交互的区域(争议帧兜底) |
你可能注意到,我加了一个“suspicious_zone”类别。这个类别是给真正恶劣的模糊视频帧准备的。很多边缘部署场景里,由于摄像头顶装角度导致人手被货架遮挡,前面的“hand_with_item”根本标不准。遇到这种情况,我宁可让模型输出一个“可能异常区域”,让下游逻辑结合时间上下文判断,也不强行标一个错误的精确框。
2.3 标注质量怎么保证
4000张图只靠一个人标很容易翻车。我当时的做法是“三轮标注法”:
- 第一轮:标注员切分关键帧,把所有出现偷窃交互的物体框粗标出来;
- 第二轮:质检员逐张检查类别是否混淆,重点看“手部持物”和“正常取货”极难区分的图;
- 第三轮:由算法工程师主导,把最终版本在YOLOv5上跑一遍,挑出置信度很高却标错的框单独修正。
这里面有个常见的坑:正常购物动作在视觉上和偷窃的前半段非常相似。比如顾客拿起一瓶饮料,然后走到隔壁货架,过程里永远是“hand_with_item”。如果只标这类图,模型就会把所有拿起商品的动作都当成偷窃,误报率爆炸。所以我在数据里特意混入了大量“正常取放商品”的负样本,占约20%。这非常重要,不要只关注正样本。
3. YOLO训练流程与关键参数配置
3.1 数据目录组织与格式转换
我先说数据怎么组织。YOLO工程里最基础的结构如下:
dataset/ ├── images/ │ ├── train/ # 3200张 │ └── val/ # 800张 ├── labels/ │ ├── train/ # 每张图对应的txt文件 │ └── val/ # 每张图对应的txt文件 └── data.yaml# data.yaml names: 0: hand_with_item 1: concealment 2: tampering 3: hiding_obj 4: suspicious_zone nc: 5 train: dataset/images/train val: dataset/images/val图片和标签文件名保持一致,txt文件中每行格式为:
class_center_x center_y width height所有坐标值都是原图宽高的归一化结果。如果你拿到手的是VOC格式XML,记得先转成上述格式,建议用脚本批量处理,不要手动改。转化时一个小坑:XML里xmin、ymin、xmax、ymax可能超出图片边界,转YOLO格式前必须先夹紧到图片内部,否则训练时会报坐标异常。
3.2 模型选型与超参数配置
我用的是YOLOv5的s/m系列,以及后来尝试过的YOLOv8。单从效果看,YOLOv8m在mAP50和推理速度的平衡上更稳,而YOLOv5s更适合低成本边缘盒子。配置如下:
# 以 YOLOv8 为例 yolo train detect \ data=/path/to/data.yaml \ model=yolov8m.pt \ epochs=200 \ imgsz=640 \ batch=32 \ lr0=0.01 \ lrf=0.01 \ momentum=0.937 \ weight_decay=0.0005 \ patience=30 \ augment=True \ seed=42这些参数里最需要关注的是imgsz和batch。超市监控截图原图尺寸普遍在1080P以上,直接把1920x1080的图缩放到640x640会损失非常多的细节。我的做法是:
- 训练时imgsz=800,让模型看到更多小目标的特征;
- 推理时用800或960,别看边缘盒子算力有限,但框精度能明显提升;
- 如果显存不足,先把batch降到16,而不是降imgsz。
关于epoch,不要机械设置。我用早期数据集试过,模型大概在120个epoch左右收敛,但到170个epoch后mAP略有回落。这里配合patience=30早停,只要连续30个epoch没有mAP提升就停下来,既省时间又避免过拟合。
3.3 损失曲线和指标评估怎么看
训练过程中我最看重两个东西:
- box_loss曲线:如果下降后再次反弹,大概率是学习率没配合好或者标签里有错误框;
- mAP@0.5与mAP@0.5:0.95的差距:两者差距太大说明模型对框位置不够稳定,需要更多回归精度。
我的最终指标大致如下:
| 模型版本 | 输入尺寸 | mAP@0.5 | mAP@0.5:0.95 | 单张推理耗时(X86 CPU) |
|---|---|---|---|---|
| YOLOv5s-800 | 800 | 0.827 | 0.586 | 62ms |
| YOLOv8m-800 | 800 | 0.854 | 0.633 | 110ms |
| YOLOv8m-960 | 960 | 0.861 | 0.645 | 145ms |
如果只追求“能不能报警”,mAP@0.5达到0.8就是可以接受的。但做安防产品,我更关注小目标类别“hiding_obj”的AP值。通常整体mAP很高,却把所有置信度得分集中在类别0上,后三类AP极低,这种数据集就有明显的类别不平衡问题。后续需要针对少数类做样本重采样或者复制增强。
3.4 数据增强策略
偷窃检测最怕的是过拟合到拍摄角度。同一个摄像头下的图,增广后训练的模型拿到另一个摄像头场景,效果往往会降一半。我开了YOLO自带的翻转、缩放、HSV扰动,另外建议加上:
- 随机旋转(不超过30度),模拟不同安装角度的摄像头;
- 随机亮度对比度扰动,模拟一天内光线变化;
- Mosaic增强,四张图拼一起,让模型在紧凑构图下还能识别小目标。
这里有个经验:不要开太强的旋转增强,尤其不要超过45度。因为标注框表现的是俯视下的交互区域,旋转过大反而会破坏“手在包口”的空间语义,让模型学到奇怪的特征。
4. 实战中踩过最痛的那些坑
4.1 漏检的关键帧和视频抽帧策略
我最初采集视频时是均匀抽帧的,每秒抽2帧。结果发现大部分偷窃动作的持续动作非常短促——一个“塞包”动作可能只有0.5秒,均匀抽帧很容易漏掉最关键的那一两帧。后来我改成两步抽帧:
- 先用快慢镜头遍历,把发生动作的几秒人工裁剪;
- 对裁剪片段做密集抽帧(每0.1秒抽一帧),再删除连续帧里几乎重复的图。
这样得到的关键帧比盲目每秒抽帧有效得多,处理下来4000张里真正有用的“决定性瞬间”占了大多数。如果感觉动作还是不明显,把抽帧间隔调整到0.06秒,同时用视频插帧算法补一下中间帧也可以,但不建议过度插帧,容易引入运动模糊。
4.2 类别边界模糊引发的标签修正
我最开始只定了3个类别:偷拿、藏匿、破坏。标注的时候标注员经常抓狂:“手拿着商品到口袋边缘,这到底是偷拿还是藏匿?”模型训练出来也乱套,类别之间互相抢框。
后来我把类别的判定标准改成动作阶段,而不是笼统的行为:
- 手离货架且商品在手,就是hand_with_item;
- 商品已接触包口/口袋边缘,就是concealment;
- 包口已经完全关闭或者手从包内抽离但商品被覆盖,就是hiding_obj。
这套规则明确之后,错误标签数量大幅下降。建议每一位做行为检测标定的朋友都自己先定义类似的“判定树”,不要依赖标注员的直觉。错标签对模型的伤害比缺少数据还大,尤其是在YOLO这种对边界框敏感的检测器上。
4.3 小目标和密集场景下的漏检
超市货架区域到处都是商品,背景纹理极其复杂。小目标“商品”可能只有几个像素,YOLO经常把货架上密集陈列的商品误解成hidden_obj或者误认为是携带的商品。我的解决办法:
- 将图像划分为四块重叠区域推理,然后合并框结果,提升小目标召回率;
- 把类别中“suspicious_zone”的置信度阈值调低,宁可多输出候选框,后续依靠逻辑过滤;
- 调整NMS阈值从默认的0.45到0.3,减少密集区域被抑制的情况。
实测调整后,小目标召回率提升了大约7个百分点。不过代价是每个画面多出了不少框,下游告警策略要增加“同目标连续多帧命中”的条件,否则误报会让人崩溃。
4.4 不平衡数据的极差结果
训练初期,hand_with_item类的样本接近3000张,而tampering类别只有400张左右。结果tampering的AP基本在0.4徘徊,模型几乎学不会“撕包装”这个动作。
我会对少数类做几种针对性的增强:
- 在图像中用复制粘贴的方法,把撕开的包装、拿出的商品粘贴到其他背景图上(先用mask扣出来再粘贴);
- 使用图像混合,让目标在多个背景叠加下保持标注有效;
- 对类别权重进行调整,让loss计算时少数类获得更高权重,YOLOv8的cls_loss支持手动配置权重。
其中最有效的还是复制粘贴增强,因为它在不改变目标形状的前提下扩充了背景多样性。实际操作上没有听起来那么高级,用分割模型把目标抠出来,再用随机旋转缩放贴到空货架或行人正常走动的画面上就行,标注框跟随目标一起变。生成一千多张增强图,tampering的AP直接到0.67。
5. 部署到门店监控的完整思路
5.1 硬件方案怎么选
门店场景不像做云端大模型,视频流必须尽可能在本地完成检测。我测试过两种主流硬件:
- NVIDIA Jetson Orin NX:适合装中间层盒子,16GB显存带得动YOLOv8m-800,4路1080P并发推理大概能跑到15 FPS;
- 带VPU/独立NPU的边缘IPC芯片:如果只能跑YOLO nano或YOLOv5s,就要在准确率上做妥协,建议把模型剪枝量化和蒸馏放在前面做。
给个实用建议:性能不够时不要只盯着模型压缩,先把推理分辨率降到640,并且把帧率降到10 FPS。在固定监控场景里,关键行为通常持续1秒以上,10 FPS足够捕捉到关键动作。
5.2 告警逻辑不能只依赖单帧模型
如果模型在每帧独立输出“concealment框”,马上报警,系统一定会成为一个“狼来了”的示例。正常的顾客弯腰拿包、把手机放进包里都可能被误检。所以真实部署时的决策逻辑要加上“多帧投票”和“区域关联”:
- 在连续30帧内,同一目标(通过成熟的目标跟踪器,例如ByteTrack)累积命中至少5次conf > 0.5的可疑类别框;
- 命中发生时,可疑框中心点必须发生在货架或者出口附近的自定义警戒区内;
- 若检测到顾客靠近收银台完成结算,则自动将所有命中判定为正常取货,清空缓存。
这样虽然会漏掉少量“动作特别快”的偷窃,但能够把误报率压到非常低的水平,门店店长才会愿意持续使用这套系统。
5.3 TensorRT量化和入口部署注意
部署到Jetson时,我一般把PyTorch模型导出为ONNX,再用TensorRT做FP16推理,速度能提升2倍左右。若需要做INT8量化,一定要先准备300到500张该场景下的真实监控图作为校准集,不要用训练集的图。因为训练集大多是清晰、角度单一的关键帧,而实际监控画面有各种遮挡和压缩噪声,校准集不贴合会直接导致量化掉点严重。
量化后模型建议重新跑一遍完整测试集,查看每类AP下降幅度。我遇到过hiding_obj的AP从0.63掉到0.41的极端情况。后来把量化校准集的曝光范围加大,加入低照度图,才恢复到了0.55左右。
5.4 与门店现有设备联动
做得再好的检测,如果只能在后台弹一堆无关紧要的框,没人愿意看。真正落地的联动方案至少要做这几件事:
- 检测到连续可疑动作后,自动推送事件片段到店长手机端;
- 本地NVR录下告警前后各20秒的短视频,作为证据链;
- 在PC端管理界面提供“历史目标回溯”,点击目标ID就能回放其轨迹和所有可疑命中帧。
我在一个模拟项目中,把告警事件和收银台POS订单做了简单的交叉验证。虽然涉及隐私规范,但在技术上提醒一下:如果可疑目标在随后几分钟内完成了POS结算,系统应当自动撤销这条告警,否则容易引发不必要纠纷。这一步能极大减少误报给门店带来的困扰。
6. 数据的后续扩展与优化方向
6.1 用真实场景做半自动标注
4000张数据集是一个好的起点,但长期运营的安防系统,需要不断从现场回流数据。我在部署后做了个很小的半自动管道:
- 现场摄像头实时推理,模型在连续帧输出高分可疑框;
- 系统自动保存这些命中区域为裁剪图;
- 隔天由人眼复核这些裁剪图,确认后自动写入数据集。
半年的积累下,原始数据集扩展到将近一万张,模型在新场景的误报率降低了三分之一。更重要的是,新场景的摄像头角度、商品摆放方式都被补充进来了,再遇到上一批完全没见过的门店也能有不错的泛化能力。
6.2 与其他行为识别的组合思路
目标检测输出框只是“空间特征”。如果想识别更复杂的动作,比如“两个人配合,一个人遮挡店员视线,另一个人把商品塞进包里”,仅靠单帧检测还不够。我目前的方向是:
- 使用检测出的框序列,结合目标跟踪器提取轨迹;
- 从轨迹中计算手部位置与包框的重合面积、重合持续帧数;
- 将“动作交互”抽象为时间序列特征,喂给一个轻量的LSTM或时序卷积模型。
这个方案比从头训练视频动作识别的成本低得多,而且能复用YOLO的检测结果。目前听说已经有团队在尝试更轻量的transformer时序模型,但对我这种资源有限的场景,LSTM已经够用了。
6.3 通用的“防损数据集”设计要尽早标准化
很多做安防的团队都各自攒数据,但格式、类别定义五花八门,换一个平台就大量返工。我个人的体会是,数据集从一开始就要把以下事情固定下来:
- 标注类别层次:先分交互对象,再分动作阶段,不混合;
- 图像分辨率信息记录在单独的文件里,方便后续缩放处理;
- 边界框坐标使用绝对像素还是归一化,与训练框架严格对应;
- 负样本占比固定在一个合理区间,避免项目迭代中数据失衡。
这样做的好处是,以后不管是训练YOLOv5、YOLOv6、还是换到更先进的检测框架,数据和代码之间能够无缝迁移。别小看数据格式的规范,我把这批数据集整理好之后,换了几次模型,每一步都省下了一到两周的适应期。
7. 最后分享一点我自己的教训
做这个数据集和模型前,我总觉得只要把“偷窃”定义好,交给模型暴力学习就完事了。结果第一版模型在演示环境里自信满满,上线到真实门店后却被店里的灯光、货架贴纸、小孩奔跑、顾客翻看手机等信息轰炸到无所适从。
后来我才意识到,行为检测的难点从来不在于“行为”本身,而在于正常行为的多样性。我们的模型要学的不只是“偷”是什么样的,还要知道“正常买”有多少种可能。所以不管做什么数据集,都要留出一部分负样本和容易混淆的场景反复测试。
如果你现在正打算做一个安防检测数据集,或者正在为超市防损项目找训练资料,我建议你把注意力放在“关键帧的清晰度”“边界类别的定义”“背景与负样本的覆盖”这三件事上。模型结构本身已经足够成熟,提升空间往往都在数据侧。这批4000张高质量标注数据也完全可以作为起点,配合你自己的店铺视频做增量训练,最终模型会更适应你实际的部署环境。