☰
海面原油泄漏检测数据集:1800张VOC+YOLO双格式实战指南
2026/9/28 16:38:40 网站建设 项目流程

简介:这份资源面向从事计算机视觉与目标检测的开发者、环保监测研究人员及高校学生,提供海面石油原油泄漏检测的标注数据集,可用于训练和验证油污识别模型,服务于海洋环境监测与应急响应场景。压缩包共2000个文件,以1817个VOC格式xml标注文件和183个YOLO格式txt标注文件为主,另附说明文档,整体约79.05MB,jpg图片与标注一一对应,标注工具为labelImg。数据集仅含oil_spillage单一类别,共3871个标注框,说明部分图像存在多个泄漏点,适合训练多目标检测模型。目前已有283人学习下载。读者可据此直接开展YOLO或Pascal VOC流程的模型训练,省去自行采集与标注成本,并借助多框样本提升模型对复杂海面场景的识别能力。

1. 海面原油泄漏检测数据集:1800 张 VOC+YOLO 双格式到底能干什么

海上溢油事故的应急响应窗口通常只有几个小时,卫星和无人机拍回来的影像如果靠人眼逐帧筛查,等发现油膜扩散边界时,清污船已经错过了最佳拦截位置。海面石油原油泄漏检测数据集 1800 张 VOC+YOLO 格式,解决的就是这个环节——它把「海面油膜」当作一个标准的目标检测任务来标注,让你能直接拿 YOLO 系列模型训练出一个自动圈出油膜区域的检测器。这个数据集同时提供 VOC 的 XML 标注和 YOLO 的 TXT 标注,意味着你不需要自己写格式转换脚本,Pascal VOC 那套老工具链和 YOLOv5/v8/v11 的训练管线都能直接吃进去。适合谁?做海洋遥感监测的算法工程师、环境应急方向的研究生、以及想拿一个真实场景数据集练手目标检测全流程的开发者。1800 张不算大,但海面溢油这个场景的类内差异极大——薄油膜、厚油块、乳化带、太阳耀斑干扰、云影误判——这些恰恰是通用数据集给不了的边界样本。

2. 拆开这个数据集:VOC 与 YOLO 双格式的目录结构和标注逻辑

2.1 拿到压缩包先看什么:目录树与文件命名规律

解压之后,你大概率会看到类似这样的结构(不同整理者命名习惯略有差异,但核心目录逃不出这几类):

oil_spill_dataset/ ├── JPEGImages/ # 所有 jpg 原图,1800 张左右 ├── Annotations/ # VOC 格式 XML,与 JPEGImages 一一对应 ├── ImageSets/ │ └── Main/ # train.txt / val.txt / test.txt 划分文件 ├── labels/ # YOLO 格式 txt,与图片同名 └── data.yaml # YOLO 训练配置文件

先做三件事:数图片数量、数 XML 数量、数 labels 数量。三者不一致是数据集最常见的翻车点。用一条命令就能查:

# 分别统计三个目录的文件数,确认是否对齐 echo "images: $(ls JPEGImages/*.jpg 2>/dev/null | wc -l)" echo "xml: $(ls Annotations/*.xml 2>/dev/null | wc -l)" echo "labels: $(ls labels/*.txt 2>/dev/null | wc -l)"

如果 labels 比 images 少,说明有图片没有对应的 YOLO 标注,训练时 YOLO 会把这些图当作「无目标」背景图处理,少量可以接受,大量缺失就必须排查。如果 XML 比 images 多,通常是标注后删了原图但忘了删 XML,需要清理。

2.2 VOC XML 里到底存了什么:读懂 bndbox 的四个坐标

VOC 格式的标注核心在<object>节点里,一个海面油膜目标对应一个<object>:

<annotation> <folder>JPEGImages</folder> <filename>oil_00342.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>oil_spill</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>412</xmin> <ymin>236</ymin> <xmax>1187</xmax> <ymax>803</ymax> </bndbox> </object> </annotation>

xmin/ymin/xmax/ymax是像素绝对坐标,原点在左上角。difficult=1表示这个目标难以识别(比如被浪花遮挡的薄油膜),训练时可以选择忽略。truncated=1表示目标被图像边缘截断。海面溢油场景里,油膜经常延伸到画面外,truncated标记的正确性直接影响模型对边界目标的回归质量。

