☰
海底鱼类检测数据集:1000张图三格式标签与YOLO11训练脚本实战
2026/10/11 1:31:51 网站建设 项目流程

简介:这份资源面向从事水下视觉与目标检测的算法工程师、研究生及竞赛选手,提供一套真实场景下的海底鱼类检测数据集,可用于海底监测项目中的鱼类识别任务,也可作为通用鱼类检测数据的补充。数据集共1000张高质量图片,覆盖浅海珊瑚礁、深海沉船周边、沙底海草区以及鱼类遮挡、严重遮挡等多种复杂场景,统一划分为“Fish”一个类别,采用labelimg标注,并同步提供VOC(xml)、COCO(json)、YOLO(txt)三种主流格式,可直接接入YOLO等检测框架训练。资源包为1个PDF文件,大小约3.81MB,内含数据集基本情况介绍与获取方式,因数据体量较大,实际数据托管于百度网盘。附赠YOLO11一键训练脚本,支持GPU、CPU及Mac(M芯片)多平台方案,并给出博主训练结果日志供参考。目前已有90人学习,适合希望快速验证海底鱼类检测方案、减少数据准备成本的读者。

1. 海底鱼类检测数据集:1000 张图、三格式标签与 YOLO11 训练脚本到底能干什么

水下图像和陆地上的 COCO 图片完全不是一个物种。光线衰减、水体散射、鱼群遮挡、背景纹理重复,这四件事叠在一起,会让一个在 COCO 上 mAP 跑到 0.5 的模型,直接迁移到海底场景时掉到 0.1 以下。所以做海底鱼类识别,第一步不是调模型,而是先找到一份标注规范、格式齐全、能直接喂给 YOLO 的数据集。这份资源就是干这个的:1000 张海底鱼类图像,配 VOC、COCO、YOLO 三种格式标签,外加一个 YOLO11 一键训练脚本。它解决的是「从零搭数据集」这段最耗时的脏活,适合做水下目标检测的算法工程师、做海洋牧场监测的落地团队,以及想拿真实场景练手 YOLO 的学生。下面我按「数据长什么样 → 怎么转格式 → 怎么训 → 坑在哪」拆开讲。

2. 数据集结构与三格式标签:先看清目录再动手

2.1 目录布局与文件对应关系

拿到一个数据集压缩包,我第一件事不是解压完就开训,而是先tree一遍看结构。这份资源的典型布局是图像一个目录、标签按格式分三个目录,训练脚本单独放。常见做法是这样:

dataset/ ├── images/ # 1000 张 jpg/png 原图 │ ├── fish_0001.jpg │ └── ... ├── labels_voc/ # VOC 格式 xml │ ├── fish_0001.xml │ └── ... ├── labels_coco/ # COCO 格式 json │ └── annotations.json ├── labels_yolo/ # YOLO 格式 txt │ ├── fish_0001.txt │ └── ... └── train_yolo11.py # 一键训练脚本

VOC 的 xml 是「一图一文件」,COCO 是「全量一个 json」,YOLO 是「一图一 txt」。这三种格式描述的是同一批标注框,只是组织方式不同。你要做的是确认图像文件名和标签文件名能一一对上,差一个后缀都会在训练时报missing label。

2.2 三种格式的字段差异与选型理由

为什么同一份数据要存三种格式?因为下游工具链吃的不一样。VOC 适合 LabelImg 这类标注工具直接打开继续改;COCO 是很多评测脚本和预训练权重的通用输入;YOLO 的 txt 是 Ultralytics 系列训练时唯一认的格式。字段对应关系如下:

格式存储单位坐标表示典型用途
VOC单图 xml左上右下绝对像素 xmin/ymin/xmax/ymax标注复核、转其他格式
COCO全量 json左上角 + 宽高绝对像素评测、迁移、多模态
YOLO单图 txt归一化中心点 + 宽高 cx/cy/w/hYOLO11 直接训练

关键差异在坐标:VOC 和 COCO 用绝对像素,YOLO 用 0~1 归一化值。归一化时如果用了错误的图像宽高(比如把标注时的原图尺寸和实际读入尺寸搞混),框会整体偏移。我一般会先抽 5 张图把 YOLO 框画回去肉眼核对,这一步能省掉后面几小时的排查。

2.3 用脚本核对图像与标签是否对齐

在训练前跑一段校验脚本,比训到一半发现标签缺失划算得多。下面这段检查每个图像是否有对应 YOLO 标签、标签行是否合法:

import os img_dir = "dataset/images" lbl_dir = "dataset/labels_yolo" bad = [] for name in os.listdir(img_dir): stem = os.path.splitext(name)[0] lbl_path = os.path.join(lbl_dir, stem + ".txt") if not os.path.exists(lbl_path): bad.append((name, "missing label")) continue with open(lbl_path) as f: for i, line in enumerate(f): parts = line.strip().split() # YOLO 每行应为 5 列:cls cx cy w h if len(parts) != 5: bad.append((name, f"line {i} cols={len(parts)}")) continue vals = list(map(float, parts[1:])) if any(v < 0 or v > 1 for v in vals): bad.append((name, f"line {i} out of range")) print("问题样本数:", len(bad)) for b in bad[:20]: print(b)

