
简介面向海洋目标检测任务的高质量数据集包适合研究船舶、漂浮物等水上目标的开发者与学习者。数据取自真实场景经专业标注工具标注包含VOC、COCO、YOLO三种格式标签可直接用于YOLO系列模型训练。包内文件共2000个以XML标签文件为主1986个另含HTML教程、Python脚本和TXT说明文件整个压缩包约75MB便于离线查阅与分享。教程详细覆盖Linux与Windows环境搭建、训练流程及数据集划分方法压缩包内目录结构清晰标签与图片分类存放便于按需生成训练集、验证集和测试集降低从数据到模型的上手门槛。目前已有264人学习下载适合需要高质量海洋目标数据并希望快速跑通训练流程的入门及进阶用户。1. 用5000张图训练目标检测前先看包里的三个角色不少朋友下载到一个目标检测数据集压缩包第一反应是解压后直接丢进YOLO训练。这个看似顺理成章的动作恰恰是后面所有报错、类别串位、验证虚高的起点。标题里这个“YOLO海洋目标检测数据集”真正值钱的地方不是那5000张图而是它同时给出了VOC、COCO、YOLO三种格式标签还附带划分脚本和训练教程。这意味着拿到手的是一套可以直接开训的工程单元而不是一堆需要自己花两三天补格式的图片原材料。适合水下机器人、海洋生态监测、海岸巡检方向的工程师和做目标检测毕设的学生。2. 海洋场景为什么比街景更磨人三种标签格式与一个转换脚本2.1 海洋目标检测难在哪不是识别问题是成像问题海洋目标检测和常规自然图像检测的难度不在一个量级。水下成像普遍偏蓝绿色悬浮颗粒会造成局部雾化离镜头稍远的目标对比度就掉得厉害。岸边巡检的船只目标虽然清晰但背景里波纹、反光和遮挡物体一直在变。模型在这种数据上经常出现一种情况训练loss降得很漂亮一到真实水域视频里鱼和漂浮物混成一团框全给错了。这就是为什么海洋数据集不能只靠“图多”取胜。5000张图听上去不少但海洋目标形态差异大同一种鱼在不同姿态、不同水质、不同光线下的特征可能完全不同。加上大量目标是小尺寸比如十几像素宽的潜水员手臂这类样本如果标签格式再出点错整个训练基本就是白跑。所以拿到数据集后第一件事不是开训而是把标签格式这条链路理清楚。2.2 VOC、COCO、YOLO三种格式同一张框图的三种存法VOC、COCO、YOLO是目标检测领域最常见的三种标签格式这套数据已经把同一个标注结果各存了一份。日常使用时你只需要根据选定的训练框架读对应格式没必要把三种格式都手工维护一遍。但你要知道它们各自的存储方式否则转换时很容易搞混。格式标注载体文件粒度框的坐标形式常见使用方VOC独立XML文件每张图一个xmin、ymin、xmax、ymax像素坐标Faster R-CNN、SSD等传统检测器COCO单个JSON文件整个数据集一个bbox为x、y、width、height像素坐标Mask R-CNN、DETR、开放词汇目标检测YOLO独立TXT文件每张图一个class、x_center、y_center、width、height全部归一化到0~1YOLO全家族VOC格式最直观xml里直接读bndbox四个字段人眼就能看懂。COCO格式把所有图片的标注信息全部塞进一个json文件包含images、annotations、categories三个顶层字段结构化程度最高但手写很容易漏字段。YOLO格式和训练框架贴得最近每行一个目标五列数字训练时直接读txt不做数据预处理。这里最麻烦的不是看懂格式而是类别顺序问题。COCO的categories里写的是类别名字符串VOC的xml里也是名字但转成YOLO后类别变成了整数id。这个id对应的顺序完全由转换脚本决定。如果转换脚本按字典顺序生成id而训练配置文件用的是另一份顺序模型就会把鱼认成船而且全程不报错。2.3 用一段Python脚本把VOC标签批量转成YOLO归一化txt手工去改几百个xml文件显然不现实常见的做法是写一个批量转换脚本。我下面给的这个版本是我自己会用的那种不花哨但把最容易翻车的两个点都堵住了类别顺序固定、框出界裁剪。import os import xml.etree.ElementTree as ET # class_list 顺序一旦确定就不要改这个顺序就是 YOLO 训练时 classes.txt 的顺序 class_list [boat, fish, diver, debris] def voc_to_yolo(xml_path, out_txt_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in class_list: continue cls_id class_list.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 部分标注工具会把框出界先裁剪再归一化 xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) w xmax - xmin h ymax - ymin if w 0 or h 0: continue x_center (xmin w / 2.0) / img_w y_center (ymin h / 2.0) / img_h nw w / img_w nh h / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {nw:.6f} {nh:.6f}) with open(out_txt_path, w) as f: f.write(\n.join(lines)) if __name__ __main__: xml_dir voc_labels out_dir yolo_labels img_dir images os.makedirs(out_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue stem xml_name[:-4] img_xml os.path.join(xml_dir, xml_name) # 这里需要拿到原图宽高一般从 xml 的 size 字段取更稳 # 更稳妥的做法是先解析 size而不是依赖图片文件 tree ET.parse(img_xml) size tree.getroot().find(size) w float(size.find(width).text) h float(size.find(height).text) voc_to_yolo( img_xml, os.path.join(out_dir, stem .txt), w, h )关键参数就三个class_list的顺序、图片宽高、输出目录。class_list是整个项目的单一事实来源必须和后续训练yaml里的names保持一致。图片宽高我一般从xml的size字段读取而不是用opencv去量原图因为检测数据集的xml里存的尺寸就是标注时的原始尺寸和图片文件一定对应。如果xml里没有size再退回用PIL读原图但那种情况要额外检查图片是否被压缩过。注意脚本里做了两次数值保护一是把所有坐标裁剪到图片边界内二是丢弃宽或高小于等于0的框。这看起来多此一举但实际标注数据里经常混入这类脏数据不处理的话训练时可能直接报negative dimension错误。2.4 为什么我不建议“先划分再转换”很多人习惯先把数据分成train、val、test再分别做格式转换。这个顺序在小项目里没问题但碰到多格式数据集会埋一个雷三个划分目录要做三遍转换只要类别顺序或者路径映射错一次三个子集的类别id就对不上了。我一般会先不管train还是val把全部数据统一转成YOLO格式再执行划分脚本。YOLO的txt文件占空间很小5000张图全部转完也就几兆。转换是一次性的划分却可能要反复调比例、换随机种子甚至因为某个类别样本太少重新组合。先转换再划分只有一个格式转换链路要维护后面的划分脚本只需要切图片和txt文件的文件名逻辑要简单得多。COCO转YOLO的逻辑也是同一个套路区别只是COCO的annotations里框的坐标是[x, y, width, height]像素坐标转换时要做一次x_center x width / 2的运算。代码十几行就能写完但容易漏掉的是那个json里可能有iscrowd字段的标注这类通常是背景大片区域直接过滤掉更安全。3. 划分脚本怎么调清洗、切分与最小训练命令3.1 划分数据前先看三样东西重复帧、类别均衡、路径规则拿到数据集包先不要急着跑划分脚本花十分钟做三件检查。第一查重复帧。海洋视频抽帧得到的数据集经常有连续几帧几乎一模一样的图或者同一目标在不同帧里反复出现。如果这些相似帧同时进了训练集和验证集验证结果会虚高得离谱。检查方法是按文件名排序后看尺寸是否连续或者直接随机抽几十张图人工翻一遍。第二看类别分布。海洋目标数据天然不均衡比如某类“潜水员”可能只有一两百个框而“船”可能有几千个。如果划分脚本是纯随机切分小类别样本很容易全部掉到一个集合里导致验证时该类mAP直接为0。第三检查路径规则。训练框架对图片路径很敏感全英文路径、不带空格、统一用相对路径这三条能避免九成的数据加载问题。压缩包解压后如果带了一层嵌套目录最好先整理成images和labels平级的标准结构。3.2 一个带随机种子和类别统计的划分脚本下面这个脚本是我反复在用的一份骨架做的事情是遍历图片目录打乱按比例切出train、val、test三份文件列表同时给出每个子集的类别分布统计。import os import random from collections import Counter random.seed(2024) IMG_EXTS (.jpg, .jpeg, .png) def split_dataset(img_dir, train_ratio0.8, val_ratio0.1): images [] for root, _, files in os.walk(img_dir): for f in files: if f.lower().endswith(IMG_EXTS): images.append(os.path.join(root, f)) random.shuffle(images) n len(images) n_train int(n * train_ratio) n_val int(n * val_ratio) train_set images[:n_train] val_set images[n_train:n_train n_val] test_set images[n_train n_val:] print(ftotal: {n}, train: {len(train_set)}, fval: {len(val_set)}, test: {len(test_set)}) return train_set, val_set, test_set def count_classes(label_dirs): counter Counter() for d in label_dirs: for label_name in os.listdir(d): if not label_name.endswith(.txt): continue with open(os.path.join(d, label_name)) as f: for line in f: cls_id int(line.split()[0]) counter[cls_id] 1 return counter if __name__ __main__: # 假设 images 和 labels 是平级目录 train, val, test split_dataset(datasets/images) # 需要根据划分结果把对应的 txt 标签也一起拷贝走 # 这里只打印类别统计实际工程中再把文件对应移动到子目录 print(count_classes([datasets/labels])) for split_name, split_list in [(train, train), (val, val), (test, test)]: with open(f{split_name}.txt, w) as f: for img_path in split_list: f.write(img_path \n)脚本里我固定了随机种子random.seed(2024)这很关键。目标检测训练本身有随机性数据划分如果每次结果不一样想复现训练结果就得连数据划分一起复现。固定种子让整个实验链条可追溯后面调模型才知道指标变化是模型改的不是数据变了一组。第三个参数是划分比例。常见做法是train:val:test8:1:1。但对这种5000张量级的数据test集可以留大一点也没关系。如果数据集包里的教程给的是train和val两份我通常建议把原始划分脚本生成的test集拿来做最终的final check不要在训练过程中反复去测test不然test就变成了第二个val失去验收意义。3.3 类别均衡检查划分完再回头看一眼划分结束后要做一次类别均衡检查。方法是把train.txt和val.txt里所有txt标签扫一遍按类别统计框的数量打印结果。如果发现某个类在val里只有几个框在train里却有几百个说明随机划分对这个类不公平需要人工调整。一个实用的处理方式是把稀有类图片单独抽出来先按比例分配到train和val再把剩下的常规类做随机划分。这个流程不需要写复杂的stratified split逻辑用两次os.rename就能完成第一次复制稀有类文件到两个专门目录第二次跑常规随机划分最后把两批数据合并。简单直观也方便随时查看到底哪个类被手动干预过。3.4 跑通一个最小训练的完整命令以YOLOv8为例把数据组织好后训练命令长这样# 用官方预训练模型起步yolov8s.pt 是通用入口 yolo train \ modelyolov8s.pt \ datamarine.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectruns/marine \ nameexp1其中marine.yaml需要自己写核心是三个字段path: datasets train: train.txt val: val.txt names: 0: boat 1: fish 2: diver 3: debris参数说明modelyolov8s.pt表示加载官方预训练权重继续训练不是从零初始化。冷门海洋数据集如果从零开始训效果通常很差因为水下特征和ImageNet特征差异大预训练权重能提供一个足够好的起点。epochs100对5000张图的中小数据集来说比较合适再往下掉到50会欠拟合往上增加到200性价比反而降低因为YOLO在100轮之后基本进入平台期。imgsz640是默认值如果数据里小目标占比高可以试试imgsz1280或做切片训练。batch16是常见起步值如果显存不够就降到8但要同步调低学习率否则损失曲线会抖得厉害。这里有个容易踩的地方如果电脑上同时装过老版本YOLOv5和新的ultralytics命令行入口可能冲突。先执行yolo --help确认当前用的是哪个版本再开始训练。训练一旦跑起来盯着前几个epoch的loss曲线如果train_loss和val_loss同时降再放着跑过夜否则提前中断排查。4. 划分和训练里最容易翻车的五个经典现场第4章想聊的这五个坑不保证你全遇到但至少四个会在第一次训练结束前出现。坑1图片路径带中文或空格训练到一半直接报FileNotFoundError现象数据集解压后放在桌面或者某个盘符下的“我的数据”目录训练跑到十几个epoch突然崩溃报找不到图片文件重开一次又跑到不同epoch才崩。原因YOLO系训练框架读取数据时路径拼接依赖相对路径和纯ASCII字符。中文目录名在部分操作系统的文件系统调用里编码不一致空格则会让命令行参数解析错位。解决压缩包解压后第一件事把数据集整个移动到纯英文路径比如D:/datasets/marine目录名不要带空格内部结构保持images和labels平级。路径问题看着小实际是最消耗新手耐心的一类报错。坑2类别编号对不上训练不报错模型全程“指鱼为船”现象训练loss正常下降验证mAP也不低但把预测结果画出来看框的位置没错类别名全是错的而且错得很有规律全部偏移一个id。原因VOC的xml里类别是字符串转成YOLO时要映射成整数。转换脚本里的class_list顺序和训练yaml里的names顺序不一致比如转换脚本按字母序排成了船、垃圾、潜水员、鱼而yaml里按原始类别定义排成了船、鱼、潜水员、垃圾两边一对齐就全乱了。解决class_list只维护一份写在单独的classes.txt里VOC转YOLO脚本读取它训练yaml的names也读取它。不要在两处手写类别表写两处就一定会有一处改漏。坑3验证集mAP高得离谱但现场测试完全不行现象val集上mAP0.5超过0.9可一旦把模型拿到新拍的视频上检测效果明显差一截。原因划分脚本没有真正打乱数据。尤其是视频抽帧数据集前几帧和后几帧往往是同一场景的连续画面如果划分时只按顺序前80%做train、后20%做val训练集和验证集之间就出现了“不是同一张图但是同一场景”的泄漏val指标虚高是必然的。解决划分前先检查帧连续性。如果数据是按视频文件名组织的按文件名分组后再划分确保同一个视频的所有帧只进一个集合。实在没有分组信息宁可按子目录整体切也不要逐帧混切。坑4训练中途BN崩溃loss变成NaN整套白跑现象训练到第几十轮loss突然变成NaN查看训练日志发现bn.running_mean全是NaN之后每一步都报数值错误。原因多数情况下是没加载预训练模型从随机初始化开始训练冷门数据集。YOLO的深层卷积在随机权重下梯度极不稳定BN统计量在一个小batch上被异常值带飞。batch太小也会加剧这个问题。解决规则很简单加载预训练模型再训练。yolov8n.pt或yolov8s.pt都行这是目前最可靠的做法。batch不要低于8如果显存只够batch4先把imgsz降到480然后加载预训练权重同时把学习率降到0.001再试。坑5训练教程命令在当前版本跑不通参数表对不上现象照着数据集里附带的训练教程执行提示没有train.py这个参数或者--weights、--data这类参数直接不识别。原因YOLO生态迭代很快老版本用argparse脚本新版本改成了一键命令。旧笔记里大量命令行参数已经不适用。解决先跑yolo --help确认当前版本支持的参数再用统一入口训练。如果确实要复现老版本用虚拟环境把依赖版本固定住不要全局直接装最新版。教程里的训练参数只作为参考以本机实际版本为准。5. 把训练结果做成能交差的东西困难样本收集与验收习惯训练跑完拿到best.pt只是第一步真正能交差的是你能说清楚“这个模型在哪些地方还会错”。我常用的一个做法是收集困难样本让模型自己把犹豫的框交出来。所谓困难样本不是指模型完全检测不出的图而是那些“差一点就对了”的预测。执行预测时把置信度阈值放低yolo predict modelbest.pt sourceval/images conf0.05 save_txtTrue保存所有框之后写个十几行的小脚本把预测txt和验证集真实txt的行数差异较大的图片挑出来。我一般先按数量粗筛预测框数量和真实框数量差超过2的说明出现了明显的漏检或误检。粗筛的结果拷进difficult_samples目录再人工翻一遍远比只看一张整体mAP曲线能得到更多信息。验收时不要只看mAP0.5。海洋场景小目标多重点盯mAP0.5:0.95这个指标在海洋数据上通常比常规数据集低不少。如果mAP0.5正常但mAP0.5:0.95掉得厉害说明定位精度不足优先考虑提升输入分辨率而不是盲目加模型容量。混淆矩阵总和不是固定的它取决于置信度阈值别拿两套不同阈值下的混淆矩阵去比总数。我现在的习惯是每次训练完不只看权重文件的mAP而是把val里所有预测结果拼成一张大图先自己盯五分钟再决定要不要发布。RAR里面给了划分脚本和教程但真正让一批数据可复现的关键是固定随机种子和固定类别清单。这不是玄学是给自己留后悔药。希望帮到你。本文还有配套的精品资源点击获取