2.3 YOLO TXT 的归一化坐标:为什么你的框总是偏

YOLO 格式每行是class_id x_center y_center width height,全部归一化到 0~1:

0 0.4164 0.4810 0.4036 0.5250

换算关系是:x_center = (xmin + xmax) / 2 / img_width,width = (xmax - xmin) / img_width。这里最容易踩的坑是——XML 里的size宽高和图片实际像素尺寸不一致。有些数据集整理时做了缩放但没更新 XML,导致转换出来的 YOLO 框整体偏移。验证方法很简单:

import cv2 import xml.etree.ElementTree as ET def check_annotation_consistency(img_path, xml_path): """检查 XML 中记录的尺寸与图片实际尺寸是否一致""" img = cv2.imread(img_path) h, w = img.shape[:2] tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') xml_w = int(size.find('width').text) xml_h = int(size.find('height').text) if xml_w != w or xml_h != h: print(f"[不一致] {img_path}: 图片({w}x{h}) vs XML({xml_w}x{xml_h})") return False return True

这个检查脚本建议在训练前对全部 1800 张跑一遍,输出所有不一致的文件列表。如果只有零星几张,直接剔除;如果大面积不一致,说明整个数据集的 XML 尺寸字段不可信,需要以图片实际尺寸为准重新生成 YOLO 标签。

2.4 类别定义与 data.yaml:一个类别也要写对

海面溢油检测通常是单类别任务,data.yaml长这样:

path: ./oil_spill_dataset train: ImageSets/Main/train.txt val: ImageSets/Main/val.txt nc: 1 names: ['oil_spill']

nc是类别数,names是类别名列表。注意 YOLO 的class_id从 0 开始,如果你的 TXT 里出现了1而nc=1,训练会直接报索引越界。有些数据集整理者把「油膜」和「疑似油膜」分成两类,那nc就要改成 2,names也要对应。拿到数据集第一件事就是确认类别数和 TXT 里的最大 class_id 是否匹配。

3. 从 VOC 到 YOLO:转换脚本、划分策略与三个必调参数

3.1 自己写一个可靠的 VOC→YOLO 转换脚本

虽然数据集号称双格式,但实际拿到的 YOLO 标签未必和 VOC 完全对齐。我一般会自己跑一遍转换,确保可控:

import os import xml.etree.ElementTree as ET import cv2 def voc_to_yolo(xml_dir, img_dir, out_dir, class_map): """ xml_dir: XML 标注目录 img_dir: 图片目录(用于读取实际宽高) out_dir: 输出 YOLO txt 目录 class_map: {'oil_spill': 0} 类别名到 id 的映射 """ os.makedirs(out_dir, exist_ok=True) skipped = [] for xml_file in os.listdir(xml_dir): if not xml_file.endswith('.xml'): continue xml_path = os.path.join(xml_dir, xml_file) img_name = xml_file.replace('.xml', '.jpg') img_path = os.path.join(img_dir, img_name) if not os.path.exists(img_path): skipped.append(img_name) continue img = cv2.imread(img_path) h, w = img.shape[:2] 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_map: continue # 跳过 difficult 目标,避免噪声标签干扰训练 difficult = obj.find('difficult') if difficult is not None and int(difficult.text) == 1: continue bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 裁剪到图像边界内,防止坐标越界 xmin = max(0, min(xmin, w)) xmax = max(0, min(xmax, w)) ymin = max(0, min(ymin, h)) ymax = max(0, min(ymax, h)) if xmax <= xmin or ymax <= ymin: continue x_center = (xmin + xmax) / 2.0 / w y_center = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{class_map[name]} {x_center:.6f} {y_center:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, xml_file.replace('.xml', '.txt')) with open(out_path, 'w') as f: f.write('\n'.join(lines)) print(f"转换完成,跳过 {len(skipped)} 张无对应图片的 XML") return skipped voc_to_yolo('Annotations', 'JPEGImages', 'labels_new', {'oil_spill': 0})

几个关键处理:difficult=1的目标直接跳过,海面溢油里这类目标通常是浪花遮挡的碎油膜,强行训练会让模型学到噪声;坐标裁剪到图像边界内,防止个别标注越界导致 YOLO 训练时 loss 爆炸;空标签文件也要生成,YOLO 会把空 txt 当作纯背景图,这对抑制误检有帮助。

3.2 训练集/验证集划分:别用随机划分

