简介这是一份基于YOLOv5实现的火焰图像与视频识别项目完整资源面向目标检测初学者、高校学生以及需要完成毕业设计、课程设计或工程实训的人群。项目围绕YOLOv5框架涵盖数据标注、模型配置、训练脚本与说明文档可帮助读者从零搭建火焰检测流程并迁移到其他小型目标检测任务中。资源共2000个文件压缩包约446MB其中1989个XML标注文件为目标框标签YAML与TXT分别承担模型配置与数据集划分MD文档提供使用指引Python脚本则负责数据预处理等辅助操作。整体目录结构清晰便于按模块检索学习。目前已有340人学习下载借助该资源可完成从训练数据准备到模型推理的闭环尤其适合以火焰检测为切入点的课设与毕设在有限时间内获得可运行、可展示的成果。1. 火焰检测为什么绕不开 YOLOv5传统火焰识别大多靠颜色阈值或帧差法背景一复杂就崩误报率能到 30% 以上。YOLOv5 把目标检测当成回归问题一次前向推理同时输出类别和边界框在 GPU 上能达到毫秒级延迟。用于火焰这个任务它的优势不是精度碾压一切而是工程上成熟PyTorch 生态、预训练权重多、部署工具链完整适合自建数据集做迁移学习。本文从零开始讲清楚怎么用 YOLOv5 做一个能跑视频的火焰识别系统数据来自 VOC 格式的 XML 标注代码里包含 maketxt.py 等脚本流程覆盖数据划分、训练、测试到视频推理。对于做毕设或课程设计的人来说这套流程可以直接复现也方便改成烟雾、电动车等检测场景。2. 数据准备与标注格式转换XML 到 YOLOv5 训练集2.1 理解输入数据的目录结构拿到一组带有 XML 文件的火焰图像典型的组织方式是 JPEG 图片和同名 XML 放在同一目录。XML 是 Pascal VOC 格式每个文件描述一张图里的目标包括物体类别名和边界框坐标。YOLOv5 本身不认识 XML它只认两种东西每行一个标注的 txt 文件以及记录图片路径的 list 文件。txt 每行格式是class x_center y_center width height坐标都归一化到 0~1。所以第一步就是把 XML 转成 YOLOv5 能吃的格式。常见的转换工具是xml2yolo.py你也可以直接用项目提供的 maketxt.py 来完成部分工作。下面是我常用的一段转换脚本它会遍历所有 XML解析bndbox里的xmin ymin xmax ymax然后除以图片宽高得到归一化坐标。import xml.etree.ElementTree as ET import os def convert_annotation(xml_path, class_names, output_dir): tree ET.parse(xml_path) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) out_lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) x_center (xmin xmax) / 2.0 / width y_center (ymin ymax) / 2.0 / height w (xmax - xmin) / width h (ymax - ymin) / height out_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if out_lines: base os.path.basename(xml_path).replace(.xml, .txt) with open(os.path.join(output_dir, base), w) as f: f.write(\n.join(out_lines))这段脚本里class_names是类别列表比如[fire]顺序要和训练时 data.yaml 里的类别顺序一致。注意宽高相除必须用浮点数否则归一化坐标会丢失小数点。脚本只处理bndbox为矩形框的 VOC 标注如果 XML 里有polygon多边形标注需要额外转成外接矩形。2.1.1 maketxt.py 的作用maketxt.py 这类脚本负责生成 train.txt、val.txt、test.txt。这些 txt 文件里每一行是图片的绝对路径或相对路径。YOLOv5 训练时通过--data dataset.yaml指定数据集配置yaml 里的train和val字段直接指向这些 txt 文件。手动划分时要注意随机性并且保证同图对应的 txt 标注和数据划分归属一致。训练前还要检查两件事第一所有图片路径不能有中文或空格否则 dataloader 解析会出问题第二标注 txt 不能为空文件空文件会导致KeyError或者训练 loss 变成 nan。我一般会用下面这行命令快速检查一个标注文件内容。cat labels/train/123.txt # 输出示例: 0 0.5123 0.3845 0.1234 0.25672.2 数据划分的坑项目里列出的 trainval.txt、train.txt、val.txt、test.txt 就是常见的四份划分。trainval 是训练加验证的总集train.txt 从里面再分一部分test.txt 单独拿出来当最终评测集。比例通常按 8:1:1。划分要避免同一场景的连续帧全部落在训练集而另一段视频的帧全落在测试集。最好按视频片段分组划分不然会高估模型泛化能力。我一般先把所有图片按文件名排序然后用random.seed(42)实现可复现的随机打乱。import random random.seed(42) paths [line.strip() for line in open(trainval.txt)] random.shuffle(paths) split_idx int(len(paths) * 0.9) train_paths paths[:split_idx] val_paths paths[split_idx:] with open(train.txt, w) as f: f.write(\n.join(train_paths)) with open(val.txt, w) as f: f.write(\n.join(val_paths))这里random.seed(42)固定随机种子保证后续复现相同划分。shuffle 之后取前 90% 做训练集后 10% 做验证集。注意如果类别不平衡比如火焰区域只有 200 个框背景图有 2000 张那么验证集里可能一张火焰都没有。建议用分层抽样先按标注文件里是否有类别来分层。3. YOLOv5 模型训练从预训练权重到火焰类别3.1 选哪个模型尺寸YOLOv5 官方代码库按参数量从小到大有 s、m、l、x 四个版本还有 n 和 t 两个更小版本。火焰检测任务里火焰通常是大目标而且纹理粗糙不需要特别大的特征提取能力。我用 YOLOv5s 就能在 640 分辨率下跑出不错的效果推理速度在 RTX 3060 上能到 80 FPS。如果部署目标是树莓派或嵌入式设备YOLOv5n 更合适但精度会掉 3~5 个 mAP。模型结构选型时可以看下面这个对比表模型参数量训练显存占用batch 16, 640px推理延迟RTX 3060适用场景YOLOv5n1.9M约 4GB约 8ms嵌入式边缘设备YOLOv5s7.2M约 8GB约 12ms常规服务器或 PCYOLOv5m21.2M约 12GB约 20ms精度优先但显存充足YOLOv5l46.5M约 20GB约 35ms大模型或离线分析火焰本身是强语义目标surrounding 环境的光照变化对浅层特征影响大所以不能只看参数量还要做几次小实验对比 s 和 m 的验证集 mAP。我一般先用 s 训练 20 个 epoch 看趋势如果 val mAP 增长斜率很陡再换 m 练到底。3.2 data.yaml 与超参数训练前要准备一个fire.yaml内容如下。train: /path/to/train.txt val: /path/to/val.txt test: /path/to/test.txt nc: 1 names: [fire]nc表示类别数这里只有火焰一类。如果还想识别烟雾就写成nc: 2和names: [fire, smoke]。注意names的顺序必须和标注 txt 里的类别 id 对应id 从 0 开始。很多初学把类别名写混导致训练时 loss 直接爆掉。超参数文件一般用hyp.scratch-low.yaml里面包含 lr、momentum、weight_decay 等。火焰检测这类单类目标不需要调整太多但有两个参数值得改anchors和fl_gamma。官方默认的锚框是针对 COCO 的 80 类物体设计的火焰形状接近圆形或扁长形我一般会在训练前用 k-means 重新聚类锚框。YOLOv5 自带聚类工具但手动改起来也简单。下面这段代码计算当前训练集所有标注框的宽高比帮你判断默认锚框合不合适。import numpy as np boxes [] with open(/path/to/train.txt) as f: for line in f: img_path line.strip() label_path img_path.replace(/images/, /labels/).replace(.jpg, .txt) with open(label_path) as lf: for line in lf: parts line.strip().split() w float(parts[3]) h float(parts[4]) boxes.append([w, h]) boxes np.array(boxes) print(平均宽高比:, (boxes[:, 0] / (boxes[:, 1] 1e-6)).mean()) # 输出比如: 平均宽高比: 1.32如果平均宽高比接近 1说明火焰框接近正方形默认锚框可以不动。如果大于 2说明有很多横向火焰需要调整锚框。调整锚框直接在模型 yaml 里改例如anchors: [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]]前一行是小尺度特征图的锚后两行是中大尺度。3.3 训练命令与迁移学习训练命令如下。python train.py \ --data fire.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 50 \ --cache ram \ --project runs/fire_train参数含义--weights yolov5s.pt是加载官方 COCO 预训练权重千万不要随机初始化火焰和 COCO 里的火把、火堆有一定相似性迁移学习能大幅减少收敛时间。--img 640表示训练分辨率如果显存不够可以降到 512但火焰小目标较多时不要低于 416。--cache ram把图片全部缓存在内存训练速度明显加快但需要看你内存是否够用。训练启动后每 10 个 epoch 会保存一次 checkpoint。观察runs/fire_train/exp/results.csv里的val/box_loss和val/cls_loss。火焰是单类cls_loss比box_loss更重要。如果val/cls_loss在 30 轮后还在下降说明还没收敛可以增加到 100 epoch。3.3.1 训练过程中的常见报错最常见的报错是CUDA out of memory解决办法是调低 batch size 或减小--img。还有一个容易忽视的是assert (img_path.suffix in (.jpg, .jpeg, .png))报错这是因为图片格式不是这三种或者图片路径指向了不存在的位置。另外如果标注里有目标框超出图片边界训练时会出现AssertionError: invalid shape (0, ...)。这时候要检查 XML 里坐标是否大于图片宽高有些标注工具会生成负数坐标需要过滤掉。# 过滤超界和非法框的代码片段 def filter_box(xmin, ymin, xmax, ymax, width, height): if xmin 0 or ymin 0 or xmax width or ymax height: return False if xmax - xmin 1 or ymax - ymin 1: return False return True4. 火焰视频识别摄像头/视频文件推理与结果输出4.1 使用 detect.py 做视频推理训练好的best.pt可以直接用于视频检测。YOLOv5 的detect.py支持输入图片、视频文件、文件夹或摄像头编号。运行命令如下。python detect.py \ --weights runs/fire_train/exp/weights/best.pt \ --source test_video.mp4 \ --conf-thres 0.4 \ --iou-thres 0.5 \ --view-img \ --save-txt \ --save-conf--conf-thres 0.4控制检测置信度阈值低于 0.4 的预测会被过滤。火焰检测阈值不宜太高因为小火苗容易被遮挡建议先设 0.25 看漏检情况再逐步上调。--iou-thres 0.5是 NMS 的 IoU 阈值重叠框较多时可以调高到 0.6。--save-txt会把检测到的类别和坐标保存成 txt--save-conf同时保存置信度。运行后输出视频会保存在runs/detect/exp/目录。如果想在显示窗口中看到实时检测加--view-img。如果是在无显示环境的服务器上跑不要加这个参数否则会报错cv2.error: ... is not a valid window。4.2 摄像头实时检测的延迟控制接摄像头时把--source 0改成摄像头索引号。要注意摄像头帧率通常为 30 FPS而 YOLOv5 推理每帧可能耗时 20~40ms加上前处理和后处理瓶颈常常在图像缩放和 NMS 上。可以用--imgsz 480降低输入分辨率牺牲一点精度换速度。另一个思路是跳帧检测比如每 3 帧只检测一帧中间帧沿用上一帧结果。import cv2 import torch model torch.hub.load(ultralytics/yolov5, custom, pathruns/fire_train/exp/weights/best.pt) model.conf 0.35 model.iou 0.5 cap cv2.VideoCapture(0) frame_skip 2 frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret: break if frame_count % frame_skip 0: results model(frame) bboxes results.xyxy[0].cpu().numpy() # 只保留 class 0 火焰 fire_boxes bboxes[bboxes[:, -1] 0] for x1, y1, x2, y2, conf, cls in fire_boxes: cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(frame, ffire {conf:.2f}, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2) frame_count 1 cv2.imshow(fire detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里torch.hub.load加载自定义权重model.conf和model.iou直接赋值作为后处理阈值。frame_skip设为 2 意味每 3 帧检测一次可以显著降低 CPU 或嵌入式设备的负载。注意results.xyxy[0]返回的数组按列依次是 x1、y1、x2、y2、confidence、class这六个字段在后续绘制和统计中很有用。4.3 视频检测结果后处理检测完的视频可以输出为 MP4也可以逐帧保存火焰框的坐标用于分析。如果你要把结果接入告警系统一般会把火焰框的中心坐标、面积和置信度写入 CSV。火焰面积占比是一个重要的告警指标因为远处小火苗可能在图像里只有 5×5 像素直接告警会造成太多误报。import csv with open(fire_log.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame, x1, y1, x2, y2, conf, area_ratio]) for frame_idx, res in enumerate(all_results): for box in res.xyxy[0].cpu().numpy(): x1, y1, x2, y2, conf box[:5] area_ratio (x2 - x1) * (y2 - y1) / (frame_width * frame_height) writer.writerow([frame_idx, x1, y1, x2, y2, conf, area_ratio])area_ratio是检测框面积占整帧图像的比例阈值可以设为 0.01低于该值的火焰框视为噪声不触发告警。这个值需要根据摄像头安装距离和角度调整如果摄像头离地面 5 米远处小火苗的面积占比会非常小阈值要放宽。5. 火焰误报抑制置信度阈值与帧间滤波的工程调优火焰识别落地最大的敌人不是漏检而是误报。红色的光照、反光的汽车表面、晚霞甚至烧红的铁块都可能被识别成火焰。YOLOv5 只从外观学习没有时间上下文所以需要加入工程化手段过滤误报。5.1 连续帧稳定性判据单帧置信度高不一定是真火但一个目标在多帧中持续出现且位置连续变化是真火的概率就大得多。我常用一个简单的状态机每个目标框维护一个连续命中计数hits如果连续 N 帧都检测到火焰才输出告警如果中间丢失 M 帧计数清零。N 取 5、M 取 3 比较合适视频帧率 30 FPS 时相当于目标持续出现 0.17 秒才触发。class FireTracker: def __init__(self, max_miss3, hit_threshold5): self.max_miss max_miss self.hit_threshold hit_threshold self.hits 0 self.misses 0 def update(self, detected): if detected: self.misses 0 self.hits 1 else: self.hits 0 self.misses 1 return self.hits self.hit_threshold and self.misses self.max_missdetected是当前帧是否有任意火焰框通过了置信度阈值。这个 tracker 没有做目标关联只统计全画面是否持续出现火焰适合单一火焰场景。如果画面里多个火焰同时出现需要用 IoU 做目标框的跨帧匹配比如用simple_iou判断前后帧中心点距离小于某个像素的框是否是同一个目标。5.2 动态置信度与闪烁检测火焰有闪烁特性火焰区域的光强在时间轴上呈现较高频率波动。可以计算检测框内图像的平均亮度方差方差高说明是火焰方差低则可能是静态红色物体。import numpy as np def fire_flicker_score(crop_frame): gray cv2.cvtColor(crop_frame, cv2.COLOR_BGR2GRAY) temporal_var np.var(gray) return temporal_var 30 # 经验阈值这个temporal_var计算的是当前帧检测区域内像素方差不是时间方差。更准确的做法是在同一位置连续取 5 帧灰度图计算每个像素亮度随时间的变化方差再求平均。这个步骤计算量不大作为二次判决条件可以使误报率降低约一半。5.3 输出告警动作抑制误报之后真正的火焰事件可以触发告警。调用 trigger.py 脚本发送 HTTP 请求到告警服务或者写入日志。下面是调用本地告警接口的一个示例。curl -X POST http://localhost:8080/api/fire-alert \ -H Content-Type: application/json \ -d {camera_id: 1, bbox: [120, 342, 250, 500], confidence: 0.93, area_ratio: 0.18}告警参数里camera_id区分不同摄像头bbox用于管理人员定位火焰位置。注意告警接口要做好频率限制比如同一摄像机每 10 秒最多发送 1 条告警否则处置人员会被连续消息淹没。最后提一个调优技巧训练时用--multi-scale让模型适应不同输入尺寸推理时再用固定 640 分辨率防御视角变化引起的误检。同时把验证集里最容易误报的红车或夕阳图片单独建一个hard_examples.txt用这些图片反复测试调整conf-thres到 0.45~0.5 区间直到误报数和漏检数达到平衡即可收工。本文还有配套的精品资源点击获取