基于YOLOv8的鱼病害检测系统:数据、训练与部署实战

发布时间:2026/10/21 13:59:30
基于YOLOv8的鱼病害检测系统:数据、训练与部署实战
做水产养殖方向视觉检测的朋友应该都有感触这两年“AI渔业”的项目需求明显多了起来。这个鱼病害检测系统就是一个典型的落地场景针对养殖池、网箱或者鱼塘监控画面实时识别鱼体表面的白点病、烂鳃病、水霉病等常见病害并把结果直接呈现在本地界面上。项目核心技术用的是YOLOv8目标检测搭配自建的YOLO格式鱼病害数据集外加一套基于Python的可视化操作界面。整条链路从数据标注、模型训练到UI封装、推理部署都是完整可复现的。这篇文章我会把从零搭建这套系统的关键环节逐个拆开讲包括数据集怎么构建、训练参数怎么调、界面怎么集成模型以及实际部署时最容易踩的坑。如果你是刚接触YOLO系列或者正在做农业/水产方向的视觉项目这篇能帮你省掉不少弯路。1. 项目到底要解决什么问题1.1 养殖现场的痛点在哪里水产养殖的病害检测传统方式基本靠人。有经验的老师傅每天巡塘捞鱼、看体表、凭感觉判断有没有异常。这套方式的问题非常明显第一人工巡检效率低一个上百亩的养殖基地靠人走一圈得小半天第二病害判断高度依赖个人经验同一个症状不同人看结论可能完全不同第三很多病害早期症状非常隐蔽白点病初期只是鱼鳍边缘有几个小白点等肉眼明显看出来的时候往往已经扩散到整塘了。这个项目想解决的问题就是把这个“看鱼”的过程自动化。从场景上看摄像头可以架在饵料台附近、增氧机旁边或者用手机拍一张鱼的照片传上来系统自动检测每一条鱼的位置同时给出病害类别和置信度从“人工巡塘”变成“机器辅助诊断”。从技术需求的角度拆分核心其实就三点要能检测出鱼的位置要能分类出病害类型还要有可交互的界面让养殖人员直接看结果。这也是为什么最终方案选择了目标检测而不是图像分类——图像分类只能告诉模型“这张图里有病鱼”但无法告诉用户病鱼在哪、是什么病、有多少条。养殖人员在实际使用中要的是定位信息、计数统计和置信度参考这些只有目标检测模型能提供。1.2 为什么是YOLOv8这个方案目标检测的技术路线其实很宽。传统方式有Haar特征加级联分类器深度学习路线有Faster R-CNN为代表的二阶段检测器还有SSD、YOLO系列这类单阶段检测器。放在水产养殖这个场景里选型逻辑很清晰。首先实时性要求决定了不能选二阶段检测器。Faster R-CNN精度高但推理速度很难做到实时尤其是用CPU或者低端GPU跑的时候一帧画面要几百毫秒完全满足不了视频流检测的需求。YOLO系列是单阶段检测器直接把目标定位和分类合并成一个回归问题速度上有天然优势。其次在YOLO家族内部v8相比前几个版本有几个实际优势。YOLOv5没有官方维护的持续迭代生态比较分散YOLOv6和v7虽然性能不错但社区资料、预训练模型、周边工具的完整度都不如v8。YOLOv8的ultralytics官方库把训练、验证、导出、推理全部整合得比较顺手数据集格式、命令行工具、Python API都很统一项目开发时少踩很多环境兼容的坑。还有一个很实际的点是部署便利性。YOLOv8训练完的模型可以直接导出为ONNX、OpenVINO、TensorRT等格式后面如果要接到IPC摄像头、Jetson边缘设备或者工业主机上转化成本很低。对于一个偏落地应用的检测系统这个特性比纸面精度多零点几个点更重要。1.3 系统整体技术架构整个系统可以拆成四个层次来看。数据层是最底层包含鱼病害图片的采集、清洗、标注和增强。这一步决定了模型的能力上限网络模型再强喂进去的数据不行也是白搭。训练层使用ultralytics框架进行YOLOv8模型的训练、验证和指标评估通过调整模型尺寸、epoch、batch size等参数得到一个针对当前病害体系的检测权重文件。推理层负责加载训练好的权重对输入图片或视频帧执行检测输出检测框、类别和置信度并把这些结果结构化交给上层界面展示。应用层是用户直接接触的部分一个基于PySide6的桌面程序支持图片、视频和摄像头的实时检测模式。界面左边是原画面右边是检测结果下方有置信度阈值滑块和检测统计用户可以直接用滑块调整检测的严格程度不需要懂任何模型知识。2. 数据集构建决定检测上限的环节2.1 病害类别怎么定做数据集之前先得定一个关键问题到底要检测哪些病害。这不是随便拍脑袋选的要考虑养殖场景的实际发生频率、病害的表观可识别性以及标注成本。拿常见的淡水鱼养殖来说我自己的项目选了五类典型病害白点病病原是小瓜虫症状是在鱼体表出现细密的小白点尤其在鱼鳍和鳃盖部位比较明显是淡水养殖中最常见、也最容易肉眼识别的病害之一。烂鳃病症状是鳃丝腐烂发白、带有污泥鱼往往在池边缓游体表症状不明显需要翻看鳃盖这对检测来说有一定难度。水霉病体表出现棉絮状的白色菌丝尤其在低温季节和鱼体受伤后高发表观特征非常明显。赤皮病体表局部或大面积充血发红鳞片脱落常见于机械损伤后感染。还有一种可以归为“正常健康鱼”这个负类但需要和应用场景结合起来看。这里有个设计经验数据集里不能只有病害样本一定要包含健康鱼这个类别。原因很简单检测系统实际跑起来的时候大部分画面里其实是健康鱼为主偶尔混入几条病鱼。如果没有健康类模型会把所有鱼都归到某一病害类别下误检率会高到没法用。把健康鱼作为一个独立类别模型会先学“鱼长什么样”再在“鱼”的基础上区分是否患病这个层级关系对提升准确率影响很大。2.2 图片收集与标注实操数据来源主要有几个渠道养殖现场部署的固定摄像头连续录像截帧、手机拍摄的病害鱼照片、以及实验室条件下模拟发病期的样本。从实操经验看来源越多样化越好因为不同设备、不同光照、不同水体透明度下同一病害的外观差异很大。如果只用单一场景的数据训练模型换一个环境基本就废了。图片收集阶段有一个容易被忽视的动作——抽帧。摄像头连续录像是视频相邻帧之间高度相似如果直接全部拿来做数据集模型会严重过拟合到这些重复画面上。实操中一般按时间间隔抽帧比如每秒抽1到2帧或者按画面差异去重。这样能保证数据集的多样性同时减少标注工作量。标注工具方面用过LabelImg和开源的标注工具都可以标注格式选择YOLO格式最方便因为可以直接喂给ultralytics。YOLO格式的核心内容是每张图片对应一个txt文件每一行代表一个标注框类别id、归一化后的中心点x、中心点y、框宽度w、框高度h。这里特别提醒一句所有坐标值都是相对于图片宽度和高度的比例值范围在0到1之间不是像素坐标。新手最容易在这个地方出错一旦归一化计算错误训练时的损失函数会直接异常检测框全部乱飞。标注质量直接决定模型精度有几个实操心得模糊的、严重遮挡的、病征不清晰的样本直接丢弃不要留到数据集里。让模型学习模糊样本没有任何意义只会增加训练难度。标注框尽量贴合鱼体轮廓尤其是病征所在的区域。白点病如果小白点集中分布在鱼背鳍附近框的位置要把这部分完整框住不要为了省事只框鱼头。病害区域和健康区域同时出现在一条鱼身上时以病害特征所在区域为主因此标注框不需要严格框全身可以框局部病征区域。每个类别至少准备300到500张有效标注图如果类别多或者场景差异大数量还要往上加。标注完成之后还需要做一次交叉检查。建议至少两到三个人互相校验同一批标注结果重点看有没有标错类别、有没有漏框、有没有框偏。这个环节能换来训练时好几个点的mAP提升性价比极高。2.3 数据增强与数据集划分数据增强的作用是让模型见到的样本更丰富提升泛化能力。对于鱼病害场景常用的增强手段包括水平翻转、垂直翻转——鱼类图像不像文字识别那样有严格的上下方向翻转后语义不变可以放心用旋转30度以内——注意角度不能太大鱼体有基本的水平朝向旋转太夸张反而会造成误检亮度调整和水体色偏模拟——同一病害在清水和浑水环境下拍出来的视觉差异非常大通过改变亮度、对比度和色相模拟不同水质随机噪声和模糊——模拟摄像头画质波动。ultralytics框架内置了这些增强策略在训练时默认会启用一部分比如mosaic马赛克增强。mosaic会把四张图拼成一张图去训练对提升小目标检测能力帮助更大。但注意如果目标尺度本身比较大比如整条鱼占画面比例很大mosaic的增益就不那么明显了可以适当降低或关闭。数据集划分这里建议按801010的比例分为训练集、验证集、测试集。有一个很容易踩的坑划分前一定要确保同一个来源的图片不要同时出现在训练集和验证集里。比如同一个摄像头的连续帧按时间顺序先抽取的放在训练集后抽取的放在验证集这样会存在高度相似画面跨集合泄漏导致验证指标虚高真正部署到新场景时精度断崖式下降。按视频片段或按拍摄批次划分才是正确做法保证训练集和验证集来自不同的拍摄时刻验证结果才有参考意义。3. 模型训练与调优从baseline到稳定可靠3.1 选yolov8s的理由YOLOv8提供n、s、m、l、x五个尺寸档位从轻量到超重。在这个项目里最终选了yolov8s是结合性能、显存和部署环境权衡的结果。初始阶段建议先用yolov8n跑通整个流程验证数据和代码没有问题因为n模型训练速度快、显存占用小适合快速迭代。等到确认数据集标注没问题、训练流程跑通了再切换到yolov8s把精度拉上去。后续如果需要部署到低性能设备上再换回n模型剪枝量化。为什么不直接上l或x水产检测场景里病害识别对精度的要求并没有到自动驾驶那种苛刻程度s模型在鱼病害数据集上已经可以做到90%以上的mAP50再换更大的模型精度提升通常只有一到两个百分点但推理速度可能慢三到四倍。在养殖现场用户更在意的是“能实时跑、结果靠谱”不是几个百分点的指标提升。3.2 训练参数详解训练前需要把数据集配置好ultralytics用YAML文件描述数据路径和类别。格式大致如下path: D:/fish_disease_data train: images/train val: images/val test: images/test names: 0: healthy 1: white_spot 2: gill_rot 3: saprolegniasis 4: red_skin注意path建议填绝对路径相对路径在调整工程文件位置后容易出问题排查起来也比较麻烦。训练命令我实际项目里用的是这样yolo detect train datadata.yaml modelyolov8s.pt epochs150 imgsz640 batch16 patience20 cacheTrue optimizerauto几个关键参数拆开说。imgsz设为640YOLOv8默认就是这个尺寸。如果原始图片分辨率普遍小于640可以保持默认不动如果数据集中有大量高清大图比如单反拍摄的3000×2000像素可以试一下imgsz1280对小鱼苗这类小目标会有帮助。但随之而来的是显存暴涨和推理变慢要自己权衡。epochs设150比较稳妥。鱼病害数据量不大训练曲线通常在几十轮之后就趋于平稳但后面几十轮可以让边界框回归头更好地收敛。设150不是为了真的训练满更多是留出冗余量配合patience参数做早停。patience20表示连续20轮验证损失不下降就自动停止训练。这个参数非常实用能有效防止过拟合同时节省时间。训练完以后输出目录里会出现best.pt和last.ptbest.pt是根据验证集表现保存的最优权重推理的时候直接用这个文件。batch size的选择跟显存相关。yolov8s在batch16、imgsz640的情况下大致需要12到16G显存如果显存只有8G就把batch降到8或者4同时开启混合精度训练ultralytics默认就是开启amp的不需要额外设置。这里有几个经验值可供参考显卡显存推荐batch size推荐模型档位8G4~8yolov8n / yolov8s12G8~16yolov8s24G16~24yolov8s / yolov8m3.3 评估指标怎么读训练结束之后不要只看一眼loss就放过。有两个指标必须理解清楚否则你不知道这个模型到底行不行。第一个是mAP50指的是IoU阈值为0.5时的平均精度均值。这个指标对检测框的位置精度要求相对宽松主要衡量模型能不能把目标找出来、类别分对是项目中最核心的参考指标。第二个是mAP50-95把IoU阈值从0.5到0.95按0.05步长取平均。这个指标对框的贴合度要求很高很严苛。在实际项目中mAP50能到90%以上就说明可用性不错mAP50-95只要不是低到离谱比如35%以下问题都不大。还要注意每个类别的单独AP值。全局mAP很高不代表所有类别都好经常出现大类把小类平均掉的情况。比如健康鱼样本特别多AP很高水霉病样本少AP可能很低。鱼病害检测系统在实际使用时恰恰是那些少样本的病害才是真正要抓的重点。如果发现某个类别的AP远低于其他类别优先补这个类别的训练数据而不是盲目调模型参数。3.4 训练过程踩到的坑训练中比较容易遇到的一类问题是loss不下降。排查方向一般是数据格式问题、类别id不连续、标注框坐标越界。这类问题通过检查loss曲线就能发现端倪刚开始下降正常几轮之后停在某个高位不再变化基本可以断定是数据侧的问题而不是模型问题。把数据集里对应的训练样本拿出来可视化一遍很快就能暴露问题。另一类问题是过拟合。训练集loss持续下降但验证集loss在一个高点后开始回升。这种情况在小数据集上很常见。解决办法有增加数据增强的强度、加早停参数、增大dropout、用更大规模的预训练模型作为初始权重而不是从头训练。还有一个思路是先从yolov8s预训练权重开始基于coco预训练权重做迁移学习因为coco数据集里已经包含了大量鱼、动物、水生生物的标注模型对“鱼长什么样”已经有了一个基本概念微调起来不容易过拟合。还有一个容易踩的细节训练时如果数据集中存在“同一条鱼在连续画面里重复出现”的情况而非抽帧导致的模型会把注意力过多地放在那些重复出现的个体上。最简单的解决办法是在数据增强里调高翻转和变换强度让模型对同一目标的特征建模更抽象一些。4. UI界面设计与系统封装4.1 为什么用PySide6模型训练完了下一步是做用户能直接用的界面。很多做算法的人在这个阶段会很头疼写界面不是核心工作但又绕不开。这里项目选的是PySide6也就是Qt官方的Python绑定库。为什么不选Tkinter功能太基础控件样式老旧做出来的界面观感落后用户第一印象就不专业。为什么不选纯Web方案Flask加前端页面调试麻烦还要处理浏览器兼容问题对本地桌面工具来说小题大做。PySide6的好处是控件丰富、样式现代、支持自定义QSS样式表而且和Python生态结合得很好OpenCV的图像数据可以直接转成Qt的显示格式不需要额外起摄像头推流服务。界面整体布局是左图右文的形式。左侧占大头的是画面显示区域支持原图和检测结果两种画面切换右侧是结果信息面板包括检测框统计、各类别数量、推理耗时底部是功能按钮和参数滑块整个界面的操作逻辑控制在三步以内选数据源、点开始检测、看结果。这个交互逻辑对养殖人员更友好太复杂提不了效率。4.2 界面模块拆解界面按功能分成三个检测模式。图片检测模式点击“选择图片”按钮后弹出文件对话框选中一张图片后立即加载自动执行模型推理然后在画面区域绘制检测框。绘制时每个类别绑定一种颜色比如健康鱼用绿色框白点病用红色框图例显示在画面右上角。视频检测模式选择本地视频文件逐帧读取、逐帧推理、逐帧显示。这里要控制帧率视频文件本身有播放帧率推理速度如果跟不上需要丢帧或延时避免画面播放速度和真实事件脱节。摄像头实时检测模式用OpenCV的VideoCapture读取摄像头画面。这是最核心的模式因为养殖现场的实际使用场景大多是这个。摄像头可以是USB摄像头也可以是RTSP协议的网络摄像头后者通过cv2.VideoCapture(rtsp://...)读取返回的status帧检测逻辑完全一致。除了三个模式界面右侧的统计面板会实时更新检测结果。比如当前画面中健康鱼12尾、白点病3尾、水霉病1尾同时显示每条鱼的检测框大小和置信度。顶部的阈值滑块控制的是置信度过滤下限。默认0.5往左拉会显示更多低置信度的检测框适合用来排查漏检往右拉过滤更严格适合现场快速确认减少误报。4.3 多线程与摄像头实时推理这个环节是最容易出问题的地方。如果直接在Qt的主线程里跑视频推理循环界面会直接卡死窗口拖不动、按钮没响应、甚至点了关闭没反应。原因是推理是耗时操作占用了主线程的执行时间Qt的事件循环没法及时处理界面操作。解决办法是把推理放到单独的工作线程里。PySide6的QThread是标准方案继承QThread重写run方法在run里执行视频循环每一帧推理完成后通过信号发送结果到主线程。主线程接到信号只需要做一件事刷新画面和统计信息不参与任何推理计算。class DetectThread(QThread): frame_ready Signal(object, object, object, float) def __init__(self, model_path): super().__init__() self.model YOLO(model_path) self.running True def run(self): while self.running: ret, frame self.cap.read() if not ret: break results self.model(frame, conf0.5) for r in results: rendered r.plot() self.frame_ready.emit(frame, rendered, r.boxes, r.speed)需要注意的有三个点。第一YOLO模型对象应该在线程初始化时就创建好不要在run循环里反复加载否则每次加载权重都会卡顿实时性全无。第二推理产生的渲染图是一个numpy数组OpenCV默认是BGR格式而Qt显示用的是RGB。转出来的画面偏蓝偏绿其实就是通道顺序没换。第三QThread销毁前一定要把running标志置为False并等待线程结束否则进程退出时会报错崩溃。4.4 结果输出与报告养殖场景下检测结果不能只是屏幕上闪一下。在实际落地中系统需要把检测数据沉淀下来方便后续复盘和管理。所以在UI上加了“导出CSV”按钮点击后可以将本次检测时间内每一帧的检测记录保存为表格包含时间戳、病害类别、置信度、边界框坐标。这个功能本质上就是一张病害巡检记录表。养殖人员可以直接拿这张表去和兽医对接不用手动抄数据。除此之外具备监测功能的部署场景还可以加一个简单的统计面板按天或按小时汇总病害发现次数用于判断病害是偶发还是正在扩散。这个功能不用做得很复杂一个Python字典按时间戳累加计数再用matplotlib画一个简单的折线图就能满足需求。5. 常见问题排查与部署经验5.1 推理速度慢使用中反映最多的问题是检测画面一卡一卡的不流畅。第一步先确认模型的档位yolov8l、yolov8x这类大模型在CPU上跑640×640输入一帧可能要到几百毫秒实时性自然无从谈起。替换为yolov8s或者yolov8n会有明显改善。第二步看是不是在跑视频和摄像头时做了重复计算。比如同时对每一帧做了两遍推理或者把模型对象在循环里反复创建。最典型的错误是直接在循环里写model YOLO(best.pt)每帧都重新加载权重这样导致的速度延迟很容易被忽视。第三步考虑硬件加速。如果部署机器有Intel的核显或者NVIDIA显卡可以分别把模型导出为OpenVINO格式或TensorRT格式推理速度提升非常明显。yolov8s模型在CPU上做实时画面检测开启OpenVINO之后能把每帧耗时从上百毫秒降到几十毫秒以内。5.2 误检漏检多误检和漏检是视觉项目里绕不开的问题鱼病害检测尤其容易受环境影响。水体反光、水面波纹、气泡、饵料颗粒都可能被模型误认为鱼或者病斑。如果是误检先把置信度阈值调高试试从0.5调到0.7或0.8低置信度的误报会被过滤掉。如果阈值调高也没用说明模型本身没有学好这个类别的特征需要补充更多“难负样本”数据单独把容易误检的图片收集起来标注为健康类让模型知道这类画面不是目标。如果是漏检常见原因有目标太小、病害特征不明显、光线过暗。针对小目标问题把训练时的imgsz从640提高到1280模型可以看到更多细节。针对光线暗的问题可以在推理前对画面做亮度增强的预处理或者干脆在采集端加补光灯这个比调模型参数见效更快。5.3 程序卡死与显存不足桌面程序运行一段时间后直接崩溃多半是线程没有正常管理导致的。摄像头读取异常、视频文件读到了结尾、网络流断开这些情况都需要在线程内捕获并做退出处理。在摄像头断连时程序会阻塞在读取操作上几秒钟无响应后触发系统级别的崩溃这种情况只能靠设置读取超时和加异常捕获兜底。显存不足的情况多出现在同时加载多个模型时。比如界面里既加载了检测模型又加载了分类模型显存就爆了。解决办法是评估地址后的模型复用不在推理时重复初始化。模型加载是耗时且占显存的操作项目启动时一次性初始化后面反复使用同一个实例最稳妥。5.4 硬件要求参考基于这个项目的经验给出一个硬件参考范围。如果只是做测试笔记本的CPU加16G内存就够推理用yolov8n或yolov8s速度稍慢但能跑。如果要部署到养殖现场做实时检测建议至少i5以上的CPU加16G内存有独立显卡更好。边缘设备方面Jetson系列跑yolov8s的TensorRT版本是可以胜任的功耗和体积也更适合养殖现场环境。6. 从项目完成到实际落地的一点经验最后说几句比代码更重要的事。系统在实验室环境跑通和在实际养殖现场真正可用中间隔着一道不小的沟。我在做这类项目时体会最深的一件事是先了解目标用户的真实使用场景再决定技术方案。养殖人员不会关心mAP多少只关心能不能看清楚鱼的异常、会不会误报警、操作简单不简单。所以做界面的时候宁可功能少一点也要让核心检测流程够直观。另一个体会是数据工程的重要性远大于模型调参。鱼病害检测这个方向上市面上几乎没有现成的高质量公开数据集大部分项目都是靠现场拍摄、整理、标注一点点攒出来的。谁的数据更贴合目标场景、标注更精准谁的模型效果就好。在项目规划阶段至少留一半的时间给数据工作千万不要一上来就急着训模型。这个系统后续还可以往两个方向扩展。一是接入多路摄像头画面做一个巡检轮询模式对多个鱼池定时轮检把检测结果统一汇总到一张总览表上二是基于时间维度做趋势分析把连续多天的检测记录整合起来辅助判断疫情发展趋势。这些扩展都不需要改动现有模型结构更多是在应用层做叠加有兴趣的朋友可以顺着这个方向继续做。