资讯动态

海面溢油检测数据集实战:VOC转YOLO与目标检测训练避坑指南

发布时间:2026/10/25 13:35:23 来源:尧图企业网站定制
简介海面石油原油泄漏检测数据集面向计算机视觉目标检测任务专为环保监测、海洋巡查与应急响应场景打造适用于YOLO、Faster R-CNN等主流检测模型的训练与验证能帮助研究者高效构建针对油污泄漏的识别系统。资源包共2000个文件以VOC格式xml标注文件与YOLO格式txt标签文件为主体压缩包大小约79.05MB每个图像样本均配有对应的xml与txt标注xml记录边界框坐标和类别txt可直接用于YOLO训练两者一一对应减少预处理工作。数据集中仅含oil_spillage单一类别标注框总数达3871个部分图像含多个泄漏点符合真实海面泄漏场景全部标注基于labelImg工具完成标注质量稳定。已有283人学习浏览适合需要高标注精度、格式可直接使用的泄漏检测样本用于算法对比、模型微调或相关课题研究的开发者和研究人员。1. 海面石油原油泄漏检测1800张影像数据集的真实用法海面溢油检测是遥感目标检测里“看起来简单、上手就翻车”的典型场景油膜在可见光下比海水更吸光画面里就是一片暗色区域但船影、云影、暗礁、低风速海区同样发暗模型很容易把“暗”当成“油”。这个海面石油原油泄漏检测数据集1800张图片同时给出VOC和YOLO两种标注格式解决的就是“疑似溢油在哪儿”这个前置问题——VOC走labelImg和pascal voc工具链做校验YOLO格式可以直接喂给yolov5/yolov8训练省掉一半格式转换工作。适合遥感目标检测方向刚起步、需要一套基线数据来调通流程的团队。下面从格式拆解讲到训练参数再把最常踩的五个坑逐个说透。2. 溢油检测任务的成像边界为什么“黑一块”不能直接当漏油油气泄漏在海面形成的油膜在可见光影像里比清洁海水更暗这是模型能学它的根本原因。但遥感目标检测从来不是比“谁更黑”——阴影、船尾迹、水下暗礁、生物油膜在灰度上高度相似如果不理解影像机理训练出来的模型换个场景就失效。看懂成像特征才能判断这个1800张的数据集到底能教会模型什么以及模型上线后会在哪里露馅。2.1 不同成像条件下的油膜表现可见光暗色、近红外低反射与SAR暗斑油膜厚度不同呈现的颜色也不同。毫米级厚油膜在可见光下几乎纯黑薄油膜偏深褐色极薄油膜会出现彩虹色干涉条纹。近红外波段油膜反射率比海水更低表现为更暗的区域但阴影区也暗单靠亮度仍然分不开。SAR影像是另一套机理油膜抑制海面短重力波降低了后向散射形成暗斑这和可见光数据集的成像机制完全不同。这个1800张的数据集从标注格式和场景描述看更接近可见光或无人机航拍影像不建议直接把训练好的模型搬到SAR影像上用特征空间对不上再好的网络也白搭。拿到任何溢油数据集第一件事就是确认影像来源和波段构成而不是急着解压训练。2.2 为什么选目标检测而不是语义分割预警场景的水平框够不够用溢油边缘不规则理论上语义分割更贴近真实油膜面积但标注成本是目标检测的3到5倍。一张海面影像要把油膜边界逐像素勾出来熟练标注员也要几分钟而水平框几秒钟就能标完。做海事巡查和应急响应的诉求通常是“有没有疑似油膜、在哪个区域、覆盖范围大概多大”水平框加一个框内灰度阈值后处理就能估算面积。实际产线里更常见的是两级级联第一级用目标检测框出疑似区域第二级在框内做分割或阈值细化边缘。这样第一级的1800张水平框标注完全够用成本可控部署也快。有些追求更高精度的团队会用mmrotate训练DOTA格式的旋转框但旋框需要重新标注这1800张的水平框底子就浪费了除非场景里有强条带状油膜否则不建议在预警阶段上旋转框。2.3 VOC和YOLO两种格式的差异同一个框两套坐标系这个数据集同时给VOC和YOLO格式本质是同一个标注的两个视图。VOC格式即pascal voc标准每张图对应一个xml文件记录图片尺寸和每个目标的名字、绝对像素坐标xmin, ymin, xmax, ymax。YOLO格式每张图对应一个txt文件每行一个目标记录类别ID和归一化的中心点、宽高。转换公式如下x_center (xmin xmax) / 2 / 图片宽 y_center (ymin ymax) / 2 / 图片高 w (xmax - xmin) / 图片宽 h (ymax - ymin) / 图片高一个典型的VOC标注片段长这样annotation filenamescene_001.jpg/filename size width1920/width height1080/height depth3/depth /size object nameoil/name bndbox xmin320/xmin ymin450/ymin xmax712/xmax ymax588/ymax /bndbox /object /annotation对应的YOLO格式txt类别ID取00 0.268750 0.480556 0.204167 0.127778两者差别直接影响后续工具链VOC的绝对坐标在图片缩放后必须同步重算YOLO的归一化坐标则不用VOC的标签是字符串YOLO是整数IDID顺序必须和训练配置里的names列表严格一致。这个差异后面会引出好几个坑。2.4 1800张的规模怎么评估关键看目标数、场景多样性与目标尺度评估一个目标检测数据集够不够用不能只看图片张数。1800张如果每张平均2到3个目标总实例数在4000到6000个训练yolov8m这类中量级模型跑单类检测拿到一个可用基线是够的。但如果类别超过3类且分布不均1800张就会吃紧重头戏就得靠增强和预训练权重。还需要看目标尺度如果标注框的平均宽高不到原图的5%属于典型小目标场景训练输入分辨率就不能用默认640。另一个重要维度是场景多样性——全是晴天开阔海的干净样本换到近岸、有云影、有船只的海况就容易翻车。把这个数据集当作基线来用是正确的姿势先跑通流程再针对自己的场景补充数据而不是指望这1800张覆盖所有海况。3. 拿到数据集先做标签体检画框可视化、类别统计与五类脏数据数据集的真实价值取决于标注质量不取决于张数。我见过很多项目拿到数据集后直接开训结果loss震荡、mAP虚高最后排查发现是标签越界或重复框。解压之后先做一轮“标签体检”一小时换后面几天不返工这笔时间花得值。3.1 解压后的目录审计不要信任zip里的文件夹结构这类开源数据集的打包结构并不统一。常见布局是VOC侧有JPEGImages、Annotations、ImageSets/MainYOLO侧有images和labels。但“常见”不代表这个压缩包一定合规解压后先跑一轮统计unzip 海面石油原油泄漏检测数据集1800张VOCYOLO格式.zip -d oil_dataset cd oil_dataset find . -maxdepth 2 -type d | sort find . -type f -name *.jpg | wc -l find . -type f -name *.xml | wc -l find . -type f -name *.txt | wc -l图片数、xml数、txt数三者应该非常接近。如果xml数量明显少于图片数说明存在漏标如果txt数量超过图片数多半有空的标签文件或重复缓存。还要检查有没有同名图片出现在多个子目录里YOLO训练按图片路径建缓存索引同名文件会造成意外混用。最后用file命令抽查几类图片确认都是jpg或png避免遇到损坏文件训练到一半报错。审计这一步的产出是一份“哪些图片没有标注、哪些标注是空的”清单带着清单进下一步。3.2 画框可视化抽查比统计更能发现标注错位统计只能发现数据缺失发现不了标注错位。把xml里的框画到原图上肉眼扫一遍胜过跑任何自动化指标。下面这个脚本遍历Annotations目录每张图生成一张带框的检查图import cv2 import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(Annotations) img_root Path(JPEGImages) out_root Path(check_vis) out_root.mkdir(exist_okTrue) for xml_path in xml_dir.glob(*.xml): tree ET.parse(xml_path) root tree.getroot() img_path img_root / root.find(filename).text img cv2.imread(str(img_path)) if img is None: print(f图片缺失: {img_path.name}) continue for obj in root.iter(object): name obj.find(name).text bnd obj.find(bndbox) xmin int(bnd.find(xmin).text) ymin int(bnd.find(ymin).text) xmax int(bnd.find(xmax).text) ymax int(bnd.find(ymax).text) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 0, 255), 2) cv2.putText(img, name, (xmin, max(ymin - 6, 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) out_path out_root / (xml_path.stem _check.jpg) cv2.imwrite(str(out_path), img)代码逻辑按xml的filename字段找图逐个object画框。图片缺失会单独打印避免静默跳过。1800张全画太耗时随机抽50到100张看即可重点盯三类问题框是否只框住油膜的一半、框是否被拉得过大把大片海水包进来、标签名字是否和框内内容对得上。这一步用目视检查比任何脚本都直观。3.3 目标尺度统计小目标判定和imgsz选择依据溢油条带在原图里经常又细又长标定框的尺度直接决定训练输入分辨率。统计一下所有框的相对宽高import xml.etree.ElementTree as ET from pathlib import Path import numpy as np xml_dir Path(Annotations) cat_counts {} rel_w [] rel_h [] aspect_ratios [] for xml_path in xml_dir.glob(*.xml): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) for obj in root.iter(object): name obj.find(name).text cat_counts[name] cat_counts.get(name, 0) 1 bnd obj.find(bndbox) xmin int(bnd.find(xmin).text) ymin int(bnd.find(ymin).text) xmax int(bnd.find(xmax).text) ymax int(bnd.find(ymax).text) box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h rel_w.append(box_w) rel_h.append(box_h) aspect_ratios.append((xmax - xmin) / (ymax - ymin 1e-6)) print(类别统计:, cat_counts) print(平均相对宽:, np.mean(rel_w), 平均相对高:, np.mean(rel_h)) print(长宽比中位数:, np.median(aspect_ratios))三个数字决定参数走向平均相对宽高在0.05以下判定为小目标场景训练时imgsz直接设置1280起步长宽比中位数大于3说明油膜条带性强方形数据增强比如mosaic会把条带截断这时要评估模型学的是整体油膜还是油膜片段——如果标注框本身就是油膜的局部片段截断训练反而能增强计数能力。类别统计则用来看是否有类别不平衡单类任务不需要调loss权重多类任务则需要。3.4 批量校验脚本越界框、重复框、空标注的自动排查人工抽查解决“错位”脚本批量解决“脏框”。把越界、超小、重复、空标注四条规则合成一个脚本import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(Annotations) for xml_path in xml_dir.glob(*.xml): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) boxes [] for obj in root.iter(object): bnd obj.find(bndbox) xmin int(bnd.find(xmin).text) ymin int(bnd.find(ymin).text) xmax int(bnd.find(xmax).text) ymax int(bnd.find(ymax).text) boxes.append((xmin, ymin, xmax, ymax)) if xmin 0 or ymin 0 or xmax img_w or ymax img_h: print(f越界框: {xml_path.name}) if (xmax - xmin) 8 or (ymax - ymin) 8: print(f超小框: {xml_path.name}) for i in range(len(boxes)): for j in range(i 1, len(boxes)): if boxes[i] boxes[j]: print(f重复框: {xml_path.name}) if not boxes: print(f空标注: {xml_path.name})越界框是最危险的一类YOLO的归一化坐标会因此出现小于0或大于1的数值训练时轻则报警告重则导致定位偏移。超小框对油膜这类目标未必是错标可能是极远处的油膜痕迹8像素阈值只是经验值先标出来人工判断是保留还是过滤。重复框会让同一个目标贡献两次loss收敛变慢。空标注则要和原图对照确认是背景图还是漏标。这批异常清单处理完数据集才算达到可以训练的状态。4. 从VOC训练YOLO转换脚本、数据划分与yolov8最小训练命令压缩包里已经带了YOLO格式为什么还要自己转一遍因为标签是死的转换过程如果有坐标越界或类别ID错位直接开训等于把脏数据喂给模型。自己写转换脚本和包里的txt交叉校验哪份可信用哪份这才是稳妥的开工姿势。4.1 双格式交叉校验宁可自己重转一遍常见做法是把VOC当作“原始标注”YOLO格式当作“导出产物”。导出过程最容易出错的是两处坐标没有clip到图片范围以及类别ID映射顺序不一致。自己转完再diff能同时抓出这两类问题mkdir -p yolo_convert # 运行下文的转换脚本输出到 yolo_convert 目录 for f in labels/*.txt; do name$(basename $f .txt) if ! diff -q $f yolo_convert/$name.txt /dev/null; then echo 不一致: $name fi done没有输出的文件说明和包里的一致可以放心用有输出的文件统一以xml重新转换的结果为准因为绝对坐标无法伪造。这一步做完后续训练遇到的标签问题基本归零。4.2 VOC转YOLO的转换脚本坐标顺序和越界处理是命门转换脚本的核心不是套公式而是处理那些不守规矩的框。我一般这么写import xml.etree.ElementTree as ET from pathlib import Path xml_dir Path(Annotations) out_dir Path(yolo_convert) out_dir.mkdir(exist_okTrue) # 类别名到ID的映射顺序必须和训练时data.yaml的names一致 class_map {oil: 0} def convert_voc_to_yolo(xml_path): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: continue bnd obj.find(bndbox) xmin int(bnd.find(xmin).text) ymin int(bnd.find(ymin).text) xmax int(bnd.find(xmax).text) ymax int(bnd.find(ymax).text) # 越界框先裁剪到图片内再归一化 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) if xmax xmin or ymax ymin: continue cx ((xmin xmax) / 2) / img_w cy ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{class_map[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) out_path out_dir / (xml_path.stem .txt) out_path.write_text(\n.join(lines)) for xml_path in xml_dir.glob(*.xml): convert_voc_to_yolo(xml_path)几个设计点值得说明。clip到img_w减1而不是img_w是为了避免归一化值恰好等于1等于1表示目标右边缘贴住图片边界训练时会被当成越界处理。转换后宽高小于等于0的框直接丢弃这些框没有学习价值。类别ID映射用显式字典而不是自动按字母排序因为自动排序可能和原始数据集约定的ID不一致埋下“训练能用但推理全错”的隐患。最后写入txt时不带额外空格每行五个值和yolov8的解析格式对齐。4.3 数据划分要按“影像来源”而非“随机帧”同一片油膜的连续性陷阱如果数据集是从航拍视频或连续遥感影像里抽帧得到的同一片油膜会出现在连续帧里。按图片随机划分会把同一个目标的邻近帧同时分进训练集和验证集训练时模型等于提前见过答案验证指标虚高落到新场景立刻现原形。正确做法是按影像来源分组文件名里通常带有场景前缀比如oil_event_001、oil_event_002同一个前缀的帧只能出现在同一侧。import random from pathlib import Path from collections import defaultdict prefix_groups defaultdict(list) for img_path in Path(JPEGImages).glob(*.jpg): prefix img_path.name.split(_)[0] prefix_groups[prefix].append(img_path.stem) items list(prefix_groups.items()) random.Random(42).shuffle(items) split_idx int(len(items) * 0.8) train_names [name for _, names in items[:split_idx] for name in names] val_names [name for _, names in items[split_idx:] for name in names] with open(train.txt, w) as f: f.write(\n.join(train_names)) with open(val.txt, w) as f: f.write(\n.join(val_names))random.Random(42)固定随机种子保证每次划分结果一致这是数据实验可以复现的前提。如果文件名没有场景前缀就用拍摄时间戳或经纬度分桶实在没有元数据可以用感知哈希对连续帧去重再按去重后的簇划分。遥感目标检测里的血泪经验是凡是从视频抽帧的数据集先分组再划分验证集才可信。4.4 最小训练命令data.yaml、imgsz和batch的取舍切分完成后需要一个yaml文件描述数据集路径和类别。把images/train和images/val目录准备好内容如下path: /data/oil_dataset train: images/train val: images/val nc: 1 names: [oil]训练命令是yolov8的标准入口yolo detect train \ datadata.yaml \ modelyolov8m.pt \ imgsz1280 \ epochs100 \ batch8 \ device0参数不是随便填的。imgsz1280是前面尺度统计推导出来的结论油膜小目标在640输入下只剩几个像素特征图下采样后基本消失1280是显存和召回率的折中。yolov8m作为起步模型n和s版本的特征提取能力对小目标太弱L版本在24G卡上又跑不快m版本三天打底够用。epochs设100不是追求百分百收敛而是给足观察窗口——训练到50个epoch左右就能看出来val的box_loss和dfl_loss是否还在下降。损失曲线要分开看单类溢油检测里class_loss通常降得很快核心看box_loss和dfl_loss因为油膜边缘不规则边界回归不准比分类错误更影响实际效果。batch大小按显存来8G显卡跑imgsz1280时batch降到2或4实在跑不动就先用640训完再转1280推理。5. 海面溢油检测避坑五个高频翻车点的现象、原因与排查这个方向我踩过的坑比训练过的epochs还多。下面五条是从实际项目里沉淀下来的每一条都按现象、原因、解决三个层次说清楚排查路径可以直接套用。5.1 船影云影被检测成油膜误检率怎么压都压不下去现象推理结果里模型在船影、云影、岛屿背光面上打出高置信度的oil框precision低到没法上报告。原因油膜和阴影在灰度统计上高度重叠模型学到的可能是“够暗就当油”而不是“油膜边缘的纹理和形状特征”。进一步看训练集里的负样本太“干净”绝大部分图片都有油膜少量背景图也是开阔海面模型的背景概念里根本没有“带阴影的海面”这个类别。解决从验证集推理结果里挑出错检框对应的原图打上背景标签作为纯背景样本重新加入训练。没有目标框的图片在YOLO格式里就是空txtyolov8会正常读取并当作负样本。另一个有效手段是随机对比度增强把训练图的明暗对比拉开逼模型去学灰度相对关系而不是绝对暗度。船影和油膜在单波段影像里本来就难分靠后处理过滤不如靠数据告诉模型“这些暗块不是油”。5.2 XML标注越界YOLO训练报坐标越界warning现象训练日志频繁出现坐标越界的warningloss曲线震荡比正常情况厉害得多训练完的模型框经常偏到图片边缘外。原因VOC标注允许框超出图片边界可能是标注员手抖也可能是转格式时没做clip。归一化后cx或cy小于0大于1模型会尝试学习一个“存在于图片外”的目标不仅浪费参数还把边框预测拉偏。解决不要信任包里的原始txt用4.2的转换脚本重新生成一份再批量检查归一化坐标是否都在0到1之间awk {if ($20 || $21 || $30 || $31 || $40 || $41 || $50 || $51) print FILENAME, $0} labels/*.txt打印出来的txt全部重新处理clip到图片范围内。这个坑最隐蔽的地方在于数据集作者可能已经修过一轮但总有漏网的交叉校验一步不能省。5.3 油膜小目标漏检一训练就想加数据其实先改输入尺寸现象训练集loss稳定下降验证集recall一直上不去。大块油膜能检测到细长油膜经常整个漏掉或者只检测出油膜的一个角。原因溢油条带在原图里就是细长形目标缩放到640后目标宽度只剩几个像素yolov8的下采样特征图里可能只有一个点模型没有足够的空间信息支撑边框回归。这种问题靠加数据解决效率极低先改输入分辨率更直接。解决imgsz提到1280甚至1536同时开启多尺度训练。如果显存不允许改用滑窗推理把原图按1024到1280的patch尺寸切块patch之间保留20%到30%重叠每个patch独立推理最后用NMS合并重叠框IoU阈值设0.5。滑窗推理比直接缩放整图对大图里的小目标友好得多是遥感目标检测部署的标配做法近岸和海面目标都适用。5.4 类别ID映射不一致明明只有一个类别模型却学不对现象训练过程看起来正常推理结果里的类别名称和置信度却对不上——有的框类别正确置信度低有的类别名字错乱。原因txt里的class_id和data.yaml里的names顺序不一致。比如xml转换时把oil映射成了1但data.yaml只配了names: [oil]yolov8会认为类别1不存在训练时把类别标签强行归零或报错等于在学一份错位标签。另一个常见场景是多类别数据集按字母顺序重排了ID但训练配置没有同步更新。解决训练前写一行命令抽查三个txt文件记录类别ID再打开data.yaml确认names的顺序。强制自己养成习惯每个新数据集第一次训练前打印class_map和names列表肉眼对齐。如果是多类别建议把class_map写成独立json文件统一管理转换和训练都从同一个文件读从源头避免两处映射分叉。5.5 验证集和训练集“串台”指标虚高部署翻车现象验证集mAP50能到0.85以上看起来很漂亮一到新地点的实拍影像上漏检成片比训练时表现的差一大截。原因同一泄漏事件的连续帧被随机分进了训练和验证两边的图片内容几乎一模一样。模型本质上记住了邻近帧的标答案验证指标没有反映真实泛化能力。解决按4.3的分组划分重建数据切分同一场景前缀的图片不能跨集合。更严格的做法是单独留一个“从未参与训练”的影像批次做终测这个批次里的事件地点、拍摄时间都跟训练数据不同。划分完成后把train.txt和val.txt各打印几行肉眼确认没有同一前缀出现在两边。从教训里总结一句模型在验证集上mAP再高都不值得高兴留一批陌生场景的数据考它才算数。6. 训练完先看误报分布再决定下一步优化方向训练跑完别急着看总指标先把误报和漏报拆开。yolov8训练结束会生成val目录下的混淆矩阵和PR曲线但一张图一张图翻比看曲线更能定位问题。用训练好的权重跑一遍验证集把每张图的检测结果落盘from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourceimages/val, conf0.25, imgsz1280, save_txtTrue, saveTrue, )随后打开保存的结果图把预测错误的框分成三类船影云影误检、油膜漏检、位置偏移。这三类对应三种完全不同的优化手段。误检集中在阴影就走负样本回流把误检图放进训练集当背景漏检集中在细长油膜优先提imgsz和滑窗重叠率位置偏移多回头检查标注框的质量和dfl_loss。一张图表上三类错误的占比比一个mAP数字有用得多。闭上眼睛复盘我做过的一个相似海面检测项目第一次训练时没做分组划分mAP80却在上线头一天漏掉了一处明显的油膜带当时才想起来验证集里混着训练集的邻近帧。那之后所有遥感抽帧数据集一律按场景前缀分组用滑窗推理替代整图缩放把漏检钉在可控范围内。数据集是死的标注是半成品训练只是一次验证真正决定模型能不能上船的是你在数据体检上花了多少功夫。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