养鱼这件事,最揪心的时刻不是行情起伏,而是巡塘时发现水面飘着几条死鱼。如果是常见的缺氧、拉网应激还好处理,怕就怕细鳞、烂身、肠道发红这些病害,看着还活蹦乱跳,隔两天就可能成片发病。传统做法全靠老师傅肉眼观察、凭经验判断,但经验这东西没法复制,一位老师傅不可能盯着每个塘口,等肉眼能看出明显症状时,往往已经过了最佳干预窗口,损失已经造成。
我搞了几年智慧水产相关项目,最近把这套基于深度学习和图像识别的鱼类病害早期筛查系统从零到一完整做了一遍。核心思路不复杂:用摄像头或手机拍摄鱼体画面,通过YOLO目标检测模型自动识别体表异常特征(烂身、白点、赤皮、打印病等),检测结果实时回传,超过设定阈值自动告警。整个过程把“人工巡塘、经验判断”变成了“视觉识别、量化预警”,一批塘口只需要一台采集设备,后台模型几分钟就能完成以往一个老师傅反复蹲点的观察任务。这套系统对想做智慧渔业落地的人、想找深度学习实战项目的开发者、以及被病害筛查困扰的养殖管理者来说,都有直接的参考价值。
整个项目从数据采集、标注、模型训练到最终部署上线,前前后后耗时三个月左右。这篇文章把我踩过的坑、调参的经验、部署的细节全部梳理出来,希望后来者不用重复走弯路。
1. 项目背景与系统定位拆解
1.1 病害早期筛查到底难在哪
水产病害的传统诊断路径,高度依赖人工观察和解剖判断。病鱼早期往往只是食欲下降、游动迟缓、体表轻微发暗这些非特异性症状,普通养殖人员很难从这些蛛丝马迹里识别出具体病因。等发展到体表出血、溃烂、白点铺满鳍条时,病害已经进入中后期,治疗效果大打折扣,交叉感染风险迅速上升。
早期筛查系统要解决的核心矛盾,就是“发现时间”。目标不是替代兽医做病理诊断,而是在大规模发病之前,先给出“这塘鱼可能出问题了”的快速判断,提示管理人员去重点关注、采样送检。检测模型承担的是“哨兵”角色,不是“医生”角色。
这一点在项目立项时就必须讲清楚。把系统定位成“诊断工具”会带来极高的准确率要求,注定难落地;定位成“早期预警辅助工具”,准确率只需在关键病种上做到实用水平,系统就有了明确的应用边界和交付价值。
1.2 为什么选择深度学习图像识别方案
早些年做水产病害识别,常见的技术路线是传统图像处理,比如颜色阈值分割、纹理特征提取加SVM分类器。这条路在实验室理想条件下能跑通,一到真实塘口就崩溃。水下光照不均匀、水体浊度变化、鱼体姿态扭曲、气泡反光、浮游生物干扰,这些因素叠加起来,传统特征工程的人工设计根本无法穷举。
深度学习特别是卷积神经网络出现后,特征提取不再靠人去设计,而是从大量标注数据里自动学习。模型能同时关注鱼体轮廓、病灶颜色、纹理变化等多个维度,对光照和水质变化的鲁棒性明显更强。我实测过在同一天不同时间段拍的同一批鱼,传统方法准确率波动能达到20%以上,深度学习方法在充足训练数据加持下,波动基本控制在5%以内。
目前目标检测领域最成熟的框架就是YOLO系列。它的优势在于单阶段检测,网络结构简洁,推理速度快,在Jetson Nano这类边缘设备上也能跑出实时帧率。水产养殖场景中,采集端分布分散,网络带宽有限,把视频回传云端做逐帧检测不现实,更合理的做法是在塘口附近部署边缘推理节点,本地完成检测,只把结果和关键帧上传。这种场景对模型部署友好性的要求比纯粹追求精度更高,YOLO系列的工程社区成熟度也保证了开发周期可控。
1.3 系统整体技术架构
这套系统从数据流向上分五个环节:
- 采集端:海康或者普通USB摄像头架在投料区、增氧机附近(这两个位置鱼群聚集密度最高),也可用手机手动拍图上传,灵活应对不同塘口条件。
- 预处理服务:统一图片尺寸、格式转换、亮度校正,解决不同设备色彩差异问题。
- 检测推理引擎:YOLOv8模型加载到ONNX Runtime或TensorRT环境,接收预处理后的图片,输出检测框、类别和置信度。
- 业务逻辑层:单张图片的检测结果经过时间序列规则过滤(如连续3次检测到白点病且置信度大于0.5才触发告警),避免单帧误检。
- 应用展示层:Web管理后台和微信小程序,展示告警记录、病害趋势、处理建议。
整体架构并不复杂,实际实施时每个环节都会遇到工程问题。第3章我会详细拆解其中最核心的两个环节:数据集构建和模型训练,这是整条链路里决定成败的部分。
2. 数据集构建与YOLO格式转换全流程
2.1 数据采集:算法虐我千百遍,我待数据如初恋
很多开发者做目标检测项目,拿到网上下载的公开数据集就直接训练,这在通用物体(人、车、猫狗)上可行,但水产病害识别基本行不通。病害鱼在体表症状、形态、色泽上的差异极其细腻,需要贴合本地水质、光照、养殖品种的数据,才能保证模型的场景适配性。
我的数据来源主要有三条:
- 塘口实拍:在合作养殖基地架设采集设备,持续两周拍摄,每天早中晚三个时段各采集一段视频,覆盖不同光照角度。然后按每5秒一帧的方式抽帧入库。
- 人工补充拍摄:发现病鱼后,捞到白色塑料盆里用手机拍摄特写,盆底放一把直尺做参考物,方便后期如果要做病斑面积计算时使用。
- 网络公开数据:搜索公开的鱼类病害数据集做补充,但只用于预训练,不直接混入训练集,防止背景差异过大影响收敛效果。
采集过程中有一条我反复强调的原则:宁可拍5000张高质量的带病鱼图,不要凑2万张模糊不清的垃圾图。模糊样本放进训练集只会让模型学到的特征一团浆糊,对提升鲁棒性没有帮助。
采集阶段建议记录每张图片的拍摄信息,比如水体浊度等级、光照条件、鱼种名,后续做数据筛选和模型迭代时这些meta信息非常有用。
2.2 数据标注:边界框怎么画直接影响模型学习
标注工具我用的LabelImg和X-AnyLabeling两种。LabelImg是老牌工具,用起来稳定,适合矩形框标注;X-AnyLabeling支持自动化辅助标注,如果后续要扩充数据量,用它的半自动模式能省下大量人力。
标注的核心规范有以下几条:
- 只标注鱼体体表暴露在水面或水中的清晰可见部分,半条鱼被遮挡时,如果病灶区域完全不可见,这一块不做标注。
- 一个鱼体实例对应一个检测框,框住整条鱼(头到尾),而不是只框病灶区域,因为后期还要按病种计算发病率,需要完整的鱼体区域做统计。
- 不同病种的病灶特征如果同时出现在同一鱼身上,以主要病灶类别为准,避免一个框内同时存在多个标签名称搞乱分类器。
- 病鱼和健康鱼都要标注。健康鱼标注为“healthy”,用途是让模型认识“正常形态”长什么样,这是降低误报率的关键操作。
标注完成后的文件结构是这样组织的:
dataset/ images/ train/*.jpg val/*.jpg labels/ train/*.txt val/*.txt每个TXT文件名和图片名一一对应,内容每行代表一个标注框,格式为:
class_id x_center y_center width heightX_center、y_center这些坐标都做了归一化处理,数值范围[0,1],YOLO训练时直接使用这个格式。如果你用COCO或VOC格式的标注文件,需要先转换成YOLO txt格式。转换脚本比较简单,核心逻辑如下:
import os import json def convert_coco_to_yolo(coco_json_path, output_dir, class_mapping): with open(coco_json_path, 'r', encoding='utf-8') as f: ann_data = json.load(f) img_info = {info['id']: info for info in ann_data['images']} ann_list = ann_data['annotations'] category_map = {cat['id']: cat['name'] for cat in ann_data['categories']} os.makedirs(output_dir, exist_ok=True) for ann in ann_list: img = img_info[ann['image_id']] img_name = img['file_name'] img_w = img['width'] img_h = img['height'] label_path = os.path.join(output_dir, img_name.replace('.jpg', '.txt')) cat_name = category_map[ann['category_id']] if cat_name not in class_mapping: continue cat_id = class_mapping[cat_name] bbox = ann['bbox'] # [x, y, width, height] (左上角坐标系) x_center = (bbox[0] + bbox[2] / 2) / img_w y_center = (bbox[1] + bbox[3] / 2) / img_h w = bbox[2] / img_w h = bbox[3] / img_h with open(label_path, 'a', encoding='utf-8') as lf: lf.write(f'{cat_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n')这个脚本的核心在于坐标系的换算。COCO的bbox是左上角坐标加宽高,YOLO需要的是边框中心点坐标加宽高的比值,搞清楚换算关系就不会出错。
2.3 数据增强与样本不均衡处理
鱼类病害数据天然存在严重的不均衡问题。健康鱼样本通常占80%以上,白点病这类常见病占10%,赤皮病可能只有3%,烂尾病等稀有病害可能连1%都不到。模型在小类别上学不到足够的判别特征,预测时就会大概率漏检,这是必须提前处理的问题。
我用的方案分三路:
- 对少数病种做离线增强,每张原始图扩展出水平翻转、垂直翻转、顺时针旋转15度、随机亮度调整、随机对比度调整、高斯模糊等版本,让稀有类别的样本量提升到与常见病种接近的水平。
- 在线增强交给YOLOv8训练时自动执行,包括mosaic、mixup、HSV扰动等,大幅提升样本多样性。
- 负样本另开一路,单独收集健康鱼在晴天、阴天、雨天、逆光、顺光、水面漂浮物多等条件下的图片,数量约占总样本的30%。
Mosaic增强把四张图片拼成一张喂进网络,最大好处是让模型学会在不同光照背景下识别缩小版的鱼体,泛化能力会有明显提升。但要注意的是,水产病害图片的病灶细节本来就细小,Mosaic过度使用可能导致小目标特征被压缩,一般建议Mosaic概率调到0.5左右,不要全程开启到最大值。
数据增强里还有一个容易踩的坑:旋转增强需要谨慎。鱼体体表病灶方向性很强,旋转90度或180度后,病理特征可能失真(比如头部和尾部的朝向变化),所以增强时旋转角度控制在正负25度以内比较合适。
2.4 数据集质量检查的最后一道关
标注工作结束后,必须花时间做一次系统性的数据质量复核。我总结了以下检查清单:
| 检查项 | 判定方法 | 问题示例 |
|---|---|---|
| 标注框贴边 | 打开标注可视化,检查边界框是否与鱼体边缘贴合 | 框整体偏移,把水波泡也包进去了 |
| 类别混淆 | 抽查每个类别的样本图 | 打印病被标成白点病,两者纹理完全不同 |
| 破损图片 | 跑一次图片完整性检测脚本 | 下载图片损坏,训练时读到空图导致中断 |
| 映射关系错误 | 验证每个TXT的坐标范围,保证都在[0,1]区间 | 某个标注框中心坐标超过1 |
| 样本重复度过高 | 计算图片哈希值,筛掉几乎相同的帧 | 视频抽帧时间间隔太短,连续帧相似度达95%以上 |
检查脚本可以很简单,用OpenCV读一遍所有图片和标注文件,统计坐标是否有越界、图片是否存在空文件:
python check_dataset.py --images dataset/images --labels dataset/labels这一步我现在特别重视,因为数据集质量直接决定后续所有训练工作的意义。有一次我偷懒没有做坐标范围检查,结果训练时loss怎么降都降不下去,排查了两天最后发现是某批标注的x_center全部写多了0.1,坐标越界导致梯度爆炸。数据集干净了,训练过程至少节省一倍时间。
3. 模型训练与关键调优经验
3.1 模型版本选择和基准测试
目前YOLO系列可选版本很多,个人测试过的YOLOv5、YOLOv8、YOLOv11都跑得通。差异主要在:
- YOLOv5:社区资料最全,部署方案成熟,但精度和效率略逊于v8。
- YOLOv8:引入了anchor-free检测头和C2f模块,在对小目标和细长型目标(比如鱼类体表病斑)的检测上表现更优异,均值平均精度mAP比v5提升约2到3个百分点,且训练代码对新手更友好。
- YOLOv11:精度进一步提升,但模型体积和推理延迟相应增加,边缘设备上的表现需要实测权衡。
我最终选用YOLOv8n作为基础版本,理由是精度和推理速度在低成本边缘设备上能达到平衡。如果采集端使用的摄像头分辨率较高(1080P以上),可以升级到YOLOv8s,精度会有小幅提升,但推理时间会增加约40%。
3.2 训练集配置与超参数选择
数据集的分割比例,我用的是训练集80%、验证集20%,测试集不单独保留(直接留出一部分病种数据做泛化测试)。很多公开教程推荐70%、20%、10%的比例,但在小样本水产数据集中,训练集占比越高,模型学习越充分。只要做好数据增强和数据采集时的多样性控制,过拟合风险是可控的。
训练配置的yaml文件内容如下:
path: ./dataset train: images/train val: images/val names: 0: healthy 1: white_spot 2: rotten_body 3: red_skin 4: print_disease训练命令按官方文档执行即可,核心参数我调过几轮后定为:
yolov8 train data=data.yaml model=yolov8n.pt epochs=200 imgsz=640 batch=16 lr0=0.01 lrf=0.01 momentum=0.937 weight_decay=0.0005 hsv_h=0.015 hsv_s=0.7 hsv_v=0.4 degrees=10 fliplr=0.5epochs设到200是为了让小病种类别充分学习。batch设为16是因为显存8G左右,batch再大容易爆显存。imgz设为640,分辨率越高、小目标检测越准,但推理时间也变长,最终测试过在1280分辨率下白点病检测的mAP能提升2%,但推理时间翻了接近4倍,实际部署时性价比不高。
3.3 训练曲线的解读方法
很多新手看到训练日志里的loss降了、mAP数值看着不错就以为完事了,实际上必须自己会看图。重点看四个指标的趋势曲线。
- loss曲线:box_loss、cls_loss、dfl_loss三条线应该在训练后期同步收敛并趋于平滑。如果cls_loss曲线一直上下波动震荡,大概率是标注噪声太大,比如同一类别中部分样本标注框明显偏小或偏大。
- mAP50与mAP50-95:mAP50反映的是简单场景下的平均检测精度,mAP50-95反映的是更严苛、更苛刻的检测能力。水产病害检测的实际场景中两张指标之间的差距,正常应该在0.1到0.2之间。如果mAP50到了0.95但mAP50-95只有0.3,说明模型对边界框的定位还不够精细,需要考虑提高输入分辨率。
- 精确率precision与召回率recall的矛盾关系:精确率高意味着模型框选出来的东西大多数是真正的病鱼,误报少;召回率高意味着病鱼大多数都被找出来了,漏检少。实际调参时,我一般优先保精确率,因为误报会消耗养殖户信任,而漏掉单条病鱼的影响可由多次检测补偿。
- 训练结束时的最终数值:我实际训练完成后,验证集mAP50 达到0.87,mAP50-95 是0.61,召回率0.89、精确率0.9。这个水平在单一固定塘口环境中已经具备实用价值。
如果发现训练结束后PR曲线末尾区域严重下坠,说明负样本不足,健康鱼图片比例过低,模型把太多环境噪声当成分辨特征对待了。
3.4 模型压缩与导出部署
训练好的模型是PyTorch格式,如果直接部署在Jetson或树莓派这类低功耗设备上,推理效率跟不上。必须先做模型转换和优化。
ONNX导出很简单:
yolo export model=runs/train/weights/best.pt format=onnx simplify=True opset=12如果部署端是NVIDIA GPU平台,可以继续转换为TensorRT格式,推理速度能提升至少2倍以上。TensorRT转换有一步关键操作是dynamic shape设置,如果批量大小固定,可以直接用固定shape转换,省去后续模型绑定的麻烦。我们最终在Jetson Orin Nano上实测,YOLOv8n-TensorRT FP16精度下,推理单张图大约15ms,满足准实时要求。
量化的问题这里提醒一下,直接使用GPU训练好的模型转成int8量化部署,精度损失可能达到10%以上,尤其是小白点这类小目标区域,量化后特征丢失更严重。如果要做int8量化,需要在量化时准备校准数据集,校准集的图片分布必须和真实场景贴合。
4. 系统落地:从检测框到实用预警
4.1 推理服务的封装实践
YOLO模型不能直接给业务系统调用,封装成HTTP服务是最常规的方案,部署方方便,链路清晰。我用FastAPI加上Uvicorn封装了一个推理接口,代码比较轻量。
# app.py from fastapi import FastAPI, UploadFile, File from ultralytics import YOLO import numpy as np import cv2 import io app = FastAPI() model = YOLO("best.pt") @app.post("/predict") async def predict(image: UploadFile = File(...)): img_data = await image.read() img_array = np.frombuffer(img_data, np.uint8) img = cv2.imdecode(img_array, cv2.IMREAD_COLOR) results = model.predict(img, conf=0.45, iou=0.5, verbose=False, device=0) detections = [] boxes = results[0].boxes for box in boxes: x1, y1, x2, y2 = box.xyxy[0].tolist() conf = box.conf[0].item() cls_id = int(box.cls[0].item()) detections.append({ "class": model.names[cls_id], "confidence": round(conf, 4), "bbox": [round(x1, 2), round(y1, 2), round(x2, 2), round(y2, 2)] }) return {"detections": detections}conf阈值我调到0.45,iou非极大抑制阈值设为0.5,这个组合在验证集上的漏检误报相对均衡。关键业务要求推送到手机的数不得超过5条/分钟,所以后端做了一个简易的限流器,对相同塘口ID的重复请求做聚合。
4.2 预警规则设计:别让模型单帧结果直接吓折腾人
这是整个系统落地最容易忽略、也是对用户体验影响最大的环节。模型单帧检测结果不能直接触发告警,因为鱼群游动瞬间、水面反光会导致偶发误检。直接使用单帧结果打告警,会频繁骚扰用户。
我最终设计的预警规则是时间加权投票机制:
- 采集端固定每分钟上传1次关键帧。
- 系统缓存最近10分钟内的检测结果。
- 如果某类病害的置信度大于0.5的帧累计不低于4次,则该10分钟窗口内标记为“疑似”。
- 连续2个窗口都标记为“疑似”,触发黄色告警,推送养殖管理员。
- 黄色告警后如果3分钟内检测置信度大于0.7的帧超过5次,升级为红色告警,同时附带最近3张检测原图。
这套规则上线后误报率大幅降低。原来的单帧方案一天能触发20多次误报,切换加权投票后,一周的误报次数才一两次。
另外,告警推送文案尽量不要用专业术语。养殖户更关心的是“哪些鱼有问题、怎么办”,而不是“检测到白点病置信度0.87”。所以推送消息前半段是“检测到白点病疑似”,后半段链接到鱼病知识库,配置对应的处理方法说明。
4.3 应用端使用与反馈闭环的设计
系统上线后暴露出一个关键的产品问题:检测结果的准确性没有得到及时纠正。归档的告警记录如果管理人员不点“确认”,系统就不知道哪些预测是对的、哪些误判了。
所以我在Web后台加了一个反馈按钮,每次检测结果都可以标记正确、误判、无法辨识三种状态。标记后的数据会被收集到一个反馈数据集文件夹,每周自动汇总一次,人工复核后加入训练集做模型迭代。这个反馈闭环比一开始就追求高质量数据更符合实际运营节奏,数据能持续积累,模型也越跑越准。
初期养殖人员的配合度很重要。我先后给不同塘口的管理员做简易培训,就教两个动作:手机拍照上传、后台看结果点反馈。这两个动作坚持两周后,约回收了几百份有效标注数据,对模型后续迭代起到了直接帮助。
4.4 现状与数据可视化
检测系统不只是用来告警的,它沉淀下来的数据本身就是有价值的资产。后台数据看板可以按时间维度展示病害发生率趋势,按塘口维度对比不同区域的健康度。
如果一个塘口的白点病检测次数连续三天上升,就算还没有达到红色告警阈值,管理人员也会提前去调节水质或隔离感染个体。这种数据驱动的管理方式,比单纯依赖告警信息更有价值。我后续计划把水质传感器(溶解氧、pH、氨氮)的数据接入看板,将图像识别结果和环境指标放在同一时间轴上做交叉分析,这样鱼病爆发的环境诱因就能被更敏锐地发现。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 远距离鱼的病灶检测不到 | 输入分辨率不够、小目标特征丢失 | 提高推理输入尺寸到1280,或在训练时使用多尺度策略,开启TTA测试 |
| 健康鱼频繁报病 | 负样本不足、conf阈值过低 | 补充健康鱼在各种光照水质条件下的样本,调高conf阈值到0.6以上 |
| 雨天和雾天识别效果劣化严重 | 训练集缺少低可见度样本 | 采集或合成雨雾天气图片,加入高斯噪声和雾气增强 |
| 训练时显存不足(Out of Memory) | batch过大、图像尺寸过大 | batch降到8或4,开启amp混合精度训练 |
| mAP训练后期不升反降 | 学习率没有衰减到位 | 调低最终学习率或将last_epoch检测提前,手动降低lr并重新微调 |
| 推理部署后速度骤降 | 未用TensorRT没有开启batch推理,或电源模式设为省电 | 转TensorRT,设置固定batch,检查设备电源策略 |
| 检测框漂移、框到水波 | 标注质量差、背景噪声被学成特征 | 重新检查标注框贴边情况,增加负难样本,关闭mosaic增强中的混合背景干扰 |
5.2 白点病小目标漏检的原因与对策
白点病早期病灶是鱼体表面细小的白色颗粒,一颗可能只有几个像素大小,在2028x1080图像中几乎接近极限分辨率。直接使用640输入训练时,模型特征图下采样后,小颗粒特征极易被卷积核筛选过程中的池化损失掉。
我尝试的解决路径是先训练一个分辨率为640的模型作为基础权重,再用分辨率1280的数据集在基础权重上做微调,冻结前两层参数,重点训练深层特征。这样模型在定位病斑时的感受野更精细,mAP50-95提升显著。
另一个有效操作是扩展检测层。YOLOv8默认有三个检测头,覆盖大部分目标尺寸,但鱼类体表病灶目标尺寸跨度极大,我在训练配置中开启更高分辨率的特征层,增加一个P2检测头,专门负责小目标检测。经过验证,白点病漏检率下降约30%。代价是推理时间增加了一些,但整体效果可以接受。
5.3 采集端安装角度对识别率的实际影响
部署摄像头时安装高度和角度对识别影响很大。摄像头正对着水面俯视时,鱼体被水体折射干扰,体表症状细节损失严重;完全平视水面又只能拍到鱼的侧面,鱼背部的病灶完全捕捉不到。
实测最理想的角度是镜头距水面约80到120厘米,向下倾斜45度,这样鱼游过镜头视野中心时,能拍到接近正侧面的完整鱼体形态,体表病灶处于图像中心区域,畸变最小。同时摄像头最好面向投料区对岸的方向架设,让鱼群逆光游动时背部轮廓有明显的逆光剪影,让模型更容易分割出鱼体的边缘区域,同时又不会因为直射强光造成过度曝光。
支架固定时建议加装一个基础减震垫,因为增氧机搅动造成的水面波动会让摄像头抖动,录制视频抽帧时会引入大量运动模糊帧,直接影响训练数据质量。
5.4 小模型迭代的实用操作心得
系统上线后不会一劳永逸,训练集需要持续更新。每次迭代建议在原有数据基础上追加新采集的样本,而不是全部推倒重来。使用原有模型继续在增量数据集上微调,比从头训练能节省大量时间,效果也稳定得多。
模型版本管理我用简单的方式:每次迭代训练完成后,把模型文件和对应数据集的描述信息按日期命名,比如best_20250110_24epochs.onnx,这样回滚和复现都比较方便。千万不要出现“最终版_final_最终”这种情况,水产病害类别、拍摄场景会不断变化,模型版本乱了,后续排查问题会特别痛苦。
6. 算力与成本控制参考
很多想做这类系统的团队会被算力问题吓退,觉得深度学习训练必须要昂贵显卡。从我实际经验看,这套系统并没有把训练和部署在资源上拉高门槛。
训练阶段用一张显存8G的消费级显卡就够了。YOLOv8n在480张训练图、200个epoch规模下,约150分钟左右训练完成,单卡完全够用。如果数据量扩展到3000张以上,训练时间会拉长到10小时左右,但依然在可接受范围内。
真正需要重点规划的是部署端的算力成本,尤其每条塘都摆一台推理设备经济上不划算。我最终采用的方案是,一台边缘计算设备(Jetson Orin Nano或RK3588开发板)同时承载4到6路摄像头的推理请求。通过在服务端做推理队列,单帧推理时间25ms时,6路视频轮询周期仍可控制在200ms以内,完全满足应用场景。
模型温启动对推理延迟的影响不可忽略。我在Nano设备上做了30秒空闲后再续推理的对比,首帧推理延迟会从15ms飙涨到1200ms,因为GPU动态调频和显存交换需要时间。建议在工程实现中加一个定时发送空帧保活,或者将推理线程常驻,保证系统响应稳定。
关于未来的投入方向,我认为当前表单缺陷最大的瓶颈不全部在算法上,更多在鱼病知识库的沉淀和病灶到病种的智能映射上。图像识别的结果如果只停在“检测到异常”而没有“可能的病因”和“处理建议”,对养殖户的价值会打折扣。我下一步会重点把检测结果和养殖环境参数(水温、酸碱度、溶解氧)联动,实现更立体的发病风险预测。
这套系统做下来,最大的体会是:深度学习模型的训练调参是能通过文档和教程习得的,但数据采集、标注规范、工程部署这些脏活累活才是决定项目成败的真正分水岭。把数据基础打牢、把部署链路跑通,整个系统就能在真实环境中持续产生价值。