逻辑说明:遍历图像目录,按同名规则找 txt;每行必须是 5 列,后 4 列是归一化坐标,超出 0~1 说明归一化时用错了尺寸。参数上,img_dir和lbl_dir按你实际解压路径改。跑完如果问题样本数是 0,说明数据对齐没问题,可以进下一步。

3. 从 VOC/COCO 转到 YOLO:转换脚本与坐标坑

3.1 VOC 转 YOLO 的完整脚本

虽然资源里已经给了 YOLO 格式,但实际项目里你经常要拿 VOC 重新转一遍(比如改了类别名、过滤了某些类)。VOC 的 xml 里size节点存了原图宽高,归一化必须用它,不能用cv2.imread读出来的尺寸——两者在图像被缩放或 EXIF 旋转时会不一致。

import os import xml.etree.ElementTree as ET classes = ["fish"] # 按你的类别顺序改 voc_dir = "dataset/labels_voc" out_dir = "dataset/labels_yolo" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(voc_dir): tree = ET.parse(os.path.join(voc_dir, xml_file)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in classes: continue cls_id = classes.index(cls_name) bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 转成中心点 + 宽高,再归一化 cx = (xmin + xmax) / 2.0 / w cy = (ymin + ymax) / 2.0 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") stem = os.path.splitext(xml_file)[0] with open(os.path.join(out_dir, stem + ".txt"), "w") as f: f.write("\n".join(lines))

逻辑说明:classes列表决定类别 id 顺序,必须和训练时的data.yaml完全一致,否则模型学到的类别会错位。归一化用 xml 里的width/height,这是 VOC 标注时的真实尺寸。:.6f保留 6 位小数,YOLO 官方推荐精度,太少会导致小目标框抖动。

3.2 COCO 转 YOLO 的注意点

COCO 的 json 里bbox是[x, y, width, height],注意是左上角加宽高,不是左上右下。很多人直接套 VOC 的公式就翻车了。转换时还要注意category_id往往不是从 0 连续开始的,得先建映射。

import json with open("dataset/labels_coco/annotations.json") as f: coco = json.load(f) # COCO 的 category_id 可能从 1 开始且不连续,建映射 cat_map = {c["id"]: i for i, c in enumerate(coco["categories"])} img_map = {img["id"]: img for img in coco["images"]} from collections import defaultdict per_img = defaultdict(list) for ann in coco["annotations"]: img = img_map[ann["image_id"]] w, h = img["width"], img["height"] x, y, bw, bh = ann["bbox"] cx = (x + bw / 2) / w cy = (y + bh / 2) / h per_img[ann["image_id"]].append( f"{cat_map[ann['category_id']]} {cx:.6f} {cy:.6f} {bw/w:.6f} {bh/h:.6f}" ) for img_id, lines in per_img.items(): stem = os.path.splitext(img_map[img_id]["file_name"])[0] with open(f"dataset/labels_yolo/{stem}.txt", "w") as f: f.write("\n".join(lines))

逻辑说明:cat_map把 COCO 的原始 category_id 重映射成从 0 开始的连续 id,这是 YOLO 要求的。bbox的宽高直接除以图像宽高得到归一化值,中心点要x + bw/2。如果 COCO 里存在iscrowd=1的框,通常要跳过,否则密集鱼群区域会引入噪声。

3.3 转换后必须做的可视化抽检

转完不抽检等于没转。我一般随机抽 10 张,把 YOLO 框画回图上存成对比图,肉眼扫一遍有没有整体偏移、框反了、类别串了。

