尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于YOLOv8的鱼类目标检测与计数系统MiroFish实战解析

基于YOLOv8的鱼类目标检测与计数系统MiroFish实战解析 把镜头架在鱼塘边盯着屏幕里游动的鱼群我看着自己写的这套叫MiroFish的小系统突然想起它名字的由来——Miro取自Mirror水面是一面镜子Fish就是鱼。MiroFish就是“透过水面这面镜子把鱼看清楚”。从最初只想解决“池塘里到底有多少鱼”这个简单问题到后来做成一整套覆盖采集、标注、训练、推理、部署的视觉识别流程这个项目带给我最多的不是模型涨了多少个点而是让我明白了“把一件事做扎实”比“用多新的模型”重要得多。这篇博客就围绕MiroFish的完整实现来写它是什么、能解决什么问题、适合谁来参考。如果你正在做水产养殖相关的视觉识别或者打算用目标检测解决一个真实场景里的问题这篇文章应该能帮你少踩不少坑。1. 整体设计与思路拆解1.1 项目定位为什么要盯着一池鱼最早想做这个系统是因为一个做养殖的朋友跟我吐槽鱼塘里有多少鱼基本靠估。拉网清点动静太大称重抽样误差不小到了投喂环节更是两眼一抹黑——投少了鱼不长膘投多了残饵污染水质饲料成本还白白流失。他问我能不能搞个摄像头自动数一数鱼。这听起来像是个“数数”的问题实际上拆开之后是三个问题第一目标检测画面里哪些区域有鱼得把鱼从复杂背景里找出来第二分类识别出现的是哪种鱼不同品种的体型、颜色、游动方式差异很大第三数量与行为统计单位面积里鱼的数量是多少鱼群活跃度怎样采食行为是否正常。人工做这些工作眼睛盯一会儿就花更不用说十二个塘口轮着看。MiroFish的定位就是一套自动化的“水下眼睛”——通过普通摄像头采集画面用深度学习模型实时检测、识别、计数再把结果汇总成直观数据让管理者打开手机就知道塘里什么情况。1.2 技术选型为什么用目标检测而不是传统图像处理我最早试过传统图像处理方案背景差分、颜色阈值分割、轮廓查找听起来很合理鱼和水的颜色差异确实大。但在真实鱼塘里这套方案脆弱得不堪一击——水面反光、水色发浑、漂浮物、螺旋桨搅起的水花任何一个干扰都能让轮廓提取瞬间爆炸。晴天和阴天同样一个塘分割效果天差地别。所以我把方案定为深度学习的单阶段目标检测具体用的是YOLO系列。原因很直接YOLO在工业落地中成熟度最高训练、推理、部署的生态齐全单阶段模型速度快在边缘设备上也能跑到实时帧率适合接入实时视频流模型对遮挡、重叠、小目标有更好的鲁棒性而鱼群恰恰是高度拥挤遮挡的场景迁移学习友好用预训练权重微调就能在一个小规模数据集上得到不错效果。除了模型选型整个系统的架构我也尽量做了简化。完整的链路是鱼塘边的摄像头通过RTSP视频流接入服务端按一定帧率抽帧把图像送入检测模型推理模型输出每一帧中鱼的位置框、类别和置信度之后汇总到统计模块最终写入数据库并通过Web前端展示。实时性要求高的场景可以降到几百毫秒延迟离线分析直接做视频批处理也行。1.3 工程上的取舍先跑通再优化做这类项目特别容易掉进“一开始就想建个大平台”的陷阱。我的做法是先做最小可用版本一个摄像头、一台带显卡的电脑、一个能出结果的检测脚本。先验证“模型在这个鱼塘里到底行不行”再考虑多路视频、Web平台、消息推送这些锦上添花的功能。这个项目名字虽然听起来轻松但背后涉及的技术栈并不少图像采集、数据标注、模型训练、推理优化、可视化统计每一环单独拎出来都有不少细节。MiroFish的价值恰恰在于把这些环节串成了一条可复制的流水线这也是我在这篇博文里想重点展开的内容。2. 核心细节解析与实操要点2.1 图像采集端摄像头选型与安装角度模型再强图像质量跟不上也白搭。MiroFish在采集端踩过的坑比训练阶段多一倍。摄像头方面我推荐从最简单的USB摄像头起步。原因不是它效果好而是成本低、驱动成熟、调试方便。USB免驱摄像头几十块钱一个接上电脑就能出图最适合在实验室或小池塘做算法验证。等到系统要长期部署在户外鱼塘再换工业网络摄像机IPC比如海康或大华的产品走RTSP协议拉流供电用POE稳定性会好很多。水下摄像头我也试过。水下画面确实更接近鱼的真实状态少了水面反光的干扰但代价是视场很窄观感偏蓝偏暗必须配补光灯而且镜头表面容易长藻结垢后期维护成本高得惊人。所以MiroFish目前的默认方案是水上俯拍水下采集作为数据补充时使用。安装角度这个参数直接影响漏检率。我实测下来的经验是摄像头与水面夹角控制在45度到60度之间效果最好。夹角太平鱼的身影会被水面反射和天空倒影干扰夹角太陡能看到的水面区域太小单位画面里鱼的数量太少统计效率上不去。安装高度方面两米到五米这个范围比较合适太高鱼的像素面积太小太低单帧覆盖范围不足。光照问题也必须提前想清楚。正午大太阳直射水面反光强烈模型误检率直线上升。可以加遮阳板也可以给镜头配偏振镜两种方式都有明显效果。如果没有改造条件直接在软件层面对采集帧做时间过滤把中午强光时段的数据打上低置信度标记也能减少干扰。2.2 数据集构建标注质量的优先级永远最高目标检测项目的上限很大程度在数据标注阶段就已经决定了。模型训练时间就几个小时数据标注却要花上几周这一点在MiroFish上体会极深。公开数据集方面MiroFish主要用了三类来源Fish4Knowledge数据集、DeepFish数据集以及一些零散的生态监测公开数据。这些数据可以用来做预训练让模型先学会“鱼”这个类别的基本特征。但公开数据拍的都是清水环境、低密度鱼群跟实际鱼塘里浑浊水质、高密度游动、水面波动的情况差距很大。所以我在项目里坚持要采一批真实场景的现场数据量不需要巨大但必须有针对性。自采数据的标注工具我推荐X-AnyLabeling。它内置了SAM分割模型的辅助标注能力框选鱼时只需点一下大致中心模型就会自动生成贴合目标的轮廓框效率比传统标注工具LabelImg提高很多。一套5000张图像的数据集用SAM辅助标注一个人两三天就能完成。标注过程中有几个容易忽略的细节严重遮挡的鱼要不要标我的原则是标但不刻意追求精确因为训练模型识别被遮挡的鱼恰好是提升泛化能力的关键场景模糊的目标要不要标模糊到人眼都无法判断是不是鱼的不标人眼能判断的标上但框要稍微紧凑一些标注框紧贴目标边缘比留白边效果好这个细节在计算IoU时影响很大训练集和验证集要按视频序列划分不能把同一段视频的不同帧拆到两边否则验证集泄漏会让评估结果虚高。2.3 模型训练关键参数不是玄学是有逻辑的MiroFish使用的检测框架是Ultralytics YOLOv8。这版模型在训练流程、API设计上都非常顺手适合做这类垂直场景的微调。训练时有个原则我一直坚持不要在数据集只有几百张的时候从头训练模型一定要用预训练权重做迁移学习。比如用yolov8s.pt做起点它对通用物体已经有了不错的特征提取能力微调时只需要让模型适应鱼类的纹理和轮廓训练负担大幅降低效果也会好很多。输入尺寸这个参数我最常用的是 640x640。常规密度场景下足够用。如果遇到小鱼目标多的场景可以试 1280 甚至更高分辨率但代价是训练和推理时间大幅上升。在实际项目中我一般先跑一版640的模型作为基线再针对漏检严重的小目标场景用1280微调对比收益再决定是否上线。训练轮次epochs的确定也不是一拍脑袋。MiroFish第一次实验跑了300轮结果到第50轮的时候验证集的loss已经不再下降后面几百轮纯属浪费时间。之后我改用早停机制patience设为30轮验证loss连续30轮不下降就自动停止训练省下来的时间用来调数据比调参数有意义得多。数据增强策略方面YOLOv8默认开启了Mosaic、MixUp、HSV扰动等增强手段。Mosaic会把四张图拼在一张里训练对提升模型在拥挤鱼群场景下的泛化能力很有帮助。但Mosaic生成的数据跟真实场景差距较大如果训练后期发现验证集效果波动可以适当降低Mosaic的概率让训练数据更接近真实分布。3. 实操过程与核心环节实现3.1 环境搭建一张入门级的N卡就够了我实际用的训练环境是Ubuntu 22.04系统、NVIDIA RTX 3060 12GB显卡、Python 3.10、PyTorch 2.0以上版本。这个配置跑YOLOv8sbatch size设置为16640分辨率一轮训练大约需要几十秒到一分钟一个完整流程下来半天内就能看到结果。如果手头没有N卡效果最好的替代方案是用云GPU按小时租用训练阶段集中跑完即可推理阶段放在CPU上也能接受。安装依赖这一块基本没有什么坑核心库就是ultralytics它会自动把torch、opencv-python等依赖拉起来。有一点要提醒建议新建一个干净的Python虚拟环境不要直接装在系统环境里不然后续版本冲突会让人崩溃。3.2 训练配置与完整命令数据集的目录结构按照YOLO格式来组织这是标准做法datasets/MiroFish ├── images │ ├── train │ └── val └── labels ├── train └── val标注文件是txt格式每一行对应一个目标框内容为“类别id 中心点x(归一化) 中心点y(归一化) 宽度w(归一化) 高度h(归一化)”。然后写一个mirofish.yaml数据配置文件path: ./datasets/MiroFish train: images/train val: images/val names: 0: crucian_carp 1: grass_carp 2: tilapia最后执行训练命令yolo detect train \ datamirofish.yaml \ modelyolov8s.pt \ epochs200 \ imgsz640 \ batch16 \ device0 \ patience30 \ project./runs \ namemirofish_exp1这里几个参数我解释一下。modelyolov8s.pt指定的是预训练权重的路径不是从头初始化网络结构。imgsz640是输入图像的缩放尺寸实际输入时会等比缩放并做letterbox处理。batch16受限于显存大小如果显存有限可以降到8如果用的是更大显存卡可以开到32。patience30是早停轮的阈值。训练结束后在runs/detect/mirofish_exp1/weights/目录下会生成best.pt和last.pt两个权重文件前者是验证集表现最好的模型后者是最后一轮的模型部署时默认选best.pt。判断训练效果时我最先看的是results.png里的loss曲线重点看val/box_loss和val/cls_loss是否在持续下降且和训练集loss接近。如果验证集loss在某个阶段开始反弹而训练集loss还在下降那就是过拟合的信号需要增加数据增强或加大Dropout而不是继续加轮次。3.3 实时推理与结果统计落地模型训练好后推理脚本的实现是整个项目里最见功力的一环。MiroFish的推理模块需要解决四个问题视频流读取、目标检测、目标计数、结果持久化。视频流读取这里有个常见的坑OpenCV的VideoCapture直接拉RTSP流时网络抖动会导致断流后无法自动恢复。我的做法是加一个循环检测的重连机制帧读取失败就释放连接、等待几秒再重新初始化保证系统能长期运行而不会卡死。下面是推理脚本的核心逻辑用Ultralytics的Python API实现非常直接import cv2 from ultralytics import YOLO import sqlite3 import time # 加载训练好的模型 model YOLO(./runs/detect/mirofish_exp1/weights/best.pt) # 初始化数据库连接 conn sqlite3.connect(./fish_stats.db) cur conn.cursor() cur.execute(CREATE TABLE IF NOT EXISTS stats (ts REAL, count INT, species TEXT)) conn.commit() # 拉流地址可以是RTSP也可以是本地视频文件 stream_url rtsp://192.168.1.100:554/Streaming/Channels/101 cap cv2.VideoCapture(stream_url) frame_interval 0.5 # 每0.5秒抽一帧 last_time 0 while True: ret, frame cap.read() if not ret: # 断流重连 cap.release() time.sleep(3) cap cv2.VideoCapture(stream_url) continue now time.time() if now - last_time frame_interval: continue last_time now # YOLO推理 results model.predict(frame, conf0.45, imgsz640, verboseFalse) # 统计结果 total_count 0 species_counter {} for r in results: boxes r.boxes total_count len(boxes) for box in boxes: cls_id int(box.cls[0]) cls_name model.names[cls_id] species_counter[cls_name] species_counter.get(cls_name, 0) 1 # 写入数据库 cur.execute( INSERT INTO stats (ts, count, species) VALUES (?, ?, ?), (now, total_count, str(species_counter)) ) conn.commit()这段代码有几个值得说明的地方。抽帧间隔设成0.5秒而不是对每一帧都做推理。鱼在水里的游动速度相对摄像头的帧率来说不快每秒2帧的采样已经足够描述鱼群数量变化却能大幅降低计算负载。如果后面要接多路视频流这个抽帧策略能救你一命。置信度阈值conf0.45是我多次实验比较出来的一个平衡值。阈值太高会漏检体色不明显的鱼太低会把漂浮的树枝、水面光影误判成鱼。不同的水质、光照条件下这个值应该重新调节我建议在实际部署时写一个配置文件而不是把阈值硬编码在代码里。写数据库是为了后续做趋势分析。光知道“这一刻有多少条鱼”意义不大把一天的检测结果按时间轴拉出来才能看到鱼群活跃度、摄食高峰等深层信息。4. 常见问题与排查技巧实录4.1 漏检与误检先从图像质量找原因MiroFish在测试阶段出现过一次很典型的漏检问题上午测试的效果还行到了下午鱼群游到阴影区域模型就大面积漏检。一开始我以为是模型泛化不够后来把当天的视频帧拉出来看才发现阴影区域的鱼在图像里对比度极低人眼都要仔细辨认模型当然更吃力。这个问题的处理思路不应该是一味增加训练数据而是先提升图像本身的可辨识度。我给摄像头加了手动曝光调节根据光线变化自动调整曝光时间同时在软件层面对输入帧做了一次简单的对比度增强。最直接的收获是漏检率从下午时段的20%降到了7%左右。水面反光引起的误检同样常见。镜头里的树叶倒影、云层倒影在模型眼里有时跟鱼的身影极其相似。我从三个方向解决物理上用偏振镜削弱反射算法上在检测区域设置ROI掩码把岸边、水面外部区域直接排除逻辑上对检测结果做时间平滑单帧出现的孤立目标通常直接忽略。4.2 数据泛化现场照片永远是救命稻草训练时模型表现很好一到现场效果崩盘这是目标检测项目最常见的翻车方式。MiroFish第一次部署到另一个鱼塘时模型mAP掉了将近15个百分点原因就是那边的水质偏绿、透明度低跟训练数据的清水差别太大。解决方式出人意料地简单我从那个鱼塘录了两个小时的视频抽了600帧现场画面标注后加到原训练集里重新微调几个轮次后mAP就恢复了。这件事给我的教训是垂直场景的目标检测现场数据永远是救命稻草多少都不嫌多。花再大力气优化模型结构不如花时间把现场数据采好标好。4.3 推理性能不够试着从输出端做减法部署的机器如果只有CPU跑YOLOv8s的640分辨率推理平均一帧要3到5秒完全达不到实时。我的优化顺序是这样的先把推理分辨率降到416速度能提升约40%代价是mAP下降2到3个百分点小目标漏检会变多然后把模型导出为ONNX格式用ONNX Runtime推理比PyTorch的GPU推理要快CPU上也有不错的加速效果最后对视频流做ROI预处理只对实际存在鱼群的水面区域做检测把画面其他区域直接裁掉计算量立刻减半。下面这个表是我在MiroFish项目中整理的常见问题速查表希望能帮你快速定位问题问题现象可能原因处理方式漏检严重鱼在画面上却不识别光照过强、反光、目标对比度低调整曝光增加对比度考虑偏振镜误检多水面倒影和漂浮物被识别为鱼训练数据缺少负样本增加无鱼区域、倒影、漂浮物的负样本并标注为背景晴天正常阴天效果变差光照分布变化大增加数据增强的HSV扰动采集多天气数据鱼群密集时计数偏低严重遮挡导致漏检提高输入分辨率使用高置信度阈值考虑ByteTrack多帧去重CPU推理速度太慢模型计算量大降分辨率、导出ONNX、ROI裁剪、量化int8模型在A塘好用换B塘就不行水质、光线、鱼品种分布不同采集现场数据进行微调少量就好系统运行一天后画面卡死RTSP流断流未重连加入重连机制超时释放资源夜间画面全黑无补光或红外不适配增加补光灯水面可见光或特定波长的LED4.4 部署稳定性长期运行才是真考验模型效果好只是第一步系统能稳定运行几个月才是真正的考验。MiroFish在测试阶段经常一跑就是一天掉线、卡死、内存泄漏都遇到过。RTSP断流重连在前面已经说过这里补充一个细节释放资源时不能只调cap.release()还需要把frame变量置空并主动调用cv2.destroyAllWindows()否则在某些OpenCV版本下会有资源句柄泄漏。内存泄漏问题则要通过定时重启服务或者监控内存占用进程来处理嵌入式设备上尤其明显。5. 应用场景与影响范围5.1 养殖端的投喂决策与异常预警MiroFish最直接的应用场景就是水产养殖。检测到鱼群活跃度下降、聚集密度异常系统可以自动标记并推送预警提醒管理者现场查看。每天定时统计的鱼群数量变化曲线还可以用来评估投喂策略的效果。有一个特别有价值的延伸功能是投喂区检测。把投喂机的投料范围设为ROI检测ROI内鱼群数量随时间的变化就能得到一个“摄食强度曲线”。鱼群在投料后迅速聚集、数量上升之后随时间推移逐渐散去。这个曲线的峰值、斜率、持续时间能非常直观地反映鱼的进食状态。如果连续几天摄食强度明显下降那就要考虑鱼是否生病或者水质出了问题。5.2 生态调查与休闲渔业的想象空间除了养殖场景MiroFish的技术路线还可以迁移到生态调查领域。河流、湖泊中的鱼类种群数量监测传统方法是电鱼采样、网具捕捞对生态干扰较大。用视觉方法对保护区的固定点位做长期监测可以降低人力成本也能获得更连续的数据。休闲渔业方面钓友用手机拍一张鱼获照片系统自动识别品种并估算体长这种功能在技术上完全可行后续如果做成小程序或者App面向C端用户也会有不小的想象空间。5.3 从MiroFish延伸出去的方向如果你也想做一个类似的项目我建议一个可行的扩展路径从单摄像头单品种的检测计数逐步扩展到多路视频流、多品种分类、鱼类个体识别与追踪。个体追踪这个方向尤其有价值配合ByteTrack之类的多目标跟踪算法不仅能数清鱼还能画出每一条鱼的运动轨迹用于分析鱼群的空间分布和行为模式。还有一个我认为很实用的扩展是把MiroFish接到自动投喂机上。系统检测到鱼群摄食强度下降到阈值时自动控制投喂机停止投料实现“按需投喂”。这个闭环一旦打通对养殖户的降本增效价值会非常大也是未来智慧养殖的重要方向。写在最后MiroFish这个项目做到现在最大的收获其实不是模型指标更好了而是让我对“把一个具体问题闭环解决掉”有了更深的理解。刚动手时我也总想追求最新的模型、最复杂的工程架构但真正让项目跑起来的反而是把摄像头装好、把数据标好、把每一帧结果看明白这些最不起眼的基础工作。如果让我给后来者一个最实在的建议那就是动手之前先花足够多的时间去现场看数据。鱼在什么光照下最清晰、什么角度最容易被遮挡、水面上哪些东西会干扰识别这些问题不坐在鱼塘边观察光靠看论文和调参是永远想不明白的。把数据基础打牢了模型哪怕用最常规的YOLO也能在实际场景中跑出让人惊喜的效果。另外分享一个小技巧给摄像头配置一个廉价的偏振镜花几十块钱就能让水面反光问题大幅缓解这是我在MiroFish里做过性价比最高的硬件改动。如果你的项目也打算做水面视觉识别不妨从这个小配件开始试试。
返回列表