多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

红外火灾检测数据集实战:从解压到YOLO训练的完整指南

红外火灾检测数据集实战:从解压到YOLO训练的完整指南 简介面向热成像目标检测与火灾预警研究的红外火灾检测数据集适配 YOLO 等主流检测框架可用于森林防火、工业安全监控等场景的模型训练与验证。压缩包共 2000 个文件包含 1608 个 YOLO 格式 txt 标签、390 张 jpg 红外图像、1 个 yaml 配置和 1 份 docx 说明文档整体约 49.06MB数据划分为训练集 1467 张、验证集 93 张、测试集 48 张。标注严格区分 Fire火与 Foc火源两类聚焦热源区域与潜在起火点可支撑高精度火情识别模型开发并有助于火灾蔓延分析、应急决策支持等衍生任务。目前已有 263 人学习下载适合算法工程师、安防与消防领域研究人员作为行业数据集参考使用。1. 红外火灾检测数据集拿到 zip 之后真正决定效果的是解压前的判断做消防报警、仓储监火这类项目最怕的不是模型选型而是数据到手那一刻就埋雷。之前有个夜间火焰报警需求我从公开渠道找到一个红外火灾检测数据集下载下来是压缩包文件名写着“红外火灾检测数据集.zip”。解压后看着图也清楚、标注也齐结果训出来的模型在夜间误报率高得离谱后来查下来问题出在预处理和标签坐标上。红外火灾检测数据集和日常用的可见光数据集差别很大图像可能是 16 位灰度、可能是伪彩色类别定义从“fire/smoke”到“高温热源”各不相同标注坐标系也可能混着 Pascal VOC 和 YOLO 两套。这篇笔记按拿到 zip 之后的实际操作顺序来写先看结构再做预处理再跑训练最后讲坑给正在做红外火焰检测和火灾智能预警的工程师一个能直接照做的流程。2. 红外火灾检测数据集 zip 的内部结构解压前先列清单一个 zip 包在下载完成之后先别急着双击解压。红外火灾检测数据集的 zip 通常来自设备厂商、竞赛发布渠道或研究机构打包习惯不一样有的把训练集和验证集拆成两个 zip有的把所有图片放在一层、标注放在另一层还有的在根目录塞一个 readme 让你先看。直接解压到桌面开训后面脚本路径全是手写魔改早晚踩坑。我一般先做一件事用 unzip -l 看压缩包内部清单。2.1 三类常见组织方式与红外数据的特别之处可见光火灾检测数据集的组织方式已经被 COCO、Pascal VOC 这类基准给固定了换到红外火灾检测数据集结构逻辑没变但多了两个变量图像位深和标签粒度。常见的打包方式有三种。第一种是“纯图片 同名标注”的扁平结构像 mmrotate 训练 DOTA 数据集那样每张图一个标注文件适合刚接触目标检测的人第二种是“images / annotations / splits”三层结构和 COCO2017 数据集结构类似splits 里是 train.txt、val.txt 的文件路径清单训练脚本直接吃清单而不是遍历目录第三种是在前两种基础上加一个 meta.csv记录每张图像的拍摄条件、热像仪型号和温度范围。三种结构我都处理过第三种最省事因为红外图像的温度语义必须靠元数据解读没有 meta 的 zip 包后期做温度阈值后处理会非常吃力。三种组织方式各有取舍并不是越复杂越好。扁平结构解压即用但目录一深文件一多路径拼接就容易出问题三层结构规范却要求标注文件和图片文件名严格一致否则会静默丢标注带 meta.csv 的结构信息最全可一旦作者没更新字段csv 里的文件名和实际图片对不上反而不如直接扫描目录可靠。拿到 zip 后先看属于哪一种再决定要不要写额外的目录适配脚本。红外数据集还有一个看得见但容易忽略的问题有些作者会同时放 raw 灰度图和伪彩色增强图这类重复样本对训练没有任何帮助只会让验证集成绩虚高后面 3.1 会细说。2.2 一个能直接训练的红外火灾数据目录长什么样我习惯先跑一条命令确认内部结构unzip -l 红外火灾检测数据集.zip | head -60-l 参数只列出压缩包内容不实际解压head -60 限制只显示前 60 行用来快速判断目录深浅和文件命名是否统一。如果前几行出现 __MACOSX 或 .DS_Store说明打包的人用的是 macOS解压后这些垃圾文件要清理。确认结构没问题再真正解压unzip 红外火灾检测数据集.zip -d ./fire_data-d 指定解压到 fire_data 目录不要把内容散在当前目录。解压完成后一个能直接用来训练的红外火灾检测数据集目录至少应该长这样fire_data/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ ├── splits/ │ ├── train.txt │ └── val.txt └── meta.csvimages 和 labels 下的文件名必须一一对应比如 frame_0012.png 对应 labels/train/frame_0012.txt。文件名对不上是 zip 数据集最常见的硬伤训练时丢标注几乎没有报错只在评估时发现 AP 极低。splits 里的每一行是相对路径或绝对路径需要先读三行看看分隔符是空格还是逗号训练脚本解析错了会直接读不到文件。meta.csv 不是必须的但如果有先不要删后面分析误报时非常依赖它。2.3 元数据与标签体系标注文件里藏着训练成败标注文件是理解一个红外火灾检测数据集的钥匙。常见标签粒度有 fire、smoke有的数据集会额外标 heat_source高温热源和 person作为干扰项出现。类别定义越细模型学到的边界越清楚但误标也越严重尤其 smoke 在夜间红外图像里和雾气几乎一样。标签名常见含义典型问题fire火焰区域有的标成整片火焰有的只标火焰根部框尺度差异大smoke烟雾区域与雾气、蒸汽难区分夜间尤其明显heat_source高温热源误标率偏高训练时容易把注意力从 fire 上拉走person人体热源属于负样本看数据集的用途决定是否忽略读取标注时我一般直接写个小脚本先打印前 5 个 XMLimport xml.etree.ElementTree as ET import glob for xml_path in glob.glob(fire_data/annotations/train/*.xml)[:5]: root ET.parse(xml_path).getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) for obj in root.iter(object): box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) print(xml_path, obj.find(name).text, xmin, ymin, xmax, ymax, img_size:, w, h)这个脚本的意义在于确认两件事一是 bbox 坐标是不是在原图尺寸下如果 XML 里的 w/h 和你用 cv2 读取到的实际图宽高不一致说明数据被缩放过而标注没同步更新二是类别名和你预想的差多少。有些红外数据集把火焰标成 fire_1、fire_2 这种带后缀的类名转换到 YOLO 时需要做一次归一映射否则类别数会莫名其妙翻倍。这套检查花不了十分钟能省下后面几天调 Loss 的时间。3. 把红外火灾检测数据集喂进 YOLO预处理、格式转换与最小训练红外火灾检测数据集解压之后不是改个 data.yaml 就能训。红外图和常见可见光图有三个差异可能只有单通道灰度可能是 16bit 位深可能带伪彩色映射。这三个差异决定了预处理不是可选项。这一章给一条从拿到数据到跑出第一个模型的最小路径核心思路是先把图像和标注统一成框架认识的语言再谈训练参数。3.1 统一通道与位深16bit 灰度图像的预处理脚本很多人习惯直接用 cv2.imread 读图丢进训练脚本这在红外火灾检测数据集上会翻车。默认 imread 会把 16bit 灰度图截断成 8bit温度细节直接丢光火焰边缘和背景的温差变小模型学到的边界自然模糊。我一般会写一个统一的预处理函数# preprocessing.py import cv2 import numpy as np def load_infrared_image(path, target_size(640, 640)): # IMREAD_UNCHANGED 保留原始位深防止 16bit 被截断 img cv2.imread(path, cv2.IMREAD_UNCHANGED) if img is None: raise ValueError(f无法读取: {path}) # 单通道灰度扩展为三通道统一后续模型输入 if len(img.shape) 2: img cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) # 16bit 数据做线性映射到 0-255 if img.dtype np.uint16: lo, hi int(img.min()), int(img.max()) img ((img.astype(np.float32) - lo) / (hi - lo 1e-6) * 255).astype(np.uint8) # 双线性插值缩放标注要按同比例换算 img cv2.resize(img, target_size, interpolationcv2.INTER_LINEAR) return img第一行 IMREAD_UNCHANGED 是关键没有它 16bit 数据在读取阶段就被截断后面再归一化也救不回来。线性映射先把整张图的最小值和最大值拿出来做拉伸让红外温差的细节落到 8bit 可见范围内加 1e-6 是为了防止整张图是纯色时除以零。resize 用 INTER_LINEAR对红外这种纹理较少的图像比 INTER_CUBIC 更稳也不会引入过多高频噪声。注意很多数据集里同一场景同时给了原图和伪彩色图文件名类似 frame_1_raw.png 和 frame_1_pseudo.png。训练时二选一即可别把两张都当独立样本塞进去否则同一目标的重复样本会让验证集成绩虚高换到真实场景立刻现原形。3.2 VOC 标注转 YOLO 格式坐标归一化要放在 resize 之前红外火灾检测数据集里 VOC 格式XML很常见但 YOLO 训练要的是 txt 格式且坐标必须是相对于原图宽高的归一化值。转换脚本本身不难难在别把顺序搞反先归一化再做任何图像缩放或者相反都会导致框错位。这里给一个最小转换脚本# voc2yolo.py import xml.etree.ElementTree as ET def voc2yolo(xml_path, txt_path, class_names): root ET.parse(xml_path).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in class_names: continue bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坐标归一化必须在 resize 之前基于原图宽高 x_center (xmin xmax) / 2 / w y_center (ymin ymax) / 2 / h bw (xmax - xmin) / w bh (ymax - ymin) / h cls_id class_names.index(cls) lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines))这段代码把 xmin、xmax、ymin、ymax 先在原始宽高下转成中心点和宽高再除以 w、h 得到 0~1 之间的归一化值。class_names 是预先定义好的类别列表比如[fire, smoke, heat_source]遇到不在列表里的类别直接跳过避免把背景噪声也当成目标。转换完成后我强烈建议抽查几个 txt 文件确认坐标值都在 0 到 1 之间且宽高不为负数。遇到过数据集里 xmax 比 xmin 还小的脏标签这种文件不处理直接训练Loss 会出现莫名其妙的尖峰。3.3 用 YOLOv8 训练自己的红外火灾数据参数与经验值数据准备好之后先建一个数据描述文件告诉训练框架去哪里读图和标注# fire.yaml path: ./fire_data train: splits/train.txt val: splits/val.txt names: 0: fire 1: smoke 2: heat_source然后跑最小训练命令。我一般先用 s 模型而不是直接上大模型红外火灾数据的公开规模通常在几千到一两万张s 模型已经能跑出可用结果l 和 x 反而容易过拟合yolo detect train datafire.yaml modelyolov8s.pt epochs80 imgsz640 batch8 device0参数我常用的值说明epochs80红外数据集小50 轮起步100 轮以内足够imgsz640火焰目标偏小可试试 512速度更快batch8由显存决定6G 以下降到 4lr00.01默认值即可不要一上来开 0.1close_mosaic10最后 10 轮关闭 mosaic稳定收敛patience20验证集 20 轮不涨就早停红外火灾检测数据集和自然图像不同背景相对单调mosaic 增强的作用有限反而容易把小火焰目标拼碎。如果训练时发现验证集 mAP 曲线震荡很大先看是不是开了 mosaic 没关。另一个经验值是类别权重如果 smoke 占了 70% 的框fire 只有 15%需要给 fire 提高 loss 权重或者用class_weight参数手动干预否则模型会偏向学 sme。第一批结果出来后先别急着调参用第 5 章的方法验证数据质量数据问题比参数问题常见得多。4. 红外火灾检测数据集使用避坑zip 损坏、伪加密、通道与坐标的五个坑这部分全是我自己在红外火灾检测项目里踩过的坑。有几个坑从表面看是训练问题实际上根源在数据集本身写在这里给后来人省点时间。4.1 解压报错 could not find eocd现象解压到一半报错invalid zip archive: could not find eocd或者不报错但解压出来的图片打开后只有半边图像。原因zip 文件在下载传输中被截断eocd 是压缩包末尾的核心目录记录找不到它说明文件结尾缺失也可能是下载工具走了断点续传但服务器不支持导致文件拼接错位。解决先对比本地文件大小和下载页标注大小不一致直接重下下载时尽量用单线程不要开太多并发如果只有部分图片损坏可以用zip -F修复但数据集类 zip 我不建议修修复后容易出现文件目录错乱。还有一种隐蔽情况下载得到的是 HTML 错误页文件名后缀是 zip实际是文本用file命令一查便知。4.2 zip 伪加密与中文乱码现象解压工具提示需要密码但数据下载页没说加密或者解压出来的文件名是乱码。原因伪加密只是把 zip 的通用位标记改成了加密标记文件内容并没有真正加密中文乱码通常是压缩时用 GBK 编码解压工具默认按 UTF-8 解码。解决用 7-Zip 打开看是否带加密标记伪加密可以绕过但我不推荐用破解工具处理来源不明的包最靠谱的是回下载页找作者留下的说明乱码文件在 Linux 下可以用unzip -O CP936解压Windows 下解压后用脚本批量改名。注意一个原则如果是真加密AES 加密任何声称“移除密码”的工具对强密码都无效唯一合理做法是找回作者提供的解压密码别在“zip密码忘记了怎么办”这件事上浪费时间。4.3 红外单通道图像被当作三通道读现象训练 Loss 正常下降但 mAP 很低输出的可视化图偏色严重。原因红外图像本质是单通道灰度cv2.imread 默认按三通道读会把灰度复制三次不影响通道数但影响语义更致命的是 16bit 被截断成 8bit温度梯度信息丢失。解决统一用IMREAD_UNCHANGED读图在预处理阶段显式转换通道脚本里加一个断言检测到输入图像不是预期形状就立刻终止避免带病训练。这个坑很隐蔽因为单通道复制成三通道不会报错Loss 也会下降只有到现场测试才会暴露。4.4 标注坐标错位与类别名不统一现象转完格式后用画框脚本一看框和火焰完全错位或者类别数比预期多一倍。原因数据集的 XML 里 size 字段与实际图像宽高不一致转换时用了错误的尺寸标注里同时存在 fire 和 fire_1导致类别 ID 错乱。解决转换前用 2.3 的脚本逐个核验 XML 里的 w/h 和实际图像宽高类别名先做一次别名映射表把 fire_1、fire_2 统一归到 fire转换后再把 txt 读回原图画框这一步是唯一可靠的验证方式肉眼确认 100 张图没跑偏再进入训练。4.5 类别不均衡和“烟火不分”现象模型把夜间高温路面、反光金属误检成 fire或者把白色炊烟误检成 smoke。原因数据集中负样本太少模型没见过足够多“像火但不是火”的场景标签粒度本身也模糊有些作者把高温热源统一标成 fire。解决从原始视频里抽帧专门收集包含路灯、车灯、电炉、热水管道的负样本加进去训练后做温度域后处理用灰度阈值过滤掉明显低于火焰温度的区域。这个坑在第 6 章会给出具体代码属于“数据不够后处理补”的典型场景。5. 验证红外火灾数据集能不能训抽检脚本与三个量化指标很多工程师拿到数据集的第一反应是赶紧跑通训练但我觉得更值得先花一小时验证数据质量。一个标注乱七八糟的红外火灾检测数据集训练过程再顺利模型的现场表现也会把你拽回现实。5.1 随机抽检把 YOLO 格式标注画回原图标注转换完成后不要只看 txt 文件里的数字直接把框画回原图是最直观的验证方式# check_annotations.py import cv2 import glob for img_path in glob.glob(fire_data/images/val/*.png)[:20]: img cv2.imread(img_path) txt_path img_path.replace(images/val, labels/val).replace(.png, .txt) with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id, xc, yc, bw, bh map(float, parts) img_h, img_w img.shape[:2] x1 int((xc - bw / 2) * img_w) y1 int((yc - bh / 2) * img_h) x2 int((xc bw / 2) * img_w) y2 int((yc bh / 2) * img_h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(check_ img_path.split(/)[-1], img)这段脚本按 YOLO 格式的归一化坐标反算像素坐标在原图上画红框。我通常会看三类问题框是否完全包住火焰框和实际火焰的重心偏移大不大有没有某张图漏标明显火焰。抽样几十张就能发现系统性错误比如某个目录下的坐标全部整体偏移大概率是图像被裁切过而标注没更新。固定抽取目录下随机文件不回看第一张正常图那样很容易产生“看着没问题”的错觉。5.2 训练后通过失败样本反查数据训练跑完后YOLO 会在 runs/detect/val 目录下生成一批带预测框的验证图。这些图不是用来炫耀结果的而是用来反查数据集问题的。我会先看预测框与真实框的 IoU 分布如果大量真实框没有对应预测说明该类别样本量不足或标注框本身偏离目标如果预测框集中在图像中央说明模型学到了“图片中心有东西”而不是“这里有一个火焰”。另外看 false positive 集中在什么区域常见结果是出现在高温路面、金属反光、白炽灯附近这些位置往往在数据集的负样本里根本没有覆盖回到 4.5 去补数据而不是继续调 conf 阈值。5.3 用三个量化指标判断“能不能训”数据质量光靠肉眼不够我会统计三个指标来决定后续策略指标建议阈值不达标的处理方式每类目标框数量大于 200少于 200 的类别考虑合并标签或补充采集框面积占图像比例的中位数0.5% 到 10%小于 0.5% 说明目标过小需要提高输入分辨率同类目标的宽高比方差控制在合理范围方差过大说明目标尺度跨度大需要多尺度训练这三个指标决定了训练策略而不是单纯靠模型调参。比如红外火焰通常是小目标如果框中位数占比不到 0.5%imgsz 用到 640 是不够的我一般直接调到 960 并开启多尺度如果 smoke 目标的大宽高比很大说明火焰呈长条形分布需要让数据增强里的旋转和裁剪更激进。数据集的可训练度不是玄学统计完这三个值后面的训练参数基本就有数了。6. 把红外火灾检测做进产线的三个技巧灰度预训练、温度阈值和难样本回流最后分享三个把红外火灾检测模型从“能跑”推到“能用”的技巧都是我实际项目中验证过有效的方式。6.1 用灰度化预训练权重避免从头学红外图像和 ImageNet 自然图像分布差异大直接加载 RGB 预训练权重不一定最优。常见做法是把预训练权重第一层卷积的输入通道改成灰度适配或者用灰度化处理过的公开数据集做一遍短期预训练。红外火灾检测数据集规模通常不大从头训练容易欠拟合灰度预训练能让模型在低温差场景下的初始化特征更合理。6.2 推理后处理加温度阈值检测框输出后再叠加一个灰度阈值过滤能显著减少高温路面、反光金属造成的误报# post_process.py for det in results.boxes.data.tolist(): x1, y1, x2, y2, conf, cls_id det roi gray_frame[int(y1):int(y2), int(x1):int(x2)] if roi.size 0: continue # 火焰区域灰度值通常显著高于背景 if roi.max() 180: continue # 灰度值过低判定为非火焰目标 output.append(det)这个阈值需要根据热像仪型号和温度范围调整不要写死。灰度阈值解决的是“物体像火但温度不够高”的问题和模型互补能有效降低现场误报。如果数据集带 meta.csv 的温度映射直接用温度值做判定会更准。6.3 难样本回流把现场误报截图定期加入训练集中的负样本目录形成一个小型增量集。连续做两三轮模型对厂区路灯、金属设备这类“看起来像火”的干扰会越来越钝感。我现在的习惯是任何数据集 zip 拿到手先花半天检查结构和标注再花一小时跑通最小训练剩下的时间留给数据迭代。希望帮到你。本文还有配套的精品资源点击获取
返回列表