简介YOLO汽油泄露检测数据集是一套面向目标检测任务的真实场景数据集适合计算机视觉学习者、算法工程师及安全监控场景开发者使用。包内包含1000张高质量标注图片采用LabelImg工具精细标注并同时提供VOCxml、COCOjson和YOLOtxt三种标准格式标签分别存放于独立文件夹可直接用于YOLO系列模型训练。资源总计2000个文件以xml标签、txt标签、Python脚本、yaml配置及HTML教程为主压缩包大小约123.63MB。除数据外还附赠YOLO环境搭建含Linux/Windows与训练案例教程、数据集划分脚本支持按需求自动切分训练集、验证集和测试集并生成ImageSets下的索引文件方便快速开展实验。目前已有255人浏览学习适合需要规范数据集练习YOLO检测流程或开展泄露检测落地项目的读者。1. 汽油泄露的视觉检测难在哪数据问题先于模型问题做加油站、化工厂罐区、输油管道附近的视觉监测很多人第一步就栽在数据集上。拿通用目标检测模型直接去跑现场画面水泥地上的暗色油渍、柏油路的黑斑、雨后地面的反光全都会被当成候选框报出来。误报一多值班人员直接把报警关了系统就成了摆设。这种安全场景要的是稳定和极低误报而模型在训练时没见过多少真实负例光靠调结构、调阈值根本救不回来。这也是「YOLO汽油泄露检测数据集」这类资源被反复讨论的原因1000张图对应VOC、COCO、YOLO三份标签外加划分脚本和训练教程等于把「从零跑通一个泄露检测模型」的最小路径打包好了。它适合先用几天时间做可行性验证的团队也适合拿来当算法基线把精力集中到数据补采和误报治理上。这包东西解决的从来不是模型创新问题而是开局问题让你第一天就能把YOLO训练起来再谈现场好不好用。2. 拆数据包三份标签的文件级差异与动手前两个检查点2.1 三种标注格式的文件形态与目录结构拿到这类数据包我一般会把三份标签当成三种不同的「协议」来看而不是先急着跑训练。VOC格式长什么样每一张图对应一个同名xml文件标注框用xmin/ymin/xmax/ymax四个像素坐标表示类别写在object/name字段里。COCO格式相反全部图片的标注集中在一个json文件里通过image_id关联图片框的表示是[x, y, width, height]像素坐标类别用整数category_id表示。YOLO格式则是每张图同名txt每行一条标注第一列是类别ID后面四列是归一化后的中心点x、中心点y、宽、高。为什么同一个数据集要准备三份因为不同训练框架读取标签的入口不一样。YOLO系列直接读txt省得训练时现转基于检测框架的很多旧项目习惯用VOC部分新框架、或者要跑评测基准时更认COCO。三份标签让模型框架的选择不被卡住这是一份资源做得完整的标志。项目VOCxmlCOCOjsonYOLOtxt存储方式每张图一个同名xml所有标注集中在一个json每张图一个同名txt坐标含义xmin/ymin/xmax/ymax 像素bbox 为 x,y,width,height 像素x_center, y_center, w, h 归一化类别表示object.name 字符串category_id 整数每行第一列整数读取特点逐文件解析载入后按image_id聚合逐行解析目录结构方面常见排布是images/放图片Annotations/放VOC的xmlannotations/下放COCO的jsonlabels/放YOLO的txt再附一个划分脚本和训练教程文档。解压之后先对照这个结构过一遍确认文件命名前缀能不能一一对上这一步不值钱但能省掉后面整夜的排查。2.2 先跑一遍数据审计清掉缺标签和空标签动手训练前我会先用脚本把数据资产审计一遍。核心就两个问题图片和标签能不能对得上、标签里到底有哪些类别。很多时候从网上下载的资源或同事转交的数据包会有零星几张图没标签训练时直接报错或者被跳过看起来是小问题实际会影响划分比例和类别统计。下面这个脚本针对YOLO目录做一次快速核对import json import os image_dir images label_dir labels # 1) 检查图片与标签是否一一对应 for img_name in os.listdir(image_dir): stem os.path.splitext(img_name)[0] txt_path os.path.join(label_dir, stem .txt) if not os.path.exists(txt_path): print(no label:, img_name) # 2) 检查是否有空标签 for txt_name in os.listdir(label_dir): txt_path os.path.join(label_dir, txt_name) if os.path.getsize(txt_path) 0: print(empty label:, txt_name)脚本逻辑很直白先按图片文件名找同名txt找不到就把图片名打出来再遍历labels目录把大小为0的txt列出来。YOLO训练时空标签文件不一定会报错但会让模型把这个样本当成纯背景进行反向传播干扰程度取决于空标签数量。COCO目录则单独检查jsonwith open(annotations/instances_train.json) as f: coco json.load(f) print(categories:, [c[name] for c in coco[categories]]) print(images:, len(coco[images])) print(annotations:, len(coco[annotations]))COCO的json结构里categories数组能直接看到类别名和ID的映射关系annotations的条数如果远小于预期说明部分图片的标注缺失了。这一步做完数据包的基本健康状况就有数了。2.3 单类别小数据也要看的类别平衡与负样本占比这1000张图是单类别还是多类别直接影响后续策略。汽油泄露检测通常就一个正类gas_leak但目录里可能混进了少量其他类别的历史标签比如water、shadow之类。我一般会统计每个类别ID在总标签里出现的次数如果次要类别占比低于5%会直接把它剔除只留主类别避免模型在1000张的小数据上学到一个几乎没出现过几次的类白占参数。更值得关注的是负样本比例也就是一张图里完全没有任何泄露框的「纯背景图」占比。工业场景里监控画面绝大多数时间都是正常的模型上线后看到的几乎全是负样本如果训练集里全是正样本模型会把「地面有点暗」当作泄露。实操中建议保留20%到30%负样本图或者额外采集一些同场景无目标画面放进去。这份数据包如果没有专门给负样本可以在补采时按这个比例配效果会比盲目加增强更明显。3. VOC、COCO、YOLO互转坐标换算与四个边界坑3.1 把VOC像素框换算成YOLO归一化框的公式三份标签的核心差异是坐标系。VOC的xmin, ymin, xmax, ymax是像素坐标左上角为原点YOLO要求中心点坐标和宽高都除以图像宽高变成0到1之间的小数。换算关系并不复杂x_center ((xmin xmax) / 2) / img_width y_center ((ymin ymax) / 2) / img_height w (xmax - xmin) / img_width h (ymax - ymin) / img_height第1章开头写了这里再强调下最容易出错的地方图像宽高必须从xml的size节点读取而不能拿文件名去猜。很多图片的宽高并非统一尺寸转出来自然不对更隐蔽的是部分xml里xmax写的是右下角坐标加一即开区间表示法转换后框会整体偏移一到两个像素。单类检测里这一两个像素影响不大但如果后续做精细分割或小目标检测会出现边界对齐问题。另外VOC里有的框边界会略微超出图像范围转换时要做clip把归一化值限制在0到1之间。3.2 VOC转YOLO的最小转换脚本下面这个脚本是我常用的转换模板只依赖Python标准库不引入额外依赖import os import xml.etree.ElementTree as ET CLASS_MAP {gas_leak: 0} # 类别名到YOLO类ID的映射 def voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() # 图像宽高必须从xml的size节点读不能用固定值 size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h # 越界保护泄露标注偶尔会把框画出图外 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) w min(w, 1.0) h min(h, 1.0) lines.append(f{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if not lines: return False # 空标签不生成txt避免训练干扰 txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) return True逻辑说明先定位xml里object的name字段命中类别映射才处理再从bndbox读四个坐标按第一节的公式换算成YOLO格式。参数说明CLASS_MAP按你自己数据包的类别去改如果三份标签里主类别ID不是0这里默认的{gas_leak: 0}需要调整成与COCO json的categories数组顺序一致越界clip的作用是防止某些框角点落在图像外的标注在后续训练中产生负数坐标导致loss异常。3.3 COCO转YOLOcategory_id减一与bbox偏移COCO json转YOLO有另外两个硬性差异。第一COCO的category_id从1开始计数而YOLO的类别ID从0开始转换时要做一次映射减一。第二COCO的bbox是左上角坐标加宽高的方式即[x, y, width, height]中心点计算时要在x上加width/2。下面是一段可直接套用的转换核心import json import os def coco_to_yolo(json_path, out_dir): with open(json_path) as f: data json.load(f) # 将COCO的category_id映射为YOLO类ID cat_map {cat[id]: i for i, cat in enumerate(data[categories])} img_info {img[id]: img for img in data[images]} anns_by_img {} for ann in data[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) for img_id, img in img_info.items(): w, h img[width], img[height] lines [] for ann in anns_by_img.get(img_id, []): x, y, bw, bh ann[bbox] if bw 0 or bh 0: continue cx (x bw / 2) / w cy (y bh / 2) / h nw bw / w nh bh / h lines.append(f{cat_map[ann[category_id]]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) img_name img[file_name] txt_path os.path.join(out_dir, os.path.splitext(img_name)[0] .txt) with open(txt_path, w) as f: f.write(\n.join(lines))逻辑说明先构建image_id到图片信息的字典再把所有标注按image_id聚合最后逐图组合成YOLO行。参数说明cat_map保证了即使COCO里类别ID不连续也能正确转换bw 0的过滤是为了跳过那些在标注过程中被误操作成空框的坏标注。COCO json里有时还带着segmentation多边形字段转YOLO时直接忽略即可YOLO的txt只吃矩形框。3.4 转完必须做可视化校验别只看坐标数值转换脚本跑完我一眼都不会信输出的txt因为坐标数值对不代表框的位置对。归一化之后人眼已经没法直接从数字判断框是否贴合目标必须把框画回图上抽查。做法是随机抽20到30张图读txt把归一化坐标乘回图片宽高用OpenCV画矩形然后保存import cv2 import os colors [(0, 0, 255)] def draw_yolo_txt(img_path, txt_path, out_dir): img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: parts line.strip().split() if len(parts) 5: continue _, cx, cy, bw, bh parts cx, cy, bw, bh float(cx) * w, float(cy) * h, float(bw) * w, float(bh) * h x1, y1 int(cx - bw / 2), int(cy - bh / 2) x2, y2 int(cx bw / 2), int(cy bh / 2) cv2.rectangle(img, (x1, y1), (x2, y2), colors[0], 2) cv2.imwrite(os.path.join(out_dir, os.path.basename(img_path)), img)这步能暴露三种问题框整体偏移说明坐标换算有系统误差框明显偏大或偏小说明VOC的坐标语义理解错了完全没有框说明该图标签缺失后面训练时要补。可视化校验是转换流程里最便宜的质量保障一定要做。如果觉得OpenCV环境装了麻烦也可以直接用标注工具打开几张图核对效果一样。4. 划分脚本与训练教程先切数据再跑YOLO4.1 划分脚本核心逻辑随机种子与三目录输出数据包里带的划分脚本功能上应该做的是同一件事把图片和对应标签按比例切成 train / val / test 三份。这类脚本我自己也写过很多遍核心就三块收集文件名、固定随机种子做shuffle、按比例复制或移动文件。下面是一个针对YOLO目录结构的最小版本import os import random import shutil SRC_IMAGES images # 原始图片目录 SRC_LABELS labels # 对应YOLO标签目录 OUT split # 输出根目录 RATIO [0.7, 0.2, 0.1] # train / val / test SEED 42 names [os.path.splitext(f)[0] for f in os.listdir(SRC_IMAGES)] random.Random(SEED).shuffle(names) train_n int(len(names) * RATIO[0]) val_n int(len(names) * RATIO[1]) parts { train: names[:train_n], val: names[train_n: train_n val_n], test: names[train_n val_n:], } for part, items in parts.items(): img_out os.path.join(OUT, part, images) lbl_out os.path.join(OUT, part, labels) os.makedirs(img_out, exist_okTrue) os.makedirs(lbl_out, exist_okTrue) for name in items: shutil.copy(os.path.join(SRC_IMAGES, name .jpg), img_out) shutil.copy(os.path.join(SRC_LABELS, name .txt), lbl_out)逻辑说明先把所有图片文件名取出按固定seed打乱再按比例切三段。参数说明SEED 42保证每次运行结果一致方便复现如果你上次实验跑得好想完全还原划分这个seed必须保持不变RATIO可以根据数据量调整小数据我更建议7:2:1而不是8:1:1因为1000张图里 val 拿200张做验证比较稳代码里shutil.copy是复制不是移动原图保留一份防止划分出错后没法找回。扩展名.jpg要实际看一下你们的图片是jpg还是png改成一致即可。4.2 比例与分组小数据集优先按场景切分随机shuffle在图片没有序列相关性时没问题但泄露检测的素材往往来自少量几段监控视频同一段视频里相邻帧的背景、光照、角度几乎一样。如果只按文件名随机切同一段视频的相似帧会同时出现在train和val里val指标会虚高模型到了陌生场景马上现原形。针对这种情况我建议按场景分组后再划分而不是按单张图。操作方法每个文件名去掉末尾的帧编号前缀保留场景标识例如site3_00231.jpg的前缀site3就是场景ID。先对场景ID列表做shuffle再把同一个场景的所有帧划到同一分区。这样val里出现的是整段没见过的场景mAP会更接近真实水平。分组划分会让val损失一些「同分布」优势但换来的是对泛化能力更诚实的估计安全检测项目值得这么干。4.3 YOLO最小训练命令gas.yaml与五个必调参数数据划分完下一步是组织YOLO训练。首先要写一个数据配置文件YOLOv8的格式如下path: /absolute/path/to/split train: train/images val: val/images test: test/images nc: 1 names: [gas_leak]path建议写绝对路径因为有些训练脚本在相对路径下会找不到数据集nc是类别数单类就写1names的列表顺序必须和标签txt里的第一列ID一一对应。配置写好后训练命令就是一行yolo detect train \ modelyolov8s.pt \ datagas.yaml \ epochs120 \ imgsz640 \ batch16 \ patience30 \ close_mosaic10 \ projectruns \ namegas_leak_s参数说明modelyolov8s.pt是带COCO预训练权重的s模型1000张图的小数据集从s起步最稳n模型更快但对小目标特征提取弱一点epochs120对小数据足够了配patience30做早停通常70到90轮就会收敛imgsz640是默认输入尺寸如果泄露区域普遍很小建议改imgsz960代价是显存和训练时间增加一半左右batch16在12GB以上显存能跑8GB显存改成8close_mosaic10表示最后10轮关闭mosaic增强让模型回归真实分布。这里特别说明下数据集里的训练教程如果是基于旧一点的流程结构也是这四个必调项模型尺寸、输入尺寸、批次、早停耐心值照着这个思路调就不会跑偏。4.4 训练产物的快速检查训练结束看目录里产出的文件重点看三样东西。results.png里有loss曲线和mAP曲线如果mAP50-95一直横盘不动说明模型容量或数据有问题不是epochs不够。confusion_matrix.png能看到背景被误判成目标的量这会直接影响误报率。val_batch_pred.jpg最直观直接展示模型在一批验证图上的预测框我会专门看小目标有没有被框住、框有没有明显偏移。best.pt 和 last.pt 两个权重文件要用best来推理last是最后一轮的断点不是最优结果。训练完先别急着部署把best.pt拿到测试目录上跑一轮预测用yolo predict modelruns/gas_leak_s/weights/best.pt sourcesplit/test/images save_txtTrue生成所有测试图的预测txt再人工数一遍漏检和误检。这一步相当于给模型做一次小考通过后再谈现场。5. 避坑汽油泄露检测训练最常翻车的五个点5.1 沥青地面的黑色斑块被误检成泄露现象白天测试时模型把沥青路面上的裂缝、深色污渍、积水阴影都框出来了置信度还不低。原因正样本里泄露区域的油渍也是深色块模型学到的区分特征是「深色区域」而不是「油渍质感」负样本不足导致决策边界太宽。解决做一轮难例挖掘把验证阶段误检的bbox截图直接从原图crop出来作为背景图加入训练目录标签文件留空。这样模型能看到更多「像油渍但不是油渍」的样本误报会明显下降。如果加了背景图还压不住就把后处理置信度阈值从默认0.25调高到0.4再测但阈值只能缓解根治还是要靠负样本。5.2 白天收敛很好夜间几乎全挂现象白天画面的mAP50有0.9以上傍晚和夜间现场推理时漏检大量增加暗光区域还会冒出奇怪误报。原因训练数据的光照分布高度集中在白天低照度样本占比太低模型没学到暗光下的目标外观夜间图像噪声模式也和白天完全不同这些差异是亮度增强模拟不出来的。解决优先补采夜间和黄昏数据哪怕只有一两百张做微调都比纯增强强如果补采条件受限用亮度对比度扰动、gamma变换模拟低照度把白天图压暗作为增强项。只要是真实安全项目夜间数据这一关绕不过去越早规划越好。5.3 小目标漏检泄露区域只有十几个像素现象管线刚出现渗漏时油渍往往只是画面里一块几十像素的暗斑模型直接忽略同一个目标在白天大图上又能框住。原因输入640分辨率经过多次下采样后小目标的特征只剩几个像素信息基本丢失训练时小目标框参与loss计算的权重也偏低。解决把imgsz提到960或1280如果显存撑不住就用切片推理思路把输入图按重叠区域切块分别检测再合并常见做法是用SAHI这类工具也可自己在外围写个滑窗逻辑另一个很有效的办法是在标注时把小目标周围的上下文轮廓一起框进去让模型学到「细长暗痕」这类形态特征。5.4 翻转增强把液体重力方向改了现象开启水平翻转和垂直翻转增强后验证mAP不升反降尤其对罐壁、斜坡这类有明确方向性的画面。原因垂直翻转会让液体流向往天上流模型学到的是与物理方向矛盾的外观先验水平翻转对部分场景同样有语义干扰比如从左侧流向右的细长油痕翻转后和真实分布相反。解决这类安全检测场景翻转增强要谨慎。我一般只保留轻微随机尺度、亮度、饱和度扰动关闭水平和垂直翻转mosaic增强也降到很低权重或只在早期开几轮因为多图拼接会让液体纹理形态变得不真实。工业数据集小而专别盲目照搬通用检测那套大增强配置。5.5 mAP很高但现场没有参考价值现象val的mAP50高达0.95一上线到现场连续误报被现场人员吐槽成「空气检测器」。原因最典型的是数据泄漏训练集和val/test来自同一段视频帧序列模型等于开卷考试另一个原因是验证图像和真实现场存在域差比如相机安装高度、角度、画幅不同。解决用第4章的方法按场景分组重新划分让val完全由模型没见过的场景组成再拿一段真实现场视频做外推验证统计误报数。如果重训后mAP明显下降别慌那才是模型真实水平的底在现场把硬件位置固定后尽量采集与部署视角一致的数据再迭代一轮。6. 交付前验证mAP之外三个指标与现场外推模型训练完先别急着写「效果很好」的总结泄露检测这类安全场景验收方真正关心的是误报和漏报不是mAP分数。mAP是模型之间的对比指标现场看的是行为指标我会在最后阶段固定测三类数值召回率、每小时误报次数、单帧延迟。指标度量方式关注点召回率在困难样本集上数漏检框泄露检测宁可误报不能漏报每小时误报连续跑一段现场视频统计误报框折算现场人员容忍度有限误报多会关系统单帧延迟包含预处理与后处理的单帧推理耗时决定能不能接实时视频流外推验证的具体做法找一段从来没进过训练集的监控视频直接跑yolo predict modelbest.pt sourcenever_seen.mp4逐帧过一遍把误报帧数和漏检帧数记下来。我一般会拉上现场同事一起看预测结果因为只有他们知道画面里哪些区域是正常的油污、哪些是真实的危险泄露。如果条件允许再做一次小规模现场mock在可操作的空地上用少量机油和清水各倒一小片用部署相机拍几十帧作为从未参与训练的真实样本。机油在光照下会有虹彩干涉色这是和清水、沥青阴影的重要视觉差异模型如果能从这片小样里框出机油而对清水无感才说明它学到了油渍的本质特征而不是单纯在找暗色区域。做mock实验时注意安全和环保规范少量液体在可控容器内操作即可不要直接倒在下水道附近。最后调阈值时我会按F1曲线选一个偏低的置信度比如0.15到0.25之间保证召回率优先同时后端加帧级判定连续三五帧命中同一个区域才触发报警能有效削掉单帧抖动造成的误报。这样阈值可以放低最终用户看到的误报次数还是可控的。有一点我一直记得之前有个项目白天指标漂亮跑夜间现场才发现训练集的夜间图片占比不到3%后来把时段划分、场景分组和现场mock写成了固定流程每次交付前先跑这三步验证再谈上线。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?