海面溢油数据有个特点——同一片海域、同一次事故的影像往往连续多帧,画面高度相似。如果随机划分,训练集和验证集里会出现几乎一样的图,验证指标虚高,实际部署时性能断崖式下跌。正确做法是按「事故批次」或「拍摄时段」划分:

import os import random def split_by_group(img_dir, label_dir, out_dir, ratio=(0.8, 0.1, 0.1)): """ 按文件名前缀分组划分,避免同批次影像泄漏到验证集 假设文件名格式为 oil_<batch>_<frame>.jpg """ groups = {} for f in os.listdir(img_dir): if not f.endswith('.jpg'): continue parts = f.replace('.jpg', '').split('_') batch = parts[1] if len(parts) > 1 else 'default' groups.setdefault(batch, []).append(f) batches = list(groups.keys()) random.shuffle(batches) n = len(batches) n_train = int(n * ratio[0]) n_val = int(n * ratio[1]) train_batches = batches[:n_train] val_batches = batches[n_train:n_train + n_val] test_batches = batches[n_train + n_val:] for name, batch_list in [('train', train_batches), ('val', val_batches), ('test', test_batches)]: with open(os.path.join(out_dir, f'{name}.txt'), 'w') as f: for b in batch_list: for img in groups[b]: f.write(os.path.join(img_dir, img) + '\n') print(f"train: {len(train_batches)} 批, val: {len(val_batches)} 批, test: {len(test_batches)} 批")

如果你的数据集文件名没有批次信息,退而求其次的做法是按图像相似度聚类后再划分,用感知哈希(pHash)做粗聚类即可,汉明距离小于 5 的归为一组。

3.3 三个必调参数:imgsz、batch、mosaic

拿到 1800 张海面溢油图,训练时这三个参数直接决定你能不能跑出可用模型。

imgsz:海面油膜在卫星/无人机影像里往往占据较大面积,但边界模糊。imgsz=640是默认值,但如果你的原图是 1920×1080 且油膜细节丰富,建议提到960或1280。代价是显存占用翻倍,一张 24G 卡跑 YOLOv8m 在imgsz=1280时 batch 只能给到 4。实测经验:海面溢油检测里,imgsz从 640 提到 960,mAP50 通常能涨 3~5 个点,因为薄油膜的纹理特征在低分辨率下会被池化掉。

batch:小数据集不要用大 batch。1800 张图,batch=16或batch=8配合accumulate更稳。大 batch 会让 BN 层的统计量偏向少数批次,海面场景里云影和太阳耀斑的分布不均匀,大 batch 容易导致 BN 崩溃——现象是训练几个 epoch 后 loss 突然变 NaN。如果遇到,先把 batch 降到 8,再把lr0从 0.01 降到 0.001。

mosaic:YOLO 默认开启 mosaic 增强,把 4 张图拼成 1 张。海面溢油场景里,mosaic 会引入大量非海面区域(比如把两张海面图和两张岸边图拼在一起),导致模型学到「岸边也是背景」的错误先验。建议在训练后期关闭 mosaic:

# 前 80% epoch 开 mosaic,后 20% 关闭 yolo detect train data=data.yaml model=yolov8m.pt epochs=200 imgsz=960 batch=8 \ mosaic=1.0 close_mosaic=40 lr0=0.001

close_mosaic=40表示最后 40 个 epoch 关闭 mosaic,让模型在真实分布上做微调。这个技巧在海上小目标数据集上几乎是标配。

4. 训练与验证:用 YOLOv8 跑通海面溢油检测的完整命令

4.1 环境准备与预训练权重选择

YOLOv8 的安装不再赘述,直接说权重选择。海面溢油检测属于「背景单一、目标形态多变」的任务,从 COCO 预训练权重微调是标准做法。yolov8n太快但精度不够,yolov8x在 1800 张图上容易过拟合。我的建议是yolov8m或yolov8l,前者在 24G 卡上imgsz=960能跑batch=8,后者需要降到batch=4。

# 安装 ultralytics pip install ultralytics # 验证环境 yolo checks

yolo checks会输出 CUDA 版本、显存、PyTorch 版本。如果 CUDA 不可用,训练会回退到 CPU,1800 张图跑 200 epoch 大概需要三天,不可接受。确保torch.cuda.is_available()返回 True。

