海面石油泄漏检测数据集详解:VOC/YOLO格式转换到YOLOv8训练全流程
简介这是一套面向海面石油原油泄漏场景的目标检测标注数据集适用于环境监测、遥感图像分析以及计算机视觉检测算法研发的工程师和研究人员。数据围绕约1800张海上溢油实景图像整理而成统一标注为单一类别共汇总3871个泄漏目标框由于单张图像常出现多个溢油位置数据分布比常规单目标数据集更贴近真实应急监测场景。标注均通过labelImg完成并提供VOC与YOLO两种标准格式可直接接入主流检测框架免去从零标注与格式转换的繁琐过程。资源包共2000个文件以XML标注文件和TXT标注文件为主体前者记录目标类别与边界框位置后者为归一化的模型输入格式整包压缩后约79.05MB命名规整并附带说明文档便于脚本按编号批量读取。已有283人学习下载适合需要快速获得高质量海上漏油样本、用于模型训练、对比验证或环境监测预研项目的开发场景。1. 海面石油泄漏检测数据集1800张图能解决什么问题做目标检测的人第一次看到「海面石油原油泄漏检测数据集1800张VOCYOLO格式.zip」这个标题第一反应多半是海面油污和常规的车辆、行人检测完全是两个世界。原油泄漏在海面上呈深褐色至黑色的浮油带边界模糊、形状不规则还会被海浪打散成碎片光线从不同角度照到油膜上反射强度一会儿高一会儿低模型很容易把云影、海浪泡沫和真正的油污混在一起。这个数据集的定位正是把最难凑的一类视觉样本——海上溢油、近岸排污、码头油污——整理成可直接拿来训练检测模型的格式VOC的XML标注和YOLO的txt标注同时给你省掉你自己写转换脚本或手动标注的功夫。它适合两类人一类是刚入门目标检测、想找一个真实场景练手的学生或转行者拿它跑通YOLO训练全流程再合适不过另一类是做海事监管、环境监测、遥感解译的工程师手头缺带标注的溢油影像拿它做模型预训练、再在自有数据上微调能省下大量标注时间。1800张图不算大但胜在标注格式齐全、任务指向明确。接下来我先把数据本身掰开看再给你完整的训练路径和避坑清单。2. 读懂VOC与YOLO双格式目录结构、标注内容和数据验收方法2.1 VOC格式的底子JPEGImages、Annotations和ImageSets缺一不可拿到压缩包解压后第一步不是急着训练而是把目录结构摸清楚。VOC格式的骨架是三个核心目录JPEGImages放原始图片Annotations放与图片同名的XML标注文件ImageSets/Main放训练集、验证集、测试集的划分文件。很多人在这一步会踩坑只看图片和XML忽略ImageSets结果自己随机划分数据导致同一张图既进训练集又进验证集mAP虚高得离谱。所以先确认压缩包里有没有那三个txt文件——train.txt、val.txt、test.txt。打开一张XML标注文件你会看到典型的Pascal VOC结构filename对应图片名size给出宽高object里包含name、difficult和bndbox的xmin、ymin、xmax、ymax。对海面溢油检测来说name通常是oil_leak或oil_spill这种单一类别也可能附带船舶、海岸线等其他类。这里有个细节值得注意difficult标记是否被跳过如果数据集中大量目标都标成difficult1那训练时模型会忽略这些样本相当于数据量变相缩水。2.2 YOLO格式的精髓归一化坐标和类别IDYOLO格式和VOC的差别不只是txt与XML的区别核心在坐标表达方式。YOLO每一行是类别ID x_center y_center width height前四个值全部除以图片宽高做归一化取值范围0到1。比如一张1280x720的图上有个目标中心点在(640, 360)框宽320、高240那么txt里那行就是0 0.5 0.5 0.25 0.333333。这个换算很容易出错尤其是整数除法和浮点数精度问题后面我会专门给转换脚本。有效数据集另外需要在意图片和标注的对应关系。检查有没有没有XML的孤儿图片、没有图片的孤儿XML、以及XML里坐标超出图片边界的异常框。我一般会写一个十几行的小脚本做一致性校验比对图片文件名和XML文件名是否一一对应再检查bndbox是否超出图像尺寸。这是整个流程里最枯燥但最有价值的一步能省下训练到一半才发现数据错乱的后悔药。2.3 数据验收的三个维度类别分布、目标尺寸和场景多样性1800张图是多是少取决于目标尺度和场景分布。海面溢油有个天然特点目标又大又稀疏。一张航拍图里油污可能占据半个画面也可能只是几根细长的漂浮带而近岸监控摄像头拍到的油污则可能只有几十个像素。这两种极端下模型的锚框设计和输入分辨率需求完全不同。拿到数据后建议先统计所有标注框的宽高分布看看中位数是多少、最大值和最小值的比值有多少个数量级。如果跨度太大训练时就要考虑多尺度训练或者把输入分辨率调高到1280甚至1536。场景多样性同样要查。海运航线、港口、近海养殖区、滩涂、开放海域这些场景的视觉特征差别很大晴天、阴天、逆光、有波浪、有船只尾迹这些条件都会影响模型的泛化能力。数据集在整理时未必覆盖所有组合这个缺口你要么通过数据增广弥补要么在后续自有数据上补采。检验方法很土也很快把1800张图全部缩略图拼成一张大图肉眼扫一遍看场景和光照是否单调。2.4 用Python快速跑一遍数据完整性校验import os import xml.etree.ElementTree as ET from PIL import Image img_dir JPEGImages ann_dir Annotations imgs set(os.listdir(img_dir)) anns set(os.listdir(ann_dir)) # 检查孤儿文件 print(无标注的图片:, len(imgs - {a[:-4] .jpg for a in anns})) print(无图片的标注:, len(anns - {i[:-4] .xml for i in imgs})) # 检查坐标越界 for ann in anns: tree ET.parse(os.path.join(ann_dir, ann)) size tree.find(size) w, h int(size.find(width).text), int(size.find(height).text) for obj in tree.findall(object): box obj.find(bndbox) x1, y1 int(box.find(xmin).text), int(box.find(ymin).text) x2, y2 int(box.find(xmax).text), int(box.find(ymax).text) if x1 0 or y1 0 or x2 w or y2 h: print(f{ann} 坐标越界: {x1},{y1},{x2},{y2})这段脚本先在内存里对比图片和XML文件名集合把对不上的孤儿文件直接打印出来然后遍历每个XML拿bndbox的四个值和图片实际宽高比较越界的输出文件名和具体坐标值。参数说明img_dir和ann_dir按你解压后的实际路径改脚本假设图片是jpg后缀如果你的数据集是png或bmp把a[:-4]和i[:-4]替换成对应的切片逻辑即可。跑完出现任何输出都先处理掉再进训练环节。3. VOC转YOLO写一个能直接复用的转换脚本附四个边界坑3.1 坐标换算的正确打开方式绝大多数人不缺转换代码缺的是对转换过程中边界情况的处理。VOC的bndbox是左上角和右下角的绝对坐标YOLO要的是中心点、宽度、高度且归一化。换算公式本身不复杂x_center (xmin xmax) / 2 / widthy_center (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height。但真正容易翻车的是以下三种情况一是某些标注工具生成的XML里bndbox的坐标已经是浮点数再套用整数除法就会丢精度二是目标贴着图片边缘时xmin可能等于xmax意味着标注框宽度为零直接归一化会在后续训练时报错三是类别名的处理VOC里叫oil_leak的类在YOLO的classes.txt里索引必须是确定的数字拼接错位会让整体训练全部作废。3.2 完整转换脚本与逐段说明import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, img_w, img_h, class_dict, output_dir): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_dict: 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) # 防止零宽度/零高度框导致训练崩溃 if x2 x1 or y2 y1: print(f跳过无效框: {xml_path}, {name}) continue # 坐标裁剪处理越界标注 x1 max(0, min(x1, img_w)) y1 max(0, min(y1, img_h)) x2 max(0, min(x2, img_w)) y2 max(0, min(y2, img_h)) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{class_dict[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if lines: txt_path os.path.join(output_dir, os.path.basename(xml_path)[:-4] .txt) with open(txt_path, w) as f: f.write(\n.join(lines))关键参数说明class_dict是类别名到ID的映射例如{oil_leak: 0}这个字典必须和后续训练用的data.yaml一致img_w和img_h可以直接从XML里的size节点读也可以从图片文件读取建议用XML的值因为它就是标注时参考的尺寸round到6位小数是YOLO官方训练时常用的精度足够表达亚像素级框。四个边界坑里零宽高框必须跳过或修正越界坐标必须裁剪类别名不在映射表里的对象必须忽略而不是报错浮点数转换必须用float()而不是int()处理。3.3 转换后怎么快速自检转换脚本跑完后别急着删XML。选三张图把生成的txt和原图叠在一起可视化用OpenCV读原图从txt读出归一化坐标反算回绝对像素坐标画矩形框。这一步能看到坐标是否偏移、框是否贴合油污边缘。更稳妥的办法是把所有txt文件统计一遍类别ID有没有超出类别总数、有没有异常大的width或height比如大于1、行的字段数是不是5个。这些检查我通常会写进同一个脚本里一次性输出可疑样本列表。import cv2 img_path JPEGImages/000001.jpg txt_path labels/000001.txt img cv2.imread(img_path) h, w img.shape[:2] with open(txt_path) as f: for line in f: cid, xc, yc, bw, bh map(float, line.split()) 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), (0, 0, 255), 2) cv2.imwrite(check.jpg, img)这段可视化的核心是按归一化坐标反算绝对坐标xc - bw / 2得到左上角xxc bw / 2得到右下角x再分别乘上图片宽w。括号里的h, w img.shape[:2]拿到的顺序是高度在前宽度在后和后面的* w、* h对应关系别搞反。如果画出来的框和油污区域明显错位先怀疑XML的size和图片实际尺寸不一致这是最容易让人一头扎进黑匣子里的原因。4. 用YOLO训练海面泄漏检测模型从数据划分到训练命令全流程4.1 数据划分不重复、不遗漏、按场景分层数据划分直接决定你验证集的可信度。随机划分虽然简单但海面溢油数据里常有同一场景的连续帧或多角度重复拍摄如果这些相似图被拆到训练集和验证集两边验证指标会被严重高估。我一般会先按场景分组把文件名前缀相同的归为一组然后基于组做划分保证同一场景的图片不会同时出现在两个集合里。比例上按照8:1:1拆成训练、验证、测试1800张图也就是训练约1440张、验证和测试各约180张。如果你的数据集里场景数很少考虑改成7:2:1把验证集做大些模型选型时更有底气。划分完成后的目录组织也有讲究。YOLO训练时推荐把所有图片归到images文件夹下的train、val、test子目录所有标签归到labels文件夹下同样结构的子目录。这样做的好处是后面写data.yaml时路径配置一目了然不会出现训练到一半发现找不到txt的窘境。最终结构长这样dataset/ ├── images/ │ ├── train/ # 约1440张 │ ├── val/ # 约180张 │ └── test/ # 约180张 ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml4.2 编写data.yaml类别名与路径一个都不能错train: ./images/train val: ./images/val test: ./images/test nc: 1 names: [oil_leak]nc是类别数量当前数据集只有一类油污填1names列表里的名字必须和前面转换脚本里的class_dict保持同一套命名否则训练时类别ID错位模型学到的全是错误对应关系。路径写法上我用相对路径的./前缀比绝对路径稳妥换机器或换目录后不用改配置。另外注意test字段在有些YOLO版本里不是必须的写上不影响不写也正常但建议写上训练完顺手做最终测试。4.3 训练命令与关键超参数调法训练这一步我用Ultralytics YOLOv8的命令行来演示这是当前跑这类数据集最省心的选择。先把yolov8n.pt换成你自己下载的权重路径或者干脆不要预训练权重从零训练溢油数据和通用目标差距太大预训练权重只能说聊胜于无。yolo detect train \ modelyolov8n.pt \ datadata.yaml \ epochs200 \ imgsz1280 \ batch16 \ lr00.005 \ patience50 \ projectoil_leak_run \ nameexp1逐项参数说明imgsz1280是这组命令里最重要的调整默认的640对海面油污这种大目标其实够用但如果你的数据里存在细长油带高分辨率能让模型多保留边缘细节lr00.005比默认的0.01低一半因为数据集只有千张级别学习率大了容易震荡patience50表示连续50个epoch验证集mAP没有提升就早停省时间也防过拟合。如果你的显存只有8Gbatch降到8imgsz降到960。RMBG等预训练技巧对这类特殊任务帮助有限别在这上面花太多时间。4.4 训练过程中盯什么指标训练日志里最需要盯的是val/box_loss、val/cls_loss和val/dfl_loss这三条曲线。正常情况是随着epoch增加一路下降然后走平如果出现反弹大概率是学习率没降下来或者数据里有标签错误。早停触发后看best.pt对应的验证集mAP50和mAP50-95前者告诉你框的定位准不准后者对框的精确度更敏感。海面溢油检测更关注mAP50因为油污区域本身就模糊边界框稍微偏差一两个像素不影响实际判断——泄漏监测的核心是别漏报框的精细度是次要的。训练完成后从runs/detect/exp1/weights/里把best.pt拷出来单独对test目录跑一次评估就是最终的交付指标yolo detect val \ modeloil_leak_run/exp1/weights/best.pt \ datadata.yaml \ splittest \ imgsz1280splittest明确指定在测试集上做最终验证而不是复用训练时的val集这一步的意义是拿从没参与过模型选择的数据做终检。输出里的mAP50如果低于0.6先别急着堆模型参数回到数据本身找问题九成原因都在标注或数据划分上。5. 海面溢油检测数据集的常见坑现象、原因与解决办法5.1 标签里混入误检对象模型把海浪泡沫当油污我在第一次跑这个数据集时就遇到过模型在验证集上mAP50有0.78但拿它去检测一段新视频海浪翻滚的白色泡沫区域全被框了出来误报率高得没法用。原因回查时发现数据集中部分图片的标注把高光波纹和反光强烈的碎浪也标成了油污标签本身就带噪。解决办法是把这些疑似误标的样本挑出来重新标注统计所有标注框的纹理特征——油污区域通常是暗色且内部纹理平滑而泡沫区域亮且纹理粗糙——用这个规则粗筛后人工复检最终清掉几十个问题框误报立刻降下来。5.2 训练时Loss为NaN坐标或标签出了鬼训练到十几个epochloss突然变成nan然后mAP全线崩盘。排查路径按顺序走先看训练集的txt标签里有没有非数字字符比如inf或空行再看类别ID有没有大于nc-1最后查有没有width或height为0的框。这类问题八成都出在格式转换阶段前面的校验脚本每一步都能拦住。如果校验脚本没发现异常把batch size调小试试偶尔是显存溢出触发的数值异常。5.3 小目标油污完全检测不到输入分辨率的锅1800张图里有一部分是无人机离海面较近时拍摄的油污只占画面很小一块边长可能只有20到30像素。用640训练时这类目标几乎全部漏检。把imgsz提到1280后漏检率下降了一半还多代价是显存占用翻倍、训练时间拉长。如果你只有8G显存可以把batch降到4把梯度累积打开或者对这类小目标图做滑窗切图再训练效果比盲目换大模型更直接。5.4 验证集mAP很高但实际检测很差数据划分泄漏场景重复是这类数据集最隐蔽的问题。海面溢油的拍摄常常是一段视频连续抽帧同一片油污在相邻帧里只有轻微位移。如果划分时不做场景分组训练集和验证集里会出现大量近似重复的帧模型相当于在验证集上做过“开卷考试”分数虚高一截。解决方法是按视频片段或拍摄时间线做组级划分保证任何一组数据只出现在一个集合里。检查手段也简单随机抽验证集里10张图去训练集里找最相似的图用感知哈希对比相似度高就是泄漏了。5.5 油膜边缘半透明导致标注不一致想要模型学得稳先统一标注口径同一个油污一个人标成规则矩形另一个人紧贴油膜边缘标成不规则多边形检测框的定位标准完全不统一。这直接导致回归损失居高不下。解决办法是定一个标准油膜可见部分全部纳入框内半透明扩散区不纳入标注时统一使用带角度的矩形。这属于管理层面的问题但它的影响会直接反映在模型的最终性能上越早统一越省事。6. 让模型更稳的进阶技巧数据增广、滑窗切图与边缘端部署验证6.1 数据增广的组合别乱加海面溢油检测的增广和通用目标检测不完全一样。HSV扰动建议只调饱和度色相千万别动油污颜色本身就是关键特征动色相等于把标签搞乱上下翻转可以开左右翻转在航拍场景里没问题但如果你的数据大多来自固定朝向的岸基摄像头别开左右翻转旋转增广对油污这种无方向性目标是好东西建议开到30度以内。马赛克增广在YOLOv8里默认开着但溢油目标经常是大块面马赛克把大目标切成碎片会让模型学到错误的上下文如果发现mAP不升反降先关掉mosaic试试。6.2 滑窗切图把大图和细长油带分解成可训练样本海面溢油检测场景里有一种情况是图像尺寸特别大、目标宽度只有几十像素比如遥感影像或高空航拍图。直接整体缩放训练会把油污细节抹掉。滑窗切图是最实用的解法设定一个窗口大小和步长例如窗口960x960、步长480让相邻窗口有50%重叠保证目标尽量不被截断。切出来的子图重新生成标注然后并入训练集。注意切图后子图数量会膨胀好几倍跨场景重复的样本又会被模型反复看到建议从切好的子图里按场景采样控制最终数据量。6.3 落地验证模型好不好要看能不能上设备海面泄漏检测最终大概率要跑在NVIDIA Jetson、工控机或者边缘盒子这类设备上。训练时顺手导出推理格式是必须的一步。我这里给一个最常用的方式——导出ONNX再用ONNXRuntime在模拟的边缘设备上跑推理验证速度yolo export modelbest.pt formatonnx imgsz1280 dynamicTrueformatonnx导出通用格式方便后面转TensorRT或OpenVINOdynamicTrue允许动态输入尺寸实际部署时如果画面分辨率不固定会更灵活。导出后写一个简单的推理脚本测单张图耗时import onnxruntime as ort import numpy as np from PIL import Image sess ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider]) input_name sess.get_inputs()[0].name img Image.open(test.jpg).resize((1280, 1280)) arr np.array(img).transpose(2, 0, 1)[None].astype(np.float32) / 255.0 outputs sess.run(None, {input_name: arr}) print(outputs[0].shape) # 格式: [1, 84, 8400] 之类推理脚本的逻辑是先把图片转成CHW顺序、归一化到0到1之间再丢给ONNX模型。输出形状里第二维的84代表4个框坐标加80个类别的分数——但这个数据集只有1个类实际应该是5加类别数具体看导出时模型头部结构。这一步能直接测量推理延迟如果单帧超过50毫秒就考虑换更小的模型或者降低输入分辨率。我自己的习惯是任何时候都保留一个ONNX版本万一部署环境变了可以直接接上比从头再训练省事得多。说到最后这个数据集的价值不只是让你跑通一次YOLO训练流程而是让你在海面溢油这个特殊视觉场景里建立起一套从数据验收、格式转换、模型训练到部署验证的完整方法论。把1800张图吃透把每一类踩过的坑记下来再做任何垂直场景的目标检测项目你都会比别人少走很多弯路——这也是我把自己做项目时用到的这些步骤写出来的原因。希望帮到你。本文还有配套的精品资源点击获取