news 2026/10/20 5:15:31

基于YOLOv11的水下鱼类检测系统实战:从数据集到界面部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv11的水下鱼类检测系统实战:从数据集到界面部署

水下鱼类检测这几年在养殖管理、海洋生态调查、渔业资源评估里用得越来越广。去年底YOLOv11发布后,很多之前基于v5、v8做的水下目标检测项目都开始往新版本迁移。我自己也把一个长期维护的水下鱼类检测系统整体重构了一遍:模型换成YOLOv11,数据集按YOLO格式重新整理,前端加了登录注册功能和可视化检测界面,最后交付的是一套能直接跑通的Python项目源码加训练好的模型文件。这篇博客就把整个系统的搭建过程完整记录下来,从数据集处理、模型训练到界面联调都会讲,关键参数和踩过的坑全部写清楚,正在做目标检测课设、毕业设计或者渔业相关实际项目的朋友可以直接参考。

1. 项目整体设计思路拆解

1.1 为什么选定YOLOv11:和旧版模型相比,水下场景赢在哪

做水下鱼类检测,模型选型是第一步,也是决定效果上限的一步。我的项目早期其实是用YOLOv8起步的,v8的成熟生态和资料丰富程度确实是优势,但测试下来有两个明显的短板:一是水下常见的小鱼目标容易漏检,二是鱼群密集遮挡时框的定位不够准。YOLOv11出来之后,我把同样一份数据集分别跑了v8和v11的对比实验,v11在mAP50-95上提升了大概3到4个点,小目标类别的召回率提升尤其明显。

从结构上理解这个提升,核心在于v11改进了主干网络的特征提取模块,在保持实时性的前提下增强了对多尺度目标的响应能力。注意力机制的引入让模型更关注鱼体轮廓和纹理区域,而不是被水下背景的散射噪声带偏。对水下场景来说,画面往往对比度低、纹理模糊,模型能不能“抓住”目标的关键特征,直接决定检测效果。

为什么不选其他方案?我也试过v9和v10,它们在特定任务上各有亮点,但部署工具链和社区集成度不如v11完整。官方训练框架可以一条龙完成训练、验证、导出,转ONNX或者端侧模型都很方便。v8当然也能用,但从项目长期维护的角度看,直接用当前迭代版本,后续升级成本和迁移成本都更低。最终选择v11,理由可以总结为三点:精度更高、部署链路完整、迁移成本可控。

1.2 四层架构设计:模型和界面解耦的思路

项目整体没有做成一个大杂烩文件,而是按四层来设计。第一层是数据层,负责水下图像的收集、清洗、标注和格式转换,标注结果统一为YOLO的txt格式。第二层是模型层,包含训练、验证、测试三套流程,训练产出best.pt和last.pt两个权重文件。第三层是服务层,主要封装模型推理逻辑,包括图片检测、视频帧推理、批量检测和摄像头采集,全部独立成一个检测引擎模块。第四层是应用层,也就是用户直接看到的登录注册页面和可视化检测界面。

分层的价值在项目推进过程中体会很深。如果模型推理和界面逻辑写在一起,改模型参数会影响界面,改界面又担心碰坏推理逻辑,调试成本会翻倍。我当时的做法是把所有模型相关操作封装成一个检测引擎类,界面只负责读取图像、调用接口、展示结果。后面我把模型导出成ONNX格式做速度对比时,只改了引擎内部实现,界面一行代码都没动。

1.3 功能模块清单与发布形态

整个系统的功能模块可以梳理成一张表,方便后续分模块开发和验收。登录注册模块负责用户注册、登录验证和会话状态管理,数据持久化使用SQLite。图像检测模块支持上传单张图片,绘制检测框并显示类别和置信度。视频检测模块按帧读取视频文件,实时推理并统计帧率。摄像头实时检测模块调用本地摄像头,适合现场演示和实际监测。模型管理模块负责加载权重文件、切换模型和查看类别信息。数据统计模块记录检测目标数量、类别分布以及单次检测耗时。

