简介:这是一套面向海面石油原油泄漏场景的目标检测标注数据集,适用于环境监测、遥感图像分析以及计算机视觉检测算法研发的工程师和研究人员。数据围绕约1800张海上溢油实景图像整理而成,统一标注为单一类别,共汇总3871个泄漏目标框;由于单张图像常出现多个溢油位置,数据分布比常规单目标数据集更贴近真实应急监测场景。标注均通过labelImg完成,并提供VOC与YOLO两种标准格式,可直接接入主流检测框架,免去从零标注与格式转换的繁琐过程。资源包共2000个文件,以XML标注文件和TXT标注文件为主体,前者记录目标类别与边界框位置,后者为归一化的模型输入格式,整包压缩后约79.05MB,命名规整并附带说明文档,便于脚本按编号批量读取。已有283人学习下载,适合需要快速获得高质量海上漏油样本、用于模型训练、对比验证或环境监测预研项目的开发场景。
1. 海面石油泄漏检测数据集:1800张图能解决什么问题
做目标检测的人,第一次看到「海面石油原油泄漏检测数据集1800张VOC+YOLO格式.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>标记是否被跳过,如果数据集中大量目标都标成difficult=1,那训练时模型会忽略这些样本,相当于数据量变相缩水。
2.2 YOLO格式的精髓:归一化坐标和类别ID
YOLO格式和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 / width,y_center = (ymin + ymax) / 2 / height,w = (xmax - xmin) / width,h = (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得到左上角x,xc + 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是类别数量,当前数据集只有一类油污,填1;names列表里的名字必须和前面转换脚本里的class_dict保持同一套命名,否则训练时类别ID错位,模型学到的全是错误对应关系。路径写法上,我用相对路径的./前缀比绝对路径稳妥,换机器或换目录后不用改配置。另外注意test字段在有些YOLO版本里不是必须的,写上不影响,不写也正常,但建议写上训练完顺手做最终测试。
4.3 训练命令与关键超参数调法
训练这一步,我用Ultralytics YOLOv8的命令行来演示,这是当前跑这类数据集最省心的选择。先把yolov8n.pt换成你自己下载的权重路径,或者干脆不要预训练权重从零训练,溢油数据和通用目标差距太大,预训练权重只能说聊胜于无。
yolo detect train \ model=yolov8n.pt \ data=data.yaml \ epochs=200 \ imgsz=1280 \ batch=16 \ lr0=0.005 \ patience=50 \ project=oil_leak_run \ name=exp1逐项参数说明:imgsz=1280是这组命令里最重要的调整,默认的640对海面油污这种大目标其实够用,但如果你的数据里存在细长油带,高分辨率能让模型多保留边缘细节;lr0=0.005比默认的0.01低一半,因为数据集只有千张级别,学习率大了容易震荡;patience=50表示连续50个epoch验证集mAP没有提升就早停,省时间也防过拟合。如果你的显存只有8G,batch降到8,imgsz降到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 \ model=oil_leak_run/exp1/weights/best.pt \ data=data.yaml \ split=test \ imgsz=1280split=test明确指定在测试集上做最终验证,而不是复用训练时的val集,这一步的意义是拿从没参与过模型选择的数据做终检。输出里的mAP50如果低于0.6,先别急着堆模型参数,回到数据本身找问题,九成原因都在标注或数据划分上。
5. 海面溢油检测数据集的常见坑:现象、原因与解决办法
5.1 标签里混入误检对象:模型把海浪泡沫当油污
我在第一次跑这个数据集时就遇到过:模型在验证集上mAP50有0.78,但拿它去检测一段新视频,海浪翻滚的白色泡沫区域全被框了出来,误报率高得没法用。原因回查时发现,数据集中部分图片的标注把高光波纹和反光强烈的碎浪也标成了油污,标签本身就带噪。解决办法是把这些疑似误标的样本挑出来重新标注,统计所有标注框的纹理特征——油污区域通常是暗色且内部纹理平滑,而泡沫区域亮且纹理粗糙——用这个规则粗筛后人工复检,最终清掉几十个问题框,误报立刻降下来。
5.2 训练时Loss为NaN:坐标或标签出了鬼
训练到十几个epoch,loss突然变成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 model=best.pt format=onnx imgsz=1280 dynamic=Trueformat=onnx导出通用格式,方便后面转TensorRT或OpenVINO;dynamic=True允许动态输入尺寸,实际部署时如果画面分辨率不固定会更灵活。导出后写一个简单的推理脚本测单张图耗时:
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张图吃透,把每一类踩过的坑记下来,再做任何垂直场景的目标检测项目,你都会比别人少走很多弯路——这也是我把自己做项目时用到的这些步骤写出来的原因。希望帮到你。
本文还有配套的精品资源,点击获取