
简介基于Python和YOLOv8的鱼类疾病检测系统源码包面向水产养殖技术人员与深度学习开发者解决传统人工巡察鱼类疾病效率低、发现滞后的问题。资源共24个文件、2.26MB包含19个png截图、4个py脚本及1个md文档其中py脚本覆盖训练、验证、预测与Web界面展示四大模块png直观呈现系统界面与检测结果md提供使用与训练说明。系统可识别出血、眼部缺陷、鳍部缺陷、溃疡等22种鱼类病症支持图片、视频和摄像头实时检测检测结果自动保存并导出Excel表格。提供完整训练教程与数据集便于自定义模型训练同时内置70余种YOLOv8创新改进点有助于提升检测精度与速度非常适合快速搭建鱼类疾病识别与预警方案。目前已有90人学习下载对水产病害智能监测系统搭建与YOLO目标检测实践均具有实用参考价值。1. 基于 Python 和 YOLOv8 的鱼类疾病检测一份可以直接落地的源码包水产养殖里看鱼病是个典型的经验活出血、烂鳍、溃疡、眼部缺陷早期症状不明显巡塘师傅凭肉眼一尾一尾看效率低且判断标准不统一。这套基于 Python 和 YOLOv8 的鱼类疾病检测系统把目标检测直接搬到了养鱼场景里——训练好的模型可以识别 22 类鱼类及其疾病支持图片、视频、摄像头三种输入方式检测结果自动保存还能导出 Excel 并提供一个 Web 界面做展示。对做毕设、课设的计算机方向学生来说它是一套完整的 YOLOv8 落地范本对水产智能化方向的技术人员来说它比从零搭一套检测管线省掉大量时间。下面从环境搭建开始把训练、验证、推理、界面这条链路整个拆开讲。2. 环境搭建与源码结构先把 YOLOv8 跑起来拿到这类源码包我一般不会直接跑 train.py而是先把环境立住、把文件结构摸清。YOLOv8 的训练和推理都依赖 ultralytics 这个框架它把网络结构、数据加载、训练循环都封装好了源码里的 train.py 实际上是在调用这个框架。环境没搭对后面每一步都会出幺蛾子。2.1 环境准备Python 虚拟环境与 ultralyticsYOLOv8 的训练对 Python 版本有要求3.8 以下基本跑不动3.9 到 3.11 都比较稳。我习惯先建一个独立的虚拟环境避免把系统 Python 搞乱。python -m venv ~/fish_venv source ~/fish_venv/bin/activate # Linux/macOS # Windows 下用 ~/fish_venv/Scripts/activate pip install ultralytics openpyxl pandasultralytics是核心依赖训练、验证、推理全走它openpyxl和pandas是给 UI 模块导出 Excel 用的没有它们 ui.py 的功能会缺一块。装完后先验证一下 torch 能不能调用 GPUpython -c import torch; print(torch.cuda.is_available())输出True说明 GPU 可用False就只能在 CPU 上跑了。这里有个经验CPU 跑 YOLOv8 不是不行但训练一个中等规模的数据集可能要数小时甚至一天推理单张图也要几百毫秒。如果机器上没有 NVIDIA 显卡先别急着换机器用小数据集、小 imgsz、少 epoch 把流程走通性能问题后面再解决。提示GPU 显存小于 6GB 时训练 batch 务必调小否则会直接 OOM这个问题在第 5 章单独讲。2.2 源码文件结构与训练推理的数据流向解压后能看到 train.py、val.py、predict.py、ui.py、README.md 以及一批 png 截图。截图是界面和效果图不参与运行。源码的文件分工很清楚文件职责train.py训练入口加载数据集配置并启动 YOLOv8 训练val.py在验证集上输出 P、R、mAP 等指标predict.py对图片、视频、摄像头做推理并保存结果ui.py图形界面加载训练好的模型做检测展示与 Excel 导出README.md安装步骤与环境配置说明这条链路的数据流向是准备数据集 → 写 data.yaml → 跑 train.py 生成 best.pt → 跑 val.py 看指标 → 用 predict.py 或 ui.py 做实际检测。train.py 训练完会在runs/detect/train/下产出权重文件val.py 则是在已有权重上评估两个脚本的输入输出是串联的。我刚拿到这套源码时第一件事是打开 README 看版本要求第二件事就是看 train.py 里默认加载的权重路径。常见做法是 train.py 默认用yolov8n.pt或yolov8s.pt做预训练如果你机器显存小把模型规模换成n档是性价比最高的选择。这里不建议直接修改源码里的默认参数而是用命令行参数覆盖源码里一般预留了 --epochs、--batch、--data 之类的入参下一章讲训练时细说。3. 数据集组织与标注labelme 转 YOLO 格式的完整流程YOLOv8 对数据集格式有硬性约定不按它的规矩来train.py 会直接报错或者训练出莫名其妙的模型。这一章把数据准备的每一个环节拆开包括目录结构、配置文件、标注转换脚本照着做就不会翻车。3.1 数据集目录结构与 data.yaml 配置ultralytics 训练时只认两种目录images放图片labels放同名 txt 标注文件。train 和 val 分开。下面是最标准的布局datasets/ └── fish/ ├── images/ │ ├── train/ # 训练图片 │ └── val/ # 验证图片 ├── labels/ │ ├── train/ # 训练标注 │ └── val/ # 验证标注 └── data.yaml # 数据集配置文件data.yaml是训练时最重要的一个文件train.py 启动后第一件事就是读它。内容长这样# data.yaml path: datasets/fish # 数据集根目录 train: images/train # 训练图片相对路径 val: images/val # 验证图片相对路径 nc: 4 # 类别数实际项目按自己的类别数改 names: [hemorrhage, eye_defect, fin_defect, ulcer]这里nc是类别数names是类别名列表。最容易踩的坑是names的顺序必须和标注文件里的类别 ID 一一对应第 0 个名字对应标注里数字 0第 1 个对应数字 1以此类推。顺序一旦错位训练出来的模型会把出血当成溃疡而且指标还很好看——因为它学习的就是错位的映射关系。这个坑在第 5 章会再展开一次属于最常见的隐性错误。提示path字段建议写绝对路径或相对你执行命令的目录的相对路径别写~这种带波浪线的路径ultralytics 解析时偶尔会出问题。3.2 标注文件转换labelme 的 JSON 多边形转 YOLO 的 txt数据准备里最耗时的一步是标注。labelme 输出的格式是 JSON里面用多边形顶点points描述目标区域YOLO 需要的是一个归一化的中心点坐标和宽高。转换脚本就是把多边形收成外接矩形再归一化到 0~1 之间。# json_to_yolo.py import json, os from glob import glob label_dir labelme_json # labelme 输出的 json 目录 out_dir labels/train # YOLO 格式 txt 输出目录 os.makedirs(out_dir, exist_okTrue) # 类别顺序要和 data.yaml 的 names 完全一致 classes [hemorrhage, eye_defect, fin_defect, ulcer] for json_path in glob(os.path.join(label_dir, *.json)): with open(json_path, encodingutf-8) as f: data json.load(f) img_w, img_h data[imageWidth], data[imageHeight] base os.path.splitext(os.path.basename(json_path))[0] lines [] for shape in data[shapes]: # 多边形顶点坐标取外接矩形 xs [p[0] for p in shape[points]] ys [p[1] for p in shape[points]] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # YOLO 格式类别 中心x 中心y 宽 高全部归一化到 0~1 cx ((x_min x_max) / 2) / img_w cy ((y_min y_max) / 2) / img_h bw (x_max - x_min) / img_w bh (y_max - y_min) / img_h # 用 index 查类别 ID顺序错一个全盘皆错 cls_id classes.index(shape[label]) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, base .txt), w) as f: f.write(\n.join(lines)) print(f转换完成共处理 {len(glob(os.path.join(label_dir, *.json)))} 个文件)这段脚本的核心逻辑是读 JSON → 取多边形外接矩形 → 计算中心点和宽高 → 归一化 → 写成 txt。为什么要归一化因为不同图片分辨率不一样YOLO 训练时会把图缩放到固定尺寸默认 640×640标注坐标如果不归一化缩放后位置就全错了。归一化后不管原图是 1920 还是 800中心和宽高都落在 0~1 区间训练时直接映射到缩放后的图上。一个实战经验如果一条鱼身上同时有出血和鳍部缺陷两个病labelme 里你会画两个框类别不同脚本会输出两行这没问题。但如果同一个目标被画了两个框、标注了两个不同类别训练时模型会无所适从loss 大概率不收敛。解决办法是先确定一个标注规范每个目标只标一个主要病灶类别拿不准的宁可标成背景也不要重复叠加。4. 训练与验证train.py 参数解读与 mAP 指标的读法训练是这套源码里最需要理解参数含义的环节。很多人把 train.py 跑起来就不管了最后拿到的模型要么过拟合要么漏检。这一章讲清楚每个关键参数怎么调以及 val.py 输出的指标到底在说什么。4.1 train.py 的启动方式与关键参数train.py 的调用一般长这样python train.py --data datasets/fish/data.yaml --epochs 100 --batch 16 --imgsz 640 --device 0参数含义建议值--data数据集配置文件路径指向你的 data.yaml--epochs训练轮数先跑 50 看趋势稳定后再加到 100--batch每次迭代的图片数量显存 8G 用 16显存小用 8 或 4--imgsz训练输入尺寸默认 640小目标多可以提到 960--device计算设备0 表示第一块 GPUCPU 机器用 cpu--patience早停轮数100 轮不涨就停防止浪费时间--batch和显存的关系是线性的batch 越大单次送入模型的图片越多显存占用越高。如果 8G 显存跑 batch 16 报 OOM把 batch 砍到 8 是最快的解法代价只是训练速度慢一点。--imgsz是很多人忽略的参数鱼病里的出血点、眼部缺陷往往只占几个像素640 的输入尺寸下这些小目标会被严重压缩特征基本丢失。我一般会在小目标场景先把 imgsz 提到 960 跑几个 epoch 对比效果如果 mAP 有明显提升就保持 960。训练过程中的 loss 曲线不需要额外写代码ultralytics 会在训练结束时自动生成results.png包含了 train/loss、val/loss、mAP、P、R 的完整曲线。看这张图有个基本判断标准val/loss 曲线和 train/loss 曲线如果差距越拉越大说明过拟合val/loss 一直横着不降说明学习率可能太大或数据有问题。提示第一次跑训练建议先用 30~50 个 epoch 试水观察 loss 是否下降、mAP 是否抬头确认流程没问题再跑全量。一上来就跑 300 epoch等于把不确定性放大到几小时之后。4.2 val.py 的指标边界mAP50 与 mAP50-95 的差距说明什么训练完的权重在验证集上表现如何用 val.py 来看。它的输入是训练产出的 best.ptpython val.py --weights runs/detect/train/weights/best.pt --data datasets/fish/data.yaml --imgsz 640val.py 会输出 Precision、Recall、mAP50、mAP50-95 四个核心数字。mAP50 是 IoU 阈值设为 0.5 时的平均精度mAP50-95 是把阈值从 0.5 到 0.95 每隔 0.05 算一次再取平均。两者的差是关键信息如果 mAP50 很高但 mAP50-95 明显低比如 mAP50 0.92 但 mAP50-95 只有 0.55说明模型框的位置不准——框大致在目标上但贴合度不够。这在鱼病检测里是个实际问题出血斑形状不规则框的边界稍有偏移就会压到 IoU 阈值 0.75 以下。我一般会同时看一下 val.py 生成的混淆矩阵图和 PR 曲线图。混淆矩阵能看出哪些类容易互相混比如鳍部缺陷和尾部溃疡在外观上确实像PR 曲线能看出模型的置信度阈值应该设在多少。默认推理阈值是 0.25但如果你的 PR 曲线显示 0.5 以上精度才稳那推理时就要把 conf 阈值提上去。5. 避坑/常见问题排查从标注到部署的五个高频翻车点这一章是血泪经验汇总。以下五个问题是我在帮别人排查这类 YOLOv8 检测系统时遇到最多的每条都按现象、原因、解决三步写你可以直接对照排查。5.1 文件路径带中文或空格导致加载失败现象运行 train.py 或 predict.py 时报 FileNotFoundError或者闪退没有任何报错。 原因ultralytics 在处理含中文、空格的路径时底层解析容易出错尤其是 Windows 环境下中文字符串编码在不同模块间不一致。 解决把整个项目和解压路径统一放到纯英文、无空格的目录下比如D:\fish_project或~/fish_project。README 里的安装步骤如果在中文路径下执行极容易触发这个问题建议第一步就改目录名。5.2 显存不足报 CUDA out of memory现象训练启动没几分钟终端报RuntimeError: CUDA out of memory然后进程被杀。 原因batch 太大、imgsz 太大或者同时开了多个占用显存的程序。8G 显存跑 batch 32 imgsz 640 几乎必炸。 解决batch 降到 8 或 4imgsz 降到 640。ultralytics 支持梯度累积train.py 中一般有--accumulate参数batch 小了可以靠多步累积模拟大 batch 的效果。另外--workers不要设太大用 2 或 4 足够workers 太大会把 CPU 和内存吃满间接加剧 GPU 等待。5.3 类别 ID 错位导致模型学了个寂寞现象训练正常跑完但 val 的精度从第一轮开始就趴在地上P、R 全是低值loss 曲线震荡不下降。 原因标注转换脚本里的类别顺序和 data.yaml 的 names 不一致。比如脚本里 index 0 对应hemorrhagedata.yaml 里 names 第 0 位却是ulcer模型学到的是一个错位的映射。 解决转换脚本里classes列表的书写顺序复制粘贴自 data.yaml 的names先复制后运行不要手动敲。训练前写一行检查代码随机抽一个 txt 标注文件解码里面每个数字对照 names 确认类别名和图片内容匹配。这一步 5 分钟不到能省掉一整轮重新训练的时间。5.4 摄像头推理时画面卡顿或黑屏现象predict.py 或者 ui.py 里选摄像头检测界面黑屏或者画面一帧一帧地跳完全不能实时。 原因摄像头输入的分辨率太高而推理线程和取帧线程没有分离取帧等推理、推理等取帧互相阻塞。 解决先把推理的 imgsz 降到 640再看输入源摄像头的分辨率在代码里可以手动限制一般用cv2.VideoCapture(0)拿流之后调cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)之类限制采集尺寸。如果还卡就把推理间隔拉大比如每 3 帧做一次检测中间帧直接透传人眼感知不到太大差异实时性会好很多。5.5 小目标鱼病漏检严重现象出血点、早期溃疡这类小目标经常检测不到但大目标的鳍部缺陷、视觉明显的溃疡能检出来。 原因imgsz 默认 640 时小目标在缩放过程中尺寸被压缩得厉害特征丢失。另一个原因是训练数据里小目标占比太少模型对小目标的学习不充分。 解决imgsz 提到 960代价是训练变慢、显存占用升高。也可以先做简单的数据增强把原图中局部小目标的区域裁切放大再加入训练集这比改模型结构简单收益更直接。不要一上来就改检测头加注意力机制那是在模型能力已经撑到极限时才考虑的事。6. 从 mAP 到投产冻结骨干迁移学习与 Excel 导出的小技巧训练好的模型能跑通只是第一步真正要把它用起来还有两个技巧值得掌握迁移学习的参数调整以及 UI 导出结果的合规化处理。先说过拟合的应对。小数据集上直接从头训练 YOLOv8几十轮后 train loss 降到底但 val loss 反弹典型过拟合。最有效的做法是冻结骨干网络只让检测头参与训练。YOLOv8 的模型结构分为 backbone 和 headbackbone 负责提特征是通用能力最强的部分head 负责回归框和类别是你数据集特有信息的主要承载者。冻结骨干的常见做法是用 ultralytics 提供的冻结参数列表from ultralytics import YOLO model YOLO(yolov8n.pt) # 冻结 backbone只训练 head model.train( datadatasets/fish/data.yaml, epochs100, freeze10, # 冻结前 10 层YOLOv8n 的 backbone 大约在这个范围 batch16, imgsz640, )freeze10表示冻结前 10 层的权重训练时只更新后面的层。好处是显存占用大幅降低训练更快而且在小数据集上不容易过拟合。参数值的把握模型规模越小freeze 的层数可以越少如果你用的是 yolov8nfreeze10 基本覆盖了骨干如果换 yolov8x骨干更深freeze 可以到 20 左右。再看 Excel 导出。ui.py 里检测结果导出 Excel 时我建议保留四列检测到的类别名、置信度、目标中心的归一化坐标、检测框宽高。归一化坐标如果不还原成实际尺寸外人拿到表也看不懂这个目标到底在哪。一般在 ui.py 里能看到这类代码import openpyxl wb openpyxl.Workbook() ws wb.active ws.append([类别, 置信度, 中心点x, 中心点y, 框宽, 框高]) for name, conf, cx, cy, w, h in results: ws.append([name, round(conf, 4), round(cx, 4), round(cy, 4), round(w, 4), round(h, 4)]) wb.save(detect_results.xlsx)真实导出前把归一化坐标乘以原图宽高还原成像素值这个细节能直接决定你导出的表格是给人看的还是给机器看的。那之后我每次拿到包含 UI 的检测源码都会强制走一遍 validate 流程先用 predict 跑三张带病图片确认能检出再用 val 看指标分布最后才动手改分类和导出逻辑。这套顺序帮我避开过无数次方向性返工希望帮到你。本文还有配套的精品资源点击获取