开发顺序上,我建议先把模型训练跑通,确定检测效果达标,再开始写界面和登录注册。很多人喜欢先搭界面,但界面做完模型效果不行,整个项目就卡住了。反过来,模型效果稳定了,界面只是一个包装壳,投入产出比会高很多。

2. 水下鱼类数据集制作与预处理要点

2.1 水下图像的特殊性:色偏、散射和遮挡

水下图像的成像环境和日常拍摄完全是两回事,处理不好这层差异,模型效果会大打折扣。水体对光线的选择性吸收会让画面整体偏蓝绿色,模型学到的颜色特征可能偏向某个特定水域,换个环境泛化能力就变差。水下悬浮颗粒对光线的散射会造成图像模糊和对比度下降,特别像雾天拍摄的图片。鱼群的聚集习性和游动特点让遮挡非常普遍,一条鱼遮挡另一条鱼的局部是常态。

这些特点直接影响数据标注和训练策略。标注时不能像普通目标检测那样想当然地框住目标,遮挡部分该不该标、小目标该不该漏,都需要定标准。训练时对数据增强的需求也更强烈,需要模拟不同水质、不同光照条件,提高模型在各种水域环境下的适应能力。

2.2 数据收集和标注标准:遮挡目标怎么处理

数据来源我用了两部分:一部分来自某开源水下鱼类检测数据集,覆盖了常见的养殖鱼种,图像分辨率差异较大,刚好能锻炼模型的尺度适应性。另一部分是项目现场采集的图像,用防水相机在养殖网箱和自然水域拍摄,补充了开源数据里缺少的特定鱼种和光线条件。

标注是整个数据流程里最花时间的环节,也是最影响最终效果的环节。我采用的标注标准有几点供参考:可见面积超过50%的个体尽量标出来,被完全遮挡的目标不标,边界模糊的不强求精确到像素级。标注框尽量贴合鱼体,鱼尾和鱼鳍部分允许少量外扩,但不要把背景或相邻鱼体包进来。

标注工具我用的是一款开源标注软件,支持Pascal VOC和YOLO格式导出。实际操作时有个经验:标注完一定要做二次检查,把导出文件随机抽一批可视化出来看,专门核对偏移和漏框问题。标注错误是系统性错误,模型训练时会反复学习到错误信息,后期很难通过调参弥补。

2.3 YOLO格式转换中三个特别容易出错的地方

YOLO格式的标注文件是每张图对应一个同名txt文件,每一行由类别ID、中心点x、中心点y、宽度、高度组成,全部归一化到0到1之间。格式本身不难,但转换过程中有三个坑需要特别留意。

第一个坑是坐标系换算用错。标注工具导出的原始坐标通常是像素单位的左上角xy和宽高,转成YOLO格式需要先计算中心点坐标,再分别除以图像的宽度和高度。有人会忘记归一化,直接把像素值写进txt,导致训练时检测框全部跑到画面外。

第二个坑是类别ID从0还是从1开始。YOLO系列的类别编号从0开始,但很多标注界面显示从1开始,切换时要格外小心。类别标错不会报错,整个模型训练完都无法区分目标类别,排查起来很难发现。

第三个坑是文件名匹配。txt文件名必须和图像文件名完全一致,包括大小写。如果有一张图漏了标注文件,训练时不会报错,只是那一眼图像相当于没有标签,默认当作背景处理,拖累整体效果。转换完成后可以用脚本统计图像文件和标注文件的对应关系,做到一张不差。

2.4 数据增强怎么调才符合水下场景

YOLO系列训练框架内置了一套数据增强策略,默认参数适用通用场景,直接套用到水下数据上效果还不够,我做了几处针对性调整。HSV色域增强的饱和度范围适当加大,因为不同水域的色偏差异很大,让模型见过更多颜色变化,能增强泛化能力。亮度范围扩展到正负25,模拟不同深度水层的明暗变化。

