1. 安防场景下的异常行为检测:这个数据集到底能干什么
安防监控这个领域,做算法的人都有一个共同的痛点:公开数据集太"干净"了。学术数据集里的人物姿态标准、光照均匀、背景简洁,模型跑出来的mAP漂亮得不行,一上真实场景就拉胯。我前两年接手过一个园区安防的项目,用COCO预训练的YOLO直接推理,结果摄像头装在高处俯拍,人在画面里只有几十个像素,模型连人都框不准,更别提判断行为了。
这次拿到的这个数据集,9100张YOLO格式的安防监控图像,核心价值就在于它贴近真实监控场景。它不是那种摆拍式的学术数据,而是包含了监控视角下的人体目标、异常行为标注。你可以用它来训练一个能识别摔倒、攀爬、徘徊、聚集、闯入等异常行为的检测模型,也可以单纯当作一个监控视角下的人体检测数据集来用。
适合谁来参考?如果你正在做以下事情,这个数据集值得花时间研究:
- 安防算法工程师,需要快速验证异常行为检测的baseline
- 计算机视觉方向的学生,想做一个落地的目标检测项目
- 嵌入式部署方向的开发者,需要监控场景的数据来测试模型在边缘设备上的表现
- 想学习YOLO训练全流程的新手,需要一个真实场景的数据集来练手
我拿到数据集之后,第一件事不是直接开训,而是先做数据探查。这个习惯救过我很多次——曾经有个项目,标注文件里的类别索引和data.yaml对不上,白跑了两天训练。所以下面我会从数据集的解构开始,一步步拆到训练、调优、部署,把整个链路讲透。
2. 数据集解构与训练前的关键检查
2.1 YOLO格式标注的目录结构与字段含义
YOLO格式的数据集,标准结构是这样的:
dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml每张图片对应一个同名的.txt标注文件,每行代表一个目标框,格式为:
class_id center_x center_y width height这里有个新手最容易踩的坑:所有坐标都是归一化到0到1之间的,不是像素值。比如一张1920x1080的图,一个人体框在像素坐标下是(960, 540, 200, 400),归一化后就是:
0 0.5 0.5 0.104 0.370计算方式很简单:center_x = 960/1920 = 0.5,width = 200/1920 = 0.104。我见过有人直接把像素坐标写进标注文件,训练loss直接爆炸,排查了半天才发现是格式问题。
9100张图的数据集,如果按7:2:1划分,大概是6370张训练、1820张验证、910张测试。但具体划分要看数据集本身的组织方式,有些数据集已经预分好了,你直接用就行。
2.2 数据探查:训练前必须做的三件事
第一件事:类别分布统计。写个脚本统计每个类别的标注数量:
import os from collections import Counter label_dir = "dataset/labels/train" counter = Counter() for txt_file in os.listdir(label_dir): if txt_file.endswith(".txt"): with open(os.path.join(label_dir, txt_file), "r") as f: for line in f: class_id = int(line.strip().split()[0]) counter[class_id] += 1 print("类别分布:", dict(sorted(counter.items())))如果发现某个类别的样本数只有几十个,而其他类别有几千个,那就要考虑类别不平衡的问题了。安防场景下,"摔倒"这种异常行为的样本天然就少,这是正常的,但训练时需要做处理。
第二件事:可视化抽查。随机抽20张图,把标注框画上去看看:
import cv2 import random def visualize_annotation(img_path, label_path): img = cv2.imread(img_path) h, w = img.shape[:2] with open(label_path, "r") as f: for line in f: cls, cx, cy, bw, bh = map(float, line.strip().split()) x1 = int((cx - bw/2) * w) y1 = int((cy - bh/2) * h) x2 = int((cx + bw/2) * w) y2 = int((cy + bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) return img这一步的目的是检查标注质量。我遇到过标注框偏移、漏标、错标的情况,如果不提前发现,模型学到的就是错误的信息。
第三件事:检查图片尺寸分布。监控摄像头的分辨率五花八门,有1080P的、有720P的、还有D1分辨率的。统计一下尺寸分布,决定训练时的输入分辨率。
from PIL import Image import os sizes = [] for img_file in os.listdir("dataset/images/train"): img = Image.open(os.path.join("dataset/images/train", img_file)) sizes.append(img.size) from collections import Counter print(Counter(sizes).most_common(10))注意:如果图片尺寸差异很大,建议统一resize到640x640再训练。YOLO系列默认输入就是640,这个分辨率在精度和速度之间取得了不错的平衡。
2.3 data.yaml的正确写法与常见错误
data.yaml是YOLO训练的核心配置文件,格式如下:
path: /home/user/dataset train: images/train val: images/val test: images/test nc: 5 names: 0: person 1: fall 2: climb 3: loiter 4: intrusion这里有几个容易出错的地方:
path必须是绝对路径,或者相对于训练脚本运行目录的路径。我建议用绝对路径,省得后面找不到文件。nc是类别数量,必须和names的长度一致。names的索引必须从0开始,且和标注文件里的class_id对应。
有个朋友曾经把nc写成6,但names只列了5个类别,训练的时候直接报索引越界的错误。这种低级错误看起来可笑,但赶项目的时候真的会发生。
3. YOLO模型选型与训练策略设计
3.1 从YOLOv5到YOLOv8:安防场景该选哪个版本
YOLO系列发展到现在,版本多得让人眼花缭乱。我按实际项目经验给个选型建议:
| 版本 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| YOLOv5 | 生态成熟,文档丰富,部署工具链完善 | 精度相对落后 | 快速验证、嵌入式部署 |
| YOLOv7 | 精度高,训练策略先进 | 代码结构较复杂 | 对精度要求高的场景 |
| YOLOv8 | 精度和速度均衡,API简洁 | 部分部署工具链还在完善 | 新项目首选 |
| YOLOv9/v10 | 最新架构,精度领先 | 生态不够成熟 | 研究性质的项目 |
对于安防异常行为检测这个场景,我的建议是:如果追求快速落地,选YOLOv8n或YOLOv8s;如果追求极致精度,选YOLOv8m或YOLOv7。
为什么?安防场景通常需要实时推理,模型不能太大。YOLOv8n只有300万参数,在V100上跑640x640的图,batch size 1的情况下能到300+FPS,完全满足实时性要求。而且YOLOv8的ultralytics库封装得非常好,几行代码就能完成训练和推理。
3.2 预训练权重的选择与迁移学习策略
千万不要从零开始训练。除非你有几十万张标注数据,否则从零训练的效果一定不如用预训练权重做迁移学习。
YOLOv8的预训练权重在COCO数据集上训练过,已经学到了通用的特征提取能力。COCO里有人这个类别,和安防场景下的人体检测有很强的相关性。所以:
from ultralytics import YOLO # 加载预训练权重 model = YOLO("yolov8s.pt") # 开始训练 model.train( data="data.yaml", epochs=100, imgsz=640, batch=16, device=0, workers=4, patience=20, lr0=0.01, lrf=0.01, momentum=0.937, weight_decay=0.0005, warmup_epochs=3, augment=True, cache=True )这里解释几个关键参数:
epochs=100:9100张图的数据集,100轮足够了。如果验证集loss在20轮内没有下降,patience=20会自动停止训练。batch=16:根据显存调整。V100 32G显存跑YOLOv8s,batch可以开到32甚至64。lr0=0.01:初始学习率。迁移学习时这个值比较合适,如果loss震荡厉害,可以降到0.001。cache=True:把图片缓存到内存,加速训练。9100张图大概占几个G内存,如果内存够大就开着。
3.3 数据增强:安防场景的针对性策略
YOLOv8默认开启了Mosaic、MixUp、HSV增强等策略。但安防场景有它的特殊性,需要针对性调整:
Mosaic增强:把4张图拼成1张,增加小目标检测能力。安防场景下人体目标通常较小,这个增强很有用,保持默认开启。
HSV增强:调整色调、饱和度、亮度。监控画面在不同光照条件下差异很大,白天、夜晚、逆光、阴影,这个增强能提升模型的鲁棒性。建议把hsv_h调到0.02,hsv_s调到0.8,hsv_v调到0.5。
随机翻转:安防场景下,人体左右翻转是合理的,但上下翻转不合理(人不会倒着走)。所以flipud=0.0,fliplr=0.5。
随机裁剪:监控画面中人体通常不在画面正中央,随机裁剪能模拟这种偏移。但裁剪比例不要太大,否则会把目标裁掉。
model.train( ... hsv_h=0.02, hsv_s=0.8, hsv_v=0.5, flipud=0.0, fliplr=0.5, mosaic=1.0, mixup=0.1, scale=0.5, translate=0.1 )实操心得:数据增强不是越多越好。我曾经把MixUp开到0.5,结果模型收敛极慢,验证集精度反而下降了。后来降到0.1,效果就正常了。增强策略要根据数据集大小来定,数据少就多增强,数据多就少增强。
4. 训练过程监控与调优实战
4.1 训练日志解读:loss曲线告诉你的真相
YOLOv8训练时会输出三个loss:box_loss、cls_loss、dfl_loss。
box_loss:边界框回归损失,衡量预测框和真实框的差距cls_loss:分类损失,衡量类别预测的准确度dfl_loss:分布焦点损失,YOLOv8特有的,用于优化边界框的分布
正常的训练曲线应该是:三个loss都稳步下降,最后趋于平稳。如果出现以下情况,就要注意了:
情况一:box_loss下降但cls_loss不降。说明模型能定位到目标,但分不清类别。可能原因是类别不平衡,或者某些类别的特征太相似。解决办法是检查标注质量,或者给稀有类别加权。
情况二:loss震荡剧烈。学习率太大了。把lr0降到0.001试试,或者开启余弦退火调度。
情况三:验证集loss先降后升。过拟合了。增加数据增强、加Dropout、或者减少模型参数量。
我习惯用TensorBoard或者WandB来监控训练过程:
model.train( ... project="security_detection", name="yolov8s_exp1", plots=True )训练完成后,runs/detect/security_detection/yolov8s_exp1/目录下会有loss曲线图、混淆矩阵、PR曲线等,这些图能直观反映模型的训练情况。
4.2 学习率调度与超参数调优
YOLOv8默认使用线性预热+余弦退火的学习率调度。前3个epoch是预热阶段,学习率从0线性增加到lr0,然后按余弦函数逐渐降低到lrf * lr0。
这套策略在大多数情况下都work,但安防场景可以微调:
- 如果数据集较小(比如只有几千张),把
warmup_epochs增加到5,让模型更平稳地进入训练状态。 - 如果发现模型在后期震荡,把
lrf从0.01降到0.005,让学习率降得更低。 momentum保持0.937,这是经过大量实验验证的值。weight_decay保持0.0005,防止过拟合。
有个技巧是用YOLOv8自带的超参数搜索功能:
model.tune( data="data.yaml", epochs=50, iterations=30, optimizer="AdamW", plots=True )它会自动搜索最优的学习率、动量、权重衰减等参数。但这个过程很耗时,30次迭代可能要跑好几天。建议在项目时间充裕的情况下使用。
4.3 类别不平衡的处理方案
安防异常行为检测中,"摔倒"、"攀爬"这些异常行为的样本通常远少于"正常行走"。如果直接训练,模型会偏向于预测多数类,导致异常行为的召回率很低。
方案一:过采样。把稀有类别的图片复制多份,让各类别样本数接近。缺点是容易过拟合。
方案二:focal loss。YOLOv8默认用的是BCE loss,可以改成focal loss,让模型更关注难分类的样本。但YOLOv8没有直接暴露这个接口,需要改源码。
方案三:类别权重。在计算loss时给稀有类别更高的权重。这个最实用:
# 在data.yaml同级目录下创建weights.yaml # 或者在训练时通过回调函数修改实操心得:我试过最简单有效的方法是分层采样。在构建DataLoader时,保证每个batch里稀有类别的样本占比不低于某个阈值。这样不用改loss函数,也能缓解类别不平衡的问题。
5. 模型评估与部署落地
5.1 安防场景该看哪些评估指标
mAP(mean Average Precision)是目标检测的通用指标,但安防场景不能只看mAP。
mAP@0.5:IoU阈值为0.5时的平均精度。这个指标比较宽松,适合评估模型的整体检测能力。
mAP@0.5:0.95:IoU阈值从0.5到0.95,步长0.05,取平均。这个指标更严格,反映模型的定位精度。
召回率(Recall):安防场景最看重的指标。漏检一个异常行为,可能意味着一次安全事故。所以召回率比精确率更重要。
误报率(False Positive Rate):安防场景下误报太多,安保人员会疲于奔命,最后干脆忽略报警。所以要在保证召回率的前提下,尽量降低误报率。
我通常这样评估模型:
metrics = model.val( data="data.yaml", split="val", conf=0.25, iou=0.5 ) print(f"mAP@0.5: {metrics.box.map50}") print(f"mAP@0.5:0.95: {metrics.box.map}") print(f"各类别AP: {metrics.box.ap}")然后根据业务需求调整置信度阈值。如果业务要求高召回率,就把conf降到0.1;如果要求低误报率,就把conf提高到0.5。
5.2 模型导出与推理加速
训练好的模型需要导出成部署格式。YOLOv8支持多种导出格式:
# 导出ONNX model.export(format="onnx", opset=12, simplify=True) # 导出TensorRT model.export(format="engine", half=True, device=0) # 导出OpenVINO model.export(format="openvino", half=True)ONNX:通用性最好,几乎所有的推理框架都支持。适合跨平台部署。
TensorRT:NVIDIA GPU上的推理加速神器。FP16精度下,推理速度能提升2-3倍。但只能在NVIDIA设备上用。
OpenVINO:Intel CPU和集成显卡上的推理加速方案。适合没有独立GPU的边缘设备。
我实测过YOLOv8s在V100上的推理速度:
| 格式 | 精度 | 推理时间(640x640) |
|---|---|---|
| PyTorch | FP32 | 8ms |
| ONNX | FP32 | 6ms |
| TensorRT | FP16 | 3ms |
| TensorRT | INT8 | 1.5ms |
INT8量化需要校准数据集,精度会有一定下降,但速度提升明显。如果对精度要求不是极致,INT8是边缘部署的首选。
5.3 边缘设备部署的实操要点
安防摄像头通常搭配边缘计算盒子,比如Jetson Nano、Jetson Xavier NX、RK3588等。这些设备的算力有限,部署时要注意:
第一,模型要小。YOLOv8n是最低要求,YOLOv8s在Jetson Xavier NX上能跑到30FPS左右,YOLOv8m就只能到15FPS了。
第二,输入分辨率要降。640x640在边缘设备上可能跑不动,可以降到416x416甚至320x320。精度会下降,但速度能提升一倍。
第三,用TensorRT加速。Jetson系列对TensorRT的支持很好,导出engine文件后,推理速度能提升2-3倍。
第四,多线程处理。视频解码、预处理、推理、后处理可以放在不同的线程里,用流水线的方式并行处理,提升整体吞吐量。
import tensorrt as trt import pycuda.driver as cuda import pycuda.autoinit # TensorRT推理示例 def infer(engine_path, input_image): # 加载engine with open(engine_path, "rb") as f: engine_data = f.read() runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING)) engine = runtime.deserialize_cuda_engine(engine_data) # 创建执行上下文 context = engine.create_execution_context() # 分配显存 # ... 省略具体代码 # 执行推理 context.execute_v2(bindings) return output注意:TensorRT的版本兼容性很坑。训练时的CUDA版本、TensorRT版本、部署时的版本必须匹配,否则engine文件加载会失败。我建议在部署设备上直接导出engine,而不是在训练服务器上导出后再拷贝。
6. 常见问题与排查技巧实录
6.1 训练不收敛的排查清单
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| loss为nan | 学习率太大 | 检查lr0 | 降到0.001或更低 |
| loss不下降 | 标注格式错误 | 可视化标注 | 修正标注文件 |
| loss震荡 | batch size太小 | 检查显存占用 | 增大batch或降低lr |
| 验证集loss上升 | 过拟合 | 对比训练/验证曲线 | 增加增强、减少epoch |
| mAP为0 | 类别索引不匹配 | 检查data.yaml | 修正nc和names |
6.2 推理时检测框偏移或漏检的处理
检测框偏移:通常是预处理和后处理不一致导致的。训练时图片resize到640x640,推理时也要做同样的resize。如果推理时用了letterbox,后处理时要把框映射回原图坐标。
漏检:可能原因有几种。一是置信度阈值太高,降到0.1试试。二是NMS的IoU阈值太高,密集场景下框被抑制了,把iou降到0.5。三是模型本身能力不足,需要更多数据或更大的模型。
误检:监控画面中的树影、灯光、旗帜等容易被误检为人。解决办法是在训练数据中加入这些负样本,让模型学会区分。
6.3 模型部署后的性能优化技巧
批处理:如果边缘设备需要同时处理多路视频,可以把多帧拼成一个batch一起推理,提升GPU利用率。
异步推理:用CUDA Stream实现异步推理,让数据拷贝和计算重叠,减少等待时间。
模型剪枝:去掉模型中不重要的通道,减小模型体积和计算量。YOLOv8可以用torch-pruning库做剪枝。
知识蒸馏:用大模型(教师)指导小模型(学生)训练,让小模型获得接近大模型的精度。YOLOv8的官方仓库里有蒸馏的示例代码。
我在实际项目中的体会是,部署阶段的优化,80%的收益来自TensorRT和FP16量化,剩下20%来自模型剪枝和蒸馏。所以优先把TensorRT跑通,再考虑其他优化手段。
6.4 数据集扩展与持续迭代策略
9100张图的数据集,对于安防异常行为检测来说,规模不算大。如果实际场景中误报漏报较多,需要持续收集数据来迭代模型。
主动学习:让模型对未标注的数据做推理,挑出置信度低或不确定的样本,人工标注后加入训练集。这样能用最少的标注成本获得最大的精度提升。
难例挖掘:把误报和漏报的样本单独拿出来,分析原因,针对性地补充数据。
数据合成:用3D渲染或GAN生成合成数据,扩充稀有类别的样本。但合成数据和真实数据有domain gap,需要做domain adaptation。
实操心得:我做过一个项目,初始模型在测试集上mAP@0.5是0.85,但上线后误报率很高。后来把误报的样本收集起来重新标注,加入训练集,迭代了3轮之后,误报率下降了60%。数据迭代比模型调参更重要,这是我在多个项目中反复验证的结论。
最后再分享一个小技巧:训练YOLO的时候,把save_period设成10,每10个epoch保存一次权重。这样如果训练中途崩溃,不至于从头再来。而且可以对比不同epoch的权重在验证集上的表现,有时候最佳模型并不是最后一个epoch的。