烟火检测VOC数据集全流程指南:从格式解析到难例挖掘 简介这份Pascal VOC格式的烟火检测数据集重制版专为训练烟雾与明火识别模型而整理适合计算机视觉学习者、算法工程师及消防、安防相关项目开发者使用。压缩包内共包含6460张jpg图片与6460个xml标注文件另附一份使用说明整体约687.88MB。标注类别覆盖smoke与fire两类分别含7901个和11066个矩形框场景涵盖交通事故、森林火灾、建筑失火、柴木生火以及蜡烛、奥运火炬等多种烟火形态。全部标注均使用labelImg工具完成图片与XML一一对应目录编号清晰便于直接划分训练集与验证集可快速用于YOLO、SSD等目标检测模型的训练与评估。已有2560人学习下载适合需要现成VOC标注数据、希望省去手动标注成本、专注调参和模型迭代的开发者。1. 烟火检测的第一步不该是开训这份VOC数据集的定位与边界拿到“烟火数据集、烟雾明火2类、VOC格式、6460张”这份标的时多数人的第一反应是解压、翻目录、找训练脚本然后直接开训。但烟火数据集恰恰是所有目标检测场景里最容易在标注细节上出问题的那一类烟雾边界半透明、形状弥散明火亮度高但目标稀疏且大量图像里两类目标同时存在——如果标注框、类别名和划分方式有一处不干净再好的模型也会在真实监控画面前翻车。这份202206重制版的核心价值不是“多了一版数据”而是把原先散乱、漏标、错标的标注重新整理成一套可复现的VOC-6460张标准集让“拿到数据”到“跑通基线”之间少踩几轮坑。它适合两类读者一类是急着验证烟火检测方案的开发者另一类是需要可控数据集做对比实验的算法工程师。后续所有章节都围绕“如何把这份数据集变成可用模型”展开先解析格式再做体检再定训练参数最后用难例挖掘把它榨干。2. 为什么VOC比YOLO格式更适合烟火数据集格式背后的标注可信度在正式训练之前我一般会先回答一个问题这份数据集的VOC表示到底值不值得信任很多人拿到的数据集是直接从标注工具里导出的YOLO格式txt每行只有“类别编号、中心点坐标、宽高”五个数字一旦出现错误——比如框跑出图外、类别编号对不上——几乎没法一眼发现。VOC格式保留的是“图片元数据每个目标的类别与边框坐标”只要配合原图就能把每个目标框画回去人工检查。对烟火这类标注歧义大的场景这一步检查是必须的不是锦上添花。2.1 VOC目录结构与XML标注的读取方式VOC格式的目录结构并不复杂通常就是三个子目录Annotations存放xml文件、JPEGImages存放原图、ImageSets/Main存放划分好的文件列表。xml里记录着图片文件名、尺寸以及一个或多个object节点每个object里有name和bndbox。不要小看这个结构xml里的width/height如果和真实图片不一致后续把坐标归一化到yolo格式时就会集体偏移所以第一步是快速读取xml并与图片做核对。我一般会先写一个解析脚本把单个xml里的对象打印出来import xml.etree.ElementTree as ET from pathlib import Path xml_path Path(./Annotations/000001.xml) img_path Path(./JPEGImages/000001.jpg) # 解析xml并输出图片尺寸与所有目标 tree ET.parse(xml_path) root tree.getroot() size root.find(size) print(fimage: {img_path.name}, width{size.findtext(width)}, height{size.findtext(height)}) for obj in root.findall(object): name obj.findtext(name) # 类别名可能是 smoke/fire bndbox obj.find(bndbox) xmin int(float(bndbox.findtext(xmin))) ymin int(float(bndbox.findtext(ymin))) xmax int(float(bndbox.findtext(xmax))) ymax int(float(bndbox.findtext(ymax))) print(f class{name}, box({xmin},{ymin})-({xmax},{ymax}), w{xmax-xmin}, h{ymax-ymin})这段脚本的逻辑很直白通过ElementTree读取根节点找到size节点里的宽高再遍历所有object节点抽出类别和边框坐标。值得留意的是xmin/ymin/xmax/ymax在小部分标注工具里会用浮点数保存所以读取时统一走int(float(...))避免直接把字符串转int报错。参数上不需要调整但如果你发现某个文件解析失败通常不是脚本问题而是xml本身缺节点或属性名不对——这时候要优先排查原数据而不是绕过去。把标注画回原图是检查数据质量最直观的方式。我习惯把预览脚本写在转换脚本之前因为用眼睛看一遍比任何统计数字都能更快发现问题。下面给出一个用OpenCV画框并保存预览图的片段import cv2 import xml.etree.ElementTree as ET from pathlib import Path def draw_boxes(img_path: str, xml_path: str, out_dir: str): # 把xml标注画回原图并保存便于人工抽检 img cv2.imread(img_path) tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): name obj.findtext(name) bndbox obj.find(bndbox) x1, y1 int(float(bndbox.findtext(xmin))), int(float(bndbox.findtext(ymin))) x2, y2 int(float(bndbox.findtext(xmax))), int(float(bndbox.findtext(ymax))) color (0, 0, 255) if name fire else (0, 200, 255) # fire用红色烟用黄色 cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, name, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(str(Path(out_dir) / Path(xml_path).stem .jpg), img)这段代码的作用是让每一条xml标注都能对应到一张看得见的预览图颜色区分两类只是为了辅助快速浏览不参与训练。使用注意点是out_dir要提前创建如果图片数量多把预览图统一缩小拼接成网格图几秒钟就能扫完几百张图和对应标注。这种“人眼抽检”的价值在后面避坑章节还会反复体现。2.2 VOC转YOLO最常用的转换脚本以及最容易出错的三个细节虽然VOC格式方便人工检查但实际训练时大多数人会转成YOLO的txt。原因很现实YOLO训练管线按行读标签每行是“class_id x_center y_center width height”归一化后训练时不再需要解析xmlIO速度更快。但转换这一步是数据集质量的试金石——坐标归一化除以图片宽高时如果原xml和真实图片尺寸不一致得出的yolo坐标就是错的如果目录里混着没有标注对象的空xml转换脚本还可能直接跳过或报错造成标签文件与图片文件的一一对应关系被破坏。下面这个脚本是转换的核心步骤包含了烟火数据集最常见的坐标越界与空标注处理import xml.etree.ElementTree as ET from pathlib import Path LABEL_MAP {smoke: 0, fire: 1} # 两类别的固定映射决定模型输出含义 def convert_one(xml_path: Path, label_dir: Path): tree ET.parse(xml_path) root tree.getroot() # 以xml里的size为准, 但转换前必须用cv2核对该值与真实图片一致 width int(root.find(size/width).text) height int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.findtext(name) if name not in LABEL_MAP: continue # 过滤无法映射的类别避免产生未知编号 cls_id LABEL_MAP[name] bndbox obj.find(bndbox) x1 int(float(bndbox.findtext(xmin))) y1 int(float(bndbox.findtext(ymin))) x2 int(float(bndbox.findtext(xmax))) y2 int(float(bndbox.findtext(ymax))) # 烟火标注容易出图边界归一化前先按图片尺寸截断 x1, y1 max(0, x1), max(0, y1) x2, y2 min(width, x2), min(height, y2) if x2 x1 or y2 y1: continue # 截断后为空框跳过 x_center (x1 x2) / 2 / width y_center (y1 y2) / 2 / height box_w (x2 - x1) / width box_h (y2 - y1) / height lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}) out_txt label_dir / (xml_path.stem .txt) out_txt.write_text(\n.join(lines) (\n if lines else )) return len(lines)这段脚本的关键点有三个一是类别映射固定为smoke0、fire1训练一旦开始就不能再改否则模型输出的类别语义会漂移二是坐标要做截断和空标注处理烟火标注经常出现“框出图外”的情况不做min/max截断送入训练的标签就可能出现中心点大于1.0的非法值三是归一化精度保留六位小数对640到1024的输入尺寸足够不要为了省空间用两位小数否则小目标的中心点偏差会被放大。脚本没有做并行处理对6460张图来说单线程也只要几分钟。转换完成后我建议立刻做一次“反向校验”随机抽20张图把txt按归一化坐标换算回绝对坐标再和xml里的坐标对比一遍差异超过两个像素就说明转换有问题。这一步虽然朴素却是最快发现目录对不齐、标签名错乱等问题的办法。把这些细节处理干净后面训练时很多“loss很奇怪”的问题都会少掉一半。3. 先体检再训练烟火数据集的类别分布与标注质量分析拿到数据集先别急着训练。6460张的规模说大不大说小不小你完全可以在半小时内跑完一轮统计分析而这轮统计的收益远大于多数调参。为什么必须做体检因为烟火数据集里的两张错误标注带来的负面影响比两张脏图更大模型会把错误框当作正确答案去拟合造成某些指标看着还行、实际推理表现一塌糊涂。下面这三步是每次拿到VOC数据集我都会做的例行检查。3.1 用脚本统计类别数、框数量与图片尺寸分布第一件事是搞清数据集的“基本盘”两类各有多少个框平均每张图有几个目标图片尺寸范围是多少框的宽高分布集中在哪个区间这些数字决定了后续把输入分辨率设多大、要不要做小目标增强、类别不平衡严重到什么程度。很多训练翻车问题其实在统计阶段就能看出端倪。import xml.etree.ElementTree as ET from pathlib import Path from collections import Counter ann_dir Path(./Annotations) class_counter Counter() box_rel_sizes [] # 每个框相对图片的宽高占比 img_sizes [] for xml_path in ann_dir.glob(*.xml): root ET.parse(xml_path).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) img_sizes.append((w, h)) for obj in root.findall(object): class_counter[obj.findtext(name)] 1 bb obj.find(bndbox) x1 int(float(bb.findtext(xmin))) y1 int(float(bb.findtext(ymin))) x2 int(float(bb.findtext(xmax))) y2 int(float(bb.findtext(ymax))) box_rel_sizes.append(((x2 - x1) / w, (y2 - y1) / h)) print(类别统计:, dict(class_counter)) print(图片张数:, len(img_sizes)) print(图片尺寸范围:, min(w for w, h in img_sizes), min(h for w, h in img_sizes), 到, max(w for w, h in img_sizes), max(h for w, h in img_sizes))这段统计脚本会直接输出两个关键结论第一两个类别的目标数是否悬殊。假如明火只有烟雾的三分之一训练时明火类的AP就会普遍偏低需要靠类别加权或复制增强来弥补而不是盲目调loss项。第二框的相对宽高比。如果绝大多数框的相对宽度大于0.3说明大目标占绝对主导模型的“小目标感知”会很弱反过来如果框大量集中在相对宽高0.05以下输入分辨率至少要提到768才不至于让小目标在特征图上只剩一两个像素。图片尺寸范围的输出主要用来判断是否需要统一缩放如果从300x300到1920x1080都有训练时batch里图像的padding方式会变得很折腾。3.2 检查漏标和错标烟雾与明火的边界案例统计解决不了的事情要靠抽查图层。烟火数据集的标注错误通常有两类一类是漏标一张图里明显有明火或浓烟但xml里什么都没有另一类是错标把烟雾和明火混在一起标成同一个框或者明火明明是一簇却用一个盖住整栋楼的大框。这两种错误对模型的影响机制不同漏标让模型学“假装看不见”错标则让框的回归目标方差变得极大最终造成测试时预测框要么过大要么偏到一边。我习惯把大量预览图汇总到一张大图上快速扫视。下面的方法从标注目录读取图片画出所有框后按指定网格拼成一张总览图适合做全量抽查import cv2 import math import xml.etree.ElementTree as ET from pathlib import Path def build_overview(xml_dir: Path, img_dir: Path, grid(10, 10), out_fileoverview.jpg): # 先取前grid[0]*grid[1]张图拼成总览实际可随机抽样 xmls sorted(xml_dir.glob(*.xml))[: grid[0] * grid[1]] tile_h, tile_w 512, 512 rows [] for row_idx in range(grid[0]): row_imgs [] for col_idx in range(grid[1]): idx row_idx * grid[1] col_idx if idx len(xmls): break xmlp xmls[idx] img cv2.imread(str(img_dir / (xmlp.stem .jpg))) if img is None: continue # 关键先在原图画框再缩放避免坐标偏移 for obj in ET.parse(xmlp).getroot().findall(object): bb obj.find(bndbox) x1 int(float(bb.findtext(xmin))) y1 int(float(bb.findtext(ymin))) x2 int(float(bb.findtext(xmax))) y2 int(float(bb.findtext(ymax))) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 200, 255), 2) img cv2.resize(img, (tile_w, tile_h)) row_imgs.append(img) if row_imgs: rows.append(cv2.hconcat(row_imgs)) canvas cv2.vconcat(rows) cv2.imwrite(out_file, canvas)这段代码把多张带标注的图拼接成一张网格图核心技巧是先画框、后缩放顺序反了会出现框整体偏移。如果要检查全量6460张可以循环多次抽样生成若干张总览图而不是一次拼完。我平时看数据会特意留意三类样例白天强光下的白烟、夜间亮红色的明火、以及明火和烟雾同时出现的大框——这三类最容易出现标注错误也最容易在后来的模型推理中误判。3.3 按难例比例划分训练集、验证集与测试集体检通过的下一件事是划分数据集。很多人拿到文件列表后直接随机打乱按7:2:1切分但烟火场景下这种做法常常埋雷监控视频连续帧会被切到不同集合里训练集和验证集里出现内容几乎相同的图片导致val指标虚高。部署到真实场景时模型遇到从没见过的机位、角度和光照变化误报率立刻失控。正确的做法是先按场景分组把同一时段同一视角的图片视作一组再按组划分。对于VOC格式的数据集划分动作最后落到ImageSets/Main下几个txt文件。先看一个最简单的随机划分命令行版本# 先把所有图片名写入一行一个的列表 cat all_image_names.txt | shuf shuffled.txt # 取前70%为train后20%为val最后10%为test total$(wc -l shuffled.txt) train_n$((total * 70 / 100)) val_n$((total * 20 / 100)) head -n $train_n shuffled.txt train.txt sed -n $((train_n 1)),$((train_n val_n))p shuffled.txt val.txt sed -n $((train_n val_n 1)),${total}p shuffled.txt test.txt这里给的是基础随机划分实际项目里我会把“按场景分组”写成python脚本先从图片名解析出场景id用groupby把同一场景的所有帧放入同一个桶再以桶为单位洗牌。6460张里拿多少做测试集没有绝对标准但如果测试集和训练集的场景高度重叠后续汇报的精度数据就要打一个折扣。划分好之后要检查一个容易被忽略的细节三个txt的行数加起来是否等于6460以及有没有重复行。重复行一旦混进训练集和验证集会导致指标虚高且难以复现。4. 用这份VOC数据集跑通训练模型选型、数据配置与关键参数体检做完接下来才是训练。烟火检测属于特定目标检测类别只有两类且目标外观有较强的颜色与纹理特征不需要上太大的模型。6460张的数据量如果用很深的骨干网络很快就会过拟合到训练集的火焰纹理上如果模型规模太小对远处小火苗的召回又不够。这个平衡点的把握是本章要回答的核心问题。4.1 小目标与遮挡场景下的模型选型我一般会先用一个中等规模的一次性检测器作为基线而不是一上来就上复杂模型。YOLO系列的v8s或同档次的模型在这个数据量下是常见起点原因有三个第一输入分辨率可以从640起步对大多数GPU都友好第二它内置了多尺度预测和数据增强能兼容大烟雾框和小明火框的尺度差异第三训练时间可控跑一轮基线只需要几小时适合快速验证数据集质量。相比之下两阶段检测器在精度上限上有优势但对6460张的小数据集来说训练技巧要求更高起步阶段容易在调参上耗费大量时间。至于要不要用更小的模型版本我个人的建议是不要烟火目标在小分辨率下容易退化成几个像素模型容量太小的话框的回归会非常不稳定。更稳妥的路线是先把基线跑通记录各类AP再根据瓶颈决定是向上换更大模型还是向下做压缩。这里有个常见误区是把输入分辨率直接拉到1280以为能解决一切小目标问题。实际上分辨率提升会把显存消耗和推理耗时一起拉高而且当标注本身的框边界就不准的时候高分辨率只会让模型更努力地拟合错误标注。4.2 数据配置与训练参数怎样设置才贴近烟火场景数据集训练配置分成两部分数据文件定义和超参数调整。数据文件把目录结构告诉训练框架里面最关键的是path、train、val和nc四个字段。类名的顺序必须和转换脚本里的LABEL_MAP保持一致否则训练出来的模型类别语义是错乱的。一个常见的数据配置如下# fire_smoke.yaml path: /data/fire_smoke # 数据集根目录 train: images/train # 训练图片目录 val: images/val # 验证图片目录 test: images/test # 测试图片目录未启用时可省略 nc: 2 # 类别数量固定为2 names: 0: smoke 1: fire数据文件中最需要反复核对的就是names的编号与顺序。有些数据集用中文名或混淆名称一旦转为训练格式就要全部替换为固定英文标签否则训练工具在读取类别名时会出现不匹配。这个配置文件的格式在不同框架里大同小异核心是“路径类别数类别名”迁移到其他训练框架时只要保持这三个信息一致即可。训练参数我不会直接照搬默认值。下面这组参数是我对烟火场景会优先调整的对象yolo train datafire_smoke.yaml modelyolov8s.pt \ imgsz768 \ epochs120 \ batch16 \ patience30 \ optimizerAdamW \ lr00.001 \ weight_decay0.0005 \ cos_lrTrue \ close_mosaic10参数含义分别是imgsz768把输入分辨率提升到768对小目标更友好epochs设成120并配合patience30做早停防止后期在验证集上持续过拟合batch16是常见显存下的合理选择显存小就降到8显存充足可以提到32optimizer换成AdamW而不是默认的SGD在较小数据集上往往收敛更稳close_mosaic10表示最后10个epoch关掉mosaic增强让模型从“看拼接图”慢慢回落到“看真实构图”的状态。参数之间不是独立起作用的——如果你提高了imgszbatch通常必须下调否则显存不够如果类别严重不平衡还可以在loss里对fire类提升权重这个数据集的平衡情况要靠前面体检的结果来定。数据增强方面烟火数据集的特殊性在于烟雾和明火都是“大色块纹理”的组合常规色彩抖动很有用但要注意不能把颜色增强调得过猛否则火焰的橙红色会被扭曲成其他颜色反而伤害检测。常见做法是把hsv_h调到0.015hsv_s和hsv_v各调到0.5和0.4比默认值保守一些。mosaic和copy_paste保留默认即可前者把不同图片拼接在一起增加上下文多样性后者对小火苗这种小目标尤其有效。4.3 验证集指标怎么读mAP、AP_s/m/l背后的信息训练结束后验证集上一串指标需要仔细拆解。总mAP高不等于模型可用因为烟火检测更关心两个具体维度一是对小火苗的召回率二是对傍晚太阳、车灯、红灯笼这些“类火物体”的误报率。验证集指标里AP_s、AP_m、AP_l分别代表小、中、大目标的平均精度如果AP_s明显低于AP_l说明小目标才是短板后续要补小目标增强或提高输入分辨率如果两个类别各自的AP差距很大就要回到数据统计里查类别不均衡。这里给出一个按类别输出细粒度评估的命令示例yolo detect val modelruns/detect/train/weights/best.pt datafire_smoke.yaml \ imgsz768 \ save_jsonTrue # 打开生成的results.json, 重点看per-class的ap50和ap50-95评估结果出来后我一般会再手动看一眼验证集里预测置信度在0.3到0.6之间的样本。这个区间是烟火场景里最易出错的区域低于0.3的框大都是背景误判高于0.6的框一般已经稳定而0.3到0.6之间混杂着难例和真错误。把这一批框可视化出来你很快就会知道模型到底在哪里迷路。这是把指标和实际画面对接起来的关键一步也是后续难例挖掘的基础。5. 烟火数据集避坑手册5个真实踩坑记录这一章写的不是理论是我和身边同事在这些烟火数据集上反复踩过的坑。你可能不会全部遇到但只要中一个训练时间就会白白浪费几天。每一条都按“现象—原因—解决”来讲希望能帮你绕过去。5.1 XML尺寸和图片尺寸不一致训练时标签整体偏移现象训练loss在前几个epoch不降反升或者loss正常但验证集的预测框普遍偏小、偏移到目标边缘。 原因xml里记录的width/height与实际jpg不一致。大部分VOC转换脚本用的是xml里的size一旦标注工具导出时尺寸信息过期归一化坐标就会整体错误。 解决在转换脚本里用OpenCV读取真实图片尺寸并以真实尺寸为准重新计算坐标。出现不一致的文件优先重新整理图片不要用脚本掩盖问题。反向校验法随机抽20张比对坐标能在五分钟内暴露这类错误。5.2 烟雾框过大模型只学大目标小火苗全漏现象训练出来的模型对整片烟雾的检测很好但单独一簇小火苗就是检不出来验证集里AP_l高、AP_s低呈明显两极分化。 原因烟雾的天然形态是大面积弥散标注框可能占整张图的一半以上。模型反复见到大框回归头会偏向输出宽大的框小目标的回归分支被挤占。 解决在预处理中对框的最大相对面积做限制例如超过整图宽高40%的大框不参与训练或按中心区域裁切更有效的做法是引入copy-paste增强把标注好的小火焰目标随机贴到其他图片上并按比例复制人为增加小目标样本。5.3 验证集按随机帧划分部署到新场景误报率飙升现象训练时验证集mAP超过0.9自我感觉良好换到另一个机位的视频后误报多到无法接受。 原因监控视频连续帧高度相似随机划分会让同一场景的帧同时落入训练集和验证集指标高是“背答案”的结果模型对新场景几乎没有泛化能力。 解决按视频时间段或场景分组重新切分。如果原数据集没有提供场景id就从文件名的时间戳或目录结构去推断宁可让训练集少几百张也要保证验证集来自不同视角。5.4 标签名大小写与别名混用类别映射无法对齐现象转换脚本统计类别数时突然出现第3个类名或者fire对应的标签文件行数明显偏少。 原因原始标注里同一类别可能同时存在“smoke”“Smoke”“fire_1”等多种写法。转换脚本里的LABEL_MAP只处理了两个名字其他名字被当成未知类别跳过或归入错误类别。 解决转换前先对所有xml的name字段做一次排序统计写一个类别归一化函数把别名统一映射到唯一英文标签再进入转换脚本。这一步在重制版数据集上通常已经做过但你拿到的版本未必规范仍然需要验证。5.5 有火又有烟的图像中明火被标成烟现象模型对独立火焰的检测不错但只要火焰周围有烟雾预测框就偏向烟雾方向甚至把火焰框成烟雾。 原因在“烟包火”的场景中部分标注框的边界是模糊的标注员可能把明火的框拉大盖住了周围冒出的烟训练时模型学到的其实是两个类别混叠后的目标。 解决把同时包含两个类别的图像单独筛出来做成一个“烟火共存难例集”。人眼复核后把明显错误的大框重新标注并将这个难例集作为验证集的固定组成部分让指标真实反映最困难的场景。6. 进阶难例句挖掘与二次标注把6460张的每一张用到极致数据集的价值不是一次训练就能榨干的。我在烟火项目上的一个习惯是跑完第一版基线后立刻做一轮难例句挖掘用模型自己找出训练数据里“让人类难判断、让模型难学习”的样本再针对它们做一次小规模补充标注和增量训练。这个流程对原始标注质量一般的场景特别有效本质上是用模型的注意力帮你做一次分级质检。难例句挖掘的核心逻辑很简单用训练好的模型对训练集做一次推理把置信度落在中间区间的框挑出来连同对应原图区域一起导出让人眼快速判断是漏标、错标还是模型错误。下面这段脚本示意了筛选逻辑import json from pathlib import Path # 读取模型预测结果results.json保留置信度0.3~0.7的框 results json.loads(Path(results.json).read_text()) candidates [] for item in results: for det in item[detections]: conf det[confidence] if 0.3 conf 0.7: candidates.append({ img: item[image], box: det[box], conf: conf }) # 按键导出为小图按置信度从低到高排序供人工复核 candidates.sort(keylambda x: x[conf]) for idx, cand in enumerate(candidates): print(idx, cand[img], cand[box], round(cand[conf], 3))筛选出来的候选下一步分两类处理一类是模型预测框正确但原始xml确实漏掉的补充到训练集里模型召回率通常会立刻上一个台阶另一类是模型在背景区域误报的比如把夕阳余晖、红色车身当成火这一类我会把它们作为负样本或额外标注的背景难例加入训练压制假阳性。对于“烟包火”的错标大框则直接做标注修正不要试图用增强去掩盖。二次标注的规模不需要很大6460张的基数上每次精选出两三百个难例做修正增量训练一轮往往就能看到mAP在小目标和共存场景上的提升。这也是我认为“重制版”这类数据集最值得做深的地方它是地基但模型的上限取决于你对难例的理解和反馈。今天写这份笔记也是因为自己曾经直接拿数据集开训最后在真实监控画面上栽了跟头才养成了先体检、再训练、后挖掘的习惯。希望帮到你。本文还有配套的精品资源点击获取