
简介面向海洋生态监测、海洋生物学研究和水下机器人视觉导航等场景的珊瑚礁物种目标检测数据集包含训练集1095张、验证集47张、测试集46张共1188张真实水下拍摄图片覆盖棘杯珊瑚属、灌木状硬珊瑚、分支状硬珊瑚、杯珊瑚属、刺叶珊瑚属五类典型珊瑚礁物种全部采用YOLO格式标注边界框与类别索引类别信息经海洋生物专家校验。整个压缩包共2000个文件主要包括1188个txt标注文件、810个jpg原始图像、1个yaml配置文件以及1个docx说明文档包体约33.01MB下载后可直接接入YOLO/SSD等目标检测流程也可迁移至珊瑚白化预警、健康状态分类等衍生任务。目前已有82人学习。资源最大的价值在于立足真实水下复杂背景与光照条件既支持非侵入式生态监测方案开发也为珊瑚种群密度分析、生态敏感区保护等研究提供高质量数据基础。1. 珊瑚礁物种目标检测数据集是什么水下场景为什么让通用目标检测翻车珊瑚礁物种目标检测数据集.zip 这个文件名看着像一份普通打包数据实际它代表着一类非常不友好的目标检测任务水下图像里硬珊瑚、软珊瑚、海绵、海葵、棘冠海星和珊瑚鱼可能同时出现目标常常只有十几个像素水体散射让对比度变低背景纹理和目标长得极其相似。通用检测模型直接拿这种数据集训练mAP 经常会掉到普通数据集的三分之一以下不是模型不行而是训练前缺少针对水下场景的数据体检和参数适配。我会按解压、体检、训练、修数据、再验证的顺序把一份珊瑚礁数据从 zip 变成可用模型的全过程讲清楚适合做水下相机监控、渔业资源调查和海洋生态监测的工程师照着复现。2. 从 zip 到可训练目录格式确认、数据体检与标注抽检先讲一个很多人跳过的步骤拿到 zip 先别急着一键解压先看压缩包内部结构。用unzip -l列出内容比解压完发现文件层级混乱再收拾要省时间得多。unzip -l coral_reef_dataset.zip | head -60 unzip -l coral_reef_dataset.zip | tail -3head 看目录结构tail 看文件总数。这一步能提前判断数据集是 VOC、COCO 还是 YOLO 组织方式也能看出图片文件夹和标注文件夹是否在同一个根目录下。如果压缩包超过 4GB老版本 unzip 可能认不出 zip64 扩展头优先用 7zip 或 bsdtar 解压。确认没问题后再解压到独立目录不要直接解到训练工作目录里mkdir -p coral_reef unzip coral_reef_dataset.zip -d coral_reef提示解压报 CRC failed 不一定代表文件坏了先df -h看磁盘是不是满了。空间不足时 unzip 提示的报错信息很有迷惑性。2.1 标注格式先确认VOC、COCO 还是 YOLO决定后续所有工具链解压后第一件事是统计标注文件后缀而不是打开某一张图find coral_reef -name *.xml | wc -l find coral_reef -name *.json | wc -l find coral_reef -name *.txt | wc -l三种格式对同一组框的表达差异很大。VOC 是每张图一个 XML框是像素绝对坐标的 xmin、ymin、xmax、ymaxCOCO 是单个 JSON框是像素的 x、y、width、heightYOLO 是每张图一个 TXT框是归一化到 0 到 1 的 cx、cy、w、h。最常见的翻车是把 COCO 的 width 和 height 当成右下角坐标或者把 YOLO 的绝对归一化结果直接画到原图上看起来框比目标大一圈。一份典型 VOC 标注长这样annotation filenamesite01_0516_001203.jpg/filename sizewidth1920/widthheight1080/height/size object namehard_coral/name bndbox xmin812/xminymin460/ymin xmax1044/xmaxymax720/ymax /bndbox /object /annotation拿到 XML 先看size字段是否和真实图像一致。很多数据集的 XML 里写的是标注时的尺寸图像后来又被压缩过一次框就会整体错位。我会把 XML 里的 size 和 PIL 读出的实际宽高做一次自动比对不一致的单独拉出来处理。同时检查有没有 classes.txt 和 README。类别 ID 的顺序是固定的后续训练代码、推理代码都依赖这个顺序。如果标注里出现数字 ID 而不是文字标签先找到类别映射文件别凭文件名猜顺序。常用标注工具像 labelImg 导出 VOCX-AnyLabeling 可以直接导出 YOLO 格式这类混合来源的数据集格式比较杂统一格式之前先把原始格式记录清楚。2.2 数据体检三件事类别平衡、图像尺寸与可见度格式确认后做统计体检。第一项是类别分布用脚本扫一遍 XMLimport glob import xml.etree.ElementTree as ET from collections import Counter counts Counter() for xml_path in glob.glob(coral_reef/annotations/**/*.xml, recursiveTrue): root ET.parse(xml_path).getroot() for obj in root.iter(object): name obj.find(name).text counts[name] 1 print(counts.most_common())这段只统计框数量不区分同一张图内的多个框。更严谨的是再统计“含某一类的图像数”因为训练增强的采样单位是图像而不是框。框数差距大说明类别不均衡图像数差距大说明稀有类别代表性不足两者要分开看。第二项是图像尺寸分布from PIL import Image import glob from collections import Counter sizes Counter() for img_path in glob.glob(coral_reef/images/**/*.jpg, recursiveTrue): with Image.open(img_path) as im: sizes[im.size] 1 print(sizes)水下相机常产出 1920×1080 的视频帧而训练脚本默认尺寸是 640×640两者的采样视野差异很大。我会把所有图像统一到一个视觉口径再进训练而不是直接让 dataloader 各拉各的。如果数据里混着带时间水印的截图和无水印原图水印区域容易成为模型判断目标的伪特征裁剪掉更稳。第三项是粗略估一下水下可见度。计算全图亮度均值和标准差把均值过低的图像数量记下来。低可见度图像占比超过 20% 时训练增强里别开太强的 HSV 扰动水下蓝绿色调一旦被拉偏模型会把水的颜色当成目标特征推理时一到深海场景就误检。2.3 把标注抽检做成脚本图像、XML、TXT 三方对照统计数字看不出标注框是否贴边漏标、错标、框过大过小都要靠目检。抽每个类别约 10 个样本把框画回图像上人工过一遍是成本最低的后悔药。以 VOC XML 为例的快速脚本import cv2, glob, os import xml.etree.ElementTree as ET os.makedirs(check, exist_okTrue) for xml_path in glob.glob(coral_reef/annotations/*.xml)[:30]: root ET.parse(xml_path).getroot() img cv2.imread(os.path.join(coral_reef/images, root.find(filename).text)) for obj in root.iter(object): name obj.find(name).text b obj.find(bndbox) x1, y1 int(b.find(xmin).text), int(b.find(ymin).text) x2, y2 int(b.find(xmax).text), int(b.find(ymax).text) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, name, (x1, max(0, y1 - 8)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) cv2.imwrite(fcheck/{os.path.basename(xml_path)[:-4]}.jpg, img)注意三件事框是否正好包住目标而不是把周围背景也框进来有没有相邻目标被画成一个大方框文件名是否对应到正确的图像。前两个是标注习惯问题最后一个说明数据整理时就错位了后面训练再好也白费。抽检发现错标比例超过 5%先修标注再训练不要指望模型自己学出忽略错标它只会把错误当成规律。3. 用 YOLOv8 跑通珊瑚礁目标检测划分脚本与训练参数怎么设下面这套流程就是日常说的 yolov8 训练自己的数据集。开局先说一个关键原则数据集划分不能随机洗牌。珊瑚礁数据里同一站点同一分钟拍摄的相邻帧高度相似随机划分会让验证集和训练集出现同一镜头画面指标虚高到没有参考价值。3.1 按镜头划分数据集防止相邻帧泄漏到验证集数据泄漏在水下图像里很隐蔽。同一块珊瑚在连续帧之间近乎不动如果这些帧被分别放进 train 和 val模型等于开卷考试。划分单位应该是镜头或站点而不是单张图。假设文件名前缀是站点编号import os, random from collections import defaultdict img_dir coral_reef/images groups defaultdict(list) for name in os.listdir(img_dir): prefix name.split(_)[0] # 按站点/镜头前缀分组 groups[prefix].append(name) gids list(groups.keys()) random.Random(42).shuffle(gids) n len(gids) train_gids, val_gids, test_gids gids[:int(n*0.7)], gids[int(n*0.7):int(n*0.85)], gids[int(n*0.85):] def write_list(path, gids): lines [os.path.join(img_dir, f) for g in gids for f in groups[g]] with open(path, w) as f: f.write(\n.join(lines)) write_list(train.txt, train_gids) write_list(val.txt, val_gids) write_list(test.txt, test_gids)42 是随机种子保证重跑结果一致。70/15/15 是常规划分比例数据量小于 2000 张时我建议改成 80/10/10。把站点前缀作为划分单位后即使某个站点光线条件和其它站点差异极大模型也能验证这种陌生场景下的泛化能力。写完三个 txt 后检查 val 和 train 有没有同名前缀的样本发现泄漏不要犹豫重新划分。3.2 训练命令与关键参数imgsz、epochs、batch 的取值逻辑YOLOv8 用一份 yaml 描述数据和类别。先建 coral_reef.yamlpath: /absolute/path/coral_reef train: images/train val: images/val test: images/test nc: 5 names: 0: hard_coral 1: soft_coral 2: sponge 3: anemone 4: reef_fishnames 的索引顺序必须和训练数据 txt 里的 class id 完全一致否则推理输出对应错类别。之后启动训练yolo detect train \ modelyolov8s.pt \ datacoral_reef.yaml \ imgsz640 \ epochs120 \ batch16 \ lr00.001 \ patience20 \ cacheTrue \ device0 \ projectruns/coral_reef参数取值逻辑这样理解。imgsz640 适合大多数中等目标如果珊瑚鱼目标小于 20×20 像素改用 960 或 1280 效果更明显代价是显存和训练时间翻倍。epochs 按数据量定几千张图 100 到 150 轮足够没收敛时优先查数据质量而不是硬加轮数。batch 由显存决定从 16 起步显存不够就减半尽量保持 8 的倍数。patience20 表示连续 20 轮验证指标不升就早停是防过拟合的关键。lr00.001 是从预训练权重继续训练的稳妥起点随机初始化才需要 0.01。注意珊瑚礁水下图像普遍偏蓝绿色默认的 HSV 增强有可能把颜色抖动搞过头。YOLOv8 里调低 hsv_h、hsv_s、hsv_v 三个参数例如把 hsv_s 从默认 0.7 降到 0.2能减少因颜色增强引入的假样本。3.3 训练过程盯哪些指标别只看 loss 曲线很多人只看训练 loss 往下走就以为模型在进步实际上过拟合时 loss 也在降。正确做法是定时观察验证集 mAP50 和 mAP50-95。mAP50 反映框的中心覆盖是否到位mAP50-95 要求预测框和真实框的 IoU 从 0.5 到 0.95 都能稳住是更硬的精度指标。水下目标边缘模糊mAP50 高但 mAP50-95 上不去通常说明框不够贴边或者标注框本身太松散。训练命令里加上--name train_s640然后启动 tensorboard 看曲线tensorboard --logdir runs/coral_reef如果看到训练 mAP 升但验证 mAP 徘徊先怀疑数据泄漏或验证集太小而不是马上加正则化。如果五类指标都快速收敛只剩某一类很低单独抽那类的图片看标注问题大概率出在标注质量上。4. 标注格式转换与类别不均衡转换脚本与采样策略边界4.1 VOC 转 YOLO坐标归一化与类别映射VOC 的框是绝对像素坐标YOLO 需要归一化到 0 到 1 的 cx、cy、w、h。转换时最容易翻车的是没除以图像真实宽高或者除数是字符串没转 float 直接报错。import os, glob import xml.etree.ElementTree as ET CLASSES [hard_coral, soft_coral, sponge, anemone, reef_fish] def voc2yolo(xml_path, out_dir): os.makedirs(out_dir, exist_okTrue) root ET.parse(xml_path).getroot() w float(root.find(size/width).text) h float(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue cid CLASSES.index(name) b obj.find(bndbox) x1, x2 float(b.find(xmin).text), float(b.find(xmax).text) y1, y2 float(b.find(ymin).text), float(b.find(ymax).text) cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cid} {cx} {cy} {bw} {bh}) txt os.path.join(out_dir, os.path.basename(xml_path)[:-4] .txt) with open(txt, w) as f: f.write(\n.join(lines))逻辑不复杂值得展开讲两句cx、cy 用 xminxmax 求中点再除以宽高bw、bh 用 xmax-xmin 得到宽度再除以宽高。浮点数不要轻易 round 到 6 位小数小目标宽度只有 0.02 左右四舍五入可能丢一半框。如果 XML 里 size 字段和真实图像不一致这段脚本会输出错误归一化坐标。严谨做法是转换前用 PIL 读取实际宽高from PIL import Image img Image.open(img_path) w, h float(img.width), float(img.height)很多“框整体偏移半个目标”的现场就是 XML 里的 size 是二次压缩前的旧值导致的。4.2 COCO 转 YOLOimage_id 与 annotation 的对应关系COCO 的 JSON 里 image 有 id、file_name、width、heightannotation 有 image_id、bbox、category_id。转换的核心是把 image_id 映射到文件名和尺寸再做归一化。import json, os with open(instances_train.json) as f: coco json.load(f) img_meta {im[id]: im for im in coco[images]} cat_meta {c[id]: i for i, c in enumerate(coco[categories])} out {} for ann in coco[annotations]: if ann.get(iscrowd): continue im img_meta[ann[image_id]] x, y, bw, bh ann[bbox] cx (x bw / 2) / im[width] cy (y bh / 2) / im[height] line f{cat_meta[ann[category_id]]} {cx:.6f} {cy:.6f} {bw/im[width]:.6f} {bh/im[height]:.6f} name im[file_name].rsplit(., 1)[0] out.setdefault(name, []).append(line) for name, lines in out.items(): with open(fcoral_reef/labels/{name}.txt, w) as f: f.write(\n.join(lines))这里面有两个经典坑。第一个是 ann 里的 bbox 四元组是 x、y、width、height不是两对角点坐标第二个是 category_id 在原始 JSON 里通常不是从 0 开始的连续整数比如 1、2、4、6 中间有空档直接喂给 YOLO 会使类别序号错位用 cat_meta 重映射成 0 到 nc-1 是必须的。转换完要抽查一个样本把 txt 读回坐标和原图叠加。快速扫非法值可以用命令grep -E (-[0-9]|1\.[0-9]{2,}) coral_reef/labels/*.txt这个正则按文本筛会误伤 1.0 这种合法值更严谨的做法是用 Python 解析浮点数判断范围小于 0 或大于 1 的单独输出到一个列表里逐条人工看。4.3 类别不均衡处理重采样与复制增强的边界在哪水下生态数据最容易出现长尾分布常见物种标了几万个框稀有物种只有几百个。YOLOv8 的损失函数不天然处理长尾常见做法是先做图像级重采样让稀有类别的图像在单个 epoch 里被多抽几次。import random rare_images [img_001.jpg, img_002.jpg] # 从统计结果里筛出来 sample_multiplier {img: 3 for img in rare_images}但重采样有边界倍率大于 5 时模型开始反复背同一批图过拟合比不均衡更严重。我更常配合复制增强使用对稀有类别图像做 90 度旋转、水平翻转和轻微亮度扰动生成 2 到 3 份增强副本进训练集而不是把原图重复喂 5 遍。如果稀有类别本身是小且模糊的目标复制增强解决不了本质需要补标注或从别的站点补数据。还有一个经常被忽略的点类别不均衡的根源可能是标注遗漏。某个类别只有 500 个框往往不是真的稀缺而是标注时把暗处的那些漏掉了。统计发现某类占比低于 1% 时先做一次漏标排查再谈采样增强。labelImg 和 X-AnyLabeling 都支持按类别过滤显示这在珊瑚礁数据复查时是最好用的功能。5. 珊瑚礁数据集常见问题排查五个典型坑的现象、原因与解法以下都是实际踩过的坑按现象、原因、解决三步写可以直接照着做排查。5.1 zip 解压报 CRC 错误解压后文件缺失现象unzip 到一半报 CRC failed或者解压完成之后文件数比 unzip -l 列出来的少。原因大概率是文件传输中断或存储介质故障某些网盘下载工具还会把文件改坏少数情况是 zip 采用了超过 4GB 的 zip64 结构老版本 unzip 不识别。解决先用unzip -t coral_reef_dataset.zip测试压缩包完整性确认损坏条目换 7zip 重试一次还不行就重新下载。下载时和发布页的 MD5 或 SHA256 校验值做对比这一步不能省。解压完成后用 find 统计图像和标注数量和发布页描述做对账缺一个文件都要追查原因。5.2 解压提示需要密码但发布页没有给出说明现象unzip 一到解压就提示输入密码数据集 README 和发布页都没有提密码这回事。原因zip 打包时文件头里的加密标志位被错误置 1内容本身可能并没有真正加密这就是常见的 zip 伪加密现象也可能是打包方人工加了密码但忘了记录两种情况在公开数据集里都出现过。解决先回到发布页核对原始说明正规公开发布的数据集一般不会用带密码的 zip 当分发格式多数是打包工具的 bug。遇到这种情况应该和发布方确认后用正常工具重新打包而不是从网上找“密码移除”之类的工具去处理。数据集的密码控制属于数据权属问题不该在数据加工环节绕开。5.3 训练 loss 正常但验证 mAP 偏低现象训练集 mAP50 到 0.8验证集只有 0.4而且来回震荡。原因第一种是数据泄漏反而把验证集变成了训练集的一部分相邻帧跨 train 和 val第二种是验证集图像数量太少且来自同一批次光线模型在该光照条件下过拟合第三种是训练和验证的预处理不一致。解决按 3.1 的做法用镜头前缀重新划分验证集扩大到实际可信的量级训练和验证共用同一份 yaml不要在代码里额外做 resize 或归一化。重划分后 mAP 还是低就从验证集按类别挑 5 个最难样本跑一次推理看框的偏差方向。5.4 小目标珊瑚鱼漏检严重现象mAP50 还行但小鱼漏检多推理图上只画出一部分目标。原因模型下采样倍数高20×20 像素的目标进入特征金字塔后只剩两三个特征点置信度上不去训练时的 imgsz640 也限制了小目标表达。解决先统计数据里归一化宽度小于 0.05 的框占比确认小目标问题规模把 imgsz 提到 960 或 1280batch 按显存减半推理阶段用 SAHI 做切片推理把大图切成 512 的小块分别检测再聚合漏检率能降不少。切片重叠率设 0.2 左右太低会切碎目标太高浪费算力。5.5 转换脚本跑完框整体偏移现象VOC 或 COCO 转 YOLO 之后把 TXT 画回图上发现框往左上或右下整体移动了一段距离。原因最常见的是 XML 的 size 字段和实际图像不一致常见于数据集被二次压缩过分辨率其次是 COCO 的 bbox 被当成角点坐标用宽高被误当成另一个角点。解决转换前强制用 PIL 读取实际宽高重写 VOC 的 size 字段转换后跑一次回读叠加脚本把框画在原图上和 XML 原框对比。这一步在 4.2 末尾提过是避免翻车最直接的手段。6. 部署前的最后验证用每类 AP 和叠加目检挑阈值训练完和真正交付之间还差最后一步。mAP 是平均值可能被优势类拉高实际部署要按用途挑阈值再做一轮目检。6.1 用每类 AP 和混淆矩阵定位薄弱类别YOLOv8 训练完会在 runs/detect 目录生成 PR_curve.png 和 confusion_matrix.png。用这段拿每类 AP 做量化对比from ultralytics import YOLO model YOLO(runs/coral_reef/train_s640/weights/best.pt) metrics model.val(datacoral_reef.yaml, splittest, conf0.001, iou0.5) print(mAP50 , metrics.box.map50) for cid, ap in enumerate(metrics.box.maps): print(metrics.names[cid], round(ap, 3))conf0.001 是为了画完整 PR 曲线不是部署阈值。弱类别 AP 一目了然接着到混淆矩阵里看它被错分成哪类。水下错分常见于 hard_coral 和 soft_coral 之间因为纹理实在太接近。如果后续想把物种类别扩到 50 类以上开放词汇目标检测可以先做零样本预筛把候选区域输出给人工复核比从零标注快得多但固定 5 到 10 类生产检测YOLOv8 这类闭集模型配阈值调整仍然更稳。6.2 推理结果叠加到原图做交付前目检指标确认后把测试集推理结果批量画出来这个动作在交付前必做。连续帧或同一站点的图都过一遍重点看小目标和重叠遮挡场景from ultralytics import YOLO import cv2, glob model YOLO(runs/coral_reef/train_s640/weights/best.pt) for img_path in glob.glob(coral_reef/test/*.jpg): img cv2.imread(img_path) results model.predict(img, conf0.3, iou0.5, imgsz960) annotated results[0].plot() cv2.imwrite(check_test/ img_path.split(/)[-1], annotated)我习惯用三档阈值 0.15、0.3、0.5 各跑一遍同样的测试集观察漏检和误检的此消彼长。水下场景如果目标是计数阈值可以低一点如果目标是物种预警阈值高一点更稳避免误报消耗人工复核精力。6.3 我的验收习惯与一条教训后来我养成的固定习惯是评估指标时把最小目标像素尺寸写进验收标准比如“20×20 以上的目标召回率不低于 0.8”。一次做珊瑚鱼检测时模型 mAP50 到了 0.83部署到 4K 水下视频里却漏掉一半以上的小目标原因就是训练用的 imgsz640 和部署的 4K 分辨率之间尺度差距太大后来按 1280 重新训练才通过验收。从那以后训练参数和部署分辨率总是写在实验备注第一行指标好和部署好这两件事分开认定。希望帮到你。本文还有配套的精品资源点击获取