马赛克增强是提升小目标检测能力的有效手段,官方默认开启,但概率我调低到0.8。水下图像的整体结构相对简单,马赛克比例太高会让模型学到过多拼贴痕迹,反而干扰真实场景的识别。随机旋转和翻转按默认参数开启,水平翻转对鱼类检测影响不大,因为鱼体本身没有明显的方向特异性。

从实际效果看,增强策略调整后模型的验证集mAP提升了一个点左右。重要的是,增强不能脱离实际场景。水下图像的噪声和模糊是真实存在的,但如果增强过度,训练集的分布会和真实场景偏离,模型在测试时会表现得不稳定。增强幅度适中,保持多样性即可。

3. 模型训练核心环节实操

3.1 基础环境配置与依赖版本

训练环境是整个项目里最需要稳定性的部分。我的配置是Python 3.10,PyTorch 2.3版本,CUDA 12.1,显卡使用本地的一张12GB显存GPU。官方训练框架对CUDA版本有要求,安装前最好先核对一下当前驱动支持的版本,装错会直接导致GPU不可用,代码报错信息往往还不明显。

个人建议创建独立的conda虚拟环境来跑训练,不要和日常开发环境混在一起。深度学习框架版本迭代快,不同项目依赖的版本可能冲突,隔离环境可以省掉大量头疼的问题。如果本地没有GPU或者显存不够,也可以考虑云GPU实例,但需要评估模型文件和数据集的传输时间。

3.2 训练参数配置:一张表说清超参数

训练参数的设置是模型效果的关键环节。数据集的配置信息放在一个YAML文件里,包括训练集和验证集的路径、类别数量、类别名称列表。路径建议写绝对路径,相对路径在切换工作目录时会出问题。

训练超参数我整理成一张表,方便直接对照使用:

参数名取值说明
imgsz640输入图像尺寸,兼顾精度和速度
batch16显存12GB下实测稳定,可调高或调低
epochs150一开始可以设100,观察收敛情况再调整
patience20验证集效果连续20轮不提升则提前停止
lr00.01初始学习率,值太大容易发散,太小收敛太慢
lrf0.01最终学习率,训练后期逐步衰减
weight_decay0.0005L2正则化系数,防止过拟合
warmup_epochs3前3轮用较小学习率预热,稳定训练过程

batch的取值主要受显存限制,如果训练时报CUDA out of memory,优先把batch减半,或者开启梯度累积。lr0不建议盲目调大,水下数据集的纹理信息相对简单,学习率太高会在前期震荡,后续即使衰减也难回到最优状态。

3.3 训练命令与核心指标监控

配置完成后直接用官方训练脚本启动训练,示例命令如下:

yolo train data=fish_data.yaml model=yolo11s.pt epochs=150 imgsz=640 batch=16 lr0=0.01

这里用的是yolo11s这个规模版本,属于小模型,参数量适中,兼顾精度和部署体积。如果你的目标类别很少、场景单一,可以尝试更大的yolo11m版本,精度会再高一些,但训练时间和推理时间都会增加。

训练过程中重点监控四个指标:box_loss边界框损失、cls_loss分类损失、mAP50和mAP50-95。mAP50是IoU阈值0.5下的平均精度,主要反映目标级别的检测能力。mAP50-95是多个IoU阈值下的平均精度,对框的定位精度更敏感,水下鱼类的框如果有偏移,这个指标会明显下降。

建议打开训练可视化功能,每个epoch的loss曲线和指标曲线都能实时看到。loss曲线的判断逻辑是:训练loss持续下降,验证loss也同步下降,说明模型在正常学习。训练loss下降而验证loss上升,说明过拟合了,需要增加数据增强或减少epochs。两边都纹丝不动,大概率是学习率设置不当或数据标注存在问题。

3.4 模型评估与导出:不只是跑个best.pt

训练结束后先跑一遍验证集评估,得到模型在验证集上的精确率、召回率、各类别mAP和整体mAP。我训练完成的模型mAP50大概在0.87,mAP50-95在0.63左右,三类常见鱼种的精确率和召回率分布比较均衡,个别小目标类别的召回率偏低,和预期一致。

