☰
YOLOv11智慧养殖实战:水下鱼群检测与Jetson部署
2026/9/29 3:13:45 网站建设 项目流程

简介:面向智慧养殖领域开发者的一份30页技术文档,系统讲解如何基于YOLOv11实现鱼类行为识别与养殖密度统计。内容从智慧养殖背景与需求入手,覆盖YOLO系列演进及YOLOv11网络结构、多尺度特征融合、损失函数优化等原理,再按数据采集、预处理、模型训练、行为分类与密度计算给出Python与OpenCV实践代码,并附实验结果分析、不同光照与密度对比及水产养殖、渔业资源保护等应用场景,兼具原理、代码与工程落地方向。资源为单个PDF压缩包,约1.95MB,支持目录章节跳转与大纲定位,可在阅读器左侧快速定位章节;已有85人学习。整份文档从数据采集、模型训练到计数评估链路完整,行为识别与密度统计的关键代码均可对照复现,适合正在做目标检测、智慧渔业或水产智能化项目的开发者参考。

1. 智慧养殖场里的YOLOv11:先解决数得清,再谈看得懂

养鱼池边最磨人的事不是投喂,是巡塘。一个十亩的池塘,凭肉眼数鱼群密度、看鱼是否浮头,靠的是老师傅的经验;到了上百个池的养殖基地,人工巡塘根本排不过来。水下相机加上YOLOv11目标检测,把「数鱼」和「看行为」这两件原来只能靠人的事交给算法,一套系统同时出鱼的边界框、类别和行为标签。密度统计这根线,是区别于普通目标检测项目的地方——要的不是框准不准,是框完以后的计数和热力分布能不能指导投喂和增氧。这篇文从数据标注讲到Jetson部署,把整条链路拆开,附命令和参数,照着做能把一套识别系统跑起来。

适合两类人:已经在做智慧养殖项目、想换掉Faster R-CNN这类老模型的;以及手里有水下视频素材、想验证YOLOv11到底能不能扛住低光照和高密度场景的。鱼类检测的难点不在模型选型,在数据采集和标签策略——水里的目标,比路面上的车难伺候得多。

2. 从水下视频到可训练数据集:标注策略决定模型上限

2.1 为什么直接用公开权重跑不动水下鱼群

很多团队拿到YOLOv11第一件事是拿COCO预训练权重直接predict,结果不出所料:淡水鱼、海水鱼在COCO的80类里一个都没有,模型能输出的只有人、车、猫狗这类陆地目标。就算拿Imagenet预训练权重做迁移,水下场景的光谱分布和空气里差别极大——水对红光的吸收让画面整体偏蓝绿,悬浮颗粒造成局部模糊,鱼体反光区域在RGB图像里跟背景的对比度低。这些分布差异让模型在前几次迭代里损失下不去。

更隐蔽的问题是标注标准。路面目标检测的惯例是「框住完整目标」,但鱼群场景里一条鱼被另一条鱼挡住大半,只露出头部或者尾部,这时候标不标?标了,模型学到的是残缺目标;不标,密度统计永远偏低。我一般定三条标框规则:遮挡超过50%不标;只露出头部且能判断类别才标;贴壁的鱼如果身体有弯曲,框要贴合实际轮廓而不是用矩形硬套。这三条规则看似简单,直接决定后续密度统计误差是±5%还是±20%。

2.2 数据采集:夜间红外、白天自然光、高密度三路并进

水下相机装的位置很关键。正对鱼群活动区的侧面视角能拍到完整鱼体,适合行为识别;俯视视角对密度统计更友好,因为鱼体重叠最少,但俯视画面里的鱼形变严重——从上方看一条鲈鱼,轮廓从细长变成椭圆。我的做法是两类视角都采集,侧视用于行为分类,俯视用于密度计数,各自训练一个模型,避免一个模型学两种空间分布。

采集时段上,凌晨和傍晚各拍一批,中午强光拍一批,夜间开红外补光拍一批,覆盖鱼全天活动的光照范围。高密度场景单独拍:投喂机启动后鱼群聚拢的1-2分钟,是密度统计最需要但最难处理的画面。每个场景录10-15分钟视频,隔帧抽成图片,一个池子能抽出300-500张有效帧。多个池子、多个品种的素材混合,模型才不会过拟合到某一个池子的水色上。

2.3 标注工具选型与导出格式转换

LabelStudio和X-AnyLabeling是水下场景用下来比较顺手的工具。LabelStudio适合团队协作,多人同时标注时能看到彼此进度;X-AnyLabeling的优势是自带SAM分割辅助,鱼体轮廓复杂时先用SAM自动生成掩膜再手动修正,标注速度能快一倍。但要注意,用SAM辅助标注时容易把背景水草也圈进去,导出前要逐张检查类别属性。

