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

基于YOLOv8的鱼病害检测系统:从数据集到UI部署全解析

基于YOLOv8的鱼病害检测系统:从数据集到UI部署全解析 ★ FEATURED ARTICLE
搞水产养殖的朋友应该都懂鱼病这东西最怕拖。一晚没巡塘第二天水面就可能漂着几条再查就是整池感染。以前全靠老师傅打着手电捞鱼、翻鳃、看体表费时间也特别吃经验。现在有了基于YOLOv8深度学习的鱼病害检测系统可以把训练好的模型直接接到摄像头或照片上自动把白点、水霉、烂鳃这些病灶框出来标出类别和置信度结果在UI界面上也看得很清楚。整套东西包括YOLOv8模型、整理好的YOLO格式数据集、Python项目源码和可视化界面对做水产智能化方向的技术人员、相关专业学生以及想验证AI落地效果的养殖场技术员都是一套可以直接照着复现的完整参考。这篇文章我就把整个项目的技术思路、数据集怎么准备、模型怎么训练、UI怎么接以及我实际踩过的坑都写出来尽量让第一次接触的人也能少走弯路。1. 项目整体思路与选型拆解1.1 鱼病检测为什么用目标检测而不是图像分类如果只判断“这张图有没有病鱼”那图像分类就够了。但真实巡塘场景里我们需要知道的是“病在哪一条鱼、病灶在哪个部位、水体里大概有多少条异常”。这是一个典型的定位加识别任务目标检测可以直接输出边界框、置信度和类别所以比分类多了一层位置信息也比语义分割更容易落地。检测框出来之后后台就可以对结果做二次处理比如某个鱼群的异常框数量超标就自动触发告警推送给管理员去现场确认。鱼病检测还有一个特殊性鱼在游动水下光照不均水面有反光不同养殖密度下鱼的大小差异也很大。检测网络需要同时应对“全塘游动的中景鱼”和“特写镜头下的患病区域”。目标检测的多个尺度输出正好能覆盖这种场景。如果第一步用分类网络画框和定位还要另写一套逻辑训练和部署都更绕维护起来也累。所以从需求上看目标检测是更贴合实际的选择。1.2 选型YOLOv8的几个决定性因素选YOLOv8并不是因为它最新而是因为它把训练、验证、推理、导出这条路串得非常顺。第一YOLOv8从v5的anchor-based变成了anchor-free少了anchor参数调优训练配置省心很多。对于白点病这种密集小目标它的decoupled head也能给出更干净的分类与回归结果。第二ultralytics这个Python包自带数据增强、超参数配置和命令行工具不用自己拼一堆预处理代码。第三生态完整训练完可以直接导出ONNX、OpenVINO甚至TensorRT后面接到UI、接到边缘设备都共用同一个模型。从我的实测看YOLOv8n版本的权重大约6MB在CPU上用OpenVINO跑640分辨率推理一张图大概一两百毫秒作为巡检辅助完全够用。如果追求更高精度换yolov8m或yolov8l模型体积和耗时会增加但mAP通常会有几个点的提升。这个项目里我建议先用yolov8m数据量不足或者只有CPU训练时再退回yolov8n否则每次迭代训练都等半天人的耐心会被消磨光。模型选型没有绝对正确结合自己电脑的显卡显存和实际检测精度来定才是对的。1.3 整个系统的模块划分整个系统拆开来看其实就是三块训练端负责用YOLO格式数据集训练出best.pt推理端负责加载模型对图片、视频、摄像头做检测UI端负责把检测结果可视化顺便提供文件选择、参数调整、结果导出。项目源码的目录也应该按这三块来组织比如fish_detect下面放inference、train、ui、models、dataset等文件夹。我见过不少同学把所有函数堆在一个main.py里临时能跑但模型一旦要换或者界面要加功能改动成本就会变得很高。这个项目规模本来不大但一开始把“模型调用”和“界面逻辑”分开后面会舒服很多。训练脚本负责产出模型推理模块只做加载模型和预测界面只管拿预测结果展示。三个部分之间通过最简单的函数接口衔接这样即使后面把PySide6换成Web界面也不用重新训练模型。这也是很多工业项目的基本结构越早养成这种习惯越好。提示如果只是想把系统跑起来看看效果不打算自己训模型直接把训练好的best.pt放进models目录就能走通整套流程。但想真正适配自己塘口的水质和鱼种还是要走一遍数据采集和重新训练。2. 数据集准备与标注实操2.1 类别设计先做5个高频病种鱼病种类很多但实际能稳定采集到数据的其实就那几类。我给这个项目建议用五个最典型的类别起步细菌性烂鳃、水霉病体表白毛、小瓜虫引起的白点病、赤皮病、打印病体表溃烂斑。类别太多会互相干扰有些病在体表表现本来就接近模型很容易混淆。少而清晰的类别可以让模型先学会抓主要特征也方便后面人工复核结果。数据采集渠道主要有三个一是自己去养殖塘拍摄最真实但成本高二是从公开的渔业图谱、论文插图和规范教材里整理素材注意尽量用可商用的来源三是通过行业群、数据集平台众包收集后自己清理。无论哪种渠道都要保证图片中的鱼和病害区域足够明确不能把一堆反光、水草遮挡的随手拍塞进去。数据量上我个人经验是每个类别最少100到150张原始图标注之后配合训练时增强基本能把mAP推到能用的水平。如果每个类别只有五十来张模型很容易过拟合验证集指标也会忽高忽低。2.2 标注工具与YOLO格式说明标注工具我用过不少最常用的还是开源图形化标注工具直接打开图片拉一个矩形框选好类别快捷键切下一张效率很高。也有在线标注平台团队协作更方便但上传下载依赖网络鱼病图片又往往比较大不如本地工具省事。YOLO格式的标签是一个txt文件每一行代表一个目标框class_id x_center y_center width height注意这里的x_center、y_center、width、height都是相对图像宽高的归一化比例范围在0到1之间。举个例子一张1280x720的图上某条鱼的bbox左上角在(320,180)右下角在(640,400)那么中心坐标是(480,290)宽度320高度220归一化之后就是3 0.375 0.4028 0.25 0.3056这里class id等于3表示第四个类别。刚开始最容易漏掉归一化直接把像素坐标写进txt训练时就会报标签越界或者loss怎么都降不下来这是新手入门的第一个大坑。2.3 标注时最容易被忽略的细节这里想重点说一个经验标注的不是整条鱼而是病害特征所在的区域。水霉病应该把白毛覆盖的区域框出来烂鳃要把鳃盖附近溃烂的位置框出来白点病则要框住含有明显白点的皮肤区域。一开始我图省事直接框整条鱼结果模型学到的全是“鱼”的特征一旦鱼游姿变化或者镜头角度不同照样输出病变等于白做。后来把所有框都调整到病灶位置模型才开始真正关注异常区域。这一步比盲目增加训练图片数量还要管用。另外一个容易被忽略的是尺度的覆盖。同类病灶在近景图片里可能很大在远景鱼群图里又小到看不清。所以每个类别必须同时包含近景、中景、远景样本否则模型对远景小目标基本无感。标注完之后可以再把一些只有小目标的图单独整理出来适当放大裁切后补进数据集。这个操作对mAP50-95指标的提升非常明显属于低成本高收益的做法。2.4 数据划分与目录结构训练前把图片和标签按YOLO惯例组织目录结构大概是这样dataset/ images/ train/ val/ labels/ train/ val/一般按8比2或9比1划分训练集和验证集并且尽量保证每个类别在训练集和验证集里都有一定数量。划分的时候不能直接随机切最好按类别分层抽样避免某个类别在验证集里只有三五张导致mAP波动很大。可以用脚本统计每张图的类别分布再按比例分配到train和val。训练时的数据增强其实不需要提前全部生成ultralytics自带Mosaic、随机翻转、HSV扰动等策略。对鱼病数据来说HSV扰动特别有用因为不同池塘的水色差异很大有的偏绿、有的偏黄模型如果只看固定颜色换个环境就失灵。Mosaic增强把四张图拼成一张模型能在训练过程中同时看到不同水质、不同光照下的目标泛化能力会提升不少。注意加入验证集的图片不要做太强的人工增强尽量保持原始分布否则验证集指标虚高部署到现场才发现不对劲。3. 模型训练与效果优化3.1 环境搭建与依赖安装训练环境比较常规Python 3.9以上PyTorchultralytics包。安装命令很简单pip install ultralytics但GPU环境要额外注意先按自己显卡的CUDA版本装对应PyTorch再装ultralytics。如果直接pip装PyTorch默认装的通常是CPU版训练速度会慢到怀疑人生。我一般先用PyTorch官方的命令指定CUDA版本安装比如pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121之后再装ultralytics。如果只有CPU也能训练但建议先用yolov8n在20张图上把流程走通不要一上来就跑大模型。3.2 准备data.yaml和训练命令在项目目录下建一个fish_disease.yaml内容大致如下path: ./dataset train: images/train val: images/val nc: 5 names: 0: bacterial_gill_rot 1: saprolegniasis 2: ichthyophthirius 3: red_skin 4: epitheliocystis类别名建议用英文加下划线避免中文路径和编码问题。接下来执行训练命令yolo detect train datafish_disease.yaml modelyolov8m.pt epochs200 imgsz640 batch16 device0关键参数要解释一下。epochs不是越大越好我建议先跑50个epoch看loss有没有降下去正常收敛了再加大。imgsz用640比较均衡想识别更小的白点病灶可以升到768但显存和耗时也会跟着涨。batch要由显卡显存决定16G显存跑yolov8m加640分辨率一般能到16或24如果显存不够降到8甚至4都可以跑只是收敛慢一点。device0表示用第一张GPU纯CPU环境就把这个参数删掉。3.3 训练日志里到底看什么指标训练结束后ultralytics会输出一组指标最需要关注的是mAP50、mAP50-95、Precision和Recall。mAP50表示IoU阈值取0.5时的平均精度均值鱼病检测场景里框的位置稍微偏一点其实可以接受所以mAP50要优先看。mAP50-95对框的回归精度要求更高鱼病病灶的边界往往很模糊这个数值通常会偏低不用太焦虑。一个判断过拟合的简单方法是看验证集loss。如果训练集loss持续下降验证集loss却开始回升基本就是过拟合了。出现这种情况优先增加数据增强、减少epochs或者从m模型降回n模型再试。如果验证集的recall很高但precision很低说明模型把健康鱼也误报成病鱼了大概率是负样本不够。解决方法是单独加一批健康鱼的图片txt标签留空让模型学到“这里没有目标”的背景信息误检率会明显下降。3.4 导出与压缩为可部署格式训练完成后在runs/detect/train/weights目录下会生成best.pt和last.pt推理部署直接用best.pt。如果想把模型接到UI或者边缘设备可以导出成ONNX格式yolo export modelbest.pt formatonnx imgsz640导出后再用ONNX Runtime推理跨平台更稳定CPU上的速度也明显优于PyTorch原生推理。一个常见的坑是ONNX模型无法像pt模型那样直接用yolo命令做可视化预测需要自己写推理代码。所以我会在项目里同时保留best.pt和best.onnx两份文件界面端优先尝试加载onnx失败再退回pt这样既能保证速度又不会少功能。4. UI界面开发与本地部署4.1 界面框架选型PySide6还是TkinterUI界面的选择我推荐PySide6。Tkinter不是不能用但做文件选择、图像缩放、线程刷新这些交互时要写大量底层逻辑样式也偏简陋。PySide6自带QFileDialog、QLabel显示图像、QThread多线程还有成熟的布局系统界面做出来更像一个正经工具。如果只是临时验证模型效果用Streamlit或Gradio也能快速搭一个网页版上传图片界面代码量更少。但桌面端UI不依赖浏览器对养殖场现场环境更友好这个项目里核心就放在桌面端。4.2 核心推理代码怎么组织不管UI多复杂模型调用的核心逻辑就这一段from ultralytics import YOLO model YOLO(models/best.pt) def run_detection(image_path, conf_thres0.25): results model.predict(sourceimage_path, confconf_thres, imgsz640) boxes results[0].boxes.xyxy.cpu().numpy() classes results[0].boxes.cls.cpu().numpy().astype(int) scores results[0].boxes.conf.cpu().numpy() return boxes, classes, scoresboxes是目标框的左上右下像素坐标classes是类别索引conf是置信度。接下来要把检测结果画到原图上一般用OpenCVimport cv2 def draw_boxes(image, boxes, classes, scores, class_names): for (x1, y1, x2, y2), cls, score in zip(boxes, classes, scores): label f{class_names[cls]} {score:.2f} cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(image, label, (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return image字体大小根据图像分辨率调整一般情况下0.6足够清楚。代码里最好把模型初始化和推理放在独立的inference模块不要直接在按钮回调里写一长串。4.3 摄像头实时检测与UI防卡死摄像头实时检测是这套系统最有吸引力的功能但也是最容易翻车的地方。不要在按钮回调里直接写while循环读取摄像头否则界面会完全卡死。正确做法是放到QThread线程里跑把每一帧结果通过信号发回主界面。PySide6的线程结构大致像下面这样class CameraThread(QThread): frame_ready Signal(object) def __init__(self, model, camera_index0): super().__init__() self.model model self.cap cv2.VideoCapture(camera_index) def run(self): while self.cap.isOpened(): ret, frame self.cap.read() if not ret: break results self.model.predict(sourceframe, conf0.25, imgsz640) annotated results[0].plot() self.frame_ready.emit(annotated)主界面里把frame_ready信号连接到update_frame方法再把OpenCV的BGR图转成RGB并缩放到QLabel上显示。这个模式同样适用于视频文件检测只要把VideoCapture的参数从摄像头索引换成视频路径就行。线程的启动和停止都要绑定到按钮事件上关窗口时记得释放摄像头资源。4.4 部署时绕不开的路径与中文问题UI项目在本地跑和部署到别人电脑上是两回事。首先是路径不要写死代码里用Path(__file__).parent定位模型文件和资源配置避免换机器后到处找不到文件。其次是模型文件比较大尽量不要放在需要管理员权限的目录下放到项目目录或用户目录更省心。第三是中文路径问题OpenCV的imread对中文路径支持很差从文件对话框里选出来的路径经常是中文调试半天发现是这里卡住。解决办法是用imdecodeimport numpy as np import cv2 def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)这个小函数我几乎每个项目都会备一份。界面上的图片预览以及后续保存检测结果图都建议走这个函数避免在用户电脑上遇到莫名其妙的读取失败。5. 常见问题与排查技巧实录5.1 训练时报CUDA out of memory怎么处理新手一上来就imgsz640 batch32什么显卡都扛不住。报错信息是CUDA out of memory但根因往往就是batch太大。解决办法很简单先把batch降到8还报就降到4同时把imgsz降到480或416基本上三分钟就能让训练跑起来。ultralytics里即使batch2也能正常训练只是需要更多迭代步数完全不用怕调小batch。另一个训练阶段常见问题AssertionError: Label class ... exceeds nc in data。这是因为data.yaml里nc5但某张标签文件里出现了编号5或者更大的编号。多半是标注类别ID对应错了或者data.yaml里的类别顺序和训练标签不一致。排查方法也很简单写一段脚本遍历所有txt标签打印最大类别编号和nc一对照就能发现问题。这种问题靠肉眼很难发现还是脚本检查最靠谱。5.2 检测效果不准大目标准、小目标差怎么办鱼病检测里最典型的问题是白点病的细小病灶在远距离图上检测不到。我的建议是三管齐下一是训练时把imgsz从640提高到768让小白点在输入图里的像素尺寸变大二是在数据集里补充大量小目标样本三是重点观察mAP50中对应类别的指标如果明显低于其他类就单独给这个类别加近景特写图。如果还是不行在UI层面提示用户把摄像头靠近鱼体或者固定在一个鱼群活动区域减小检测距离比死磕模型参数更高效。如果模型把健康的鱼也框成病鱼而且置信度还不低那大概率是背景负样本不足。训练集里应该加入一批完全健康、不做任何标注的鱼图txt文件留空。模型会学到“这个画面里没有目标”的先验信息误检率能肉眼可见地下降。这个方法成本很低很多人却容易忽略。5.3 UI端常见崩溃与卡死UI方面最常见的问题是点击“开始检测”后窗口假死原因就是推理放在主线程里。PySide6必须用QThread这一点前面已经强调过了。更进一步不要把摄像头对象创建在窗口构造函数里因为摄像头可能被其他软件占用程序启动就会报错。我一般把摄像头初始化放到线程的run()方法里再用try/except包住万一打不开也能给出提示而不是崩溃。另一个高频问题是cv2.VideoCapture在部分笔记本上打不开摄像头。摄像头索引0可能被其他软件占用可以先释放摄像头进程再试索引1。有些摄像头设置分辨率后不会马上生效所以设置完最好读一帧确认cap.isOpened()状态否则界面挂掉也没有有效提示。UI开发时还要注意关闭窗口时是否释放了资源不然下一次启动摄像头依然会被占用。5.4 我最想分享的一条经验最后说一个很影响心态的问题训练出来的模型在训练集上mAP很高但一到现场图片上就废。后来发现是现场图片过曝、水色偏绿偏黄和我做训练素材的图片分布差异太大。解决方法是采集数据时多覆盖不同光源、不同水质、不同拍摄角度同时训练时把HSV增强打开。标完数据后先跑一遍yolo detect val看每个类别的recall哪个类别低就专门补哪个类别的图这个思路比瞎加总数据量有效得多。经验不要总想着做一个“万能”系统先让模型在同一个池塘、同一个场景下稳定识别再逐步增加场景。一次塞进来太多场景和太多类别模型参数没调好数据又杂最后的结果往往就是“每个类别都差一点”反而更难推进。实际把这套鱼病害检测系统完整做完之后我最大的感受是模型训练只占三成数据和工程反而占了剩下的七成。鱼病场景比工业质检更麻烦因为鱼会动、水会反光、病灶也在慢慢变化但正是这些麻烦让YOLOv8的端到端能力加一个顺手UI的组合显得更实用。我建议你拿到源码后不要急着改算法先把项目跑通再用自己的鱼病照片做一批小数据重新标注、重新训练感受数据和模型之间的联动。等你能够解释清楚某个类别为什么检不出来、某个误检为什么反复出现时这套系统的价值就不只是一个项目了。
阅读完成 · 觉得有帮助?
咨询建站