ARTICLE DETAIL

资讯详情

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

火灾烟雾检测数据集详解:VOC/YOLO双格式标注与YOLO训练实战

火灾烟雾检测数据集详解:VOC/YOLO双格式标注与YOLO训练实战 简介一套面向计算机视觉目标检测任务的火灾烟雾图像标注数据集主要服务于算法工程师、科研人员及竞赛团队用于训练和评估火灾烟雾识别模型。资源包含两千二百五十七张经专业人员精细标注的高质量图像每张均绘制准确的边界框并标注类别同时采用VOC与YOLO两种主流标注格式可无缝对接常见目标检测框架省去自行转换标注格式的麻烦。压缩包整体约二百六十六兆字节便于下载与部署。目前已有两千二百八十一人学习使用在智能安防、智慧家居、应急响应及无人机巡检等场景中具有切实的落地价值。借助该数据集开发者可快速完成数据增强、超参数调优与不同检测器效果对比降低自采数据和人工标注成本为构建高精度、高鲁棒性的火灾烟雾检测方案提供扎实数据支撑。1. 火灾烟雾图像标注数据集2257 张双格式标注图拿来就能训 YOLO 系检测器做安防监控和消防预警的人应该都遇到过同一个尴尬模型结构可以抄训练代码可以借唯独标注好的火灾烟雾图像千金难求。自己标 2257 张图一个人运气好也要两周标完还得核对坐标有没有歪。我拆完这份数据集之后可以明确告诉你它最值钱的地方不是那 2257 张图而是每张图同时给了 PASCAL VOC 和 YOLO 两套标注格式意味着你下载之后不用再写一次格式转换脚本U 盘拷下来改个data.yaml就能喂给 YOLOv5/YOLOv8 开训。这篇笔记我会把数据集内部结构、两种格式的坐标换算逻辑、训练前必须做的检查项和我在实际训练里踩过的坑逐一摊开照着走基本不会翻车。2. 数据集的真实结构VOC 和 YOLO 两套标注各自怎么组织拿到任何标注数据集第一步不是看图片而是先把目录树摸清楚。这个数据集既然提供了两种格式目录组织上基本沿用两个生态的习惯VOC 派系用 XML 存标注信息YOLO 派系用 txt 存归一化坐标两者读取方式完全不同混着用必出问题。2.1 目录结构与文件清单先把家底盘清楚我按照最常见的发布形式帮你把目录预期列出来你解压之后对着看fire_smoke_dataset/ ├── VOC/ │ ├── JPEGImages/ # 原始 JPG 图像2257 张 │ ├── Annotations/ # 与图像同名的 .xml 标注文件 │ └── ImageSets/ │ └── Main/ # train.txt / val.txt / trainval.txt ├── YOLO/ │ ├── images/ # 图像可能直接复制或软链 │ └── labels/ # 同名 .txt 标注文件 ├── classes.txt # 类别清单通常两行fire, smoke └── README.md # 类别顺序和来源说明如果你拿到手的压缩包和这个结构有出入比如Annotations和labels被合并到一个目录下不用慌关键只认三样东西图像文件名、标注文件是否同名、类别顺序表。classes.txt里第一行对应标注 txt 里的 class id 0第二行对应 class id 1这个顺序在 YOLO 格式里是唯一权威比 README 里写的都管用。这个数据集的标注对象就两类fire火灾/火焰和 smoke烟雾总共 2257 张图里包含单目标和多目标混合场景。所谓“高质量标注”指的是每张图的 bounding box 都尽量贴住目标边缘没有那种只框一半火焰的粗放操作。2.2 VOC 格式的 XML 标注绝对像素坐标人眼可读VOC 格式的核心是 XML 文件每张图对应一个.xml文件名和图片名完全一致。打开任意一个文件结构长这样annotation folderJPEGImages/folder filenamefire_001.jpg/filename size width1280/width height720/height depth3/depth /size object namefire/name bndbox xmin240/xmin ymin156/ymin xmax893/xmax ymax674/ymax /bndbox /object object namesmoke/name bndbox xmin410/xmin ymin98/ymin xmax1176/xmax ymax520/ymax /bndbox /object /annotation这里每个object块就是一个目标实例name是类别名bndbox里存的是绝对像素坐标。解读规则很简单xmin/ymin是包围框左上角xmax/ymax是右下角单位是像素不用做任何归一化。图像尺寸在size里后面验证标注是否出界时全靠它。VOC 格式最大的优点是可读性强拿记事本打开就能人工检查缺点是每个目标标签字节开销大。所以它适合做数据验收和二次修改但不适合直接喂给 YOLO 系模型——你训练时还得写个解析器把 XML 读出来再转成张量纯属多此一举。2.3 YOLO 格式的 txt 标注归一化坐标一行一个目标YOLO 格式的做法是完全反过来每张图对应一个.txt文件名和图片名一致目标多的文件行数就多。每行五个数字空格分隔含义固定class_id x_center y_center width height注意这个坐标不是像素值而是相对图像宽度和高度的比例取值区间在 0 到 1 之间。比如一张 1280×720 的图里一个框的左上角是 (240, 156)、右下角是 (893, 674)换算步骤如下# VOC 像素坐标 - YOLO 归一化坐标 import os img_w 1280 img_h 720 xmin, ymin, xmax, ymax 240, 156, 893, 674 x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h print(f0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 输出示例0 0.442578 0.576389 0.510156 0.719444这段代码里我先算边框的几何中心再把中心坐标和宽高都除以图像边长得到的就是 YOLO 需要的归一化值。这里有三个最容易写错的地方一是width和height必须和图像尺寸的宽对宽、高对高不能交叉二是类别 id 从 0 开始不是从 1 开始三是保留小数位数至少 6 位精度太低会导致小目标框偏移几个像素训练时 mAP 会掉得莫名其妙。VOC 格式转 YOLO 格式的脚本网上有很多版本参数细节略有差异核心逻辑就是我上面这段。如果你手里只有 VOC 格式的数据想转 YOLO拿这段当成函数体套进循环里遍历整个Annotations目录即可。这个数据集帮你把两套都准备好了省掉的就是这一步。3. 训练前的数据准备类别统计、格式校验和标注可视化很多人在这一步栽跟头拿到数据直接跑训练跑到第三个 epoch loss 不降回头才发现训练集里混了几张没有标注 txt 的图或者是坐标全为 0 的“假标注”。数据检查不是走形式它决定了你是花 3 天训完一个能用的模型还是花 3 周在玄学报错里挣扎。3.1 类别比例统计先摸清火灾和烟雾的样本分布目标检测模型对类别不平衡极其敏感。2257 张图听起来不少但如果其中 90% 的标注框都属于烟雾那模型大概率会把所有模糊目标都判成 smoke。我一般拿到数据集做的第一件事就是写个小脚本统计每个类别的边框数量# 统计 YOLO 格式标注中每个类别的目标数量 import os label_dir YOLO/labels class_counts {fire: 0, smoke: 0} for filename in os.listdir(label_dir): if not filename.endswith(.txt): continue with open(os.path.join(label_dir, filename), r) as f: for line in f: class_id int(line.split()[0]) # 每行第一个数字是类别 id if class_id 0: class_counts[fire] 1 elif class_id 1: class_counts[smoke] 1 print(class_counts)这段代码只是最基础的统计但它能直接暴露两个问题如果某个类别的框数量不到另一个类别的十分之一基本可以判定类别不平衡后续要在损失函数或采样策略上做补偿如果某个类别框数量为 0说明类别 id 对应关系搞反了classes.txt的顺序和标注文件对不上。这种情况我见过不止一次根因往往是标注工具导出时选了不同的类别顺序。统计输出的具体数字不用太纠结重点看数量级。两类的目标数量在同一个数量级内比如几百比几百、一千比一千直接训练没有问题。如果烟雾框是火的 10 倍那就得用第 6 章说的加权方案不能硬训。3.2 用 OpenCV 把标注画回图像仅凭肉眼锁定错误统计只能发现数字异常无法发现“框偏了”“框漏了”“类别标错了”这类语义错误。我的习惯是随机抽取 100 张图把标注框直接画回原图肉眼扫一遍。# 可视化 YOLO 格式标注用于人工检查 import cv2 img_path YOLO/images/fire_001.jpg label_path YOLO/labels/fire_001.txt img cv2.imread(img_path) h, w img.shape[:2] colors {0: (0, 0, 255), 1: (128, 128, 128)} # 0fire 红色, 1smoke 灰色 with open(label_path, r) as f: for line in f: parts line.split() cls_id, xc, yc, bw, bh parts[0], float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 还原为像素坐标 x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), colors.get(cls_id, (255, 255, 255)), 2) cv2.putText(img, cls_id, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, colors.get(cls_id, (255, 255, 255)), 2) cv2.imwrite(check_fire_001.jpg, img)这段代码把归一化坐标还原成像素坐标再画框。xc - bw / 2是框的左边界xc bw / 2是右边界乘上w和h还原实际像素位置。前面提过归一化坐标范围是 0 到 1但偶尔会出现 0.98 这种接近边界的值还原成像素后框的一部分落在图像外这倒不一定是标注错误可能是烟雾本身从图像边缘冒出来目标主体仍然完整训练时可以保留。人工检查这一步能发现的最典型问题是“标注只有半截”火焰或烟雾目标延伸到图像边缘但标注框在边缘处被截断导致模型学到的目标形态不完整。遇到这种情况我的建议是保留它但不要让这种样本超过总量 5%否则模型会把“半截烟雾”当成有效特征。3.3 训练集划分不要直接拿全部数据开训这个数据集本身大概率已经带了划分好的train.txt和val.txt也就是 VOC 目录下ImageSets/Main里的文件。但如果你要用 YOLO 格式训练需要把划分同步到YOLO目录下。常见做法是做一个 8:2 的数据划分并且保证同一个场景的多角度图像尽量只在训练集或只在验证集里出现。真实的火灾烟雾图像往往来自连续视频帧同一个火源会在相邻帧里反复出现。如果这些高度相似的帧同时进了训练集和验证集验证指标会虚高换到真实场景立刻打回原形。从项目正文看这里收的是 2257 张静态图像不一定有连续帧问题但我在划分时依然会先按文件名排序再每隔几帧抽一张进验证集从根上避免数据泄漏。4. 避坑排查这个数据集训练时的五个经典翻车现场下面五条是我在火灾烟雾检测项目里亲眼见过或者亲手踩过的坑每一条都按“现象 → 原因 → 解决”给你写清楚。这章不能跳你后面遇到诡异报错时回来对照大概率能找到病根。4.1 训练跑完 mAP 为 0但 loss 正常下降现象训练日志里各类 loss 都在降val 阶段也能算出 loss但最终 mAP50 和 mAP50-95 全是 0预测出来的框和标注完全没有重叠。原因标注类别 id 和数据配置文件里的类别清单对不上。比如classes.txt里 fire 在第 0 行、smoke 在第 1 行但某个标注 txt 文件里把 smoke 写成了 0模型把烟雾学成了火或者是验证集的类别 id 和训练集不一致。解决写一遍第 3.1 节的统计脚本分别跑训练集和验证集确认两个集合里每个类别 id 的分布都在预期范围内。再随机抽取验证集一批预测结果做可视化确认框不是贴死在图像角落。4.2 训练时报错 “All labels are empty”现象启动 YOLOv8 训练时刚加载完数据就报All labels are empty in dataset或者类似提示。原因标签文件中的坐标全是 0或者标签文件是空文件。这通常发生在 VOC 转 YOLO 时某些图片的标注框坐标读取失败写成了0 0 0 0 0。解决写一个过滤脚本遍历所有 txt凡是整行五个数字全是 0 的就把对应图片连标签一起移出数据集。我在处理多来源合并数据时习惯加一步“坐标有效性校验”任何一行 width 或 height 等于 0 的直接删除。注意删除后要么重新划分数据集要么让训练脚本自行过滤空标签。4.3 训练集图像尺寸参差不齐导致小目标丢失现象同一批训练里有的图是 1920×1080有的是 640×480模型收敛后对远距离小火焰完全无感但对大目标识别很好。原因YOLO 训练时会对输入做 letterbox 缩放宽高比差异大的图像会被填充大量灰边小目标在缩放后面积不足几个像素特征直接消失。解决训练参数里固定imgsz640或imgsz1280不要启用多尺度训练至少第一轮先固定输入尺寸跑通基线。如果数据集中 1080p 图像占比高用imgsz1280效果更稳代价是显存占用翻倍。4.4 验证集里混进标注框缺失的图片现象验证阶段 loss 正常但 Precision 和 Recall 数值怪异比如 Recall 突然掉到 0.2怎么调参都拉不回来。原因验证集里若干张图片明明有目标但对应的 txt 里没有任何标注行。模型在这几张图上预测出目标后被当作误检惩罚Recall 被严重拉低。解决把标注文件里有内容、且和图片文件一一对应的样本单独筛选出来组成一个全新的验证集。做法是先遍历所有图片名检查同名 txt 是否存在且非空再做交集过滤。4.5 把 VOC 的 XML 和 YOLO 的 txt 混在同一份数据配置里现象训练脚本能跑但中途频繁警告“found no labels in xxx.jpg”有的报错指向 XML。原因有人在整理数据时把 VOC 目录里的Annotations也复制到了YOLO/labels子目录下导致同一个图片对应了多个标注文件训练器无法确定用哪个。解决YOLO 格式训练时labels目录下只允许放.txt把 XML 文件全部移走。更稳妥的做法是单独建一个干净的dataset_clean/yolo目录重新拷贝一份图片和标签进去不用原始目录直接训练。5. YOLOv8 训练自己的火灾烟雾模型数据配置与参数调试数据验证通过后训练本身反而是最简单的一步。我拿目前社区用得最广的 ultralytics YOLOv8 做例子这套配置和参数同样适用于 YOLOv5 和 YOLOv11。模型结构不用动关键是data.yaml和三个参数imgsz、epochs、batch。5.1 数据配置文件的写法YAML 里每个字段都有用新建一个fire_smoke.yaml内容如下path: /absolute/path/to/fire_smoke_dataset # 数据集根目录 train: YOLO/images # 训练集图片目录 val: YOLO/images # 验证集图片目录先用同一个后面可换 names: 0: fire 1: smoke三个最关键的字段分别是train、val和names。这里我故意没有写nc字段因为 ultralytics 会从names的长度推断类别数写了反而容易和names不一致导致报错。train和val指向图像目录框架会自动在同级目录下找labels子目录所以你的标签必须放在YOLO/labels下不能自定义改名。第一次跑通验证时val可以直接指到train同目录跑个 5 个 epoch 确认流程没问题后再换成正式的验证集。这个策略能帮你快速排除是代码问题还是数据问题。5.2 训练命令与参数选择不是 epoch 越大越好yolo detect train \ datafire_smoke.yaml \ modelyolov8s.pt \ imgsz640 \ epochs150 \ batch16 \ patience20 \ projectruns \ namefire_smoke_exp我来逐项说明我为什么这么设。modelyolov8s.pt是 s 规模预训练权重1200 万参数级别火灾烟雾类别本身不算难学s 完全够用直接上 l 或 x 只会白白增加训练时间对精度提升有限。imgsz640是速度和精度的折中点前面踩坑记录说过如果多数图像是 1080p 及以上可以加到1280但要确保显存够。epochs150配合patience20是防止过拟合的组合拳模型连续 20 个 epoch 验证集指标没提升就自动停止你不用死盯着训练过程。batch16是个安全值24GB 显存跑 s 模型没问题显存小调到 8。从头训练不用预训练权重不是首选。火灾烟雾的图像特征和 COCO 数据集里的常见物体差别不大预训练权重里的低级特征边缘、纹理、颜色完全可以直接迁移。直接加载yolov8s.pt能比随机初始化少训练 30% 以上的时间最终收敛精度也更高。5.3 训练过程看什么指标不只看 loss训练日志里最值得盯的是验证集的mAP50和mAP50-95不是训练 loss。训练 loss 下降只说明模型在拟合训练集mAP50才反映模型在没见过的图像上的真实表现。火灾和烟雾这两个类别对 mAP 指标的敏感度不一样火焰边界分明、颜色强烈模型学得快mAP50 通常前 30 个 epoch 就冲上 0.8烟雾是半透明、边缘模糊的目标mAP50-95 会被它拖累。如果你发现最终 mAP50 能到 0.85 但 mAP50-95 只有 0.5别焦虑这是烟雾类别的正常水平。训练结束后用下面命令输出最终指标yolo detect val \ modelruns/fire_smoke_exp/weights/best.pt \ datafire_smoke.yaml \ imgsz640重点看两个值验证集mAP50和每个类别的AP。如果smoke的 AP 明显低于fire直接跳转下一章的不平衡处理方案。指标跑平后best.pt和last.pt都在runs/fire_smoke_exp/weights/下推理时用best.pt即可last.pt是最后一个 epoch 的快照断点续训时用得上。6. 进阶技巧类别不平衡时的加权策略与推理参数微调如果你的统计脚本跑出来 smoke 的框数量是 fire 的 5 倍以上直接训练出来的模型会偏向烟雾把远处的云、水汽、甚至浅色建筑误检成 smoke。解决思路有两个层次一是从损失函数入手让模型更重视少数类别二是在推理阶段调整阈值用后处理弥补模型偏好。6.1 用类别权重平衡损失贡献目标检测的损失由分类损失和定位损失组成类别不平衡主要冲击分类损失。ultralytics 提供的做法是在data.yaml里通过cls参数调分类损失的权重系数。假如 fire 有 800 个框、smoke 有 4000 个框权重比值就按数量反比近似取 5:1path: /absolute/path/to/fire_smoke_dataset train: YOLO/images val: YOLO/images # 建议换成独立验证集 names: 0: fire 1: smoke训练时追加yolo detect train \ datafire_smoke.yaml \ modelyolov8s.pt \ imgsz640 \ epochs150 \ batch16 \ cls5.0 \ patience20cls5.0表示把分类损失的全局系数放大到默认值默认 0.5的 10 倍。这个值不是越大越好模型会因此更努力地识别少数类别的 fire但也可能把一些背景误判为火。经验做法是从cls2.0起调跑完一轮看验证集里fire类别的 Recall 是否明显上升没有变化就加到 5.0。这个参数名在 YOLOv5 里换成了cls_pw作用类似但要注意你用的具体框架版本。6.2 推理阶段用置信度阈值和 NMS 微调模型训练好后推理时的默认置信度阈值是 0.25NMS 的 IoU 阈值是 0.45。这两个值对火焰和烟雾应该区别对待。如果你部署在消防预警系统里目标是“宁可误报不能漏报”把置信度阈值降到 0.1召回率会明显提升代价是每帧出现几个误检框。如果你的场景是无人机巡检希望报警足够精准阈值调到 0.5 更合适。# 推理参数调整示例 from ultralytics import YOLO model YOLO(runs/fire_smoke_exp/weights/best.pt) results model.predict( sourcecamera_frame.jpg, conf0.15, # 降低置信度阈值提高召回 iou0.35, # 降低 NMS IoU 阈值减少重叠框 imgsz640, saveTrue )我在部署时习惯跑两组阈值对比验证集指标一组conf0.25, iou0.45作为标准配置一组conf0.1, iou0.3作为高召回配置。高召回配置下如果 mAP50 仍然能保持 0.6 以上这个模型就敢直接接进告警链路。6.3 对烟雾类别的针对性增强烟雾和火焰视觉特征差异很大火焰有明确颜色和边缘烟雾则是半透明、形状不断变化。训练阶段给烟雾这类低纹理目标单独做亮度扰动和模糊增强效果很有限真正有效的是把烟雾图像做随机裁剪而不是整体缩放——裁剪后烟雾在图像中占比变大模型更容易学到纹理特征。实际操作时我一般单独写一个离线增强脚本把训练集中的烟雾目标区域随机放大 1.2 到 2.0 倍再放回原图跑 200 轮增强后用增强前后两组数据对比测试集 mAP。如果 mAP 没有明显提升就说明基础模型已经够用不要再叠加无谓的增强。从那以后我每次启动一个新数据集训练都会强制要求自己把类别统计、空标注过滤、边界框还原这三步可视化流程走完才允许开训练。数据检查本身不产生任何模型精度提升但它能拦住 80% 的诡异报错省下的全是复现时最珍贵的时间。希望这篇笔记对你后续的火灾烟雾检测项目有实际帮助。本文还有配套的精品资源点击获取
返回列表