首页 / 资讯中心 / 文章详情

基于YOLOv8的鱼类疾病检测系统:从数据集标注到模型部署全流程

基于YOLOv8的鱼类疾病检测系统:从数据集标注到模型部署全流程 ★ FEATURED ARTICLE
简介这是一款基于Python和YOLOv8的鱼类疾病检测系统源码包面向水产养殖从业者、计算机视觉学习者与AI应用开发者旨在自动识别鱼类出血、眼部缺陷、鳍部缺陷、溃疡等常见病症支持22种类别的实时检测可广泛应用于智慧养殖监测场景。资源包共收录24个文件包括4个Python脚本分别承担模型训练、推理预测、效果验证和用户界面交互、19张示例图片以及1份Markdown格式的说明文档整体压缩后仅2.26MB结构紧凑、易于部署。目前已有90人学习/下载。源码覆盖从模型训练到推理部署的完整链路既支持图片和视频文件分析也支持摄像头实时监测检测结果能够自动保存并导出为Excel报表同时借助Web前端可视化界面直观展示方便管理者及时掌握鱼群健康状态。项目还支持用户自定义数据集进行模型训练描述中提及集成70余种YOLOv8改进创新点可依据实际场景调整网络结构、提升检测精度与速度无论是作为智慧水产监测系统的核心模块还是用于毕业设计或技术竞赛都有较强的参考价值。1. 一张病鱼照片如何变成一套可落地的疾病检测系统水产养殖里最怕的不是鱼价跌而是半夜巡塘发现整池鱼开始浮头、体表发白。等请到病害专家到场往往已经错过最佳处理窗口。基于Python和YOLOv8的鱼类疾病检测系统解决的就是这个卡点用目标检测模型替代人工肉眼巡检在鱼出现体表出血、烂尾、白点、水霉等典型病灶时第一时间报警。这个方向适合三类人做毕业设计需要完整落地项目的学生、养殖场或水产企业的技术负责人、以及想把手头标注数据变成可用模型的算法工程师。系统本身不复杂——一张图进去带类别标签和置信度的检测框出来难的是数据质量、训练参数和部署细节这三关。这篇笔记就是围绕这三关展开的每一步都会给到可以直接抄走的命令和代码。2. 先有数据集再谈模型标注格式转换与数据质量关口2.1 标注工具选型与目录组织常见做法是先用 Labelme 或者 LabelImg 做人工标注这两个工具都免费跑在本地 Windows 或 Linux 上都很稳。以 Labelme 为例标注结果是 JSON 文件里面记录的是多边形顶点坐标不是 YOLO 要的归一化中心点加宽高。如果你只有几十张图手工在脚本里改也行但要做成系统几百上千张图必须写转换脚本否则光格式转换就能把你耗到凌晨。目录结构我一般这么组织照着建就行fish-disease/ ├── datasets/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── data.yaml ├── weights/ ├── runs/ └── detect.py这里labels/下每一张图片对应一个同名的.txt标注文件每行格式是class_id x_center y_center width height四个坐标值都是 0 到 1 的归一化浮点数。class_id 从 0 开始不能跳号不能从 1 开始这是新手最容易踩的坑之一。2.2 把 VOC 转成 YOLO 格式转换脚本与四个边界坑下面这段脚本处理 Labelme 的 JSON 标注import json import os from glob import glob # 类别字典注意顺序要和之后 data.yaml 里的 nc 保持一致 CLASS_DICT {scale_spot: 0, rot: 1, white_spot: 2, ulcer: 3} def convert_labelme_json(json_path, out_dir, img_w, img_h): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in CLASS_DICT: # 遇到没见过的标签直接跳过避免污染训练集 continue points shape[points] # Labelme 存的是多边形顶点这里取外接矩形 xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 边界保护防止坐标等于 0 或等于宽高导致归一化后越界 x_min max(0, x_min) y_min max(0, y_min) x_max min(img_w, x_max) y_max min(img_h, y_max) # 过滤掉过小的框这类框大多是误标或噪声 if x_max - x_min 10 or y_max - y_min 10: continue x_center (x_min x_max) / 2 / img_w y_center (y_min y_max) / 2 / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h lines.append(f{CLASS_DICT[label]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) if not lines: return # 没有有效目标就跳过避免生成空标注 base_name os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(out_dir, base_name .txt), w) as f: f.write(\n.join(lines)) if __name__ __main__: labelme_root path/to/labelme_jsons out_root path/to/labels os.makedirs(out_root, exist_okTrue) for json_path in glob(os.path.join(labelme_root, *.json)): # 图片宽高从标注文件里读或者从对应图片读取 convert_labelme_json(json_path, out_root, img_w1280, img_h720)这段脚本值得留意的点是边界保护、最小框过滤和类别跳过。很多转换脚本翻车不是因为坐标算错而是因为标注人员在画框时偶尔把顶点拖到图片外导致归一化坐标变成负数或大于 1。YOLO 训练时遇到这种样本会报错或产生梯度异常表现为 loss 突然冲到几十。另外img_w和img_h别在脚本里写死如果你是 1280x720 的图写死了还能用换了采集设备就全错。我一般会在脚本里加一句读取同名图片的宽高而不是手动传参。2.3 数据增强的取舍YOLOv8 自带增强参数比如hsv_h、hsv_s、degrees、translate、scale等。鱼类数据有个特殊情况鱼的身体颜色本身是诊断线索白点病的白点、出血病的红斑都是靠颜色区分的。如果hsv_h调得太大模型会学会“忽略颜色”这对鱼类疾病检测是灾难。我的习惯是hsv_h0.01、hsv_s0.3只做微小的色彩扰动不做色相平移。翻转方面鱼类疾病病灶没有左右语义差异fliplr0.5放心开但flipud我一般不开——鱼在正常游动姿态下很少上下颠倒开了反而引入背景干扰。3. 训练自己的鱼类疾病检测模型环境、配置与调参3.1 环境搭建GPU 和 CPU 两条路线环境搭建是很多人卡住的第一关。有 NVIDIA 显卡的直接装 CUDA 版 PyTorch然后pip install ultralytics即可。没有独显的笔记本也能用 CPU 训练只要把图片尺寸调小、epoch 减少依然能跑通全流程只是训练时间会从小时级别变成天级别。以下命令在 Ubuntu 20.04 上验证过Windows 同样适用只是激活环境的方式略有差异。# 创建虚拟环境Python 3.9 或 3.10 均可 conda create -n fish python3.10 -y conda activate fish # 安装 PyTorchGPU 版CPU 版去掉 --index-url 参数即可 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 YOLOv8 核心库 pip install ultralyticsultralytics包会一并拉入 opencv-python、numpy、pandas 等常见依赖。这里有一点要知道YOLOv8 是直接通过ultralytics这个包来调用的不需要额外 clone 仓库。网上很多教程还停留在 clone 源码再安装的老流程没必要。装完之后执行yolo predict modelyolov8n.pt sourcetest.jpg能跑通就说明环境 OK。3.2 修改数据配置与训练命令数据配置文件是训练的入口datasets/data.yaml内容如下# 数据集根路径用绝对路径最省心 path: /home/user/fish-disease/datasets train: images/train val: images/val test: images/test # 类别数必须和标注脚本里的 CLASS_DICT 长度一致 nc: 4 # 类别名称顺序必须和标注文件里的 class_id 一一对应 names: 0: scale_spot 1: rot 2: white_spot 3: ulcer这里train和val填的是相对于path的路径我用的是图片所在目录而不是包含图片的父目录。很多人会把train写成images/train/images多套一层导致训练时报错“dataset not found”。其实检查方式很简单用 Python 读一下 YAML然后拼接路径确认指向的目录里真的有 .jpg 文件。之后执行训练命令yolo detect train datadatasets/data.yaml \ modelyolov8m.pt \ epochs150 \ imgsz640 \ batch16 \ device0 \ patience20 \ projectruns/fish_train \ nameexp1 \ pretrainedTrue \ optimizerAdamW \ lr00.001 \ freeze10这段命令里值得解释的参数有patience20验证集指标连续 20 轮没有提升就停止训练省时间也省电费实际效果和硬跑满 150 轮差不多。freeze10的含义是冻结前 10 层不参与训练适合数据量小的情况如果你的标注数量超过 2000 张把freeze删掉让全网络都参与更新。batch16在 8GB 显存的卡上跑yolov8m刚好如果是 6GB 显存就把模型换成yolov8s或者把imgsz降到 480。3.3 训练日志的阅读与早停判断训练过程中第一行输出是模型结构紧接着是每 10 轮打印一次的指标表字段包括box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。新手往往只看 mAP50我建议也看cls_loss——如果 mAP 在涨但cls_loss居高不下说明类别特征没有学好大概率是某个类的样本数量太少或者标注框里混入了大量背景。训练结束后runs/fish_train/exp1/weights/下会有best.pt和last.pt两个权重文件。best.pt是验证集 mAP 最高的那一轮部署时默认选它。我自己会把last.pt也留着因为有时候best.pt连续几轮 mAP 变化只有 0.001 的差别而last.pt在另一组测试集上反而更稳——这种玄学不用太纠结两个都跑一遍推理对比即可。要画损失函数曲线图的话训练产物里自带results.png里面已经画好了train/box_loss、val/box_loss等曲线不需要自己单独做图。如果文档要求提供曲线图直接引用这张图即可。4. 避坑与常见问题排查从数据集到训练全流程的五大高频故障4.1 现象训练 loss 收敛了但 mAP 始终上不去原因最典型的场景是类别不均衡。假设四类疾病里 “rot”烂尾有 1500 个标注框“white_spot”白点只有 80 个框。模型把多数类学得很好少数类几乎没有正样本可学mAP 自然被拉下来。解决先去统计一下每类标注框的数量代码很简单import os from collections import Counter label_dir path/to/labels counter Counter() for txt in os.listdir(label_dir): with open(os.path.join(label_dir, txt)) as f: for line in f: cls_id int(line.split()[0]) counter[cls_id] 1 print(counter)如果某类少于 100 框优先补数据而不是调参。补数据的方式可以是去养殖场补拍视频截帧也可以是从网上下载公开鱼类病害图做二次标注。调class_weight属于治标标注数量上去才是治本。还有一个思路是用 mosaic 增强提升小样本类别的表现——YOLOv8 默认开 mosaic但如果你发现验证集 mAP 异常先关掉 mosaic 试试有些背靠背景的病灶在 mosaic 下会被裁掉。4.2 现象标注文件明明有 3 类训练时却报 “class 3 not in dataset”原因data.yaml的nc写的是 3但标注文件里的class_id出现了 3说明类别编号不连续或者标漏了类。这是标注环节最常翻车的位置。解决写一个脚本扫描所有标注文件的 class_id 取最大值加一再和nc对一下。也可以用下面的命令快速检查# 统计每个类别出现的标注次数 cat path/to/labels/*.txt | awk {print $1} | sort | uniq -c输出结果里如果出现了3: 45而data.yaml只写了nc: 3就把nc改成 4或者在转换脚本里把没有标注样本的类别删掉。这里要说一个原则类别编号必须从 0 开始连续递增类似0, 1, 2, 3可以0, 1, 3不行模型会报错。4.3 现象CPU 训练实在太慢如何定位瓶颈原因老笔记本跑 YOLOv8一个 epoch 可能要 20 分钟不是代码问题是算力问题。解决先用yolov8n在 640 分辨率下以batch8跑 10 轮验证流程能走通再把imgsz降到 416epochs降到 50出一个“能用的低精度模型”先顶上。如果以后有了 GPU 服务器用同一份数据集和高分辨率重新训练数据集目录结构和标注文件不用改只需要重新执行一次训练命令。这就是数据集和权重分离带来的最大好处。4.4 现象推理很快但 CPU 占用率持续 100%原因摄像头视频流推理时如果采集帧率是 30 FPS而模型推理只有 5 FPSCPU 就会一直追赶新帧队列堆积。解决在推理脚本里加一个“抽帧”逻辑比如每 5 帧执行一次检测处理完再读下一帧或者直接限制输入帧率import cv2 cap cv2.VideoCapture(0) frame_interval 5 frame_count 0 while True: ret, frame cap.read() if not ret: break frame_count 1 if frame_count % frame_interval ! 0: continue # 这里执行检测 results model(frame) annotated results[0].plot() cv2.imshow(fish_disease_detect, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()frame_interval的取值取决于你的检测速度和需求响应时间。如果检测一张图需要 0.2 秒而你需要 1 秒内看到结果间隔就取 3。不要盲目追求每帧都检测部署环境下的功耗和发热都不允许。4.5 现象检测框太多病鱼身上出现大量重叠框原因NMS 的 IoU 阈值设置太高默认值 0.45 有时不够或者模型在训练时没有学到抑制同类近距离目标的能力。解决推理时调低 NMS 阈值同时提高置信度阈值from ultralytics import YOLO model YOLO(runs/fish_train/exp1/weights/best.pt) results model.predict( sourcetest_images/, conf0.35, # 低于这个置信度的框直接丢弃 iou0.4, # IoU 超过 0.4 的两个框只保留一个 saveTrue )conf0.35是我在鱼类数据集上的常用起点。如果检测目标的颜色和背景非常接近比如白色病斑在浅色鱼身上可以适当降到 0.25如果误检太多就往上提到 0.5。这个参数没有标准答案必须在你的验证集上扫一遍。5. 把模型封装成检测系统推理脚本与本地可视化界面5.1 推理脚本设计单张图片、文件夹、摄像头三合一模型训练完只是第一步系统要能被人用起来必须有稳定的推理入口。我写的detect.py会同时支持三种输入模式import argparse import os from ultralytics import YOLO def main(): parser argparse.ArgumentParser(descriptionFish Disease Detection) parser.add_argument(--weights, typestr, requiredTrue, help模型权重路径) parser.add_argument(--source, typestr, requiredTrue, help图片/文件夹/摄像头ID) parser.add_argument(--conf, typefloat, default0.35, help置信度阈值) parser.add_argument(--iou, typefloat, default0.4, helpNMS IoU阈值) parser.add_argument(--save, actionstore_true, help是否保存结果图) args parser.parse_args() model YOLO(args.weights) if args.source.isdigit(): # 摄像头模式 import cv2 cap cv2.VideoCapture(int(args.source)) while cap.isOpened(): ret, frame cap.read() if not ret: break results model(frame, confargs.conf, iouargs.iou, verboseFalse) annotated results[0].plot() cv2.imshow(fish-detection, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() else: # 图片或文件夹模式 results model.predict( sourceargs.source, confargs.conf, iouargs.iou, saveargs.save, save_txtTrue, # 输出检测框坐标TXT格式 projectruns/detect, nameexp ) print(f检测完成结果保存至 runs/detect/exp) if __name__ __main__: main()这个脚本的接口设计有一个细节值得说明--source参数接受数字和路径两种类型用isdigit()判断后走不同分支。实际做系统时摄像头采集和文件夹批处理往往要分开写因为前者要求低延迟后者要求批量吞吐。save_txtTrue输出的文本文件每行对应一个检测框格式是class_id x_center y_center width height conf方便后续接入数据库统计发病率。5.2 结果输出的工程化设计不只是画框如果你只是把检测结果画在图上养殖场工人用起来并不方便。更合理的做法是把每次检测的类别、置信度、数量、时间戳写成一张 CSV 表这样一天的检测记录可以直接拉曲线看趋势。import csv import datetime result_csv detection_log.csv fieldnames [timestamp, image, class_name, confidence, box] def log_detection(image_name, results): with open(result_csv, a, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) if f.tell() 0: writer.writeheader() for box in results[0].boxes: cls_id int(box.cls[0]) cls_name model.names[cls_id] conf float(box.conf[0]) xyxy box.xyxy[0].tolist() writer.writerow({ timestamp: datetime.datetime.now().isoformat(), image: image_name, class_name: cls_name, confidence: round(conf, 4), box: [round(v, 2) for v in xyxy] })CSV 的好处是通用性强任何表格软件都能打开不需要额外安装数据库。如果是接入现有养殖管理系统可以再加一个 MQTT 推送或者 HTTP POST 上报这类接口逻辑可以后续再扩展。5.3 界面层够用就好别让 UI 拖垮项目很多源码包里会带一个app.py或window.py用 Tkinter 或 PySide6 写个简单界面。Tkinter 是 Python 自带库不需要额外安装。界面里的核心交互其实只有三件事选择图片、点击检测、显示结果。不要把界面做成一个复杂的软件耗时且容易出错。我见过太多人一个月花在美化按钮上模型精度却没有任何提升这是方向性错误。6. 模型可信度验证用交叉验证和混淆矩阵堵住漏检最后一步也是最容易被跳过的环节是验证模型在真实场景下的表现而不仅仅看 mAP 数字。6.1 数据不完整导致的过拟合在鱼类疾病检测里最常见的过拟合是颜色过拟合。比如训练集里的 “scale_spot”鳞片白斑全是在蓝色池底拍的模型可能学到的是“蓝色背景上的白块”而不是“鱼鳞片上的白斑”。验证方法很简单拿几张不同背景、不同光照下的同类病鱼图去推理。如果掉点严重说明模型过拟合到了背景需要补充不同场景的数据或者做针对性的背景增强。6.2 交叉验证对小数据集我一般不看单次划分的验证集结果而是做 5 折交叉验证。YOLOv8 没有内置直接的 K-Fold 命令你可以手动把数据集分成 5 份每次用 4 份训练、1 份验证跑 5 次训练最后取 mAP 均值和标准差。虽然训练成本是原来的 5 倍但对于标注量在几百到一千张的项目这是值得的。6.3 存储格式交叉验证产出的 5 个best.pt可以都留着推理时用torch.hub.load分别加载对同一张图投票表决。这个方案在真实部署时能让鲁棒性上一个台阶代价是推理时间增加到原来的 5 倍。如果设备性能允许推荐使用如果算力紧张选择 fold 里 mAP 最高的那个权重即可。6.4 置信度阈值我建议做一次简单的阈值扫描找到 Precision-Recall 曲线上的平衡点。具体做法是在验证集上以 0.05 步长从 0.2 扫到 0.6画一张表格记录每个阈值下的 F1-Score取 F1 最高的阈值作为部署默认值。这个流程只需要写十几行 Python 脚本但比拍脑袋定阈值靠谱得多。6.5 混淆矩阵最后一步看 YOLOv8 训练生成的confusion_matrix.png它会告诉你哪两个类最容易被搞混。比如 “rot” 和 “ulcer” 经常互相误检说明两者的视觉特征太接近。这时候不是加数据而是重新审视标注标准——是不是两个类的标注本身就含混不清。统一标注规范后重新标注、重新训练通常能明显涨点。这些年做检测系统的习惯是拿到权重先不急着部署验收在测试集上人工过一遍检测结果把每一张误检和漏检的图找出来看它们的共性和边界。别看这个动作简单它能给我带来比所有炼丹技巧都要大的提升。希望本笔记里这套从数据到验证的完整流程能帮到你少走几步我当时走过的弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站