导出格式上,LabelStudio默认输出COCO JSON,X-AnyLabeling输出的是自己的格式。YOLOv11训练要的是txt格式的标签文件,每行一个目标:类别id、中心点x、中心点y、宽、高,前四个值都是相对图片尺寸归一化后的0-1小数。转换脚本的常见写法:

import json from pathlib import Path # 读取LabelStudio导出的COCO格式标注 with open('annotations.json', 'r', encoding='utf-8') as f: coco = json.load(f) img_id2name = {img['id']: img['file_name'] for img in coco['images']} img_id2size = {img['id']: (img['width'], img['height']) for img in coco['images']} cat_id2idx = {cat['id']: idx for idx, cat in enumerate(coco['categories'])} # 每张图生成一个txt标签文件 for ann in coco['annotations']: img_name = img_id2name[ann['image_id']] txt_path = Path('labels') / (Path(img_name).stem + '.txt') w, h = img_id2size[ann['image_id']] # COCO的bbox格式是[x, y, width, height],需转为YOLO的归一化中心点格式 x, y, bw, bh = ann['bbox'] cx = (x + bw / 2) / w cy = (y + bh / 2) / h bw_norm = bw / w bh_norm = bh / h class_idx = cat_id2idx[ann['category_id']] with open(txt_path, 'a') as f: f.write(f"{class_idx} {cx:.6f} {cy:.6f} {bw_norm:.6f} {bh_norm:.6f}\n")

这段脚本的核心在坐标系换算。COCO的bbox存的是左上角坐标和宽高,YOLO需要的是归一化的中心点坐标。cx和cy的计算必须加括号——x + bw / 2如果漏了括号,中心点会偏移半个框的位置,训练时模型看到的标签全是歪的,Loss曲线显示收敛但实际效果一塌糊涂。另外注意,多次运行这段脚本前要清空旧的labels目录,open(txt_path, 'a')是追加模式,重复跑会叠加出多余行。

2.4 数据集划分与验证集采样

水下数据集的划分比普通视觉项目更讲究。同一个池子不同时间段的画面相关性很高,如果随机切分,训练集和验证集里混着同一批鱼在不同帧的影像,验证集的指标会虚高到失真。正确做法是按视频片段划分:每一段视频抽出的帧整体属于同一个集合,保证验证集里出现的鱼是训练阶段完全没见过的个体。

数据量上,单类别(例如「鱼」)检测10-20个池子的素材,3000-5000张标注图基本够用;如果按品种分多类别,每类至少保证800-1000张。类别不平衡在水下场景常见——某个池子主养鲈鱼,标注了4000张,另一个池子的鲫鱼只有200张,模型会把鲫鱼误检成鲈鱼。解决方式是用datasets目录里的train: val:比例划分,加上mosaic=1.0的数据增强,让模型多看混合样本。

3. YOLOv11网络结构与训练参数:小目标优化的关键配置

3.1 YOLOv11相比v8改了哪些东西

YOLOv11在骨干网络里用C3k2模块替换了v8的C2f,同时加入了C2PSA注意力模块。C3k2的核心是把梯度流分成两路,一路走标准卷积提取细节特征,另一路走捷径保留原始信息,在参数量增加不明显的前提下,让特征金字塔每一层的信息更完整。C2PSA则是在空间注意力机制上做了轻量化改造——水下场景里鱼体边缘模糊,注意力机制能引导模型把权重放在鱼头、鱼尾这些辨识度高的区域。

对鱼类检测来说,涨点真正明显的是检测头部分。v11的检测头在训练阶段默认启用DFL(Distribution Focal Loss)的改进版本,边界框回归更加保守,对遮挡严重的鱼群会产生更紧致的预测框。直观感受是:同样一批重叠鱼群的图片,v8的预测框会互相套叠,v11的框更贴合单条鱼的可见区域,这对后续密度统计的精度有帮助。改进的另一个体现是Anchor-Free策略的稳定化——v11沿用了v8的解耦头结构,但对正负样本分配做了调整,小目标的正样本匹配阈值放宽,让只有几十个像素的小鱼也能参与训练。

3.2 训练前的环境配置与权重选择

环境配置的坑主要在ultralytics包版本上。pip install ultralytics默认装最新版,但YOLOv11在2024年末之后的小版本迭代里,对旧显卡的CUDA兼容性做了一些调整,实测RTX 3090在CUDA 11.8下跑v11.0-v11.2没问题,新版本如果报AssertionError: cuda available,优先检查torch版本而不是显卡驱动。推荐组合:

