简介:基于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 虚拟环境与 ultralytics
YOLOv8 的训练对 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 能不能调用 GPU:
python -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_ok=True) # 类别顺序要和 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, encoding='utf-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 表示第一块 GPU,CPU 机器用 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.pt:
python 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 或 4,imgsz 降到 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 对应hemorrhage,data.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 和 head,backbone 负责提特征,是通用能力最强的部分;head 负责回归框和类别,是你数据集特有信息的主要承载者。冻结骨干的常见做法是用 ultralytics 提供的冻结参数列表:
from ultralytics import YOLO model = YOLO('yolov8n.pt') # 冻结 backbone,只训练 head model.train( data='datasets/fish/data.yaml', epochs=100, freeze=10, # 冻结前 10 层,YOLOv8n 的 backbone 大约在这个范围 batch=16, imgsz=640, )freeze=10表示冻结前 10 层的权重,训练时只更新后面的层。好处是显存占用大幅降低,训练更快,而且在小数据集上不容易过拟合。参数值的把握:模型规模越小,freeze 的层数可以越少;如果你用的是 yolov8n,freeze=10 基本覆盖了骨干;如果换 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 看指标分布,最后才动手改分类和导出逻辑。这套顺序帮我避开过无数次方向性返工,希望帮到你。
本文还有配套的精品资源,点击获取