拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

YOLO数据集制作与鱼病检测:从标注规范到训练避坑指南

YOLO数据集制作与鱼病检测:从标注规范到训练避坑指南 简介一套专门用于鱼类健康监测的YOLO格式目标检测数据集覆盖细菌性疾病、真菌性疾病、寄生虫病、白尾病及健康鱼共5个类别适合计算机视觉初学者、水产科研人员及正在做YOLO实战改进的开发者直接训练与验证。数据严格按YOLOv5文件夹结构组织训练集约720张图片及对应txt标签验证集与测试集各约100张类别class文件一并备好解压即可投入训练。包内共1831个文件以915个txt标注文件与914张jpg图像为主体另附1个数据可视化py脚本和1张示例png脚本无需修改随机传入一张图片即可绘制边界框并保存至当前目录方便快速检查标注质量。整体以7z格式压缩仅28.65MB轻量易下载已有750人学习使用适合作为鱼类疾病检测、迁移学习或目标检测入门的实战数据资源。1. 鱼身上那些“看不出名堂”的病斑为什么值得先整理成 YOLO 数据集做了几年水产视觉项目最磨人的不是模型选型而是数据本身。鱼身上的白点、棉絮状水霉、烂尾处的溃烂区域在监控画面里往往只有几十个像素人眼隔着水纹和反光都容易看漏模型更是经常把病斑和背景杂质混在一起。这个标题里说的“YOLO 数据集鱼身上疾病图像目标检测”本质上是把“识别鱼病”这件事落成一份可以直接喂给 YOLO 训练的标准数据包图片和标签已经划分好训练集、验证集、测试集类别名称写进了 class 文件还附带数据可视化脚本帮你检查样本分布和标注质量。适合谁一是做水产养殖病害自动监测的算法工程师二是刚接触目标检测、想拿一个真实场景练手的学生三是需要快速验证“这条鱼有没有病、病在哪”的软硬件集成项目。它的价值不在算法多新而在于把“训练前最容易被敷衍的数据检查”这一环补上了。2. 先对齐目录与标注格式这份数据集的“约定”决定了后续能不能直接训练2.1 标准 YOLO 目录布局images 与 labels 必须平行拿到任何声称“划分好的数据集”我第一件事不是看图片而是看目录结构。YOLO 训练数据最常见的组织方式是把图片和标签放在同一个父目录下的 images 和 labels 两个平行目录里文件名一一对应。这份鱼病数据集按 train、val、test 三个子集划分典型结构如下fish-disease-dataset/ ├── train/ │ ├── images/ │ │ ├── img_0001.jpg │ │ └── ... │ └── labels/ │ ├── img_0001.txt │ └── ... ├── val/ │ ├── images/ │ └── labels/ ├── test/ │ ├── images/ │ └── labels/ ├── classes.txt └── visualize.py这里有一个容易被忽略的约束labels 目录下的 txt 文件名必须和 images 目录下的图片名完全一致包括扩展名去除后的部分。如果你发现某个图片没有对应标签而它恰好在训练集里那么 YOLO 训练时会把这个样本当作背景图处理等于隐性引入了负样本。常见的做法是写一个循环先做文件名比对把“有图无标”和“有标无图”的文件都找出来再决定是补标还是剔除而不是直接开训。2.2 标签内容的 YOLO 格式规范类别编号、中心点坐标与归一化YOLO 的标签文件每一行代表一个目标框格式是class_id x_center y_center width height。这里要注意框的坐标是归一化后的即用真实像素坐标除以图片的宽和高结果范围在 0 到 1 之间。比如一条鱼在 1920x1080 的图中病斑中心在 (960, 540)宽 200、高 120那么这一行应该写成0 0.5 0.5 0.1041667 0.11111110.1041667来自 200/19200.1111111来自 120/1080。训练脚本会直接读取这些数值反算到特征图尺度所以一旦归一化出错框的位置会整体偏移。检查标签是否合法我一般会在可视化脚本里加一个框渲染功能把标签画回原图上用肉眼确认边框是否贴住病斑边缘。只看 txt 文件本身很难发现问题因为坐标看起来都是 0 到 1 之间的小数但往往差 0.05 在真实图上就已经偏出去半个鱼身了。2.3 class 文件与 data.yaml 的配合类别顺序错位是无声的灾难class 文件在这类项目里通常叫 classes.txt一行一个类别名行号就是类别编号。鱼病数据集的类别通常按发病部位或病原体类型区分例如white_spot water_mold tail_rot hemorrhage dropsy那么 white_spot 的编号是 0water_mold 是 1以此类推。YOLO 训练时还需要一个 data.yaml 文件里面声明训练集、验证集路径和类别列表例如train: ./fish-disease-dataset/train/images val: ./fish-disease-dataset/val/images test: ./fish-disease-dataset/test/images nc: 5 names: 0: white_spot 1: water_mold 2: tail_rot 3: hemorrhage 4: dropsy这里最容易翻车的地方是如果你把 classes.txt 里的某一类删掉或调换顺序而标签文件里的行号没改训练就会把旧类别编号对应到新类别上。比如把 hemorrhage 从第 3 行移到第 2 行那么原本标签里所有3都会被解读为 water_mold模型会学到完全错误的概念。所以拿到数据集后核对classes.txt、data.yaml和标签文件里的实际编号三者是否一致应排在所有预处理工作的第一位。3. class 文件与类别设计鱼病分类的粒度决定了标注成本和模型上限3.1 鱼病类别怎么定按“外观可区分度”而不是按“病原体学名”很多刚开始做水产检测的人会照着水产养殖手册把病分成十几种比如小瓜虫、车轮虫、指环虫、黏孢子虫但到了标注阶段就发现根本标不动不同寄生虫引发的体表症状在低分辨率监控画面里几乎一模一样。我自己的经验是鱼病目标检测的类别应该按“影像学外观”划分而不是按病原体细分。像这份数据集里常见的做法是把体表白点归为 white_spot把棉絮状菌丝归为 water_mold把尾部溃烂归为 tail_rot把体表出血斑归为 hemorrhage把鳞片竖立、腹部膨大归为 dropsy。这五类在视觉上有明显边界标注员能稳定复现模型也更容易收敛。如果你硬要细分到病原体只会得到一堆互相重叠的框AP 值永远上不去。3.2 类别不均衡在鱼病数据里比通用数据集更严重水产场景有个天然倾向健康鱼的图片远多于患病的而患病鱼里 white_spot 往往又远多于 dropsy。这会导致训练时模型偏向多数类少数类被当成背景。处理办法有三个按效果排序第一扩充少数类样本哪怕是同一批鱼换角度多拍几帧第二在可视化脚本里统计类别分布把少于某个阈值的类别单独做一遍复制粘贴式增强注意不要直接复制进训练集容易过拟合第三在训练参数里给少数类增加 loss 权重。# count_class_distribution.py import os from collections import Counter def count_labels(label_dir): counter Counter() for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) with open(path, r) as f: for line in f: cls_id int(line.strip().split()[0]) counter[cls_id] 1 return counter if __name__ __main__: label_dir ./fish-disease-dataset/train/labels dist count_labels(label_dir) for cls_id, count in sorted(dist.items()): print(fclass {cls_id}: {count} boxes)这个脚本输出的是每个类别在训练集中的目标框数量。注意它统计的是框数而不是图片数因为一张图上可能同时出现多个同类的病斑。高于平均水平的类别如果特别夸张比如 white_spot 占了 80%就要考虑是标注标准太宽松造成的——可能把鱼身上的沙粒、气泡也框进去了。3.3 class 文件的最佳实践给你自己的项目留一份“类别变更记录”这份鱼病数据集里的 classes.txt 是固定五类但实际用的时候肯定要改。我的习惯是每次调整类别都同步更新三处classes.txt、data.yaml 的 names、以及标签文件里的行号。改完后跑一遍全量扫描凡是出现的类别编号超出nc-1的标签直接定位到具体文件。这一步可以用几行 Python 完成import os def scan_label_ids(label_root, nc): bad_files [] for split in [train, val, test]: label_dir os.path.join(label_root, split, labels) if not os.path.isdir(label_dir): continue for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) with open(path, r) as f: for line in f: parts line.strip().split() if not parts: continue cls_id int(parts[0]) if cls_id nc or cls_id 0: bad_files.append((path, cls_id)) return bad_files bad scan_label_ids(./fish-disease-dataset, 5) for path, cls_id in bad[:20]: print(f{path}: invalid class {cls_id})在脚本里nc是类别总数脚本会遍历 train、val、test 三个子集把所有不在[0, nc-1]范围内的标签行挑出来。这类错误在手工改 classes.txt 时极容易产生早发现能省出半天排错时间。4. 数据可视化脚本把训练集“看明白”比调参重要十倍4.1 目标尺寸分布可视化小目标占比高不高直接决定要不要改 imgsz鱼身上的病斑在整幅图中的占比通常很小尤其是水面监控视角下一条鱼可能只有 200x80 像素上面的白点更是只有指甲盖大小。可视化脚本里我会专门画一张“目标框宽高分布散点图”横轴是归一化宽度纵轴是归一化高度点的大小代表数量。以下代码可以完成这个统计# plot_bbox_size_distribution.py import os import matplotlib.pyplot as plt def load_boxes(label_dir): boxes [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) with open(path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue _, xc, yc, w, h parts boxes.append((float(w), float(h))) return boxes label_dir ./fish-disease-dataset/train/labels boxes load_boxes(label_dir) if boxes: ws [b[0] for b in boxes] hs [b[1] for b in boxes] plt.figure(figsize(8, 6)) plt.scatter(ws, hs, s2, alpha0.5) plt.xlabel(normalized width) plt.ylabel(normalized height) plt.title(bbox size distribution) plt.savefig(bbox_size_dist.png, dpi150) print(ftotal boxes: {len(boxes)})alpha0.5是让密集区域的点显示半透明否则几个点叠在一起会变成一片黑看不出分布中心。如果图中大部分点的宽高都小于 0.05说明小目标占主导训练时imgsz就不能设成 320至少 640 起步甚至要开 1280 配合切片推理。这是决定模型精度的前置判断而不是事后调参的补救。4.2 标注框渲染可视化把标签画回图上查错效率最高坐标范围检查只能过滤格式错误过滤不了“框标偏了”“框太小把整块病斑框了一半”这类人为标注问题。所以可视化脚本里的核心功能是把标注框画回原图并输出到 out 目录。遍历验证集每一个文件然后用 OpenCV 画矩形和类别文本# render_annotations.py import os import cv2 def render_one(image_path, label_path, class_names, out_path): img cv2.imread(image_path) if img is None: return h, w img.shape[:2] with open(label_path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, xc, yc, bw, bh map(float, parts) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color (0, 255, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) label_text class_names[int(cls_id)] cv2.putText(img, label_text, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(out_path, img) if __name__ __main__: class_names [white_spot, water_mold, tail_rot, hemorrhage, dropsy] img_dir ./fish-disease-dataset/val/images label_dir ./fish-disease-dataset/val/labels out_dir ./render_out os.makedirs(out_dir, exist_okTrue) for fname in os.listdir(img_dir): if not fname.endswith(.jpg): continue stem os.path.splitext(fname)[0] label_path os.path.join(label_dir, stem .txt) if not os.path.exists(label_path): continue render_one(os.path.join(img_dir, fname), label_path, class_names, os.path.join(out_dir, fname))这段代码把每个框从归一化坐标反算回像素坐标再画出绿色矩形并在框上方写类别名。抽样看几十张渲染图基本就能判断标注员的水平有没有把两个相邻病斑框成一个有没有把鱼鳍的边缘误标成烂尾。这些错误在指标上表现得非常隐蔽——它们不会让 loss 发散只会让 mAP 停在某个上不去的值。4.3 位置分布热力图病斑在画面中的位置是否偏向某一边拍摄角度固定的水产监控场景里鱼通常只出现在画面的下半部分或某个角落这会让模型学到“位置偏置”换个角度就失灵。可视化脚本还可以做一张病斑中心点热力图把所有框的中心坐标叠加到归一化平面上import numpy as np import matplotlib.pyplot as plt def plot_position_heatmap(label_dir, bins32): points [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue path os.path.join(label_dir, fname) with open(path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue _, xc, yc, _, _ map(float, parts) points.append((xc, yc)) if not points: return xcs [p[0] for p in points] ycs [p[1] for p in points] heatmap, _, _ np.histogram2d(xcs, ycs, binsbins, range[[0, 1], [0, 1]]) plt.imshow(heatmap.T, originlower, cmaphot) plt.colorbar() plt.savefig(pos_heatmap.png, dpi150)np.histogram2d以 32x32 的网格统计中心点落在每个格子里的数量originlower保证纵轴朝上和图片坐标系一致。如果热力图明显集中在一个小区域就要在训练时做随机裁剪、平移增强或者干脆调整拍摄角度让病斑样本尽可能分布在画面各个位置。5. 鱼病目标检测实践中常踩的坑从标注偏移到训练崩溃5.1 白点病标注偏移小目标框的边界“虚”导致 AP 忽高忽低现象white_spot 类别的验证集 AP 在训练过程中一直波动甚至到后期还在 0.3 到 0.6 之间跳。原因白点病的小白点边缘模糊不同标注员对“一个点算不算病斑”的判定不一致同一个点有人标 8x8 像素有人标 20x20 像素标签框本身就带噪声。解决重标一遍统一规则——直径小于 5 像素的斑点不标5 到 15 像素的标最小外接矩形多个粘连斑点合并成一个框。然后重新渲染可视化检查确保标准落实。5.2 水霉病背景干扰棉絮状菌丝被误标成整片白色区域现象water_mold 类别的框经常覆盖鱼身以外的空白区域模型学到的特征里混入了水池背景。原因水霉在鱼身上长成棉絮状颜色接近白色塑料膜或水面反光标注员为了省事把整个白色区域框了进去。解决在数据集划分时就把这类“大框套背景”的图片单独筛出来重新收窄框边界让框紧贴白色絮状物的核心区域。宁可漏标一部分也不要标入背景。5.3 train/val 划分泄漏同一条鱼在多张图里出现导致指标虚高现象验证集 loss 很低但部署到真实监控画面时误检率很高。原因鱼病数据集往往来自一段连续监控视频抽帧同一条鱼出现在几十帧里如果划分时随机切分训练集和验证集里会有大量“同鱼不同帧”的样本模型等于变相见过验证集。解决按鱼个体划分而不是按图片划分。先通过跟踪算法或人工给每条鱼编号再用鱼 ID 做分组划分保证同一条鱼的所有帧只进入训练集或只进入验证集。5.4 训练中 BN 崩溃小 batch 大学习率让模型直接发散现象训练到第几十轮时 loss 突然变成 nan或者 mAP 掉到接近 0 后无法恢复。原因批次设置过小BatchNorm 的均值和方差统计不稳加上学习率偏高统计量被少数异常样本带偏这在数据量不大的鱼病数据集里非常常见。解决把batch调到 16 以上一个 batch 内尽量包含不同鱼类样本lr0从默认值降到 0.005 左右如果模型还在发散检查是否有标签坐标越界——比如归一化坐标出现负值或大于 1这也是触发 BN 异常的直接原因之一。5.5 混淆矩阵总和不为 1不是 bug是读数方式错了现象训练结束后打印混淆矩阵发现每一行加起来不等于 1怀疑计算有误。原因Ultralytics 等框架的混淆矩阵默认未归一化行列里除了预测类别还包含 background 类且数值是绝对数量不是比例。解决看每一行除以该行真实样本总数后的比值分母在验证集统计里打印出来核对。重点是看对角线上的类别是否明显高于同一行的其他格子而不是纠结总和是不是 1。这个误区在新手里出现频率很高属于“看起来像 bug其实是用法没对齐”。6. 把这套鱼病数据集送进 YOLO 训练最小命令与验证节奏6.1 最小训练命令与参数选择目录和标签都检查完之后就可以把数据集交给 YOLO 了。常见的做法是用 YOLOv8 或同系列框架data.yaml 放在数据集根目录然后执行yolo detect train \ data./fish-disease-dataset/data.yaml \ modelyolov8n.pt \ epochs120 \ batch16 \ imgsz640 \ lr00.005 \ patience30modelyolov8n.pt表示加载预训练权重鱼类病斑属于小目标n 模型参数量少、跑得快适合先验证数据本身如果训练稳定且 AP 还有上升空间再换 yolov8s 或 yolov8m。patience30是早停轮数验证集 mAP 连续 30 轮不涨就停止避免无效训练。imgsz640的理由来自第 4.1 节的分布图——如果病斑归一化宽度普遍小于 0.05建议直接改成 960 或 1280小目标检测对分辨率极其敏感。训练结束后用验证集评估看每一类的 AP 和 PR 曲线yolo detect val \ model./runs/detect/train/weights/best.pt \ data./fish-disease-dataset/data.yaml这一条命令会输出每个类别的精确率、召回率和 mAP50、mAP50-95。重点关注某个类别的 AP 是否远低于其他类。如果是回到可视化脚本看这个类别的样本是不是框太小、样本太少或者类别之间外观重叠。6.2 进阶验证技巧换一个验证集视角看“数据有没有漏”在调优阶段只盯着 mAP 容易产生错觉。我的习惯是把验证集的预测结果画回图上单独挑出“假阳性样本”和“漏检样本”各存一摞图每张图上写清楚置信度分数。然后回答三个问题误检的框都框在什么位置是不是水面反光、饲料碎屑、池壁青苔这类固定背景漏检的病斑是不是都比训练集里同类目标小得多把答案记录到训练日志的第一页下一迭代改的就不是随机参数而是明确针对某类误差的数据补充和增强策略。6.3 一套值得长期保持的节奏先验数据、再训模型、最后看错例总结我这几年做鱼病检测的习惯每次拿到新数据先用统计脚本算出类别分布和目标尺寸分布再用渲染脚本抽 50 张图人工看过确认没有标注偏移和背景污染然后才写训练命令。模型跑完第一件事不是发训练曲线给同事而是把验证集的错例图片逐张翻一遍。这个流程看起来慢但能挡掉八成无用训练。希望这份拆解能帮你少走一段弯路把精力留在真正决定模型上限的数据质量上。本文还有配套的精品资源点击获取
返回列表