评估完成后导出模型文件。训练输出目录里会有best.pt和last.pt,best.pt是验证集效果最好的权重,last.pt是最后一个epoch的结果,通常选择best.pt用于部署。我还会额外导出ONNX格式做一次推理速度对比。ONNX格式在CPU环境下的推理速度比PyTorch原生模型快一些,如果后续要部署在服务端,转ONNX是更合适的选择。

模型导出时可以加上输入尺寸参数,固定为640x640,这样转换后的模型不会有动态尺寸的额外开销,推理速度更稳定。

4. UI界面与登录注册模块开发

4.1 界面框架选型:为什么用PySide6

界面框架我选了PySide6,也就是Qt的Python绑定。选择它主要看中三点:控件丰富度足够,文件选择、列表、标签、按钮这些基础控件齐全,组合起来能做出完整的检测操作台;样式可定制性强,登录页和检测页都能做得美观统一;和OpenCV、NumPy这些图像处理库配合很顺畅,图像数据可以直接在界面中显示和更新。

当然也可以用Tkinter,它是Python自带的,不用额外装依赖,但做成完整的检测系统会比较吃力,布局、样式、响应速度都有明显差距。如果是快速做一个教学演示,Tkinter够用。项目要长期维护,PySide6是更好的选择。

4.2 登录注册模块实现:密码不能明文存

登录注册模块是整个应用的门面,也是容易被低估的部分。数据库用的是SQLite,轻量、无需安装服务端,适合桌面应用。数据表字段包括用户名、密码哈希、盐值、注册时间。密码存储使用加盐哈希方案,Salt值用随机字符串生成,密码和盐值拼接后做哈希计算,即使数据库泄漏,也无法直接从密文还原原始密码。

注册流程做了三步校验:用户名不能为空且不能重复,密码长度不能少于6位,两次输入的密码必须一致。登录流程是:输入用户名密码后查询数据库,取出盐值和哈希值,对输入密码用同样的盐值做哈希计算,对比结果是否一致。连续登录失败次数限制也加了,防止暴力破解。

核心登录校验逻辑的示例代码如下:

import hashlib import sqlite3 import secrets def hash_password(password, salt): return hashlib.sha256((salt + password).encode()).hexdigest() def register_user(username, password): salt = secrets.token_hex(8) hashed = hash_password(password, salt) with sqlite3.connect('users.db') as con: con.execute( "INSERT INTO users (username, salt, password_hash) VALUES (?, ?, ?)", (username, salt, hashed) ) def verify_user(username, password): with sqlite3.connect('users.db') as con: row = con.execute( "SELECT salt, password_hash FROM users WHERE username = ?", (username,) ).fetchone() if row is None: return False salt, stored_hash = row return hash_password(password, salt) == stored_hash

代码这样写有个细节:SQLite连接用完就关,避免多线程环境下数据库锁冲突。Python自带的sqlite3模块在并发读写时容易报database is locked,用上下文管理器或每次操作都重新连接,可以规避大部分锁问题。

4.3 检测主界面:三种输入模式统一封装

主界面布局分为左侧操作栏和右侧图像预览区。操作栏包含输入方式选择、文件选择按钮、参数设置区和检测结果显示区。输入方式支持三种:图片、视频、摄像头。图片模式单帧推理,显示检测结果并保存。视频模式逐帧读取,实时推理,界面会显示当前帧率和已处理帧数。摄像头模式调用本机摄像头,适合现场演示。

推理逻辑封装成一个检测引擎类,对外统一提供detect_frame接口。这个接口的输入是一帧图像,输出是带标注的渲染图像和检测统计信息。界面不关心模型内部是怎么推理的,只拿结果展示。核心实现大致这样:

from PySide6.QtCore import QThread, Signal from ultralytics import YOLO class DetectionEngine: def __init__(self, model_path): self.model = YOLO(model_path) self.conf_thres = 0.4 # 置信度阈值 self.iou_thres = 0.45 # IoU阈值 def detect_frame(self, frame): results = self.model.predict(frame, conf=self.conf_thres, iou=self.iou_thres) rendered = results[0].plot() counts = {} for cls in results[0].boxes.cls.tolist(): name = self.model.names[int(cls)] counts[name] = counts.get(name, 0) + 1 return rendered, counts class DetectionThread(QThread): frame_ready = Signal(object) def __init__(self, engine, input_source): super().__init__() self.engine = engine self.input_source = input_source self.running = True def run(self): # 视频帧循环读取,推理,发射信号 while self.running: frame = self.get_next_frame() rendered, counts = self.engine.detect_frame(frame) self.frame_ready.emit((rendered, counts))

置信度阈值默认设为0.4,水下图像噪声多、目标特征不清晰,阈值太高会把低置信度的鱼漏掉。如果误检多,再往上调。IoU阈值用于后处理去重,鱼群密集时适当调高一点,可以减少相邻目标的框被合并的情况。

4.4 线程处理:界面卡顿问题怎么根治

首次接入视频检测时,界面卡顿非常严重,画面一帧一帧地卡,操作按钮完全没响应。原因很简单:推理是耗时操作,跑在主线程里,阻塞了界面的事件循环。Qt的界面刷新依赖事件循环,推理耗时期间界面就冻结了。

解决办法是把推理放到后台线程,通过信号把结果传回主线程更新界面。视频检测还需要注意生产者-消费者模式:视频读取线程负责一帧一帧读图,推理结果通过信号传递,不要让推理速度拖累视频读取速度,也不要让界面刷新的速度跟不上推理速度,否则内存里堆积的帧越来越多。

线程停止是另一个容易忽略的问题。视频检测运行中点击关闭,需要设置一个运行标志位,线程在每帧处理前检查标志位,为假则退出循环。直接强制终止线程会导致资源没有释放,下次打开视频时可能报错。我在线程里加了停止标志,让线程自然结束,实测下来很稳定。

5. 常见问题与排查技巧实录

5.1 数据与标注阶段的高频问题

标注文件格式错误导致loss不降,这个问题排查了整整两天。数据转换时坐标系写反了,中心坐标和宽高的比例关系错乱,模型训练出来后在验证集上几乎没有有效检测。排查方法是把标注信息可视化,把归一化坐标还原成像素坐标画到原图上,一眼就能看出框的位置全乱了。

类别ID错位是另一种隐蔽问题。从公开数据集拿到时类别顺序和本地定义的顺序不一致,表面看标注文件没问题,实际训练时模型把所有目标都识别成同一个类别。这个问题只能通过官方框架训练时打印的类别名称逐一核实,类别数量不用时注意对齐。

图像分辨率差异过大也会影响模型效果。训练集里既有1920x1080的大图,也有640x480的小图,统一resize到640后,小图的信息损失远大于大图,小目标会变得更小更难检测。建议在数据筛选阶段就把分辨率过低的图像剔除,或者单独做一次高分辨率增强,让低分辨率图像的数量占比不要过高。

5.2 训练阶段的高频问题

显存不足是最常见的报错,不管本地还是云GPU都会遇到。解决路径很直接:减小batch、降低输入分辨率、开启梯度累积。我的建议是优先减batch,因为降低分辨率会直接影响检测精度,梯度累积虽然能临时解决显存问题,但会拉长训练时间。

loss不降或下降极慢,在排查完标注问题后,最可能的原因是学习率设置不合适。初始学习率太高会让loss发散震荡,太低则收敛缓慢。我习惯先跑20个epoch观察趋势,loss曲线持续不动的,就把学习率调大或调小一个数量级再试。

过拟合在水下数据集上比较容易出现,原因是水下图像的背景相对单一,模型容易记住训练集的背景特征而不是鱼本身。表现为训练loss很低但验证loss偏高,或者说训练集mAP高、验证集mAP低。对策是增加数据增强强度、增大mixup概率,或者提前停止训练。

5.3 部署与UI阶段的高频问题