# CUDA 11.8 + torch 2.4.0 + ultralytics 8.3.x 的组合稳定 pip install torch==2.4.0 torchvision==0.19.0 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics==8.3.30

权重选择上,yolov11n.pt是nano版本,模型文件约5MB,适合Jetson Nano这类边缘设备,但精度有限;yolov11s.pt是small版本,精度和速度的平衡点最好,训练时间也友好。做水产养殖项目的常见起点是yolov11s.pt预训练权重,在COCO上收敛过的特征对水下目标迁移有帮助——毕竟鱼的纹理和背景纹理的区分能力,是从陆地目标上学到的边缘特征迁移过来的。

3.3 训练命令与关键参数详解

训练命令的写法:

yolo detect train \ model=yolov11s.pt \ data=fish.yaml \ epochs=150 \ imgsz=640 \ batch=16 \ patience=30 \ optimizer=SGD \ lr0=0.01 \ mosaic=1.0 \ close_mosaic=10 \ mixup=0.2 \ val=True

fish.yaml的内容包括path指向数据集根目录、train和val的路径、nc填写类别数、names列出类别名。训练参数里close_mosaic=10是容易被忽略的——它表示最后10个epoch关闭mosaic增强,mosaic在训练后期会导致小目标被拼接缝隙切割,模型学不到完整的鱼体语义,关闭后让模型微调收敛,最终mAP能提升1-2个点。mixup=0.2让两张图混合训练,增强模型对半透明鱼体的适应力,水下场景里鱼鳍和鱼尾的透明度高,mixup模拟了这种半遮挡状态。

3.4 小目标优化:切片推理和anchor参数调整

水下鱼群图片里大量目标是小于32x32像素的,这类目标在YOLOv11的P3特征层(stride 8)上只有4x4个格子,特征是稀疏的。提升小目标召回率,常规手段有两条路。

第一条是训练阶段的anchor参数——YOLOv11虽然用的Anchor-Free,但数据预处理的锚框分配策略会影响小目标的匹配。在yaml文件里调整anchors参数:默认设置对中大型目标友好,小目标多的数据集建议改成anchors: [5,5, 10,10, 15,15, 25,25, 45,45]这类尺度更密集的锚框组合,让浅层特征层承担更多小目标匹配任务。

第二条是推理阶段的SAHI切片策略。SAHI把大图切分成若干带重叠的小块,每块单独推理,再合并结果去除重叠框。鱼类检测场景里整池画面分辨率往往在1920x1080以上,直接缩放成640x640输入,小目标会缩到几个像素;SAHI保持原分辨率切片推理,小目标在切片里依然有足够像素。用SAHI配合YOLOv11的推理命令:

from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction # 加载训练好的v11模型权重 detection_model = AutoDetectionModel.from_pretrained( model_type='ultralytics', model_path='runs/detect/train/weights/best.pt', confidence_threshold=0.25, image_size=640, ) # 切片尺寸和重叠率:slice_size=512保证小目标像素充足 result = get_sliced_prediction( 'pond_01.jpg', detection_model, slice_size=512, overlap_height_ratio=0.2, overlap_width_ratio=0.2, postprocess_type='NMS', postprocess_match_threshold=0.5, )

slice_size=512是常见配置里兼顾速度和效果的选择,256更适合极小目标但对算力消耗翻几倍;overlap_*_ratio=0.2的意义是防止目标恰好落在切片边界被截断。这里的postprocess_match_threshold是NMS的IoU阈值,0.5对密集鱼群是安全值——设高了会把同一目标在相邻切片的重复框都保留,设低了会把相邻两条鱼的框合并成一个。

4. 鱼类行为识别与密度统计:从检测框到业务指标的转换

4.1 行为类别定义与模型输出设计

鱼类行为识别不能靠一套通用动作分类解决,得跟养殖业务指标挂钩。常规做法是定义四类行为:正常游动(swimming)、抢食(feeding)、浮头缺氧(gasping)、离群停滞(stagnant)。每类行为对应不同的投喂和增氧决策——抢食意味着投喂机可以继续,浮头必须立即开启增氧机。

模型输出设计上,我推荐检测头只负责「鱼」这个类别,行为分类另接一个轻量分类分支,而不是把所有行为都塞进同一个检测器的类别列表里。原因有两个:同一帧画面里不同区域可能有不同行为,比如池子左侧在抢食、右侧在浮头,一个目标一个类别标不现实;行为类别的标注成本远高于目标类别,数据量短期内不足以支撑多类别检测。架构上,主模型负责输出每个鱼目标的位置框,行为分类模型对每个位置框内的图像块做四分类,属于「检测+ROI分类」的组合路线。

