ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

YOLOv11鱼类行为识别与密度统计实战:从数据采集到落地避坑指南

YOLOv11鱼类行为识别与密度统计实战:从数据采集到落地避坑指南 简介PDF文档《智慧养殖新突破YOLOv11实现鱼类行为识别与密度统计》面向水产养殖从业者、算法学习者与智慧农业研究者聚焦YOLOv11在鱼群行为识别与密度统计中的实际落地。文档共30页从智慧养殖背景与行业痛点切入系统讲解YOLO系列算法演进、YOLOv11网络结构及创新点并完整给出数据采集、预处理、模型训练、行为识别与密度统计的实现流程包含Python与OpenCV示例代码。全文条理清晰支持目录章节跳转与阅读器大纲定位便于按需查阅。资源为单个PDF文件大小1.95MB已有85人学习。读者可从中获取一套可借鉴的鱼类检测方案包括数据集构建思路、模型配置与调优方法、评估指标选择以及养殖监控、疾病预警等应用场景分析有助于快速掌握YOLOv11目标检测技术并迁移至同类场景。1. 智慧养殖的 YOLOv11 落地鱼类行为识别与密度统计到底在解决什么深夜的水产养殖监控室里值班人员同时盯着十几个池塘的画面鱼群到底是在正常游动还是出现了应激反应单靠肉眼其实很容易错过。YOLOv11做鱼类行为识别与密度统计就是把这件事拆成两步先用目标检测框出水里的鱼再把框按行为类别分开最后落在每帧的鱼口数和聚集程度。对投喂机联动、异常浮头预警、出塘数量估算都有直接价值。这套方案适合两类人一类是水产养殖企业的技术负责人想用低成本摄像头替代人工巡塘另一类是计算机视觉工程师接了渔业场景的项目想知道水下小目标检测和通用目标检测哪里不一样。本文不会给你一张跑满分的海报图而是把从数据采集、标注、训练到密度计数落地的每一步说清楚包括我踩过的一些坑。2. 为什么鱼类检测选 YOLOv11水下小目标、网络结构与选型边界2.1 水产场景对检测模型提出的三个真实约束水下鱼群检测和通用目标检测不一样。通用检测里人、车、猫、狗通常占画面足够大的面积而池塘里的鱼在 720P 画面上经常只有十几到几十个像素加上水体浑浊、水面反光、气泡画面噪声比行人检测高一个量级。如果直接把 YOLOv11 默认的 640 输入分辨率搬过来常见结果是小鱼漏检然后把气泡和饵料颗粒误报成鱼。养殖场景的第二个约束是密集遮挡。鱼群聚堆摄食时中后排的鱼几乎完全被遮挡人的肉眼都很难数清检测框也会重叠。第三个约束是并发路数。一个养殖基地动辄几路摄像头同时推流模型算力消耗大不能为了精度把推理速度拖垮。YOLOv11 的检测头用了 anchor-free 结构和 YOLOv8 同属一条技术路线。它不依赖预设锚框而是直接预测目标中心点和宽高对形状不规则、相互遮挡的鱼群比较友好。网络结构把关 C2f 改进后的模块堆叠成不同深度版本从 n 到 x 按宽度和深度逐步变大。对我而言选 YOLOv11 不是因为它在某个榜单上排名最前而是因为它有一个足够稳的训练链路、CLI 命令行、以及现成的推理保存机制能让我把精力放在数据清洗和密度统计上而不是从头写检测头。2.2 从 0 到 1 跑通 YOLOv11环境配置、权重下载与最小推理命令先解决环境问题很多人第一步就卡在依赖安装。常见做法是用 conda 建一个独立环境Python 用 3.10 比较稳然后安装 ultralytics 包。# 创建虚拟环境Python 3.10 实测比较稳 conda create -n yolo11 python3.10 -y conda activate yolo11 # 安装 ultralytics会自动带上 torch 的 CPU/GPU 版本依赖 pip install ultralytics # 跑一次最小推理没有测试图就用本地任意一张照片 yolo predict modelyolo11n.pt sourcetest.jpg imgsz640这段命令里yolo predict是 ultralytics 提供的命令行入口。modelyolo11n.pt指定权重文件第一次运行会自动下载到当前目录如果自动下载慢可以手动把 yolo11n.pt 放到脚本目录再指定路径。source可以指图片、视频或目录imgsz640是输入分辨率。跑完后结果保存在runs/detect/predict图片上会画出检测框和置信度。用 Python 调用也一样from ultralytics import YOLO model YOLO(yolo11n.pt) results model.predict(test.jpg, saveTrue)这里的saveTrue会保存推理结果图适合新手确认模型能不能正常出框。如果连这一步都跑不通大概率是 CUDA 版本和 PyTorch 不匹配先退回 CPU 推理跑通流程再解决 GPU 加速问题。2.3 模型规格怎么选yolo11n/m/x 与注意力增强的边界对于鱼类小目标我的选型习惯是先在同一个数据集上跑一遍 yolo11n 和 yolo11m观察漏检率而不是一开始就上最大模型。模型体量适用场景yolo11n最轻边缘设备、多路实时预览、初筛yolo11m中量级单路/双路离线分析、鱼体稍大的监控画面yolo11x最重离线分析、科研精度验证、小目标极多的场景如果已经换了更大的模型漏检还是压不下去再考虑在 YOLOv11 的 backbone 后面插入通道注意力模块类似 HCANet 在人群计数里用注意力强化特征的做法。HCANet 的核心思路是让网络更关注高密度区域的特征这个思路可以迁移到鱼群密度估计。但我的经验是养殖项目不要一上来就魔改网络结构先把输入分辨率、数据质量、置信度阈值这三件事做好效果通常比换结构明显得多。3. 从视频到训练集鱼类行为识别的数据集构建、抽帧标注与小目标增强3.1 先定采集标准再谈算法精度很多项目翻车不是因为算法不行而是因为摄像头装得太随意。采集阶段需要固定的机位和角度最好让鱼群在画面中的大小保持相对一致。如果摄像头装在岸边鱼群忽远忽近同一个模型很难在各种尺度下都稳住。我一般要求每个场景至少采集两天视频覆盖清晨、中午、傍晚、夜间补光、投喂前后、不同水质条件下各一段。夜间红外画面对鱼类识别的影响很大采集时一定要单独留出一部分数据防止训练集白天太多、夜间推理崩盘。另外要记录真实密度标尺在画面覆盖的水域里用人工方式估算一个大概的鱼口数作为基准。后续做密度统计时这个标尺是校验模型输出的依据没有它模型给出的密度就是一个不知道误差多大的黑匣子数字。3.2 用 OpenCV 把两天监控视频变成干净训练帧视频抽帧不能逐帧存否则连续几十帧几乎一样训练集冗余严重。最省事的做法是每秒抽 1 帧再用人工浏览把模糊帧、水花飞溅帧、鱼群完全离开画面的空帧去掉。import cv2 import os video_path pond_camera_01.mp4 out_dir frames_01 os.makedirs(out_dir, exist_okTrue) cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) frame_interval max(1, int(fps // 1)) # 每秒抽 1 帧 idx 0 saved 0 while True: ret, frame cap.read() if not ret: break if idx % frame_interval 0 and frame is not None: # 这里可以加一个简单清晰度过滤拉普拉斯方差过低就丢弃 save_path os.path.join(out_dir, f{saved:06d}.jpg) cv2.imwrite(save_path, frame) saved 1 idx 1 cap.release()这段代码逻辑很简单frame_interval按视频帧率计算保证每秒只落一帧避免大量相似帧挤在一起保存时按序号命名方便后续和标注文件对齐。如果画面里有大量空帧可以再补一步用cv2.Laplacian(frame, cv2.CV_64F).var()做清晰度过滤方差小于某个阈值的帧直接丢弃。对养殖这种长期固定摄像头的场景这个过滤比想象中更能减少标注工时。3.3 行为类别怎么定义少而精才是关键鱼类行为识别并不是类别越多越好。常见错误是把“摄食”“游动”“静止”“蹭缸”“浮头”全定义一遍结果标注员自己都分不清边界训练出来的模型互相混淆严重。我建议第一版只保留三类类别 ID名称行为定义标注建议0fish_swim正常游动无明显异常大部分帧1fish_feed头部朝下、聚集在投饵区抢食、嘴部动作明显投喂时段2fish_surface靠近水面、浮头、鳃盖翻动慢异常时段单独采样标注工具可以用 X-AnyLabeling 或 LabelImg最终导出为 YOLO 格式的 txt 文件。每个 txt 文件和图片同名每一行是class_id x_center y_center width height。YOLOv11 训练时读的就是这个格式。行为识别后续要结合时间上下文所以标注时最好对连续帧做序列标注而不是随机抽帧标注。只做随机帧会导致行为连续性信息丢失。为了给密度统计做准备标注时所有鱼都要框尽量别漏。密度统计本质上是检测数量统计漏标一个框模型就少学一个目标。高密度画面里如果鱼叠得太厉害可以只标画面里清晰可见的前排鱼但训练集所有图片的标注风格必须统一。3.4 数据增强与 data.yamlyolov11 小目标优化最先改这里在训练前先把数据配置文件写好并打开适合小目标的增强项。# fish.yaml path: /dataset/fish train: images/train val: images/val nc: 3 names: 0: fish_swim 1: fish_feed 2: fish_surfacepath是数据集根目录train和val是训练集、验证集的图片目录相对路径。nc是类别数量names必须按类别 ID 顺序写不能乱。增强参数在训练命令里传。我在小目标项目里常用的几项是yolo detect train \ datafish.yaml \ modelyolo11m.pt \ epochs300 \ imgsz1280 \ batch8 \ mosaic1.0 \ fliplr0.5 \ scale0.5 \ translate0.1 \ hsv_h0.015 \ hsv_s0.7 \ hsv_v0.4mosaic把四张图拼成一张能让模型在小目标多、背景复杂的情况下学到更鲁棒的特征但注意不要开满尤其当鱼太小、拼图后目标被割裂时mosaic0.5更稳。scale0.5允许目标缩放对鱼在不同水深出现的尺度变化有帮助。hsv增强模拟不同水质下的颜色偏移对浑水场景很重要。imgsz1280是这次优化最关键的一项等于把输入分辨率翻倍小目标对应的像素占比也翻倍。4. 用 YOLOv11 训练自己的鱼类模型并跑通密度统计参数、命令与两种计数法4.1 训练启动yolo11m 与 imgsz1280 的组合拳训练命令并不复杂复杂的是对齐数据路径和显存预算。下面是一份我常用的启动命令适合单张 24G 显存左右的显卡yolo detect train \ datafish.yaml \ modelyolo11m.pt \ epochs300 \ imgsz1280 \ batch8 \ device0 \ patience50 \ save_period10 \ projectruns/fish \ nameexp1modelyolo11m.pt指定预训练权重第一次运行会自动下载权重文件如果你的服务器不方便联网先在本地机器下载好再传到服务器然后用绝对路径指向权重文件即可。patience50表示 50 个 epoch 验证指标不涨就早停省时间。save_period10每 10 个 epoch 存一次权重用来做后悔药后面发现训练中断还能恢复。这里特别说一下batch8。显存不够是最常见的翻车点很多人把 batch 降到 2训练震荡剧烈。遇到这种情况我会先保持 batch8把imgsz降到 960 或 640而不是继续降 batch。因为小目标模型对 batch 的敏感度比大目标更高batch 太小损失函数曲线会像心电图一样跳动。4.2 关键训练参数imgsz、lr0、patience 怎么调参数建议范围说明imgsz640~1280小目标优先 1280显存不够先降这里batch8~16不要低于 4除非在跑 yolo11xlr00.005~0.01鱼群背景噪声大lr0 取中低段patience50~100早停耐心值根据显存和时间来optimizerAdamW 或 SGD水花噪声多AdamW 更容易收敛训练时还有一个容易忽略的参数是workers。服务器 CPU 核多workers8能快不少但如果数据在机械硬盘上读图会成为瓶颈反而拖慢训练。我一般把数据放到固态盘上再开高 workers否则就调成 4。4.3 推理与结果保存别只把结果 print 出来训练完成后runs/fish/exp1/weights/best.pt就是最优权重。推理时除了画框还要把每帧的检测结果落盘否则后面做密度统计就要重新跑一遍模型。from ultralytics import YOLO model YOLO(runs/fish/exp1/weights/best.pt) results model.predict( sourcefish_test.mp4, conf0.35, iou0.45, imgsz1280, saveTrue, # 保存画框后的视频/图片 save_txtTrue, # 保存每帧检测 txt save_confTrue, # txt 里附带置信度 projectruns/infer, nametest1 )conf0.35是置信度阈值养殖场景默认 0.35 左右比较稳太低会涌出大量气泡误报太高会漏掉模糊小鱼。iou0.45是 NMS 的 IoU 阈值鱼群密集时我会降到 0.4减少相邻检测框被合并。save_txtTrue会在runs/infer/test1/labels下生成每一帧的 txt格式是class_id x_center y_center width height conf。接下来从results里把坐标和类别取出来密度统计全靠这些数据import numpy as np for r in results: boxes r.boxes.xyxy.cpu().numpy() # [N,4] 左上角和右下角坐标 cls r.boxes.cls.cpu().numpy().astype(int) # 类别 ID conf r.boxes.conf.cpu().numpy() # 置信度 # 坐标单位是像素结合画面实际面积做后续密度归一化xyxy是像素坐标后面做密度网格时直接使用即可。注意一点类别 ID 顺序必须和data.yaml的names对齐否则行为识别结果会对不上。4.4 从检测框到密度统计两种可落地的计数方法第一种方法最简单直接统计每帧的检测框数量作为鱼群密度近似值。如果鱼不重叠、画面清晰这种方式足够用。第二种方法适合高密度鱼群先取每个检测框的中心点把中心点投到固定网格再用高斯滤波得到密度热力图这样能看出鱼群是否在局部聚集而不是只给一个总数。from scipy.ndimage import gaussian_filter def count_and_density(boxes, img_w1920, img_h1080, grid16, sigma2): # 输入 boxes: [N,4] 的 xyxy 像素坐标 count len(boxes) density np.zeros((grid, grid)) if count 0: return count, density cx (boxes[:, 0] boxes[:, 2]) / 2 / img_w * grid cy (boxes[:, 1] boxes[:, 3]) / 2 / img_h * grid np.add.at(density, (cy.astype(int), cx.astype(int)), 1) density gaussian_filter(density, sigmasigma) return count, density这段代码把每个检测框的中心点映射到grid × grid的网格np.add.at用来累加每个网格里的目标数最后用高斯滤波把稀疏的计数锤平形成热力图。sigma2控制平滑半径鱼群密集时调到 3 更容易看出聚集中心鱼体分散时保持 1~2避免把两个鱼群糊在一起。如果鱼群密度特别高、检测框已经互相重叠这时密度热力图比单纯计数更稳定。你甚至可以在这个基础上复现 HCANet 的思路把网格密度作为监督信号用一个轻量回归头去预测热力图。但对工程落地来说先做检测后做密度的两段式方案最可靠出了问题也好定位。5. 鱼类行为识别避坑清单漏检、计数抖动与行为分类翻车的 5 个现场问题5.1 训练集 AP 高、验证集 AP 低模型学会了水花和反光现象训练集 loss 降得漂亮验证集 mAP 却一直不高调参也不涨。打开验证集预测图发现大量框打在气泡和反光上。原因养殖池里气泡、波浪、饵料颗粒的颜色和鱼很接近而训练过程和推理过程都被这些高频背景干扰。模型学到的是“这个局部纹理像鱼”而不是“鱼有轮廓有运动”。解决清洗阶段不要只保留鱼清晰的帧要专门留出一批“全是水花和气泡但几乎没有鱼”的画面。训练时把这些画面增加进去同时打开hsv增强让颜色抖动更强。如果误报集中在某个时段把它单独分到验证集里观察模型能不能压住。这条做完比换任何模型结构都管用。5.2 高密度鱼群漏检严重先调 imgsz再动 NMS现象鱼群聚堆时模型只框出前排十几条鱼后排被遮挡的鱼全部漏掉每帧计数和人工目测差一半以上。原因一是输入分辨率不够后排鱼只有不到十个像素二是 NMS 在相邻检测框重叠度太高时把置信度低的框直接删掉了。解决先把imgsz提高到 1280这一步通常能明显改善中后排小目标然后把iou0.4减少 NMS 合并。如果还漏再换yolo11x或增大mosaic参与概率。注意调完要重新跑验证集不能只看训练集 loss。高密度场景的漏检往往在验证集上集中呈现不像普通场景那样平均分布。5.3 密度统计抖得像心电图单帧计数噪声怎么压现象同一段视频相邻两帧的计数分别是 102 和 78下一帧又蹦回 95。这种抖动直接让密度统计失去意义。原因单帧检测置信度受水波、光线、鱼转身的影响很大。鱼时隐时现导致检测框数量变化。直接使用每一帧的计数等于把所有随机噪声都保留了下来。解决给计数结果加一个滑动窗口中位数滤波。常规地窗口长度取 15~25 帧中位数比均值更能抵抗突变帧的干扰。from collections import deque count_buf deque(maxlen15) def smooth_count(new_count): count_buf.append(new_count) return int(np.median(count_buf))这个函数的逻辑是维护一个定长队列每来一个新计数就把它加进去然后取中位数作为输出。窗口越长曲线越平滑但滞后也越明显。对于投喂预警这种场景15 帧窗口是比较好的折中。5.4 行为类别张冠李戴单帧分类做不了“摄食”判决现象鱼从饵料区旁边游过就被标成摄食静止在水底的鱼被标成浮头行为识别的准确率远低于检测准确率。原因摄食、浮头这些行为在单帧图像上缺乏足够上下文。单帧只看到鱼的位置和形态无法判断它是正在抢食还是路过。解决把单帧检测升级为“检测 轨迹 行为判决”。先用跟踪赋予每条鱼一个轨迹 ID再基于连续 10~20 帧的速度、位置偏移和在投饵区停留的时长来判决行为。例如摄食行为需要同时满足三个条件在投饵区内停留超过 3 秒、轨迹点密集、嘴巴区域朝向饵料。单帧模型只输出粗分类最终行为标签放在后处理阶段做。5.5 推理掉帧严重问题不一定在模型在解码和预处理现象视频流实时推理时显卡利用率不满但画面掉帧CPU 却高得离谱。原因摄像头 RTSP 拉流和解码默认走 CPU解码跟不上每秒 25 帧的推流后面图像处理全被卡住。解决先用cv2.VideoCapture确认解帧耗时如果超过 30ms就需要硬解码或者降低输入帧率。另一个办法是跳帧推理每 2 帧取 1 帧做检测然后对检测结果做时间插值。模型方面如果还在用yolo11x imgsz1280先换成yolo11m再把输入降到 960通常就能满足现场需求。记住密集养殖项目的瓶颈经常在 I/O不在模型参数量。6. 验证与进阶技巧用轨迹去重和时间滑窗把密度统计误差压到 5% 以内先建立一把“标尺”。随便找一段 5 分钟视频人工选取 10 到 15 帧数出画面里清晰可见的鱼口数记录范围。然后用模型跑同一段视频把逐帧计数经过中位数滑窗后的值和人工标尺对比。如果偏差稳定在一个固定比例比如模型输出总是人工的 70%那大概率是漏检造成的系统性偏差而不是随机噪声。这时不要急着调 conf先把漏检原因找出来如果只是轻微漏检可以用校正系数把密度输出乘以 1.4 来对齐标尺。这个系数只在同一个场地、同一个机位下有效换摄像机位置必须重新标定。进阶做法是把单帧计数换成独立轨迹数。YOLOv11 在 ultralytics 里可以直接调用model.track用内置跟踪器给每个检测框分配一个跨帧 ID。有了 ID就不用在每帧重复计数而是统计当前画面里共出现了多少条独立轨迹这样鱼游过遮挡区时不会被重复计数。from ultralytics import YOLO model YOLO(best.pt) results model.track( sourcefish_test.mp4, persistTrue, conf0.35, imgsz1280, saveTrue ) for r in results: if r.boxes is not None and r.boxes.id is not None: ids r.boxes.id.cpu().numpy() # 当前帧独立轨迹数len(set(ids))对结果再做一次中位数滑窗persistTrue让跟踪器跨帧保持同一个 ID不会每帧重新编号r.boxes.id就是跟踪 ID。把每帧len(set(ids))输出再套用上一章的中位数滤波得到的密度曲线会比单帧计数平滑得多。我现在的习惯是每次更换养殖场地先用标尺视频验证一次再做参数调整。养殖场景最怕的不是平均精度而是高密度那几秒的漏检看板上一旦出现密度骤降系统就可能在乱叫。把这些技巧沉淀下来你的方案也能从“检测结果”变成“养殖现场能用的决策依据”。希望帮到你。本文还有配套的精品资源点击获取
返回列表