模型加载慢的问题不是模型推理慢,而是权重文件读取和初始化需要时间,UI在启动加载模型时如果放在主线程,启动过程会卡住几秒甚至十几秒。解决方法是把模型加载放到启动线程里,界面先显示加载提示,加载完成后通过信号通知界面切换状态。

视频检测掉帧严重时,第一步看是不是推理分辨率太高。640x640的推理尺寸在显卡上没问题,在CPU上会很吃力,可以把推理尺寸降到416或320,帧率会明显提升,代价是精度小幅下降。还可以做跳帧策略,每隔一帧处理一次,中间帧复用上一次结果,肉眼基本看不出差别。

登录注册数据库锁死的问题,排查后发现是界面上的登录按钮被多次点击,多个线程同时写入SQLite导致锁冲突。解决方法是登录按钮点击后立即置灰,请求完成后恢复,同时SQLite连接改成每次操作新建,避免多线程共用一个连接。

6. 调试体会与后续扩展方向

这个系统从数据整理到模型训练再到界面整合,全部跑通后我还是有一些体会想分享。水下鱼检测的难点其实不在模型本身,而在数据质量。标注标得不够细心,模型再好也救不回来。另一个经验是,做带界面的检测系统,模型推理和界面逻辑一定要分开,否则后续任何一个小改动都牵一发动全身。模型可以随便换,界面稳定不变,这才是一个项目应该有的结构。

项目目前是以Python源码和模型文件的形式交付,整体已经是一个比较完整的闭环。后续扩展可以做两件我认为最有价值的事:一是针对实时视频流做推理优化,把推理分辨率动态调整,保证长时间运行的帧率稳定性;二是加一个检测统计报表页面,把每一次检测结果写入数据库,按日期生成鱼类数量趋势图,对渔业资源评估的参考价值会更大。如果你正准备做类似的项目,或者已经跑起来但卡在某个环节,欢迎在评论区把问题描述清楚,我看到都会回复。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 5:14:22

AA-Scan:可编程扫描仪行为模拟与缺陷图像生成工具

简介:AA-Scan是一款基于Arduino与Android平台的开源全自动3D扫描仪软硬件方案,面向嵌入式开发初学者、创客及三维建模爱好者,解决低成本、易组装、可复现的简易3D扫描系统搭建问题。资源包共5个文件,含2个核心Python脚本&#xff…

作者头像 李华
网站建设 2026/10/20 5:13:58

深度学习火焰检测识别毕设开源:模型选型、训练调参与避坑全指南

作为一名参加过多个视觉类毕设项目、也在开源社区和论文复现上踩过不少坑的从业者,我特别想聊聊“毕设开源 深度学习火焰检测识别(源码论文)”这类项目。它听起来是标准的学生作业,但真正把它做成一个有技术深度、能跑能演示、论文…

作者头像 李华
网站建设 2026/10/20 5:13:57

Dynadot MCP入门:用自然语言实现AI域名管理自动化

"帮我把 example.com 续费一年,顺便查一下最近的优惠码。"以前听到这种话,我第一反应是打开注册商后台,翻半天菜单。现在不一样了,域名注册商 Dynadot 推出了 MCP 服务器,把域名管理整个搬进了 AI 对话里。你…

作者头像 李华
网站建设 2026/10/20 5:12:53

皮肤影像分类实战:从数据清洗到模型调参全流程解析

皮肤数据分析这行,我见过的最常见误区就是:拿到一份数据集,连图像是什么拍的、标注怎么来的都没摸清,就直接上预训练模型微调,最后跑出一堆“看起来很高”的指标,一换环境就露馅。我之前做的一个皮肤影像分…

作者头像 李华
网站建设 2026/10/20 5:12:43

彻底解决SSH Host key verification failed:known_hosts与指纹验证详解

早上到工位,稀里糊涂打开 VS Code 想接着昨晚的进度改代码,Remote-SSH 一点,远程窗口没弹出来,倒是先弹出一行红字:Host key verification failed。终端里手动ssh过去,也一样被拒。那一刻的心情&#xff0c…

作者头像 李华