4.2 密度统计的两种方法:直接计数与热力图回归

密度统计有两种主流路线。

直接的路线是把检测框的数量求和,但这种方法在鱼群高度重叠时严重偏低——一个框中可能挤着3-5条鱼。我的经验是,当单帧中重叠率超过30%时,直接计数误差会超过15%。改进方式是给每个检测框加一个置信度加权:高置信度的框计1.0,低置信度的框计0.5-0.7,这个系数要在实际池子里校正——对同一个池子拍50帧,人工数出真实数量,再回归出置信度和计数值的关系。

更稳健的路线是密度图回归(密度统计的本质是估计鱼群分布的连续函数,而不是输出离散整数)。把鱼头的位置做成高斯核渲染的密度图,用深度学习回归密度图,再对密度图积分得到总数。高斯核的带宽sigma是超参数,鱼体长50像素时sigma取8-12像素效果较好,sigma太大会让密度图过于平滑、相邻鱼的高斯峰融合,积分总数偏低。YOLOv11先检测出可靠的目标框,用这些框的位置生成密度图的种子点,再用一个轻量的UNet回归细化,比纯端到端密度回归收敛更快。

4.3 密度统计在养殖池里的实用部署方案

实际项目里我不推荐在单帧上做密度统计,而是做时间窗口的滑动平均。水面波动、鱼群瞬间聚散会让单帧计数抖动30%以上。按秒级采样,统计过去10秒的平均密度,抖动降到一个稳定的区间。具体流程:

  • 每秒抽2帧进行检测和计数
  • 维护一个10帧的环形缓冲区,输出缓冲区平均值
  • 密度超过预设阈值时触发告警,阈值按池子面积和规格标定

这个方案对算力的要求低,嵌入式设备也能跑。YOLOv11的检测频率在Jetson Nano上约8-10 FPS(TensorRT优化后),2 FPS的采样不会成为瓶颈。

4.4 跟踪与多目标ID分配的辅助作用

如果要输出「某条鱼持续20秒浮头」这样的事件级行为判断,需要目标跟踪来保持ID稳定性。YOLOv11配合ByteTrack是现阶段比较顺手的组合。ByteTrack的思路是同时用高置信度和低置信度的检测框参与关联:先关联高置信度框,再用低置信度框填补被遮挡目标的位置,这在鱼群互相遮挡时尤其有效。

from ultralytics import YOLO from boxmot import BYTETracker model = YOLO('runs/detect/train/weights/best.pt') tracker = BYTETracker() cap = cv2.VideoCapture('pond_video.mp4') while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model(frame, conf=0.3, iou=0.5)[0] # 提取检测框和置信度,交给ByteTrack关联ID dets = results.boxes.data.cpu().numpy() # [x1, y1, x2, y2, conf, cls] tracks = tracker.update(dets) for track in tracks: track_id, x1, y1, x2, y2, conf = int(track[0]), *track[1:6] # 按track_id统计每一条鱼的停留时间 cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, f'ID:{track_id}', (int(x1), int(y1)-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow('tracked', frame)

conf=0.3在跟踪场景里比默认的0.25略高,可以减少误检干扰ID关联;iou=0.5控制NMS的框合并强度,鱼群密集时可以放到0.4,合并得更激进一些,减少同一目标输出多个框导致ID分裂的情况。boxmot包的BYTETracker需要输入[x1,y1,x2,y2,conf,cls]格式的检测结果,输出的每个track除了ID还包括卡尔曼滤波预测后的框坐标。

5. 训练与部署避坑:水下场景里常见的六个翻车现场

5.1 水面反光和倒影让模型把影子当鱼

现象:模型在测试集上mAP有0.85,拿到养殖池现场一跑,水面上漂浮的树叶、池壁的反光区域全被框出来。

原因:训练数据里没有加入包含水面反光的负样本。水下相机装在池边时,水面波光粼粼的区域在模型眼里和鱼体的高反光区域纹理相似。

解决:在训练集中加入500张以上完全没有鱼、只有水面波动和反光的背景图,标注文件为空txt。让模型学到「有反光不等于有鱼」。同时推理时开启augment=True,模型会对输入做水平翻转和色彩抖动,对反光的敏感度会降低。

5.2 鱼群密度太高时,个别鱼的框被NMS压掉

现象:投喂时段鱼群聚拢,单帧画面有几十条鱼重叠交叉,输出的框数明显比实际鱼数少。

原因:NMS的IoU阈值默认0.5,两条鱼的重叠面积超过50%时,置信度低的框直接被抑制。

