ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

水下鱼类实例分割数据集:从COCO转YOLO到YOLOv8-seg训练实战

水下鱼类实例分割数据集:从COCO转YOLO到YOLOv8-seg训练实战 简介水下鱼类实例分割数据集面向水产养殖监测、海洋生态调查、渔业资源管理及水下机器人视觉等场景包含7种经济鱼类的精细多边形轮廓标注总计1274张真实水下图像划分为训练集901张、验证集250张、测试集123张可直接用于YOLOv8-Seg、Mask R-CNN等实例分割模型的训练与评估。包体共2000个文件以1274个YOLO格式txt标注文件为主辅以724张水下实拍jpg图像、类别配置yaml及说明文档docx压缩包大小约61.17MB即取即用。目前已有167人学习下载适合算法工程师、科研人员及水产智能化开发者。数据集采集自真实水下环境涵盖光照变化、水体浑浊等实际挑战标注精度达到像素级可用于鱼种识别、种群计数、尺寸统计等细粒度任务同时7类鱼种覆盖不同姿态与视角为海洋生态保护和可持续渔业决策提供可靠视觉数据支撑。1. 水下鱼类实例分割数据集.zip压缩包里装的不只是一堆鱼图“水下鱼类实例分割数据集”这个标题乍看像是一个随手命名的压缩包但解压之后你会发现它是一套典型的垂直场景实例分割训练资产水下拍摄的鱼类图像、逐实例标注的掩码、类别清单以及分布在训练和验证目录下的标注文件。它的目标非常具体——让实例分割模型在蓝绿色调主导、低能见度的水下画面里把每条鱼单独分割出来而不是把整群鱼糊成一团。它适合水产养殖监测、海洋牧场巡检、鱼类行为分析和生态调查这类场景。对刚入门实例分割的新手来说这个数据集的价值在于把水下低对比度、色彩偏移、鱼群遮挡这些真实痛点提前封装进了标注里省去重新采集视频、逐帧勾边的功夫。2. 从解压到可训练目录结构、标注格式与数据完整性校验2.1 先分清两种标注载体COCO JSON与YOLO txt在实例分割里的差异解开水下鱼类实例分割数据集.zip之后第一件事不是双击看图片而是先分清楚标注放在哪里、以什么格式存放。实例分割数据集的标注载体主流就两种COCO JSON和YOLO txt。COCO风格用一个全局JSON承载所有信息至少包含images、annotations、categories三块。images记录图片文件名、宽高和IDannotations记录每个实例的image_id、category_id、bbox以及关键字段segmentationcategories记录类别ID与名称。segmentation字段有两种写法一是多边形顶点列表二是RLE运行长度编码。多边形直观但标注粗糙时顶点动辄上千RLE编码体积小人眼没法直接读解码时得靠pycocotools。COCO2017数据集结构就是这么设计的很多水下鱼类数据集也沿用了这套规范。YOLO风格则相反不搞全局文件每张图片对应一个同名txt。txt每行是一个实例第一列为类别ID之后是归一化的多边形坐标即“x1 y1 x2 y2 …”。这种格式喂给YOLO训练脚本最直接文件夹挪动也方便但你想做类别分布统计、面积分析时反而要把所有txt重新解析一遍。实例分割和语义分割的差异在格式上也体现得很明显语义分割给每个类别一张全图mask实例分割必须为每个个体单独写一组多边形。实操中碰到最多的组合是数据集.zip解压后是COCO格式而你想跑YOLOv8-seg或YOLO26这一类实例分割训练。这时候必须做一次格式转换COCO JSON解码出每个实例的mask再转成多边形最后归一化写入txt。第二常见的情况反过来数据集给的是YOLO txt但没有类别名只有类别ID你得自己维护一份类别映射表才能接训练。这两步决定后面所有流程能不能顺利走通值得在开头就理顺。2.2 解剖一个典型样本图像、mask、标注三者的对应关系一个合格的鱼类实例分割样本至少由三样东西组成图像本身、掩码、标注条目。水下鱼类图像和通用目标检测图的差别很大。常规场景里目标物体与背景有明确颜色分界水下则是蓝绿色调覆盖全场鱼的体色与背景灰度接近而且随着深度、光照不同同一条鱼在不同帧里的色彩偏移可以漂移很多。因此这个数据集的图像帧质量往往决定了训练上限而不是模型结构。掩码则是数据集的核心资产同一个类别的不同个体要有独立标注这也是实例分割和语义分割在数据层面的根本区别。标注条目记录类别、实例ID、bbox和分割轮廓。目标检测只要框住鱼就算对实例分割要求轮廓贴着鱼身走。框里的背景像素在检测里是容忍噪声在实例分割里会直接污染mask边缘表现为训练后轮廓锯齿、鳍部丢边。水下鱼类的胸鳍、尾鳍经常与背景融为一体标注时如果没逐帧放大钩边很容易留下锯齿边或把鳍裁掉。这种边缘质量的问题后面训练时会集中爆发。还有一个容易被忽略的问题类别颗粒度。有的数据集只分到“鱼”有的分到具体物种。颗粒度越细类别不均衡和样本量需求就越大。拿到压缩包后先看分布再决定要不要重采样不要直接拉进训练脚本。我在处理类似数据集时养成一个习惯第一件事永远是跑统计脚本把类别数量、bbox面积、平均实例数打印出来再做后续决策。这个习惯帮我避开过不少坑。2.3 用Python脚本快速scan数据集的完整性解压之后先写一个校验脚本用数据看全貌不要靠肉眼去猜。# scan_coco.py import json from collections import Counter from pathlib import Path data_root Path(underwater_fish_dataset) ann_path data_root / annotations / instances_train.json with open(ann_path, r, encodingutf-8) as f: coco json.load(f) images coco[images] annos coco[annotations] cats coco[categories] print(图片总数:, len(images)) print(实例总数:, len(annos)) print(类别:, [c[name] for c in cats]) cat_id_to_name {c[id]: c[name] for c in cats} cat_cnt Counter() area_list [] for ann in annos: name cat_id_to_name.get(ann[category_id], unknown) cat_cnt[name] 1 bbox ann[bbox] area_list.append(bbox[2] * bbox[3]) print(类别分布:, dict(cat_cnt)) if area_list: print(平均bbox面积:, round(sum(area_list) / len(area_list), 1))这段脚本回答三个问题体量够不够、类别分布是否均匀、目标平均尺度多大。水下鱼类场景里类别分布几乎天然不均衡容易拍的鱼占大头难拍的鱼只有零头。如果分布相差十倍以上训练时少数类极易被淹没。此时应先做类别重采样比训练完再抢救便宜得多。脚本跑完还要人工抽查一下file_name是否真实存在于图片目录。JSON指向的图片如果根本不存在训练缓存生成阶段不会报错但训练批次里会混入空标注样本数量对不上。这种问题最隐蔽属于典型的“黑匣子”阶段难排查的类型。先把扫描做完再进转换顺序不要颠倒。3. 把COCO标注转成YOLO-seg格式RLE解码、轮廓提取与全量转换脚本3.1 读取COCO JSON并解析实例标注RLE解码与轮廓提取COCO JSON里的segmentation字段对同一个实例可能有两种形态多边形顶点列表或者RLE字典。pycocotools的mask模块能把两者统一转成二值mask。解码之后一个实例对应一张与图像同尺寸的二值图像素值为1的位置就是这条鱼的身体。from pycocotools import mask as maskUtil import numpy as np def coco_ann_to_mask(ann, h, w): seg ann[segmentation] if isinstance(seg, dict): # RLE 压缩编码直接解码 rle maskUtil.frPyObjects([seg], h, w)[0] return maskUtil.decode(rle).astype(np.uint8) # 多边形顶点列表可能是多个子区域 rles maskUtil.frPyObjects(seg, h, w) merged np.zeros((h, w), dtypenp.uint8) for r in rles: merged maskUtil.decode(r).astype(np.uint8) return (merged 0).astype(np.uint8)这段代码的逻辑是先判断segmentation的类型。如果是字典说明是RLE直接用frPyObjects包装后decode如果是列表说明是多个多边形顶点序列先逐个转成掩码再累加最后压回0/1。为什么要累加而不是直接覆盖因为COCO允许同一个实例由多个不相邻的轮廓组成比如鱼鳍被水草遮挡后分成了两块。直接覆盖会丢掉第二块区域累加再二值化最稳妥。decode得到的mask默认是uint8但某些版本的numpy相加时要注意溢出。水下鱼类的mask面积通常不大累加次数有限溢出风险不高。为保险起见最后统一做布尔比较转成二值这一步习惯保留。拿到mask后下一步是提取轮廓坐标。这里用OpenCV的findContours配合approxPolyDP做简化。import cv2 def mask_to_polygons(mask, eps_ratio0.002, min_points4): contours, _ cv2.findContours( mask.astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE ) polys [] for cnt in contours: cnt cnt.squeeze(axis1) if len(cnt) min_points: continue peri cv2.arcLength(cnt, True) simplified cv2.approxPolyDP(cnt, eps_ratio * peri, True) polys.append(simplified.reshape(-1).tolist()) return polysRETR_LIST模式会返回所有轮廓不建立层级关系。水下鱼类掩码如果有内孔这些内轮廓也会被一并返回转换成YOLO多边形时能保留孔洞的负空间。CHAIN_APPROX_SIMPLE只保留必要的拐点减少坐标数量。approxPolyDP的eps_ratio是关键参数它表示简化容差占轮廓周长的比例。我一般从0.002起步分辨率高的图片可以调到0.001以下否则边缘会明显失真。3.2 归一化坐标与类别映射转换脚本全量代码把上面两步拼接起来就是一个完整的COCO转YOLO-seg工具。下面这个版本是我常用的最小实现。# coco_to_yolo_seg.py import json import cv2 import os import numpy as np from pathlib import Path def coco_to_yolo_seg(coco_json, img_root, out_root, cat_id_offset0, min_area4, eps_ratio0.002): Path(out_root).mkdir(parentsTrue, exist_okTrue) with open(coco_json, r, encodingutf-8) as f: coco json.load(f) # 类别ID重映射从0开始或偏移指定值 cat_map { c[id]: i cat_id_offset for i, c in enumerate(coco[categories]) } for img in coco[images]: img_path os.path.join(img_root, img[file_name]) image cv2.imread(img_path) if image is None: print(跳过:, img_path) continue h, w image.shape[:2] # 找到属于当前图片的所有实例 anns [a for a in coco[annotations] if a[image_id] img[id]] lines [] for ann in anns: mask coco_ann_to_mask(ann, h, w) if int(mask.sum()) min_area: continue poly_list mask_to_polygons(mask, eps_ratioeps_ratio) for poly in poly_list: norm [] for i in range(0, len(poly), 2): norm.append(f{poly[i] / w:.6f}) norm.append(f{poly[i 1] / h:.6f}) cls_id cat_map[ann[category_id]] lines.append(str(cls_id) .join(norm)) if lines: out_txt out_root / f{Path(img[file_name]).stem}.txt with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines)) print(转换完成)执行流程是读取COCO全局JSON重映射类别ID遍历images块逐图读取图像尺寸匹配该图所有标注解码mask过滤小面积噪声提取轮廓归一化坐标最后写txt。类别ID重映射这步很多新手会漏COCO的category_id可能从1开始甚至有跳号YOLO训练要求从0开始连续编号。cat_id_offset参数用来处理“0留作背景”的自定义约定。归一化坐标统一保留6位小数。太短会丢掉轮廓细节太长文件膨胀。对常见分辨率6位小数已经能把精度控制在亚像素级。如果图片宽高不是整数倍直接用float除法即可YOLO训练时会做双线性插值坐标越界反而麻烦。3.3 转换的四个边界坑坐标越界、空mask、多实例合并、类别对不齐转换脚本看着简单实际能踩的坑不少我整理成四条边界情况。第一是坐标越界。标注员勾边时手抖多边形某个顶点可能落在图像外面。这样的坐标写进txtYOLO训练时会把该顶点解释为“按比例放大到整幅图”轻则mask变形重则整个batch出错。做法是在归一化之后做一次clip把x限制在0到1之间y同理但要注意如果整个轮廓都飞出画面就直接丢弃。第二是空mask。有些标注的segmentation解码之后面积几乎为零可能是标注过程中误操作留下的空轨迹。min_area参数就是兜底用的我习惯设4像素低于这个阈值直接跳过。设太高会把小鱼丢掉设太低又挡不住噪声点。第三是多实例合并。鱼群密集时标注可能把两只鱼的轮廓画进了同一个ann条目。转换后的txt会输出一个类别ID两组坐标模型训练时无法区分这是两只鱼还是一条鱼的两个碎片。处理办法是转换时按轮廓是否封闭、面积占比做拆分至少要在转换后的结果里人工抽查10%的txt。第四是类别对不齐。水下鱼类数据集有时候会出现categories块和annotations块的类别ID不一致某个ann里写了一个categories中不存在的ID。cat_map里直接取会抛KeyError代码里应改成cat_map.get(ann[category_id], -1)转换时把-1跳过并打印警告避免整个脚本中断。这四条坑处理完转换出来找一组图片叠加可视化确认轮廓确实贴着鱼身再进入训练环节。4. 用YOLOv8-seg训练水下鱼类实例分割YAML配置、最小命令与可视化验证4.1 数据集目录组织与YAML配置转换完成后的标签要和图片按train/val划分对齐。常见的做法是数据目录里放四块images/train、images/val、labels/train、labels/val图片和同名txt分别放进去。水下鱼类数据集的拍摄往往是一条视频截多帧帧之间高度相似划分时最好按视频片段分不要随机分帧否则验证集会泄漏训练信息mAP虚高。数据目录组织好之后建一个YAML文件指向图片路径并声明类别名。# fish_seg.yaml path: ./underwater_fish_dataset train: images/train val: images/val names: 0: carp 1: tilapia 2: perchpath字段写绝对路径或相对路径ultralytics会以它为基准拼接train和val。names的ID必须和转换脚本里的类别映射对应这里不需要写labels路径训练器会自动把images/train替换成labels/train来找txt。如果你自己改了目录名比如把标签放在annos/train就必须在训练参数里额外指定或者保持默认同名约定。YOLOv8-seg的标签格式来自YOLO生态约定水下属性的 txt 文件与第3章转换脚本输出一致。类别0、1、2的顺序决定了模型输出的索引训练阶段不要频繁改动否则权重和类别对齐关系会乱。4.2 用YOLOv8实例分割训练的最小命令目录和YAML就绪后跑一次完整训练的最小命令如下。yolo segment train \ modelyolov8n-seg.pt \ datafish_seg.yaml \ imgsz640 \ epochs100 \ batch16 \ cacheTrue \ patience15 \ projectrun/fish_seg \ nameexp1model参数换成yolov8n-seg.pt这是实例分割系列里参数量最小的预训练权重适合先验证数据链路是否通畅。imgsz设成640是起步值水下小目标多时可以升到768但训练速度和显存占用会明显上升。batch要按显存调整12GB显存跑n模型640分辨率batch16基本能放下如果换yolov8s-seg或更大模型batch得往下压。cacheTrue会把数据集预加载到内存几万张图时能大幅缩短读取时间。注意如果图片总量超过内存不要硬开会直接把机器拖死。patience15是早停耐心值也就是连续15个epoch验证mAP没有提升就提前结束。水下数据集噪声大损失曲线经常抖动patience设太小容易在真正收敛前被叫停。这里需要补充一句关于yolo26和实例分割生态的说明YOLO系列从v8到更新的版本训练入口和标签格式保持稳定选择实例分割还是语义分割只影响输出头的配置不改变数据集txt结构。因此第3章的转换脚本对后续版本依然可用。4.3 训练完成后先做可视化验证训练结束先别急着看mAP数字先用模型在验证集上跑一次推理把预测mask叠加到原图上肉眼看几条鱼。yolo segment val \ modelrun/fish_seg/exp1/weights/best.pt \ datafish_seg.yaml \ plotsTrue \ save_jsonTrueplotsTrue会生成预测图和混淆矩阵保存到run/fish_seg/exp1/下。save_jsonTrue会输出一个包含每张图预测结果的JSON文件之后做统计分析和mask面积比对时非常有用。打开生成的验证图重点看三类情况小鱼是否被漏检鱼身轮廓是否贴着真实边缘两条鱼紧贴时是否被合并成一个mask。这三类问题靠mAP难以直接反映但肉眼看图几秒钟就能判断。如果轮廓有轻微锯齿不用急着改标注先调整验证时的conf阈值和iou阈值。若mAP数值正常但mask边缘飘大概率要回第3章检查epsilon参数。要是基本轮廓都对得上再进入后面的细分优化。5. 水下鱼类实例分割训练避坑五个常见问题的现象、原因与对策5.1 现象一训练Loss稳定下降验证mAP却高频震荡这在水下场景里太常见了。现象是Training loss一路走低甚至降到0.01以下验证mAP却每隔几个epoch大幅摆动最终收敛值很低。原因在于水下光照和色温随深度变化剧烈训练集里如果混入了多个拍摄时段的数据模型学到的颜色统计特征并不稳定在验证集上表现飘忽。解决方法是先做一层简单的色彩归一化把图像的RGB均值拉到相近水平再从数据增强里引入亮度、对比度扰动让模型不能依赖单一色偏。同时把学习率调低一档比如把lr0从0.01改成0.001并适当加大patience。5.2 现象二分割mask边缘锯齿明显轮廓“整圈毛边”推理图上mask边缘像毛刺一样局部凹凸不平。原因有三层原始标注就不够精细鳍部轮廓勾得很粗3.1节里approxPolyDP的容差设得太大把真实细节抹平了训练时输入尺寸被下采样高分辨率边缘信息丢失。解决优先级从低到高先把imgsz从640提到768看边缘是否改善再把eps_ratio降到0.0005重新转换数据集最后对少数边缘极差样本做人工修补。如果前两步做完仍严重问题大概率在标注本身不用纠结模型参数。5.3 现象三类别不均衡少数类被多数类“吃掉”水下拍摄天然不均衡某种鱼容易被拍到实例数占绝大多数其他种类样本极少。现象是训练完成后少数类几乎不出现在预测结果里mAP对多数类虚高。解决的关键在数据层面不要在模型层面硬调loss。我一般先按第2.3节统计分布对数量不足的类别做水平翻转、轻度旋转、尺度抖动等增强仍不够就把该类别的实例复制到训练集或者用类别权重给损失函数加权。类别重采样是这里性价比最高的手段。5.4 现象四小鱼群漏检尤其是远离镜头的个体鱼群画面里近处的大鱼分割得很干净远处的小鱼直接丢。原因是imgsz640下采样后小鱼可能只占几个像素特征提不出来。解决思路按代价从低到高排列先提高输入分辨率最常见的做法是把imgsz升到768甚至960再对大图做tile切分把原图切成几块分别推理最后按坐标拼接结果。后者会增加推理耗时但水下小目标场景里往往比换大模型更有效。5.5 现象五两条鱼重叠时mask被合并成一个整体观测结果是两只鱼紧贴游动时模型输出的mask覆盖了两只鱼连成一片的区域无法分开。原因是实例分割头在两个实例交叠区域得分接近后处理里对重叠mask的抑制力度不足。解决方法是调整验证和推理时的NMS阈值在ultralytics里对应nms_iou参数。默认0.7在通用场景好用水下紧密遮蔽时降到0.5附近能明显减少合并。同时检查转换后的标签里是不是存在“两个实例共享同一区域”的情况这种标注本身会教坏模型发现后直接修数据。这五条坑贯穿数据、转换、训练、推理四个环节没有一条是纯模型结构的锅。先排数据链路再动训练参数是处理水下鱼类实例分割最稳妥的路线。血泪经验是遇到mAP不涨先怀疑数据不要急着换大模型。6. 跑通之后的验证技巧mask面积直方图与混浊水体推理微调训练出了best.pt第一件值得做的事不是反复横跳调参而是用验证集预测结果做一次mask面积统计。理由很简单mAP是综合指标掩盖了模型在“偏大”还是“偏小”上的系统性偏差而mask面积直方图能直观看出来。用第4.3节生成的JSON预测文件做一个面积比统计# mask_area_ratio.py import json import numpy as np with open(run/fish_seg/exp1/predictions.json, r) as f: preds json.load(f) ratios [] for p in preds: # 用bbox面积近似或用mask像素数 pred_area p[bbox][2] * p[bbox][3] true_area p[true_bbox][2] * p[true_bbox][3] if true_area 0: ratios.append(pred_area / true_area) print(面积比中位数:, round(np.median(ratios), 3)) print(面积比P10:, round(np.percentile(ratios, 10), 3)) print(面积比P90:, round(np.percentile(ratios, 90), 3))中位数如果低于0.8说明模型预测的mask系统性偏小鱼鳍边缘丢得严重这时应回查标注边缘质量和imgsz设置。P10到P90的跨度如果过大说明模型在部分遮挡、低对比度样本上极不稳定优先处理最差的10%。这样一刀比自己眼扫几百张图靠谱得多。混浊水体推理时还有一个微调习惯不要盲目提升对比度。水下图像增强常把蓝绿色通道拉平但对实例分割模型而言它学习的边缘特征来自亮度梯度暴力增强会制造假边缘反而增加mask抖动。我在推理阶段只会做轻度直方图均衡confidence阈值保持在0.25左右不再往高调给低可见度鱼留出召回空间。这个面积比统计方法我几乎每次在新数据集上跑出模型之后都会立刻执行因为mAP只是一个数字面积分布才暴露模型真正学会了多少轮廓。希望帮到你。本文还有配套的精品资源点击获取
返回列表