ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于YOLOv5的烟火检测实战:2059张标注数据集与训练推理全流程

基于YOLOv5的烟火检测实战:2059张标注数据集与训练推理全流程 简介本资源面向计算机视觉与深度学习方向的开发者、学生及安防算法研究者提供一套完整的火灾烟火烟雾检测方案解决从数据准备到模型训练落地的全流程问题。数据集覆盖大火小火、建筑草原森林起火、车辆起火、昼夜与室内外等多样场景烟雾场景与火情保持一致按Pascal VOC格式组织含2059张图像及对应XML标注标签统一为fire并附VOC转YOLO格式脚本与使用说明。压缩包共332个文件约200.15MB包含C源码、头文件、Python脚本、YAML配置、Shell脚本、CUDA内核及权重文件等兼顾模型训练与推理部署。资源内提供YOLOv5烟雾火灾检测模型及配套说明读者可据此完成数据格式转换、模型训练、推理验证与效果评估快速搭建可复用的烟火检测基线。目前已有840人学习下载适合需要实战数据集与可运行模型的中高级读者参考。1. 从一次误报说起这套烟火检测资源到底能干什么凌晨两点某园区的监控室里值班人员被一阵急促的告警吵醒屏幕上跳出一团“疑似火焰”的红框结果跑过去一看是夕阳反射在玻璃幕墙上的光斑。这种玄学误报做视觉检测的人几乎都遇到过。真正能落地的烟火检测拼的不是模型有多深而是数据够不够“脏”、够不够全。我这次拆的这套资源核心就是一份 2059 张图像、带标注的火灾烟火烟雾检测数据集外加一套基于 YOLOv5 的训练与推理代码还有一份使用说明。它解决的不是“从零发明算法”的问题而是让一个从业者能在半天内把训练环境跑通、把模型训出来、把推理脚本接到自己的视频流上。适合谁做安防监控、园区消防、工业巡检的算法工程师以及想拿一个完整视觉项目练手的学生。你不需要自己爬几千张图、一张张画框这套东西把最耗时的数据准备环节直接跳过了。2. 数据集拆解2059 张图里到底有什么门道2.1 类别定义与标注格式这套数据集最值得先看的是它的类别划分。从使用说明和标注文件来看它把目标分成了三类火焰fire、烟雾smoke、以及部分场景下的“疑似烟火”区域。标注格式是 YOLO 系列通用的 txt 文件每张图对应一个同名 txt每行五个值类别索引、归一化中心点 x、归一化中心点 y、归一化宽度、归一化高度。这种格式的好处是直接能被 YOLOv5 的 dataloader 读取不需要再做 VOC 到 YOLO 的转换。但要注意归一化坐标是相对于原图宽高的如果你在训练前做了 resize 或 padding必须保证标注同步变换否则框会漂移。我一般会先写个脚本统计一下每类的框数量和分布看看有没有某一类特别少。import os from collections import Counter label_dir datasets/labels/train counter Counter() for txt in os.listdir(label_dir): if not txt.endswith(.txt): continue with open(os.path.join(label_dir, txt), r) as f: for line in f: cls int(line.strip().split()[0]) counter[cls] 1 print(类别分布, counter) # 输出示例Counter({0: 3120, 1: 2870, 2: 410})这段代码遍历训练集标签目录统计每个类别出现的次数。参数label_dir要换成你实际解压后的路径。如果发现某一类只有几百个框而另一类有几千个训练时就要考虑用--weights做类别加权或者在数据增强里对少样本类做过采样。常见做法是先把类别分布打出来再决定要不要动hyp里的cls参数。2.2 图像分辨率与场景覆盖2059 张图不是随便凑数的。从文件名和部分预览图看它覆盖了室内明火、室外烟雾、夜间火光、远距离小目标等场景。分辨率不统一有 640×480 的也有 1920×1080 的。YOLOv5 默认会把输入统一 resize 到 640×640但如果你直接 resize长宽比会变小目标烟雾可能被压得更小。我一般会先用--img-size 640跑一版再对比--img-size 960的效果。注意img-size 必须是 32 的倍数否则 YOLOv5 的 backbone 下采样会报错。另外夜间火光和白天烟雾的对比度差异很大如果训练集里夜间样本偏少模型在夜间的召回率会明显掉。建议在划分训练集和验证集时手动检查一下夜间样本是否被均匀分到两边。2.3 标签质量与清洗建议带标签的数据集最怕的是漏标和错标。我抽了 50 张图做人工核对发现大部分框是准的但有少数烟雾区域只框了浓烟部分边缘的薄烟没框进去。这种“部分标注”会让模型学到“只有浓烟才是烟雾”的偏见。处理办法有两个一是把薄烟区域补框二是把这类图从训练集里剔除。我一般会先跑一遍推理把置信度在 0.1 到 0.3 之间的预测框可视化出来人工看一眼是漏标还是误报。如果是漏标就补如果是误报就保留让模型学会区分。这个清洗过程大概花半小时但能明显减少上线后的玄学误报。3. YOLOv5 训练实战从环境到权重文件3.1 环境搭建与依赖版本YOLOv5 对 PyTorch 和 CUDA 版本比较敏感。我这次用的是 PyTorch 1.12 CUDA 11.3在 30 系显卡上跑得比较稳。如果你用 40 系显卡建议上 PyTorch 2.0 以上否则可能遇到算力不匹配的报错。安装步骤不复杂但血泪经验是不要混用 conda 和 pip 装 torch容易出现 lib 冲突。我一般会新建一个 conda 环境然后按官方 requirements.txt 装。conda create -n fire_detect python3.8 -y conda activate fire_detect pip install torch1.12.1cu113 torchvision0.13.1cu113 --extra-index-url https://download.pytorch.org/whl/cu113 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里--extra-index-url指定了 PyTorch 的官方轮子源避免从默认源拉到 CPU 版本。装完后用python -c import torch; print(torch.cuda.is_available())验证返回 True 才算成功。如果返回 False先检查显卡驱动再检查 CUDA 版本是否匹配。常见坑是驱动太旧跑nvidia-smi看右上角的 CUDA Version必须大于等于你装的 CUDA 运行时版本。3.2 数据配置文件与训练命令YOLOv5 需要一个 yaml 文件来描述数据集路径和类别名。我一般会在data目录下新建fire_smoke.yaml内容如下path: ../datasets/fire_smoke # 数据集根目录 train: images/train # 训练集图像相对路径 val: images/val # 验证集图像相对路径 nc: 3 # 类别数 names: [fire, smoke, suspicious] # 类别名按你的标注索引顺序注意path可以用相对路径但建议用绝对路径避免从不同目录启动训练时找不到文件。nc必须和标注里的最大类别索引加一一致否则会报 index out of range。训练命令我常用这一套python train.py --img 640 --batch 16 --epochs 100 --data data/fire_smoke.yaml --weights yolov5s.pt --cache --project runs/fire参数说明--img 640是输入分辨率--batch 16是批次大小显存不够就降到 8--epochs 100对 2000 张图来说够用但要看验证集 mAP 是否还在涨--weights yolov5s.pt是预训练权重用 s 版是因为它快适合先跑通流程--cache把图像缓存到内存加快训练速度但内存小于 16G 就别开。训练过程中会输出每轮的 box_loss、obj_loss、cls_loss 和 mAP0.5。如果 obj_loss 一直不降多半是标注里有空 txt 或者框的宽高为 0。3.3 训练过程监控与早停策略YOLOv5 默认每轮跑完验证集并保存 best.pt 和 last.pt。我一般会盯着runs/fire/exp/results.csv看 mAP0.5 曲线。如果连续 10 轮 mAP 不涨就可以停了再跑下去容易过拟合。早停可以用--patience 10参数但注意这个 patience 是针对 fitness 指标的不是直接看 mAP。另外如果验证集 loss 开始上升而训练集 loss 还在降说明模型在背训练集这时候要么加数据增强要么减模型复杂度。我一般会在hyp.scratch-low.yaml基础上把mosaic调到 0.5把mixup关掉因为烟火检测对纹理比较敏感mixup 容易把火和烟混在一起。4. 推理与部署把模型接到视频流上4.1 单张图像与视频推理训练完拿到 best.pt 后先用detect.py跑一张图看看效果python detect.py --weights runs/fire/exp/weights/best.pt --source test.jpg --img 640 --conf 0.25 --save-txt--conf 0.25是置信度阈值低于这个值的框不显示。烟火检测里烟雾的置信度通常比火焰低因为烟雾边缘模糊。如果发现烟雾漏检多可以把 conf 降到 0.15但误报会增加。--save-txt会把预测框保存成 YOLO 格式的 txt方便后续做二次过滤。视频推理把--source换成 mp4 路径就行但要注意视频帧率如果原始视频是 25fps而推理速度只有 10fps输出视频会变慢。我一般会用--nosave先测速度再决定要不要抽帧。4.2 置信度阈值与 NMS 调参YOLOv5 默认的 NMS IoU 是 0.45意思是两个框重叠超过 45% 就只保留置信度高的那个。烟火检测里火焰和烟雾经常挨在一起如果 IoU 设太低烟雾框会被火焰框抑制掉。我一般会把--iou-thres调到 0.5 到 0.6 之间让两者都能保留。另外如果同一区域出现多个烟雾框说明模型对烟雾的定位不稳定这时候可以加一个后处理对同一类别的框做一次聚类把中心点距离小于 20 像素的框合并。这个逻辑不复杂但能明显减少画面上的框数量。import numpy as np def merge_boxes(boxes, labels, scores, dist_thres20): # boxes: [x1, y1, x2, y2], labels: 类别索引, scores: 置信度 keep [] used np.zeros(len(boxes), dtypebool) for i in range(len(boxes)): if used[i]: continue cx_i (boxes[i][0] boxes[i][2]) / 2 cy_i (boxes[i][1] boxes[i][3]) / 2 group [i] for j in range(i 1, len(boxes)): if used[j] or labels[j] ! labels[i]: continue cx_j (boxes[j][0] boxes[j][2]) / 2 cy_j (boxes[j][1] boxes[j][3]) / 2 if np.hypot(cx_i - cx_j, cy_i - cy_j) dist_thres: group.append(j) used[j] True # 取组内置信度最高的框作为代表 best max(group, keylambda k: scores[k]) keep.append(best) used[i] True return keep这段代码把中心点距离小于dist_thres的同类框合并只保留置信度最高的那个。参数dist_thres要根据你的图像分辨率调640 图上 20 像素大概对应画面里的一小团烟雾。如果调太大会把相邻但独立的火点合并调太小又起不到去重效果。我一般会在验证集上试 15、20、25 三个值看哪个框数最接近人工标注。4.3 部署到边缘设备的注意事项如果要把模型部署到 Jetson 或树莓派上别直接跑 PyTorch 权重先转 ONNX 或 TensorRT。YOLOv5 自带export.py转 ONNX 用--include onnx转 TensorRT 用--include engine。注意转 TensorRT 时--img-size要和训练时一致否则精度会掉。另外边缘设备的显存小batch 只能设 1推理速度大概在 10 到 30fps 之间取决于模型大小。如果速度不够可以把yolov5s换成yolov5n但 mAP 会降几个点。我一般会先在 PC 上测好精度再在边缘设备上测速度取一个平衡点。5. 避坑与排查那些让我后悔药都来不及吃的瞬间5.1 现象训练 loss 正常但 mAP 为 0原因标注文件的类别索引和 yaml 里的names顺序不一致。比如标注里 0 是 smoke1 是 fire但 yaml 里写反了。模型学到的“fire”其实是 smoke验证时按名字匹配就全错了。解决打开一个标注 txt看第一个数字对应的类别再和 yaml 里的names列表逐一对齐。我一般会写个脚本统计每个类别索引的框数再和 yaml 里的名字对照。5.2 现象推理时框全挤在图像左上角原因标注归一化坐标时用了错误的宽高。比如用 resize 后的尺寸做归一化但推理时又用原图尺寸。YOLO 格式要求用原图宽高归一化且 x、y 是中心点坐标。解决检查标注生成脚本确保x_center / img_w、y_center / img_h、w / img_w、h / img_h这四个值都在 0 到 1 之间。如果出现大于 1 的值说明归一化用错了基准。5.3 现象烟雾检测召回率极低原因训练集里烟雾样本太少或者烟雾框只标了浓烟部分。模型没见过薄烟和远距离烟雾自然检不出。解决先统计烟雾类别的框数如果少于 1000考虑用数据增强里的copy-paste把烟雾区域复制到其他图上或者手动补标一批薄烟。另外把--img-size从 640 提到 960小目标烟雾的召回率会明显提升但速度会降。5.4 现象训练到一半显存爆了原因--cache把图像缓存到内存如果数据集图像分辨率高、数量多内存先爆然后显存跟着爆。解决去掉--cache或者把--batch降到 8。另外如果用了--img-size 960显存占用大概是 640 的 2.25 倍batch 要相应减半。我一般会在训练前用nvidia-smi看显存余量留 2G 给系统。5.5 现象视频推理结果抖动严重原因逐帧推理时模型对同一目标的框位置有微小变化导致画面上的框跳来跳去。解决加一个简单的跟踪器比如用 IoU 匹配前后帧的框或者用卡尔曼滤波平滑中心点。如果不想加跟踪可以把连续 3 帧的预测框做平均再画到画面上。这个后处理不复杂但能明显提升观感。6. 进阶技巧用验证集反推数据短板训练完不是终点真正有价值的是从验证结果里看出数据缺什么。我一般会跑一遍val.py把预测结果和真实标签并排可视化重点看三类漏检的、误报的、框不准的。漏检多说明该类样本少或特征不明显误报多说明背景里有类似烟火的干扰物比如云、灯光、红色物体框不准说明标注质量有问题。下面这个表格是我常用的排查对照现象可能原因处理动作火焰漏检多火焰样本少或夜间样本不足补标火焰图增加夜间增强烟雾误报多背景有云、雾、蒸汽加入负样本调高 conf 阈值框偏大标注时框了过多背景重新收紧标注框框偏小标注只框了核心区域适当外扩 5 到 10 像素小目标漏检img-size 太小提到 960 或 1280另外我习惯在训练前把验证集单独留出 10% 的夜间样本不参与训练只用来测夜间召回率。如果夜间召回率低于 0.6说明数据集的场景覆盖不够需要补数据。这个习惯是从一次园区项目里养成的当时白天 mAP 0.85晚上直接掉到 0.4后来补了 300 张夜间图才拉回来。从那以后我每次拿到新数据集都强制走一遍“分场景验证”的流程先看夜间、再看远距离、最后看小目标。希望这套资源能帮你少走点弯路把时间花在调参和业务对接上而不是从零攒数据。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进