ARTICLE DETAIL

资讯详情

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

基于边缘AI与视觉追踪的观赏鱼健康监测系统实践

基于边缘AI与视觉追踪的观赏鱼健康监测系统实践 MiroFish这个项目最早是我在鱼缸前蹲了三个星期之后被逼出来的。家里几条观赏鱼状态一直不对食欲越来越差游动也懒懒散散但表面看起来又没有明显病症等我终于发现不对劲时已经有一条救不回来了。后来复盘才发现观赏鱼生病前期的信号其实很明显比如游动节律改变、鳃盖呼吸频率异常、长时间贴底或者蹭缸但人不可能24小时盯在鱼缸前面。于是就有了MiroFish——一个用普通摄像头加边缘小主机对鱼缸做全天候视觉监测的智能系统。它用AI模型识别鱼的位置和姿态再通过连续性视频帧计算游动活跃度、摄食强度、呼吸频率这些健康相关指标一旦出现异常就推消息到你手机。这套东西不是实验室里的Demo而是我在家里跑了几个月的可用方案从硬件选型到模型训练再到告警落地踩了不少坑这篇就把完整过程拆开讲清楚。适合养鱼爱好者、AIoT开发者以及想用边缘设备做计算机视觉实践的人参考。1. 项目概述与设计思路1.1 为什么要做一个“盯着鱼看”的系统观赏鱼的问题出在“信号延迟”。水族圈有句话叫“病鱼看不出病看出病就晚了”这句话是有实际依据的。鱼类是变温动物新陈代谢会随着水温环境变化很多致病过程在早期只是行为层面的细微改变游动速度明显变慢、喜欢停留在出水口附近、鳃盖开合频率比平时急促、对投食反应迟钝。这些信号如果靠人眼观察需要每天固定时间蹲在缸前而且还得对每条鱼的个体习惯足够熟悉才能发现“今天这条鱼有点不对劲”。我一开始也考虑过纯传感器方案比如用水质监测仪测氨氮、亚硝酸盐、pH和溶解氧靠水质异常间接推断鱼的健康状态。但这套思路有个天然缺陷——水质指标是“环境代理指标”它不直接反映鱼本身的状态。鱼已经出现应激反应了水质数据可能还在正常范围内反过来水质短暂波动时鱼也不一定马上出问题。视觉监测的优势在于它直接盯着监测对象本身用鱼的行为数据做判断响应速度比环境指标领先一拍。MiroFish这个项目的定位很明确用低成本的视觉硬件在鱼缸旁边做一个常驻的边缘AI服务把“鱼的行为数据”变成可以量化、可以回溯、可以设置告警阈值的时间序列信号。项目本身不追求用一套模型解决所有鱼类疾病诊断问题而是先把“异常行为检测”这件事做到可靠、可落地。1.2 技术路线对比视觉AI为什么比单纯传感器更合理在真正动手之前我对比过三类方案。第一类是传统图像处理方案比如帧间差分、背景建模、光流法。这类方案的优势是无需训练数据部署简单但问题也很明显鱼缸场景里水面波纹、气泡、玻璃反光、灯光变化都会干扰前景提取很容易把非目标物体当成运动目标误检率高到没法用。实测下来单纯用背景减除法跟踪一条游动中的鱼能连续跟踪超过十分钟都算不错而异常行为判断恰恰需要分钟级以上的连续轨迹。第二类是商业成品方案市面上有专门的智能鱼缸也有带摄像头的喂食器但这些产品都是封闭系统检测逻辑不透明往往只会报水温、喂食记录不给行为数据接口。对于想深入研究鱼类行为的用户来说这类产品几乎没有扩展空间。第三类就是MiroFish采用的方案目标检测模型加多目标跟踪器在边缘设备上做实时推理再把轨迹数据转换成行为指标。深度学习模型能泛化到不同鱼种、不同缸体环境不会被水面波纹这类静态干扰轻易骗过跟踪器则负责维持鱼的身份保证“这条轨迹是同一只鱼”的连续性。选这条路线的判断依据是我需要的不是“画面里有没有东西在动”而是“画面里这条鱼在做什么、状态怎么样”这必须靠实例级别的识别和跟踪来实现。1.3 系统整体架构边缘推理、消息上行、终端通知整个系统的数据流我拆成了三段采集与推理、数据存储与分析、消息通知。采集与推理放在鱼缸旁边的一个小主机上我用的是带NPU的开发板接一个普通USB摄像头。推理服务用YOLOv8n做鱼体检测用ByteTrack做轨迹关联每秒处理约5到10帧画面。这个帧率是权衡过的——观赏鱼尤其是中小型灯科鱼游动速度不算快常见异常行为在秒级尺度上就能体现不需要跑到30帧每秒的实时视频分析帧率太高反而徒增功耗和算力负担。数据存储与分析层放在了同一台小主机的本地数据库里用SQLite就够跑单缸场景。每隔一段时间系统会把滑动窗口内的检测结果、轨迹数据汇总成一条行为指标记录包括平均速度、静止占比、转向频率、摄食区逗留时长等。为什么要做这个汇总因为单帧的检测结果和原始轨迹数据量太大全量上报既不现实也没必要真正有价值的是这些统计量。消息通知层使用现成的Webhook渠道对接企业微信或者钉钉机器人。告警触发逻辑分两级单指标越界触发普通提醒多个异常指标叠加时触发紧急告警。实测下来这套通知链路从发现异常到推送到达延迟控制在2到3秒内远快于人眼发现问题的速度。2. 核心细节与算法拆解2.1 目标检测与跟踪YOLOv8n与ByteTrack的实际搭配MiroFish的感知核心是两个部分检测器负责在画面中找到所有鱼跟踪器负责把连续帧中属于同一条鱼的检测框连接起来。检测器我选的是YOLOv8nn代表nano是YOLOv8系列中最轻量的版本参数量大概3.2M在RK3588这类边缘NPU上跑640分辨率的推理单帧延迟能做到30毫秒以内。为什么不用更大、精度更高的模型因为鱼缸检测任务的目标普遍很小一条成年斑马鱼在1080P画面里所占区域往往只有几十乘几十个像素模型再大对小目标的提升也有限反而会拖慢推理速度。真正提升小目标检测效果的是训练数据里小目标样本的占比以及推理时是否开启合理的多尺度测试我在训练时专门做过目标尺度分布统计。跟踪器选ByteTrack而不是传统的DeepSORT原因是它在处理低置信度检测框时更聪明。鱼缸场景里目标频繁被水草、装饰物或者其他鱼短暂遮挡遮挡期间检测器给出的置信度会骤降甚至出现断帧。DeepSORT会把低置信度框直接丢掉导致轨迹断裂ByteTrack会把高置信度和低置信度框分开关联低置信度框也参与轨迹匹配对遮挡场景的鲁棒性明显更好。实测在鱼缸这种遮挡频繁的场景下ByteTrack的轨迹保续率比DeepSORT高出约18个百分点。2.2 行为指标怎么算活跃度、摄食强度与呼吸频率检测和跟踪只是手段真正对养鱼有参考价值的是行为指标。活跃度用滑动窗口内的综合运动量来衡量。我对每条鱼维护一个持续20秒的轨迹队列每一帧计算鱼的位移和转向角然后统计窗口内的平均速度、最大速度、静止帧占比和方向变化频率。四个指标归一化后加权合成一个0到100的活跃度分数。正常状态下多数小型观赏鱼的活跃度会稳定在一个区间段比如我养的斑马鱼在白天活跃期分数基本在65到85之间浮动夜间关灯后降到20以下这是一种正常的昼夜节律。如果一条鱼在白天的活跃度持续低于30那就要警惕了。摄食强度指标依赖“喂食时间段”标记。每次人工投食时通过Webhook或物理按钮给系统发送一个“投食事件”系统会记录投食后5分钟内鱼在喂食区预先标注的ROI区域的出现频次、停留时长和游动路径密度。正常鱼在投食后摄食强度会有一个明显的脉冲上升如果连续多次投食后脉冲幅度越来越低说明鱼的食欲在下降这往往是疾病前期的第一信号。呼吸频率是另一个重要指标。鱼的呼吸活动体现为鳃盖或胸鳍的规律性往复运动我用检测框内下部区域的像素时序变化来提取这个周期。具体做法是在鱼体检测框的下半部分放一个竖条ROI逐帧记录该区域的平均像素强度形成一维时序信号再通过快速傅里叶变换找到主频换算成每分钟呼吸次数。这个方法要求帧率稳定在10FPS以上对摄像头的最低帧率有一个硬性要求。2.3 视觉异常与水质数据的融合判断MiroFish从第二版开始加入了水质传感器的数据但这个融合方式不是简单的“所有数据画在一起看”而是设计了一套基于规则的双通道告警逻辑。视觉通道输出的是行为异常候选事件水质通道输出的是环境异常候选事件两个通道独立判断再进入一个简单决策矩阵。比如水温超过30度且鱼的呼吸频率加快这两个信号单独看都可能是正常生理反应但叠加出现时大概率是水体溶氧不足或者热应激告警优先级就要上调。反过来如果视觉通道监测到鱼有明显的蹭缸行为鱼体反复在缸底或装饰物上摩擦但水质数据全部正常这时候我不会把告警降级因为蹭缸在多数情况指向体表寄生虫或细菌感染和常规水质指标相关性很弱。这个融合逻辑背后的思路是每个通道都有自己的盲区传感器测的是环境视觉测的是本体只有把两个维度的信息都纳入判断才能减少单一数据源造成的误判。我见过很多爱好者只装水质监测却不看鱼的行为等发现鱼状态不对时水质数据其实早就恢复正常了单靠环境指标根本追溯不到问题。2.4 时间线数据存储与展示逻辑行为指标的价值不只在于实时告警更在于长期趋势分析。我设计了一个以“小时”为最小分辨率的聚合存储结构。每条鱼的原始轨迹不会全量保存系统每隔5分钟把轨迹队列里的统计量压缩成一条记录包含平均速度、速度标准差、静止占比、呼吸频率均值、摄食区CPDcount per duration等信息。到了整点再把12条5分钟记录聚合成小时级数据。这样数据库里既保留了足够精细的日内模式又不会让数据量失控。一台单缸系统跑一整年下来SQLite数据库文件也就几MB非常轻量。展示端我用的是一个简单的Web页面显示每条鱼最近24小时、7天、30天的活跃度曲线和呼吸频率曲线。曲线叠加投食时间点和告警事件标记这样回溯问题的时候一眼就能看出“鱼是在哪天开始出现行为异常的”再联动回忆当天的水族操作换水、加鱼、调整灯光基本能锁定诱因。这套时间线回溯逻辑后来成了整个项目里我自己用得最多的功能。3. 实操过程与关键环节实现3.1 硬件选型与安装布局摄像头怎么摆是关键硬件清单我需要明确一件事MiroFish的核心不是算力是稳定的图像输入。图像输入不稳定后面所有算法都是空中楼阁。摄像头我用的是普通免驱USB摄像头1080P分辨率支持红外夜视。之所以不选工业相机是因为鱼缸监测需要长时间连续运行工业相机的散热、供电和价格在这个场景下没有必要。但有一个参数不能妥协必须支持手动关闭自动曝光和自动白平衡。鱼缸的灯光会定时开关环境亮度变化很大如果摄像头自动调整参数画面亮度会在开灯关灯瞬间剧烈波动导致检测器性能骤降。我在代码里固定了曝光时间和白平衡值只在每天凌晨零点和中午十二点做一次自动校准实测这样处理之后目标检测的稳定性提升了一个档次。摄像头安装位置直接决定了整个项目的成败。我踩过最大的坑是把摄像头架在鱼缸正前方偏高处结果玻璃反光把客厅的窗户倒映得一清二楚检测器频繁把窗框边缘当成鱼误检率一度超过40%。后来换成两个办法一是让摄像头以15度左右的俯角斜向下拍摄缸体正面镜头尽量贴近玻璃减少大角度反光二是在画面里预先设置ROI掩膜把水面上方、缸外区域全部屏蔽掉只保留水体区域作为检测范围。ROI掩膜启用后误检率直接降到了5%以下。3.2 从采集标注到模型训练的完整流程这套系统的检测模型是在自己采集的数据集上微调过的整个流程可以拆成四步。第一步是采集。我连续采集了大约两周的视频覆盖白天自然光、夜间LED灯、夜间红外灯三个光照条件以及鱼在摄食、追逐、休息等不同行为状态。采集到的原始视频按每秒抽2帧的方式切图得到约4500张有效图片。为了让模型对不同缸体环境也有泛化能力我还额外引入了公开鱼类数据集中的约1500张图片混合训练包含不同水质、不同拍摄角度下的鱼。第二步是标注。标注工具我用的是LabelImg框的类别只有一类fish。这里有一个细节鱼的尾巴和身体经常呈现半透明状态标注时如果只框不透明的躯干部分会导致目标框过小影响检测稳定性。我的做法是只要肉眼能分辨出是鱼身的一部分就把整个可见鱼体框进去宁可框得略大也不要漏掉尾巴边缘。第三步是训练。我用YOLOv8n做基础模型输入分辨率640乘640batch size为16训练150个epoch。优化器用SGD初始学习率0.01采用余弦退火调度。训练集和验证集按9比1划分最终在验证集上的mAP50达到0.93mAP50-95是0.67。这个精度对鱼缸检测来说已经足够用毕竟目标类别只有一类背景相对固定。第四步是导出部署。训练好的模型导出为ONNX格式再用NPU工具链转换成边缘设备专用的模型格式。转换后需要做精度对齐验证一般会有微小掉点但只要mAP50保持在0.85以上就不影响实际使用。3.3 推理服务与报警推送的落地代码思路服务端推理逻辑核心就是一个循环读帧、检测、跟踪、更新指标、判断告警。我用Python加OpenCV实现整体逻辑用伪代码描述大概是这样的import cv2 import numpy as np capture cv2.VideoCapture(0) tracker ByteTrack() window SlidingWindow(seconds20) while True: ok, frame capture.read() if not ok: continue # 仅处理ROI区域减少无效推理 roi_frame apply_roi_mask(frame) detections yolov8_infer(roi_frame, conf_threshold0.35) # 更新轨迹 tracks tracker.update(detections) for track in tracks: fish_id track.id bbox track.bbox velocity, turn_rate calc_motion(track) window.push(fish_id, velocity, turn_rate, bbox) # 每5秒计算一次行为指标 if should_aggregate(): metrics window.compute_metrics() save_to_sqlite(metrics) alert_list check_alert_rules(metrics) for alert in alert_list: send_webhook(alert)这段代码看起来不长但实际调试过程中有几个隐藏的坑。一个是推理频率管理。如果每帧都做检测边缘设备CPU占用会冲到很高我最后采用了隔帧推理的策略视频读取保持15FPS检测推理降到5FPS左右行为指标计算基于全部帧的轨迹数据插值效果一样稳定CPU占用却降了约60%。另一个坑是检测结果和轨迹数据的坐标系对齐。ROI掩膜会让部分画面区域失效如果直接拿原始帧的坐标去匹配ROI外的轨迹会出现幽灵目标必须把坐标统一到ROI裁剪后的坐标系里。3.4 几个关键参数的调试经验值这部分参数是我经过多轮实验得到的具体数值可以作为参考但不必照搬因为不同鱼缸的光线和目标大小差异很大。置信度阈值我调试的时候发现设成0.35到0.4之间最合适太低的置信度会引入大量误检尤其在有气泡和水草晃动时太高又容易漏掉被遮挡的鱼。这个值比通用目标检测场景低一些是因为鱼体目标小模型输出置信度天然偏低。NMS的IoU阈值保持默认的0.5即可不需要动。真正需要调的是ByteTrack的match_threshold也就是轨迹匹配时允许的最大距离。我按画面中鱼的体长比例来算典型设置是鱼体宽的2.5倍。设太大的话两条交叉游过的鱼会被串成一条轨迹设太小鱼转身时检测框轻微偏移就会断档。鱼呼吸频率提取的FFT窗口我设成10秒频率分辨率刚好是0.1Hz对应每分钟6次呼吸的精度能满足常见观赏鱼的呼吸频率范围每分钟30到120次的测量需求。窗口再短频率分辨率不够窗口再长实时性又受影响。4. 实测效果、常见问题与排查技巧4.1 平稳期与异常期的检测效果跑了一个季度的真实数据MiroFish在我家的缸上连续运行了大约四个月期间经历过斑马鱼、红绿灯鱼两个鱼种出过两次真实异常事件整个系统的表现可以分成两种情况来说。平稳期也就是鱼的健康状况正常时系统的作用更像一个“行为记录仪”。活跃度曲线会呈现出非常规律的昼夜节律白天开灯后上升晚上关灯后下降投食时间点附近出现短时脉冲。呼吸频率曲线同样稳定同一种鱼在相同水温下呼吸频率波动范围很窄斑马鱼在26℃时基本稳定在每分钟70到95次。这段过程验证了系统输出的指标具有稳定性和可重复性也为后续异常判断提供了基线数据。真实异常事件发生在第三个月。一条斑马鱼逐渐脱离鱼群活跃度从平时70多分连续三天下降到40分左右呼吸频率从80次/分钟升到110次/分钟以上。系统在第2天就触发了普通告警我没有处理继续观察到第3天紧急告警触发。随后我检查鱼体发现腹部有轻微立鳞迹象隔离处理后情况缓解。这个案例让我确信一件事行为指标的异常确实早于外部可见症状出现视觉监测完全能承担“早期预警”的角色。4.2 家庭鱼缸场景的四大干扰源真实环境不可能像实验室那样干净。我在调试过程中总结出四个最典型的干扰源以及对应的处理方案。第一个是玻璃反光。这是最初导致大量误检的元凶。除了调整摄像头角度和ROI掩膜还有一个好用的技巧贴偏振膜。在鱼缸外侧贴上手机屏幕防窥膜或者专用偏振膜可以有效消除一部分玻璃反射偏振光画面通透度提升明显。这个方法的缺点是会略微降低画面亮度需要增加一点点补光。第二个是水面的周期性波动。水泵出水口附近的水面会持续波动产生规律的亮度变化检测器偶尔会把波动的高光区域识别为目标。我的处理办法是把ROI上边界压到水位线下方约2到3厘米处彻底避开水面交界区既不损失有效水体范围又能消除这类误检。第三个是打氧产生的气泡串。气泡在上升过程中会形成连续的小目标区域尤其当背景颜色较暗时气泡很容易被当成小型鱼体。针对这个干扰我在检测后增加了一个目标大小过滤器面积小于正常鱼体30%的检测框直接丢弃气泡目标基本都能被过滤掉。第四个是缸体内壁的藻类和污渍。长期运行后缸壁会长出褐藻或绿斑这些斑点在某些光照角度下会产生类似鱼体的边缘特征。这个问题的解决靠的不是算法优化而是维护习惯——每周擦一遍缸壁内侧系统误检率会随之下降。算法解决不了保养问题。4.3 误报与漏报的经典处理案例有一种漏报场景值得单独讲。一条鱼游到过滤器的出水口正下方水流冲击让鱼的身体剧烈摆动检测器虽然能稳定框住目标但轨迹数据里的转向频率和速度明显异常甚至超过了系统预设的“应激阈值”触发了误报。这种误报不是检测失败而是行为指标本身被环境因素扭曲了。我的处理方式是在active度计算中加入了“环境流场掩码”出水口区域的速度和转向值以50%的权重参与最终指标合成。这样既不会完全忽略鱼在出水口区域的异常表现又避免了水流干扰主导整体判断。鱼缸里水草晃动也会带来类似的指标污染但水草晃动的模式是低频摆动和鱼的高频运动叠在一起后频谱特征上能区分出来。我尝试过用高通滤波器对轨迹速度做预处理把低于0.5Hz的低频分量滤除一部分对减少水草干扰有一定帮助但效果有限后来还是靠ROI划分把水草密集区单独出来监测。4.4 降低系统负担的两种思路边缘设备的算力始终是有限资源。运行一段时间后我发现如果直接把推理频率提高到10FPS设备温度会维持在65℃以上长时间运行稳定性下降。解决思路有两个方向。一是按需推理调度。夜间关灯后鱼的活动量本身很低异常事件发生的概率也低系统在夜间可以把推理频率降到2FPS只记录基础指标不跑高频率行为分析。白天开灯期间再恢复到5到8FPS的完整模式。这个逻辑简单粗暴但对降低整体功耗和温度非常有效。二是预筛选加精检测的两级架构。先用轻量级的背景差分算法做运动检测只有画面运动量超过阈值时才把这一帧送入完整的YOLO模型做检测。鱼缸场景里大部分时间是相对静止的两级架构可以把有效推理次数减少将近一半。这个优化做下来设备待机功耗从平均8W降到了5.5W左右。5. 常见问题与排查技巧实录5.1 目标频繁丢失怎么办症状是鱼的轨迹经常中断几秒后又重新出现跟踪ID频繁变化。这种情况多半不是模型问题而是检测阈值和跟踪参数不匹配。先看置信度阈值是否设得过高其次看跟踪器的match_threshold是否过紧。还有一个容易被忽略的点是检测框的稳定性鱼体边缘带有半透明鳍条检测框会在几帧内出现微小抖动如果跟踪器对框的抖动敏感就会导致匹配失败。我的处理方式是在送入跟踪器之前对检测框做一次时域平滑用当前帧和上一帧的检测框取加权平均减少抖动带来的轨迹断裂。5.2 CPU占用过高先检查是不是推理频率设置得太高或者模型没有真正跑在NPU上。很多人把模型转到NPU平台后代码里还是走CPU推理路径等于白转。排查方法是看设备运行时的进程资源占用如果NPU负载正常但CPU占用率仍然很高问题可能出在OpenCV的帧读取或者颜色空间转换上这两步在CPU上的开销其实也不小。可以尝试降低读取分辨率或者在读取时直接设置色彩格式。5.3 夜间红外模式下检测效果变差可见光模式下模型表现很好切到夜间红外模式后漏检变多这很正常因为模型在训练时红外图占比不够。解决办法是在训练数据里提高红外图像的比重我最终把红外图的比例控制在四分之一左右后才稳定。另外一个技巧是启用红外补光灯时注意画面里不要出现局部过曝区域鱼游过高亮区域时检测框会丢失调整补光灯角度和强度可以缓解。5.4 告警频繁触发但鱼状态正常这是误报率问题优先查行为指标阈值是不是定得太紧。不同鱼种、不同大小的缸体指标分布差异很大不要直接套用默认阈值。先在平稳期跑一周系统统计各项指标的正常分布范围再按“均值加减2.5倍标准差”这个经验公式设置告警线误报率能大幅下降。如果个别指标仍然频繁越界需要回到数据里看是否存在环境干扰源。5.5 常见问题速查表问题现象排查方向处理建议轨迹频繁断裂跟踪参数、置信度阈值调低置信度至0.3-0.35放宽match_threshold白天误检多玻璃反光、ROI未设置调整摄像头角度粘贴偏振膜开启ROI掩膜夜间漏检多红外训练样本不足增加红外图片比例到25%以上调整补光角度自助关灯后指标无节律采集时间与空间确认系统时钟准确检查灯控设备是否成功联动设备温度过高推理频率、散热降低夜间推理频率加装主动散热风扇数据库增长过快聚合周期太短延长聚合周期至5-10分钟压缩原始数据保留策略6. 想清楚这些事再做能省一半的功夫最后聊点项目之外但比项目更有价值的体会。MiroFish做到后半程我最深的感受不是“AI很强大”而是“养鱼的核心问题其实是观察意识”。很多人买智能设备是为了省心但真正的价值是帮你建立起对生物状态的敏感度。系统把一个模糊的担忧变成了一条条可回溯的数据曲线养鱼从“凭感觉”变成了“看数据”这是这个项目最让我满意的地方。技术上的收获同样不少。边缘设备的资源限制逼着你重新审视每一行代码的开销从模型大小到推理频率再到数据存储策略每一步都需要做取舍。这种在资源约束下做平衡的能力和单纯在服务器上跑通一个模型是完全不同的经验。如果你也想做一个类似的监测系统我的建议是不要一开始就堆功能。先把“持续稳定的目标检测和轨迹跟踪”跑通老老实实记录一周的正常数据再考虑加告警、加水质融合、加Web展示。系统只有先稳定才有资格谈智能。MiroFish目前还在持续迭代后续我计划加入自动喂食联动的闭环检测到摄食强度下降时自动暂缓下一餐投喂量减少因过度喂食导致的水质恶化。鱼不会说话但它的每一帧游动都在传递信息我们要做的只是把这些信号翻译成看得懂的语言。
返回列表