4.2 启动训练:完整命令与参数注释

yolo detect train \ data=./oil_spill_dataset/data.yaml \ model=yolov8m.pt \ epochs=200 \ imgsz=960 \ batch=8 \ lr0=0.001 \ lrf=0.01 \ momentum=0.937 \ weight_decay=0.0005 \ warmup_epochs=3 \ mosaic=1.0 \ close_mosaic=40 \ mixup=0.1 \ copy_paste=0.1 \ degrees=10.0 \ translate=0.1 \ scale=0.5 \ fliplr=0.5 \ flipud=0.0 \ hsv_h=0.015 \ hsv_s=0.7 \ hsv_v=0.4 \ project=./runs/oil_spill \ name=exp01 \ patience=50 \ save_period=20

逐项说明:lr0=0.001比默认的 0.01 更保守,小数据集上更稳;lrf=0.01是最终学习率因子,200 epoch 后 lr 降到 0.00001;warmup_epochs=3让前 3 个 epoch 学习率从极小值线性上升,防止初始梯度炸掉;flipud=0.0关闭上下翻转,因为海面影像上下翻转后天空跑到下面,不符合真实分布;degrees=10.0做小角度旋转增强,模拟无人机不同航向角;hsv_s=0.7饱和度增强幅度较大,因为海面油膜在不同光照下颜色差异明显。

4.3 看训练曲线:哪些指标值得盯,哪些是噪声

训练启动后,runs/oil_spill/exp01/下会生成results.csv和results.png。重点看四个指标:

指标含义海面溢油场景的正常范围
box_loss边界框回归损失前 20 epoch 快速下降,之后缓慢收敛到 0.5~1.0
cls_loss分类损失单类别任务,通常降到 0.1 以下
mAP50IoU=0.5 时的平均精度1800 张图,YOLOv8m 通常能到 0.75~0.88
mAP50-95IoU 从 0.5 到 0.95 的平均通常比 mAP50 低 15~25 个点

如果box_loss震荡剧烈,检查标注框是否有大量重叠或极小框(宽高小于 5 像素)。海面溢油里,远距离的薄油膜可能只有十几个像素宽,这类目标建议在转换阶段就过滤掉,或者单独做一轮高分辨率训练。

4.4 验证与推理:用混淆矩阵和 PR 曲线判断模型是否可用

训练结束后,跑验证:

yolo detect val \ model=./runs/oil_spill/exp01/weights/best.pt \ data=./oil_spill_dataset/data.yaml \ imgsz=960 \ batch=8 \ conf=0.25 \ iou=0.5 \ plots=True

plots=True会生成混淆矩阵、PR 曲线、F1 曲线。海面溢油检测最需要关注的是误检率——模型把太阳耀斑、云影、白浪花误判为油膜。混淆矩阵里如果背景被大量误分为oil_spill,说明conf阈值设低了,或者训练集里负样本(无油膜的海面图)不够。解决办法:在data.yaml里加入纯背景图(空 txt),比例控制在 10%~15%。

推理单张图:

yolo detect predict \ model=./runs/oil_spill/exp01/weights/best.pt \ source=./test_images/ \ imgsz=960 \ conf=0.3 \ save=True \ save_txt=True

save_txt=True会输出每张图的检测框坐标,方便后续和 GIS 系统对接,把像素坐标转成经纬度。

5. 避坑与排查:海面溢油数据集训练中最容易翻车的五件事

5.1 现象:训练 loss 正常下降,但验证 mAP 始终在 0.3 以下

原因:训练集和验证集划分时发生了数据泄漏,或者验证集里的图片在训练集里出现过(同一批次连续帧)。海面溢油影像的帧间差异极小,随机划分必然导致这个问题。

解决:按拍摄批次或时间段重新划分,确保同一批次的图只出现在一个集合里。用感知哈希检查训练集和验证集之间是否存在汉明距离小于 5 的图片对,有就重新分配。

5.2 现象:模型把太阳耀斑和白云全部框成油膜

原因:训练集里缺少负样本。1800 张图如果全部包含油膜,模型没见过「无油膜的海面」,就会把任何亮色区域都当成目标。

解决:加入 150~250 张纯海面背景图(无油膜、有云、有耀斑),生成对应的空 txt 文件。训练时这些图只参与背景损失计算,能显著降低误检。如果找不到纯背景图,可以从现有图片里裁剪不含油膜的区域作为负样本。