解决:推理时把IoU阈值调低到0.3-0.4,保留更多重叠框;如果还是不够,切换到SAHI切片推理,把高密度区域放大后再检,重叠鱼在切片里会被拆开。

5.3 夜间红外补光下模型掉点明显

现象:白天训练的模型在夜间红外画面下漏检率超过40%。

原因:红外图像是单通道灰度映射成的伪彩色,分布和白天的RGB图像差异巨大,模型在骨干网络第一层卷积上没学到红外的纹理模式。

解决:训练数据里加入30%比例的夜间红外帧,mixup增强把白天和夜间帧混合,让模型对光照变化鲁棒。如果夜间场景占比高,单独用红外数据finetune一个专用权重更稳妥。

5.4 标签框太小导致训练不收敛

现象:Loss曲线在第50个epoch后仍然震荡,验证集的recall一直上不去。

原因:大量鱼目标只有十几个像素,标注框尺寸小于默认锚框尺度,正样本匹配不上。

解决:在模型配置里调整anchors为更小的尺度,同时把imgsz从640调到960——图像分辨率增大后,小目标在特征图上的响应区域扩大,匹配难度下降。代价是训练显存增加约40%,batch需要相应调小。

5.5 Jetson部署时TensorRT推理报错

现象:yolo export转TensorRT成功,在PC上推理正常,部署到Jetson Nano上跑到engine加载阶段报显存不足或维度不匹配。

原因:Jetson的GPU架构和PC不同,TensorRT的engine文件是硬件绑定的,在PC上生成的engine直接拷贝到Jetson用不了。

解决:在Jetson上重新执行导出流程,用device=0指定在Jetson上生成engine。Jetson的算力有限,输入分辨率建议降到416x416,输出精度略有损失但在可接受范围内。部署脚本固定用yolo predict model=fish.engine source=...把导出和推理分开两步,避免运行时反复转engine。

5.6 密度统计值在生产环境漂移

现象:第一周密度统计误差±5%,一个月后误差扩大到±20%。

原因:水质变化导致图像浑浊度上升,相机镜头被藻类附着,输入分布偏移后模型检测率下滑。

解决:把部署做成带反馈的闭环,定期抽帧人工复核,误检率高时用最近数据增量训练。镜头位置固定后,可以加一个水质浊度传感器,浊度超标时先做图像去雾预处理再进模型——比重新训练划算得多。

6. Jetson Nano上的实际部署:TensorRT导出与帧率调优

边缘端部署首选NVIDIA Jetson系列,功耗低且natively支持TensorRT。Jetson Nano在鱼池边可以做到8-10 FPS的推理速度,配合2 FPS的采样频率绰绰有余。部署链路是:PyTorch权重 → ONNX → TensorRT engine → DeepStream管道接入。

# 在Jetson Nano上训练好的权重导出为TensorRT yolo export model=runs/detect/train/weights/best.pt format=engine device=0 # 导出时为FP16精度,推理速度约提升1.5倍 # 精度损失在鱼类检测上表现不明显,但输出框的置信度会比FP32低0.03-0.05

导出时要注意Jetson的显存只有4GB,imgsz=640的engine导出是上限,超过会报显存溢出。我的经验是明确部署目标是「数数」和「看行为」而非高精度检测时,输入分辨率降到416能跑到15FPS,此时5像素以下的小鱼会漏,但用SAHI在池子重点区域切块补偿,整体密度统计精度能保住。

推理脚本在Jetson上的写法和PC有差异。开启CUDA内存池复用、关闭PIL的线程池,推理延迟能再降10%。部署时顺便开启fp16=True做TensorRT的FP16推理,注意不要在CPU上做预处理缩放——用Jetson的GPU做CUDA缩放,能省下每帧的延时。视频流接RTSP时设置GST_DEBUG=3查看pipeline状态,拉流失败优先查摄像头IP和编码格式,H.265的流在Jetson上硬解比H.264吃显存,4GB版本建议强制转码H.264。

部署时还有一个尺寸取舍:鱼密度统计的精度和输入分辨率强相关。一个1920x1080的池子画面,直接缩到640推理和切成512的三块分别推理,后者能数出多20%的鱼,但推理耗时也变成三倍。折中做法是平时用640全局推理做实时监控,只在密度阈值触发时切高分辨率精确复核——日常和告警两档模式切换,算力和精度都能兼顾。

这套方案做了三个养殖周期以后,我最大的教训是别把模型当一次性交付,池塘水质、光照、鱼体大小都在变化,定期抽帧复盘、把新场景增量训练数据喂回去是必须的运维流程。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询