import cv2 import random img_dir = "dataset/images" lbl_dir = "dataset/labels_yolo" samples = random.sample(os.listdir(img_dir), 10) for name in samples: img = cv2.imread(os.path.join(img_dir, name)) h, w = img.shape[:2] stem = os.path.splitext(name)[0] with open(os.path.join(lbl_dir, stem + ".txt")) as f: for line in f: c, cx, cy, bw, bh = line.split() cx, cy, bw, bh = map(float, (cx, cy, bw, bh)) x1 = int((cx - bw / 2) * w) y1 = int((cy - bh / 2) * h) x2 = int((cx + bw / 2) * w) y2 = int((cy + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(f"check_{name}", img)

逻辑说明:把归一化坐标反算回像素,画框。如果框整体偏左上或偏右下,多半是归一化时宽高用反了;如果框大小对但位置飘,检查是不是漏了中心点换算。这一步是血泪经验,跳过它后面训练 loss 不降你会怀疑人生。

4. YOLO11 一键训练脚本:参数怎么设、日志怎么看

4.1 环境与依赖确认

YOLO11 走的是 Ultralytics 那套,装依赖就一行。但水下数据集有个坑:图像分辨率普遍偏高,显存吃紧。先确认环境和显卡:

pip install ultralytics python -c "import torch; print(torch.__version__, torch.cuda.is_available())" nvidia-smi

逻辑说明:ultralytics会自动拉 torch,但版本可能和你的 CUDA 不匹配,所以单独打印确认。nvidia-smi看显存,8G 以下建议把imgsz降到 640 甚至 512。

4.2 data.yaml 的写法

YOLO11 训练认的是data.yaml,路径和类别名必须对。资源里的一键脚本通常会带这个文件,内容大致:

path: ./dataset train: images val: images names: 0: fish

逻辑说明:path是根目录,train/val是相对路径。这份资源只有 1000 张,常见做法是按 8:2 切分,把val指向单独的验证目录。如果直接train: images且val: images,等于在训练集上验证,mAP 会虚高,别被这个数字骗了。

4.3 一键训练脚本与关键参数

from ultralytics import YOLO model = YOLO("yolo11n.pt") # n 最轻,显存不够先用它 results = model.train( data="data.yaml", epochs=100, imgsz=640, batch=16, lr0=0.01, patience=20, project="runs/fish", name="exp1", )

逻辑说明:yolo11n.pt是官方预训练权重,迁移学习比从零训收敛快得多。epochs=100对 1000 张图够用,patience=20表示 20 轮没提升就早停,省时间。batch=16在 8G 显存上跑 640 分辨率比较稳,爆显存就减半。lr0=0.01是初始学习率,小数据集别调太大,否则 loss 震荡。

4.4 训练日志里该盯哪几个数

跑起来后终端会刷一堆指标,重点看三个:box_loss是否稳定下降、mAP50是否上升、cls_loss有没有突然飙高。box_loss不降通常是标签坐标有问题;cls_loss飙高多半是类别 id 和 yaml 对不上。验证集 mAP 到 0.6 以上对水下场景就算不错了,别拿 COCO 的 0.5 标准硬套。

5. 避坑与排查:海底数据集最容易翻车的五件事

5.1 现象:训练 loss 一直是 nan

原因:YOLO 标签里有坐标超出 0~1 或宽高为 0 的脏框,归一化时除零或越界。解决:跑第 2.3 节的校验脚本,把out of range和cols != 5的样本挑出来删掉或重标。

5.2 现象:mAP 高得离谱但实际检测全错

原因:val和train指向同一批图,等于开卷考试。解决:按 8:2 切分,验证集单独放一个目录,data.yaml里val指向它。

5.3 现象:小目标鱼完全检不到

原因:水下小鱼在 640 分辨率下只剩几个像素,特征被下采样吃掉。解决:把imgsz提到 960 或 1280,或者用yolo11s/m这种更大 backbone;也可以在data.yaml里开mosaic增强,但水下图像 mosaic 拼接后颜色分布会很怪,慎用。

5.4 现象:显存爆了,batch 降到 1 还报 OOM

原因:图像原始分辨率太高,dataloader 预处理占显存。解决:训练前统一把图 resize 到 1280 长边存一份,或者设workers=2减少并发加载,cache=False关掉缓存。

5.5 现象:类别名对但检测框全串到一类

原因:VOC/COCO 转 YOLO 时classes列表顺序和data.yaml的names不一致,id 错位。解决:两边都按同一份类别顺序写,转完用 3.3 节可视化确认每类框的颜色和类别对得上。

6. 进阶:用验证集反查标注质量与导出部署

训完不是终点。我习惯拿训练好的模型在验证集上跑一遍预测,把预测框和标注框叠一起看,凡是模型「自信地检错」的地方,八成是标注本身有问题——水下数据集尤其明显,鱼群重叠处标注员经常漏标或框歪。下面这段把预测结果和 GT 一起画出来:

from ultralytics import YOLO import cv2 model = YOLO("runs/fish/exp1/weights/best.pt") results = model.predict("dataset/images", conf=0.25, save=False) for r in results: img = r.orig_img.copy() # 画预测框(红) for box in r.boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite(f"pred_{r.path.split('/')[-1]}", img)

逻辑说明:conf=0.25是置信度阈值,调低能看到更多漏检候选,调高只看高置信。把预测框和 GT 对比,如果某个位置模型反复检出但 GT 没有,基本可以判定是漏标。这一步做完,你会对这份数据的真实质量心里有数。

导出部署时,YOLO11 支持 ONNX,边缘设备上跑很常见:

yolo export model=runs/fish/exp1/weights/best.pt format=onnx imgsz=640

逻辑说明:format=onnx导出通用格式,imgsz要和训练时一致,否则推理框会偏。导出后在目标设备上用 onnxruntime 加载即可,注意预处理(归一化、通道顺序)要和训练时对齐,这是部署阶段最常见的翻车点。

从那以后我每次拿到新数据集,都强制先跑一遍标签校验和可视化抽检,再动训练脚本——省下的排查时间远比那十分钟多。希望这份海底鱼类数据集和训练流程能帮你把水下检测的第一版 baseline 快速立起来。

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

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

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

立即咨询