5.3 现象:训练到第 50 个 epoch 左右 loss 突然变成 NaN

原因:BN 层统计量崩溃,通常由过大 batch、过高学习率或标注框坐标越界引起。海面溢油数据集里如果有几张图的标注框超出图像边界(xmax > width),YOLO 在计算 loss 时会产生极大梯度。

解决:先跑一遍坐标越界检查(第 2.3 节的脚本),清理越界标注;把batch降到 8 或 4;lr0从 0.01 降到 0.001;加warmup_epochs=5。如果还崩,在data.yaml里把nc和names再核对一遍,确保 TXT 里的 class_id 没有超出范围。

5.4 现象:推理时小油膜全部漏检,大油膜框得很准

原因:imgsz太小,或者训练时 mosaic 增强导致小目标被缩得更小。海面溢油里,远距离薄油膜可能只占原图的 1%~2%,在imgsz=640下经过 32 倍下采样后只剩几个像素,特征完全丢失。

解决:把imgsz提到 960 或 1280;在data.yaml里开启rect=True做矩形训练,减少 padding 对小目标的影响;如果显存不够,用yolov8m代替yolov8l,把省下的显存用于提高分辨率。另外,可以在训练后期关闭 mosaic(close_mosaic=40),让小目标以原始尺度参与微调。

5.5 现象:验证集 mAP 很高,但部署到实际视频流上检测结果闪烁严重

原因:单帧检测没有时序平滑,海面波浪导致油膜边界在帧间跳动,模型对同一片油膜的框时大时小。

解决:在推理后处理阶段加跟踪算法(如 ByteTrack 或简单的 IoU 匹配),对连续帧的检测框做平滑。如果只是做离线筛查,可以对同一视频段的多帧检测结果做非极大值抑制(NMS)的时序版本——把相邻帧的框按 IoU 合并,取置信度最高的作为最终输出。这个后处理能把视频里的闪烁降低 70% 以上。

6. 把 1800 张图用透:小数据集的进阶技巧与验证习惯

1800 张图在目标检测里属于小数据集,但海面溢油这个场景的特殊性在于——它的背景相对单一(海面、天空、云),目标形态却极其多变(薄油膜、厚油块、乳化带、油水混合物)。这意味着你不能靠堆数据量取胜,得靠「把每一张图用透」。

第一个技巧是分层采样验证。不要只看整体的 mAP50,把验证集按油膜面积占比分成三档:小目标(面积 < 5%)、中目标(5%~20%)、大目标(> 20%),分别统计每档的召回率。我见过太多模型整体 mAP 0.85,但小目标召回率只有 0.4,部署到无人机巡检时远距离油膜全部漏检。分层统计能让你提前发现这个问题,针对性补充小目标样本或提高imgsz。

第二个技巧是用测试时增强(TTA)榨干精度。YOLO 支持推理时开启 TTA,对同一张图做多尺度、多翻转推理后融合结果:

yolo detect predict \ model=./runs/oil_spill/exp01/weights/best.pt \ source=./test_images/ \ imgsz=960 \ augment=True \ conf=0.25 \ save=True

augment=True会做水平翻转和三个尺度的推理,mAP 通常能再涨 1~2 个点,代价是推理速度降到原来的 1/3。离线筛查场景值得开,实时视频流不建议。

第三个技巧是定期用新数据做增量验证。海面溢油检测模型最怕的是「过拟合到某一片海域的光照条件」。我一般会留出 100~200 张完全不同海域、不同季节、不同传感器的图作为「外部测试集」,不参与训练和调参,只在模型定稿前跑一次。如果外部测试集 mAP 比验证集低超过 15 个点,说明模型泛化能力不够,需要加更多样的训练数据或更强的颜色增强。

最后一个习惯:每次训练完,把 best.pt 在 10 张典型图上跑一遍,人眼看框。指标是黑匣子,人眼才是后悔药。我踩过最深的坑是一个 mAP 0.91 的模型,在薄油膜上框得完美,但把一条白色泡沫带稳定框成油膜——验证集里恰好没有泡沫带样本,指标完全看不出来。从那以后,我每次定稿前都会手动过一遍典型样本,尤其是那些「看起来像油膜但不是」的负样本。这个习惯花不了十分钟,但能帮你省掉一次线上翻车。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询