简介:YOLO海洋目标检测数据集面向计算机视觉学习者与从业者,聚焦海洋生态监测、渔业资源调查等真实场景,涵盖5000张高质量图片及对应标注,解决海洋领域公开数据集稀缺、格式转换繁琐的痛点。压缩包共2000个文件,核心为1986个xml标签文件,并配套yolo格式txt、coco格式json标签,另有6个html教程、3个python划分脚本和5个txt列表文件,整体体积75.39MB。目前已有264人关注学习,适用于需要海洋数据快速开展YOLO系列模型训练的实验与课程项目。配套教程覆盖Windows和Linux双平台的环境搭建,指导如何根据案例修改配置以训练自定义数据集;三个划分脚本可自由切分训练集、验证集与测试集,免去手动标注与格式整理的重复劳动,帮助读者从数据准备到模型部署形成完整闭环。
1. 为什么这份 YOLO 海洋目标检测数据集值得你花时间:三种格式一次配齐
做目标检测的人都有同一个体会:模型架构迭代再快,真正卡住进度的永远是数据。YOLO 海洋目标检测数据集这份资源,就是冲着这个痛点来的——5000 张真实海洋环境图片,已经用 labelimg 逐张标注完,并且同时给了 VOC(xml)、COCO(json)、YOLO(txt) 三种格式的标签,分别存在不同文件夹下,拿到手不需要再折腾格式转换,直接能喂给 YOLO 系列训练。对刚入门目标检测、想把流程整个跑通的学生,或者要做海洋、近海场景检测的从业者来说,它省掉的是最枯燥也最容易出错的那几步。更难得的是还打包了环境搭建教程、训练案例教程和划分脚本,从零到出权重一条龙,我第一次拿到时最大的感受是:这资料包把从数据到模型的每个坑都提前替你踩了一遍。
2. 数据集内部结构:5000 张图与 voc/coco/yolo 标签的真实分布
2.1 图片质量与标注方式:为什么标得干净比数量多更重要
先看图片本身。这 5000 张是真实场景采集的海洋环境图像,不是合成图,场景覆盖了不同光照、不同海况、不同远近尺度下的目标。用 labelimg 标注,标注框质量高——这一点在实际训练里的权重比很多人想象的大。我见过不少公开数据集,数量是够的,但框画得潦草,目标边缘留白过多,或者把小目标漏标了一半,训练出来的模型 mAP 就是上不去,怎么调参数都没用,最后查数据才发现是标注问题。
这份资源的标注框属于比较规矩的那种,框紧贴目标轮廓,类别标签也统一。真实场景图 + 框质量高,这两个条件对海洋目标这种背景复杂、目标尺度差异大的检测任务来说,基本决定了模型收敛的上限。另外,图片和标签是严格同名的,这一点虽然基础,但在后续划分脚本阶段会省很多事。
2.2 三种标注格式对不上的问题:一份数据为何要出三份标签
很多初学者不理解:为什么要同时给 VOC、COCO、YOLO 三种格式?直接用 YOLO 的 txt 不就行了?
原因是不同训练框架的输入格式要求不一样。YOLO 系列原生吃的是 txt 格式,每个框一行,内容是类别 中心点x 中心点y 宽度 高度,四个坐标值全部归一化到 0~1;VOC 系模型(比如 Faster R-CNN 的某些实现)习惯读 xml,里面记录的是左上角和右下角的绝对像素坐标;而 COCO 格式的 json 是很多检测框架的通用输入,MMDetection、Detectron2 都用它。你手里只有一种格式,就意味着被某一个框架绑死,换个框架就得重新写转换脚本。
同一张图在三种格式里的样子完全不同,我举个实际例子。假设图片宽度是 1920、高度是 1080,图中有一个目标,标注框左上角在 (500, 300),右下角在 (800, 700):
<!-- VOC 格式:xml 文件内容片段 --> <annotation> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>ship</name> <bndbox> <xmin>500</xmin> <ymin>300</ymin> <xmax>800</xmax> <ymax>700</ymax> </bndbox> </object> </annotation>// COCO 格式:json 中的 annotation 片段 { "image_id": 1001, "category_id": 1, "bbox": [500, 300, 300, 400], "area": 120000 }YOLO 格式:txt 文件内容,注意坐标是归一化后的相对值 0 0.3385 0.4630 0.1563 0.3704VOC 和 COCO 这里都是像素绝对值,但 COCO 的 bbox 存的不是右下角坐标,而是x, y, width, height四元组。YOLO 的 txt 则是把中心点坐标和宽高都除以图片尺寸做了归一化。这份资源把三种格式的标签按文件夹分好,你训练时只需要在配置里指定对应路径,不用写任何转换代码,这是它最实在的地方。
2.3 目录组织方式:拿到压缩包后先核对这个结构
解压后建议先按下面的结构核对一遍,确认图片和标签对应关系没乱:
dataset/ ├── images/ # 所有图片,jpg/png 混存 ├── annotations/ # VOC 格式 xml 标签 ├── coco/ # COCO 格式 json 标签 ├── labels/ # YOLO 格式 txt 标签 ├── 训练集、验证集、测试集划分脚本(图片标签划分写入新文件夹).py ├── 训练集、验证集划分脚本(图片标签划分写入新文件夹).py └── split_train_val生成ImageSets下txt文件划分脚本.py提示:三个脚本在压缩包根目录,使用时最好先复制到 dataset 同级的目录再跑,避免脚本里的相对路径把数据目录搞乱。
3. 划分脚本实操:train/val/test 按你的比例重切
3.1 三个脚本的分工:先想清楚你要哪种划分方式
资源里给了三个划分脚本,很多新手一上来就懵,不知道跑哪个。我先帮你捋清楚:
第一个是三分脚本,把全部数据按你设定的比例切成训练集、验证集、测试集三份,每份都包含图片和对应的标签,且会复制到新文件夹里;第二个是二分脚本,只切训练集和验证集,适合你暂时不需要独立测试集、打算用验证集代替测试集快速迭代的场景;第三个不太一样,它不复制文件,而是生成 ImageSets 下的 txt 索引文件(也就是 train_list.txt 这类文件),这是 VOC 风格训练里用来告诉框架"哪些图片属于哪个集合"的列表文件。
实际使用中我的建议是:先跑三分脚本,把测试集真正独立出来。原因后面避坑章细说,这里先说你最关心的——脚本怎么改参数。
3.2 三分脚本怎么改:比例、路径、文件名前缀一个都不能错
训练集、验证集、测试集划分脚本(图片标签划分写入新文件夹).py这类脚本的核心逻辑是:遍历所有图片文件名,按比例随机分配,然后用 shutil 把图片和同名标签复制到目标子目录。下面是我按常见做法整理的一个等价实现,方便你对照理解原脚本里每个变量是干嘛的:
import os import random import shutil # 原图与标签目录,改成你自己的实际路径 images_dir = "dataset/images" labels_dir = "dataset/labels" # 如果标签在 annotations,改成对应路径 output_dir = "dataset/split" # 输出根目录 # 划分比例,和必须等于 1 train_ratio = 0.8 val_ratio = 0.1 test_ratio = 0.1 # 收集所有图片文件名,不带后缀 all_images = [f for f in os.listdir(images_dir) if f.endswith((".jpg", ".png", ".jpeg"))] random.shuffle(all_images) # 打乱顺序,避免同类场景连续出现 # 按比例切分 train_cnt = int(len(all_images) * train_ratio) val_cnt = int(len(all_images) * val_ratio) splits = { "train": all_images[:train_cnt], "val": all_images[train_cnt:train_cnt + val_cnt], "test": all_images[train_cnt + val_cnt:], } # 逐个复制图片与同名标签 for split_name, file_list in splits.items(): img_out = os.path.join(output_dir, "images", split_name) lbl_out = os.path.join(output_dir, "labels", split_name) os.makedirs(img_out, exist_ok=True) os.makedirs(lbl_out, exist_ok=True) for img_name in file_list: stem = os.path.splitext(img_name)[0] src_img = os.path.join(images_dir, img_name) src_lbl = os.path.join(labels_dir, stem + ".txt") if os.path.exists(src_lbl): # 标签不存在直接跳过,防止训练时报错 shutil.copy(src_img, os.path.join(img_out, img_name)) shutil.copy(src_lbl, os.path.join(lbl_out, stem + ".txt")) print(f"train: {len(splits['train'])} val: {len(splits['val'])} test: {len(splits['test'])}")逻辑说明:脚本先读取所有图片文件名并打乱,随机打乱这一步很关键——海洋数据里相邻图片往往是同一个时间段、同一片海域采的,不打乱的话训练集和验证集会存在场景重叠,导致验证集指标虚高。然后按比例切三个集合,最后用 shutil.copy 把图片和同名标签复制进新目录,名字一致才配得上对。
参数说明:train_ratio=0.8, val_ratio=0.1, test_ratio=0.1是通用默认值,数据量小、样本不均时我一般改成 0.7/0.15/0.15;如果你的场景类别极少、每类只有几十张,那应该先做数据增强而不是调整比例。注意脚本只复制不移动,原目录不会被破坏,这点设计得比较稳妥,跑错了也有后悔药。
3.3 生成 ImageSets txt 文件:为什么要给框架一份名单
split_train_val生成ImageSets下txt文件划分脚本.py要做的事是:不复制文件,只在 dataset 下生成 ImageSets/Main/train.txt、val.txt、test.txt,文件里每行是一个图片路径(通常是不带后缀的文件名,配合 train_list.txt 使用)。Darknet 框架训练时,通过 train_list.txt 告诉程序去哪读图片、对应的标签自动按同名规则去找。
我一般这样用:先跑完三分脚本,再跑这个脚本生成 train_list.txt,一份数据两种消费方式都能满足。跑之前检查两件事:一是脚本里的root_path是否指向你的图片目录,二是确认生成的是相对路径还是绝对路径——换机器训练时,绝对路径直接失效,相对路径只要保持目录结构一致就没问题。
4. 环境搭建选型:Linux 和 Windows 各走一遍关键差异
4.1 先定 GPU:驱动、CUDA、cuDNN、PyTorch 的版本咬合关系
资料包里有两份环境搭建教程,分别对应 Linux 和 Windows,还附带了一份 Ubuntu 安装教程。我的建议是:如果你的机器能用 Linux,优先走 Linux 路线;Windows 也能跑,但在驱动和编译环节更容易翻车。不管哪条路线,第一关都是确定 GPU 驱动、CUDA、cuDNN、PyTorch 四者版本匹配。常见做法是先装好显卡驱动,然后nvidia-smi看驱动支持的 CUDA 版本上限,再往下选对应版本的 PyTorch:
# 查看显卡驱动和驱动支持的 CUDA 版本 nvidia-smi # 环境里确认 PyTorch 实际使用的 CUDA 版本 python -c "import torch; print(torch.__version__); print(torch.cuda.is_available())"逻辑说明:nvidia-smi输出的右上角是驱动支持的最高 CUDA 版本,PyTorch 编译时带的 CUDA 必须不高于这个值。很多人踩的坑是驱动版本太老,装完最新版 PyTorch 一跑就报CUDA driver version is insufficient——这不是代码问题,是版本错位。
参数说明:torch.cuda.is_available()必须返回 True 再继续,这里有一步没验证就往后跑,训练时才会暴露问题,排查成本成倍增加。如果你用 conda,推荐建独立环境,别动 base。
conda create -n yolo python=3.9 -y conda activate yolo4.2 Linux 路线:Ubuntu 下最稳的顺序
Linux 这边,Ubuntu 是社区支持最好的发行版,资料包里的 Ubuntu 安装教程就是从系统装起。硬件驱动装完后,环境搭建顺序一般是:装 Anaconda → 建虚拟环境 → 用 pip 装 PyTorch → 拉取 YOLO 项目代码 → 安装依赖。这里有个小坑,pip 源在国内建议直接换成镜像源,否则下载大文件时网络波动容易中断:
# 临时用镜像源安装 PyTorch,避免默认源过慢 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118逻辑说明:repo 安装依赖时requirements.txt里的包版本是配合开发环境锁的,按它装一般没问题;如果遇到个别包编译报错,常见做法是单独升级该包或先装系统级的libgl1、libglib2.0-0这类基础库。
4.3 Windows 路线:哪些坑只有 Windows 才有
Windows 路线整体流程一致,但有几个 Linux 上不会遇到的特有问题。第一,路径不能含中文和空格,别把数据集放在"新建文件夹 (2)"这种路径下,YOLO 读取标签时有概率直接崩;第二,Windows 默认的shutil行为在某些情况下会锁文件,跑划分脚本如果提示PermissionError,关掉所有占用该目录的窗口再试;第三,nvidia-smi在 Windows 上同样可用,驱动和 CUDA 的判断逻辑不变。
另外一个容易被忽略的点:Windows 上装 PyTorch 时,CPU 版和 GPU 版的包名不同,装错了torch.cuda.is_available()会返回 False,很多人误以为显卡坏了,其实只是装成了 cpu 版:
# Windows 下 GPU 版 PyTorch 安装示例 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118逻辑说明:Python 的包管理机制决定了同一环境下只能装一个 torch 变体,之前装过 CPU 版的话要先pip uninstall torch torchvision再换 GPU 版,否则会提示冲突。
5. 训练避坑:标签、显存、过拟合三个方向的高频翻车现场
5.1 标签越界和空文件:训练直接中断的元凶
现象:训练刚起步几十个 iteration,就报AssertionError: labels require no nan values或者 xml 解析错误,程序退出。
原因:YOLO 格式要求坐标是归一化后的 0~1 数值,但有少数目标靠近图片边缘时,标注框可能出现 xmax 超出图片宽、ymax 超出高的像素值,导致归一化后出现大于 1 的异常值。还有一种情况是图片里有目标没标,生成了空标签文件,或者 labelimg 标完漏存了同名 txt。
解决:训练前写一个清洗脚本,把所有标签读一遍,剔除异常框和空文件,同时把越界框裁剪回 0~1 范围内:
import os labels_dir = "dataset/labels" for f in os.listdir(labels_dir): if not f.endswith(".txt"): continue path = os.path.join(labels_dir, f) lines = [] with open(path, "r", encoding="utf-8") as fp: for line in fp: parts = line.strip().split() if len(parts) != 5: continue # 跳过格式不对的行 cls, cx, cy, w, h = int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 修正越界值 cx = min(max(cx, 0.0), 1.0) cy = min(max(cy, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{cls} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") if lines: with open(path, "w", encoding="utf-8") as fp: fp.write("\n".join(lines) + "\n") else: os.remove(path) # 空标签文件直接删掉,训练时省得报错逻辑说明:这段脚本做两件事,一是把坐标值强制裁剪到合法区间,二是把空标签文件删掉。训练之前的清洗环节我非常建议保留在流程里,尤其换数据集时更要做一次,数据集本身标签质量高,但经过人为复制移动,难保不会出现个别文件被改动的情况。
5.2 类别编号对不上:训练能跑,但 AP 全为零
现象:训练正常跑完了,验证集 mAP 显示 0,损失也迟迟降不下来,损失曲线发散。
原因:VOC 格式里的类别是字符串("ship"、"person"),而 YOLO 的 txt 里存的是类别编号(0、1)。如果你手动改过标签文件,或者用脚本转换时没有维护好类别到编号的映射表,就会造成标签里的编号和训练配置文件里的class_names顺序不一致。比如数据里"ship"在 VOC 中是第一个对象,编号应为 0,但你的模型配置里 class 0 对应的是"person",模型学到的东西全是错的。
解决:训练前输出一份类别分布统计,确认编号类别对应关系。写一行命令就能看:
# 统计 labels 目录下每个类别编号的出现次数 awk '{print $1}' dataset/labels/*.txt | sort | uniq -c逻辑说明:输出的每一行是"数量 + 编号",对照训练配置里的类别名列表,从 0 开始按顺序确认。我在实际项目中遇到过最隐蔽的翻车是:labels目录里混进了一个从别的数据集复制来的标签文件,类别编号跟当前项目完全不是一套体系,训练时这个样本对损失的贡献异常大,最后花了一整天才定位到。
5.3 显存溢出:OOM 之后不是调 batch size 就完事
现象:训练到一半报CUDA out of memory,显存直接被打满,然后程序退出。
原因:batch size 设置过大,或者输入图片尺寸img-size设得太高。YOLO 训练时的显存消耗跟batch_size和img_size几乎成正比,但很多新手只改 batch size,无视了 img_size 的影响。
解决:两步降显存,先降 batch size 到原来的一半,再降 img_size,一般就能跑起来:
# 用一半的 batch size 和较低分辨率先验证显存是否够 model.train( data="data.yaml", batch=16, # 原来是 32 就从 16 开始 imgsz=640, # 原来是 1280 就先降到 640 workers=8 )逻辑说明:workers是数据加载线程数,Windows 上设太高容易报DataLoader worker相关错误,建议 4~8 之间。另外,即使显存能放下,batch size 过小会导致 BN 层的统计量不准,训练不稳定,这时候优先考虑的是梯度累积,而不是硬扛。
5.4 划分后图片和标签数量对不上:训练集里出现无标签图片
现象:训练时日志里反复出现WARNING: no labels found in ...,或者最终训练完的类别数是 0。
原因:划分脚本运行时,有些标签文件缺失或者没有和图片同名。比如你在 Windows 上跑脚本,.jpg和.JPG后缀大小写不一致,导致匹配失败;又或者复制时标签文件被占用没拷过去。
解决:划分完成后立刻做一次数量比对:
# 分别统计两个目录下的文件数量,应该严格一致 ls dataset/split/train/images/ | wc -l ls dataset/split/train/labels/ | wc -l逻辑说明:两边数量相等是基本前提。数量不一致时,优先检查是否有大小写混用的情况,其次看看是否有图片文件本身后缀特殊,比如.jpeg和.jpg并存,文件名取 stem 的时候也会受影响。这个检查是我每次划分完数据集后固定跑的一步,省下来的排错时间比脚本运行时间长得多。
5.5 验证集和训练集场景重叠:指标虚高,一上线上就崩
现象:训练时验证集 mAP 曲线很漂亮,85% 往上,但把模型拿去对实测视频测试,漏检率明显偏高,跟在验证集上的表现完全不是一回事。
原因:原始数据是按时间序列采集的,同一时间段、同一片海域的图片在打乱前是连在一起的。如果划分前没有随机打乱,或者分成训练集和验证集时太随意,验证集会包含跟训练集极相近的同类图片,等于"开卷考试",指标自然好看。
解决:回到 3.2 节的脚本,确认random.shuffle生效,并且划分完之后人工抽查验证集图片和训练集图片是否有明显的时间连续性。极端情况下,如果单类数据极少,按场景分组再划分才是可靠的,但这份数据集场景丰富度足够,正常随机划分问题不大。
提示:如果你同时用了三分脚本和 ImageSets 脚本,要保证两次划分的随机种子一致,否则可能出现 train.txt 里的图片和实际训练目录里的图片对不上的情况。跑训练前把 train_list.txt 打开随便看几行,核对第一张图确实存在于训练文件夹中。
6. 一套我常跑的验证流:训练完如何快速证明模型没白练
训练完不是看到mAP数字就收工,我有一套固定流程:用训练好的模型对验证集批量推理,把预测框和对应的真值框做 IoU 比对,统计不同 IoU 阈值下的命中率,同时把结果图片导出来人工扫一眼。这套流程能帮我快速判断模型是真正学到了目标特征,还是仅仅靠数据集重叠刷高了指标。
import cv2 import torch import numpy as np model = torch.hub.load("ultralytics/yolov5", "custom", path="runs/val/exp/weights/best.pt") # 挑一张验证集图片,推理并输出 img_path = "dataset/images/test/00321.jpg" results = model(img_path, size=640) boxes = results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls] # 读对应的 VOC 真值框做 IoU 比对 import xml.etree.ElementTree as ET xml_path = "dataset/annotations/00321.xml" tree = ET.parse(xml_path) gt_box = None for obj in tree.findall("object"): bnd = obj.find("bndbox") gt_box = [int(bnd.find(t).text) for t in ["xmin", "ymin", "xmax", "ymax"]] def iou(box1, box2): x1 = max(box1[0], box2[0]); y1 = max(box1[1], box2[1]) x2 = min(box1[2], box2[2]); y2 = min(box1[3], box2[3]) inter = max(0, x2 - x1) * max(0, y2 - y1) area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) return inter / (area1 + area2 - inter + 1e-6) if (area1 + area2 - inter) > 0 else 0 if gt_box is not None and len(boxes) > 0: for box in boxes: if box[4] > 0.5: # 置信度阈值 score = iou(box[:4].tolist(), gt_box) print(f"conf={box[4]:.2f} IoU={score:.2f}") # 可视化推理结果,肉眼确认框的位置 results.show()逻辑说明:先用 YOLO 官方接口加载训练好的权重,对单张图推理;再解析同名 xml 拿到真值框,计算 IoU。IoU 大于 0.5 视为命中,这一步能快速量化模型的框精度。conf是置信度,实际项目里要根据漏检和误检的代价调阈值,不是越高越好。
参数说明:size=640是推理时缩放的输入尺寸,要和训练时设置保持一致,否则小目标检测率会明显波动。conf=0.5是通用起点,如果业务上更在意召回(比如漏检一个目标代价极高),降低到 0.3 再看效果,代价是误检增多,这个权衡没有标准答案,只能按业务目标定。
从那以后,我每次训练完都强制自己走一遍这套验证流程,而不是只盯 mAP 数字,至少能筛掉一半的"假收敛"。这份资料包里还有环境搭建和训练的完整教程,照着顺序操作基本能通;数据集的详情展示和更多说明,可以去博客文章页看。希望帮到你。
本文还有配套的精品资源,点击获取