前阵子手头接了一个水面漂浮物识别的探索项目,需求说起来很简单:用摄像头拍河道水面,自动识别漂浮的塑料瓶、泡沫块、树枝这些垃圾,出框并告警,最后还要有个网页能看实时画面和统计结果。当时第一反应是“这不就是目标检测吗,跑个YOLOv8就行”,真正做下来才发现,水面这种场景远比常规的行人、车辆检测要麻烦。反光、倒影、小目标、边缘设备部署、上下游数据链路,每一个环节都有各自的门道。
这篇文章就按我实际趟过的顺序来写,从模型选型聊到数据标注,从训练调参讲到边缘部署,再把可视化平台那段一并串联起来。内容会比较长,但每段都是实操记录,希望能给正在做环保监测、智慧城市河道治理或者无人船巡检这类方向的朋友一些参考。不管你是在校学生做课题,还是工程师落地项目,应该都能找到用得上的东西。
1. 水面垃圾检测为什么是个“看着简单、做着头疼”的视觉任务
1.1 垃圾和水面背景的“长相”差异:传统算法为什么搞不定
最早接触这个需求时,我脑子里飘过几个传统方案:用颜色阈值分割、用边缘检测找轮廓、或者用背景差分建模。这些方法放在室内固定场景或许够用,放到水面上就全乱套了。
水面的特点是极度不固定。晴天太阳一照,整片水面都是高光反光,白色塑料瓶和亮白色反光区域在像素层面几乎无法区分;阴天水质偏暗,深色树枝和波纹阴影又是一伙的。再加上风一吹,倒影晃动、涟漪散开,边缘检测出来的是满屏的纹理碎片,你根本分不清哪条边是垃圾边缘,哪条边是水波造成的假边缘。背景差分更不用讨论,水面本来就在时刻变化,没有稳定的“背景”可言。
所以结论很直接:这种场景必须上学习型模型,让算法自己从大量样本里学出“垃圾长什么样”。这也是我一开始就锁定深度学习目标检测的原因。但深度学习也不是万能药,水面垃圾种类杂、尺寸差异大、样本获取难,后面每一步都需要针对“水面”这个特殊场景去优化,不能拿通用目标检测的套路直接套。
1.2 同是水面垃圾,三种落地场景的需求其实完全不同
标题里写到的环保监测、智慧城市、无人船巡检虽然都叫“水面垃圾检测”,但实际做起来,约束条件差异相当大。
环保监测往往是大范围水域巡航,关注的是垃圾密度和分布趋势。比如一次巡检测完整个湖区,需要输出“哪片区域堆积了多少垃圾”的报表,而不是实时逐帧告警。这种场景对单帧精度要求高,可以接受离线分析,模型可以适当选大一些。
智慧城市的河道监控用的是固定摄像头,全天候盯着某几段河面。这里更关心实时性,发现问题能立刻报警、联动清理。摄像头安装角度通常比较高,能看到大范围水面,但垃圾在画面里普遍偏小,还伴随路灯、桥梁、岸边的行人倒影干扰。
无人船巡检则是另一个极端:摄像头装在船上,贴近水面,视角低、晃动大,垃圾在画面里通常很大甚至充满整个视野,但运动模糊和船体倾斜又会让检测变得很难。同时船上算力受限,模型必须瘦身,还得保证不掉帧。
这三个场景侧重点完全不同,对应到工程上就是模型大小、输入分辨率、推理频率、后处理逻辑都要分别调。如果你只想“跑通一个demo”,可以不管这些差异,但要做到“能落地”,第一步就该把目标场景定清楚。
1.3 先分清“检测垃圾”和“清运治理”:需求拆解比调参重要
做这个项目头几天我犯了个典型错误:客户一说“识别水面垃圾”,我就闷头去调模型精度,结果发现对方真正要的不是“框出垃圾”这个动作,而是“这段河道是不是长期有垃圾堆积”“每天大约有多少垃圾流经”这类统计结论。
这个差别很关键。如果只做目标检测,模型输出的是每一帧的框;但业务层需要的是时间维度的聚合结果。比如同一个塑料瓶被风吹着漂过三个摄像头,如果不做跟踪和去重,你会统计出3倍的垃圾量;比如白天垃圾多、夜间垃圾少,如果不做时段聚合,日报表就是一堆剧烈抖动的数字,没法看。
所以在动模型之前,我建议先用一天时间把业务问题拆清楚:是要实时告警,还是要统计报表?是需要识别具体垃圾类别,还是只要“有没有垃圾”?最终数据是给大屏展示用,还是要传给清运工单系统?想明白这些,再决定检测类别、推理频率、可视化平台的页面结构。模型调参永远在需求之后。
2. 模型选型:YOLOv8凭什么在这个场景里成为“默认答案”
2.1 从YOLOv5到YOLOv8:两年迭代到底改了什么
选YOLOv8之前,我其实犹豫过是不是直接用YOLOv5,毕竟它的社区教程多、踩坑资料丰富。但仔细对比之后,YOLOv8的几项改动对水面垃圾场景确实更友好。
首先是网络结构上的C2f模块,它是在CSPNet基础上的进一步改造,可以在不显著增加计算量的前提下提升梯度流动能力。翻译成白话就是:同样大小的模型,yolov8s比yolov5s对细节特征的提取能力更强,这对小尺寸垃圾的召回很重要。
其次是检测头的变化。YOLOv8改成了anchor-free的decoupled head,不再需要像YOLOv5那样预设一组anchor框来匹配目标。水面垃圾形状多变,有圆的饮料瓶、有长条的树枝、还有不规则泡沫块,anchor-free结构对这类形状跨度大的目标更不容易出现“预设框和真实目标对不上”的问题。
再一个是训练过程的整体框架。Ultralytics官方仓库把训练、验证、导出、预测都做成了命令行就能跑的工具,数据格式统一,配置文件是简单的YAML,省去了大量写训练脚本和数据处理脚本的时间。对于我个人这种还要兼顾前后端开发的人,这个“省心”分值是实打实的。
2.2 和主流方案横向对比:为什么不选Faster R-CNN、RT-DETR或纯分类模型
做个简单的横向对比表格,方便你按自己情况选型:
| 方案 | 精度表现 | 推理速度 | 部署复杂度 | 对水面场景的适配度 |
|---|---|---|---|---|
| Faster R-CNN | 高,小目标表现好 | 慢,难上实时 | 高,需要自己处理很多细节 | 精度够但实时场景吃力 |
| YOLOv5 | 中上 | 快 | 中 | 稳定选手,社区资料多 |
| YOLOv8 | 高 | 快 | 低,官方导出链路完善 | 综合体验最好 |
| RT-DETR | 高,但训练和调参门槛高 | 中 | 中 | 精度潜力大,但生态还不成熟 |
| 分类模型+滑窗 | 低 | 中 | 低 | 只适合“有没有垃圾”的极简需求 |
从表格能看出,Faster R-CNN的精度上限不低,尤其是小目标检测确实有优势,但它的训练流程繁琐,从RPN到Fast R-CNN一堆环节要处理,部署到边缘设备还要单独做优化,项目周期不允许。RT-DETR我也试过,精度和速度的平衡理论上有竞争力,但训练日志里很多参数需要自己去理解,调出来一次不容易,社区里能参考的案例也少,出问题排查困难。
最终选YOLOv8还因为它有一个对工程最友好的点:导出的后端多。它自带ONNX、TensorRT、OpenVINO这些格式的导出脚本,跨平台部署时省掉了“模型转换”这一大块工作。我在后面的边缘设备部署阶段深有体会,这个特性直接砍掉了至少两天的工期。
2.3 从第一次训练就开始考虑部署:模型复用的几个现实问题
很多人做模型训练只盯着精度,忽略了一个问题:训练好的模型最终是要接进可视化平台和边缘设备的,中途会反复迭代。YOLOv8在这方面有个很省心的设计:不管我后来导出成ONNX还是TensorRT格式,原始训练环境和推理时的调用方式都保持了一致。
比如同一套Python代码里,训练时用YOLO("yolov8s.pt"),推理时用YOLO("best.pt"),导出引擎后还是用同样的类去加载.engine文件,接口没有换成另一套。这意味着可视化平台的后端推理服务只需要针对Ultralytics的接口做一次封装,模型文件随便换,代码不用动。
这一点在水面垃圾场景里特别有用。初期数据量小、模型不够准,等补充了大量反光样本再训练一个新模型,平台侧只需要替换模型文件路径,重启服务,前端什么都不用改。如果你选一个训练框架和推理框架分离的方案,每次更新模型都要重新改接口、调参数,项目的后期维护成本会成倍上涨。
3. 数据决定上限:水面垃圾数据集的采集、清洗与标注
3.1 公开数据集很多,但水面垃圾场景真的得自己动手
做深度学习项目,第一步就是找数据。VOC、COCO这些公开数据集里虽然有瓶子、杯子这类物体,但它们的背景绝大多数是室内、街道或自然风景,水面垃圾样本少得可怜。直接拿COCO预训练的模型去测试河道画面,效果很差,这不是模型不行,是训练数据分布和实际场景差太远。
后来我找到了少量和水面漂浮物相关的开源数据,也参考了一些论文里提到的水体垃圾数据集,但数量普遍不大,而且拍摄视角、气候条件和我自己项目里的差异很大。最靠谱的办法只能是自己采一部分,再结合公开数据混合使用。
我当时的做法是:在目标河道的几个固定点位用摄像头连续录了一周素材,每天白天多个时段,包含晴天、阴天、起风天气。素材量不小,但有用的帧其实不多,因为垃圾不会连续出现。我就写了个简单的抽帧脚本,每5秒抽1帧,再手动筛掉没有目标的空画面。这样一个星期录下来,最后保住的有效目标帧也就一千多张。数据采集这件事永远是笨功夫,没有捷径。
3.2 标注规范怎么定:类别、遮挡、小目标三件事
标注环节直接决定模型能力的边界。这里的每一条规则都是踩坑踩出来的。
第一件事是类别划分。我一开始分了“塑料瓶、泡沫、树枝、船只”四个类,后来发现“泡沫”在远处和白色反光区域太像,误检严重;而“树枝”和“木棍”在不同光照下形态差异又很大,标注人员都经常犹豫。最后干脆按项目实际需求合并成两个大类:“漂浮垃圾”和“疑似漂浮物”,只在日志里保留细分类置信度,这样模型的学习任务简化了,准确率明显提升。
第二件事是遮挡目标怎么标。垃圾经常只有半截露在水面上,另一半在水下或者被波纹遮挡。标注规范里明确写了“只要可见面积超过目标整体的四分之一,就必须标注”,不能因为“不确定是不是完整瓶子”就漏掉。否则模型训练时会觉得“不完整的物体不是目标”,推理时漏检率飙升。
第三件事是小目标的标注。水面监控画面里,远处一个塑料瓶可能只有十几个像素。这类目标即使人眼看着也费劲,但偏偏是无人船巡检和大范围监控里最常见的样本。我的规则是“只要有明确轮廓,再小也标”,并且单独统计小目标样本数量,不够就继续补录。你可以想象一下,一个目标在画面里只有米粒大小,标注的时候要放大到几百倍才能看清轮廓,这个工作确实枯燥,但直接影响小目标召回率。
3.3 数据增强:反光、雨雾、季节差异怎么“模拟”出来
水面场景有个特点:不同光照条件下,同一个垃圾拍出来长得完全不一样。晴天正午的高光下,白色泡沫盒几乎和反光融为一体;阴天暗光下,黑色塑料袋又和深色水面难以区分;雨天的涟漪、晨雾的朦胧感都会大幅改变图像特征。
数据增强在这时候就派上大用场了。我用的增强手段分几类:亮度对比度随机扰动用来模拟一天中不同时间段的光照变化;HSV色相饱和度扰动模拟不同水质和藻类浓度差异;随机旋转翻转模拟不同摄像头安装角度;Mosaic增强用来提升模型在小目标上的鲁棒性,四张图拼成一张训练图,小目标密度成倍上来了。
这里有一个很重要的心得:增强不是越猛越好。我一开始把亮度扰动范围调得特别大,希望模型能适应各种极端光照,结果训练时loss怎么都降不下去,模型反而在正常光照下的表现也变差了。后来把扰动范围收到和实际采集素材差不多的区间,精度立刻上了一个台阶。增强的作用应该是“覆盖真实场景中的正常变化”,而不是“凭空制造模型没见过的情况”。
4. 训练调参实录:从环境配置到损失函数的完整过程
4.1 环境配置清单和版本兼容排查
训练环境这块看着简单,但版本不匹配会带来各种莫名其妙的问题。我最终折腾下来比较稳定的组合是:Python 3.10 + PyTorch 2.1.x + CUDA 11.8 + ultralytics 8.x版本。这几个版本组合在Windows和Linux上都验证过,能正常跑通训练和导出流程。
安装命令很简单,一行就行:
pip install ultralytics它会自动拉取PyTorch和依赖项。但我建议装完以后先跑一个小的验证实验再开始正式训练,比如用官方自带的coco8.yaml数据集训练10个epoch,确认GPU能用、数据管线没坏、loss能正常下降。这一步只花几分钟,却能排除掉“显卡驱动和CUDA版本不匹配”“cuDNN加载失败”“数据路径配错”等一大堆坑。
数据格式上,YOLOv8用的是标准的YOLO格式目录结构,需要准备好images和labels两个目录,标签是TXT文件,每行是“类别id x_center y_center width height”,坐标全部归一化到0到1之间。我的数据配置文件长这样:
path: ./water_garbage_dataset train: images/train val: images/val nc: 2 names: ['rubbish', 'suspect']这里的nc和names必须和标注时的类别顺序一一对应,顺序错了整个模型输出就全乱了。这个错误我犯过一次,后面在可视化平台里调试了好久才发现原来是类别顺序对不上。
4.2 超参数选择的思路:imgsz、batch、epoch怎么定
超参数这一节,我直接给出实践结论。
输入尺寸imgsz是最容易被忽略但影响最大的参数。YOLOv8默认是640x640,对大多数行人车辆检测足够,但水面垃圾里小目标含量很高,640输入下远处的小塑料瓶可能只有3x3像素,网络根本无从学起。我的做法是训练时用1280起步,推理时也保持相同分辨率。不要担心这个尺寸会让训练变慢,YOLOv8内部会自动调整batch和图像比例,实测精度提升比任何增强都显著。
batch大小取决于显存。一块24G显存的卡,1280分辨率下用batch=8比较稳妥;如果显存只有8G,建议要么降到batch=2,要么把输入尺寸回退到960。batch太小会影响BatchNorm的稳定性,所以你不用刻意去划算法,能填多大填多大,同时保证不爆显存就行。
epoch不是越多越好,我一般设置200,然后开启patience=30的早停机制。如果连续30个epoch验证集没有提升,训练自动结束。实际上我的模型大多在第120到150个epoch之间就收敛了,再往后纯属浪费算力。
优化器方面,官方默认的AdamW表现稳定,学习率初始值我用的是默认的0.002,没有额外调整。如果你想进一步提点,可以在训练结束后用auto模式让YOLOv8自动搜一遍超参数,但那个搜索过程比较耗时,数据量不够大时不建议开。
4.3 训练曲线怎么看:过拟合、类别不平衡、不收敛
训练过程中不要只盯着终端打印的mAP数字,我习惯每隔几个epoch存一张训练图表。重点看两个指标:mAP50和mAP50-95。mAP50表示预测框和真实框的IoU超过0.5就算正确,比较宽容,适合看大目标效果;mAP50-95要求IoU在0.5到0.95之间取平均,非常严格,能反映检测框位置和类别判定的综合质量。
如果出现训练loss一路下降、验证loss却回升,基本可以断定过拟合了。水面垃圾数据集一般都不大,这种问题很常见。解决办法是增加数据增强强度、补充更多同类样本,或者适当减小模型复杂度,从yolov8s降到yolov8n试一下。
类别不平衡这个问题我做的时候深有体会。我的样本里,塑料瓶数量是树枝的三四倍,训练出来的模型对塑料瓶灵敏度很高,树枝却经常漏检。看训练曲线时,树枝类别的AP值低得可怜。后来我用两个手段联合解决:一是给数量少的类别增加样本,二是用加权损失函数,让数量少的类别在loss计算时权重更大。YOLOv8没有内置直接的类别权重参数,我是在训练脚本里手动调整了样本采样权重,经验上能把弱势类别的AP拉高五六个百分点。
4.4 训练命令参考
最终我的完整训练命令如下,可以直接参考:
yolo train \ model=yolov8s.pt \ data=water_garbage.yaml \ epochs=200 \ imgsz=1280 \ batch=8 \ device=0 \ patience=30 \ project=runs/train \ name=water_v1如果想在训练基础上继续微调,比如在模型快收敛时换用更细的增强策略,就加载上次的权重接着训练:
yolo train \ model=runs/train/water_v1/weights/best.pt \ data=water_garbage.yaml \ epochs=50 \ imgsz=1280 \ batch=8每次训练的结果会单独存一个目录,里面包含weights和训练曲线图表,方便对比不同版本的效果。这个习惯很重要,后面部署阶段经常会需要回滚到某个历史版本。
5. 走出实验室:模型导出、推理优化和边缘端实测
5.1 导出ONNX和TensorRT:精度对齐和性能对比
训练完成后,模型文件是.pt格式,它能train能predict,但直接塞进边缘设备跑效率很低。我的做法是先导出ONNX,再在目标设备上转成TensorRT引擎,因为TensorRT的推理速度能比ONNX再快两三倍。
导出命令:
from ultralytics import YOLO model = YOLO("runs/train/water_v1/weights/best.pt") model.export(format="onnx", imgsz=1280, dynamic=False)导出后一定要做一次“精度对齐”检查。拿同一批测试图片分别跑.pt和ONNX模型,对比输出框的数量和坐标。理论上ONNX和PyTorch结果应该几乎一致,但如果某个算子导出后有偏差,你会发现框的位置有细微漂移。实测YOLOv8的导出链路做得很完善,我没有遇到精度掉点的问题。
边缘端转TensorRT时,如果设备支持FP16,可以启用半精度:
model.export(format="engine", imgsz=1280, half=True, device=0)FP16在推理速度上有巨大提升,精度损失通常不超过1个百分点,水面垃圾检测对框的位置精度要求不高,这个损失完全可接受。再激进一点可以做INT8量化,但INT8对数据分布敏感,量化后小目标很容易直接消失,我建议慎重。
5.2 推理速度实测:不同设备的性能数据
我在测试阶段记录了不同配置下的推理耗时,给你一个参考区间。一张1280x1280的输入图,在消费级桌面显卡上,yolov8s的FP16 TensorRT推理大约需要15到25毫秒;在常见的边缘计算设备上,同模型大约40到80毫秒;如果用CPU跑ONNX,那就要按秒算了,基本告别实时场景。
对于智慧城市的固定摄像头场景,40到80毫秒的延迟完全够用,视频流25帧每秒完全能跟上。对于无人船巡检,由于船速不快,10帧每秒就够,所以40毫秒的推理时间也满足需求。
这里有个实战技巧:不要对整个3200x1440的原始监控画面直接推理,那样显存占用大、小目标又看不清。正确做法是先按场景画一个“水面检测区域”的ROI,只对ROI区域做推理。这样既减少了无效计算,又显著降低了岸上行人、车辆等因素造成的误检。我这套方案实测正确率提升不少,而且推理耗时直接砍半。
5.3 无人船视角的特殊考验:运动模糊和低位视角
说完固定摄像头,再重点聊聊无人船巡检这个场景。船上摄像头是随船晃动的,曝光时间稍长一点画面就全糊了。我试过几种缓解办法:一是把相机曝光时间调短,牺牲一点暗部细节换取画面清晰;二是在船上加一个简单的机械防抖云台;三是在算法层面,对连续帧做去模糊预处理,但这个成本高,效果有限。
还有一个视角问题。固定摄像头是高视角俯拍,垃圾是“浮在水面上”的;无人船是低视角平拍,垃圾在水面上“立起来”了。同样的塑料瓶,在两种视角下特征差异巨大。如果你只拿固定摄像头的样本训练模型,直接上船跑,漏检率会非常难看。所以无人船项目一定要单独采集低视角样本,并且船舷附近经常有白色水花飞溅,这些水花和白色泡沫垃圾在模型眼里也非常像,必须补充大量负样本让模型学会区分。
6. 可视化平台:检测结果怎么变成能用的业务闭环
6.1 平台功能设计:不要贪多,MVP先跑通这四件事
可视化平台是标题里提到的核心交付物,但别一上来就想做成大屏加报警加工单派发加报表分析的全套系统。我做第一版时只做了四件事:实时画面、检测框叠加、报警记录、时段统计。
实时画面是最直观的展示,前端播放视频流,后端把模型检测的结果在每一帧上画框再推给前端。报警记录就是后端检测到目标后写入数据库,前端有一个列表展示,包含时间、点位、置信度、截图缩略图。时段统计按小时聚合报警数量,用柱状图展示,让运营人员一眼看出一天中哪几个时段垃圾出现频率高。
不要小看这个极简MVP,它能让你在一周内把完整链路跑通,验证每个环节是否可靠。等基础设施稳固了,再加垃圾密度热力图、清理工单联动、趋势预测这些进阶功能。
6.2 技术选型:FastAPI做推理服务,WebSocket推检测结果
后端我用的FastAPI,理由很简单:它是异步框架,天然适合处理视频流推理这种IO密集型任务;内置OpenAPI文档,调试方便;和Python端的YOLOv8推理代码同属一个生态,不用跨语言。
整体链路是这样的:摄像头RTSP流通过FFmpeg拉流解码成帧,送入推理服务,推理结果生成标注后的JPEG图像,再通过WebSocket推送到前端展示。报警信息同时写入数据库,供统计报表查询。前端用一个轻量的Vue应用,实时视频区域铺低延迟的视频流,底下是报警列表和图表。
队列管理是个容易被忽略的细节。多个摄像头同时接入时,如果每个摄像头都独立起一个推理进程,硬件资源很快耗光。我的做法是设计了一个producer-consumer模型:采集端只管把帧丢进队列,推理端从队列取帧处理,队列满时主动丢帧,保证系统不堆积延迟。这套架构下接入十路摄像头问题不大。
6.3 模型和前端之间的“翻译层”:为什么我一直强调类别顺序
我第一次搭平台时犯过一个特别蠢的错误:模型里训练时类别顺序是['rubbish', 'suspect'],但前端展示面板里写死了类别列表为['塑料瓶', '泡沫', '树枝', '船只']。结果模型输出的框全是“倒挂”的,明明检测到树枝,前端却标成塑料瓶。
这个问题听着低级,但实际很容易踩。因为你每次更新模型,类别顺序可能因为重新标注而变化,前端和后端如果没有一个配置文件统一管理类别列表,就很容易出现这种错位。我的解法是:在后端增加一个类别映射配置文件,每次模型更新时必须同时更新这个配置,前端展示时只认配置里的名称和颜色,不关心模型输出里具体的id是多少。这样即使类别顺序变了,前端展示也始终正确。
7. 踩坑记录:水面反光、小目标漏检与一堆工程细节
7.1 反光和倒影导致的误检:加了“检测区域掩码”才压下来
晴天下午时段,我的模型误检率突然飙升,查了日志发现大量的“垃圾”其实都是岸上树木和建筑物的倒影。水面平静的时候,倒影和水面本身几乎是镜像关系,模型很难区分“真实的物体”和“水中的影子”。
第一版我用提高置信度阈值的办法,从0.25提高到0.45,误检确实少了,但真实的目标也被滤掉不少,漏检率上来了。后来我换了个思路:在图像上叠加一个检测区域掩码,把摄像头画面中明显是岸边的区域直接置为黑色,模型根本看不到岸上的物体,倒影问题从源头被切断。再配合负样本积累,比如把没有垃圾但有强烈倒影的画面单独存了一批,让模型见过足够多“假目标”,误检率才真正压到可接受的范围。
这里有个成本极低的小技巧:对摄像头做一次物理遮挡,把镜头前岸上区域贴掉一部分,或者在算法层面对ROI坐标做一次固定写死,都能大幅降低干扰。不需要依赖高深算法,工程思维往往比模型技巧更有效。
7.2 小目标漏检:切片推理和加重训练样本,按需二选一
虽然我把训练分辨率提到了1280,远距离的小塑料瓶还是会出现漏检。我试了两种主流方案来解决小目标问题。
第一种是提高输入分辨率到1600甚至1920,代价是显存和推理时间几乎翻倍,收益却逐步递减。实测从1280提到1600,小目标mAP只涨了1个百分点,不划算。
第二种是切片推理。把原始大图按长宽各50%的重叠切成多个小块,每一块分别送进模型推理,再按坐标拼回原图,相当于“放大”了每个小目标的像素面积。这个方法对小目标召回有显著改善,但计算量成倍增加,而且还要处理拼接边缘处目标被切断的问题。对于无人船这种实时性要求高的场景,切片推理并不友好。
我的最终方案是基于项目场景权衡的:固定摄像头画面里垃圾出现位置相对固定,我先用ROI区域把可能出现的区域缩小,再在这个区域内适度提高输入分辨率;无人船视野里垃圾普遍偏大,小目标问题反而不严重。所以小目标优化没有银弹,最终要根据你自己项目的摄像头视角来决定。
7.3 模型版本更新的“暗坑”:灰度发布和历史回滚
模型迭代过程中还有一个容易被忽视的问题:并不是每次训练完的新模型都一定比旧版好。有几次我给模型补充了新数据,重新训练后发现某个类别的AP反而不如旧版,这种情况在数据量小、类别分布变化时非常常见。
应对方法是把训练好的模型文件按版本管理。我用的规则是模型文件和配置文件放进同一个目录,文件名带日期和关键参数,比如best_20250108_s1280_fp16.pt。更新到可视化平台时,先保留当前生产环境的模型文件,新模型先在测试环境里跑一周,对照报警准确率和漏检率,确认没有退化再切换。
同时模型的类别配置文件也必须有版本对应。每次训练完,我会把数据配置YAML和模型文件一起打包存档,这样即使三个月后要回滚到某一版,也能保证类别顺序和模型输出绝对匹配。这个习惯帮我省过不止一次麻烦。
常驻在推理服务里运行模型时,还要注意显存释放问题。长时间运行的边缘设备,如果模型推理代码里有内存泄漏,运行几天后显存会被耗尽,导致服务崩溃。我在推理服务启动时加了一段显存监控日志,每半小时记录一次占用情况,一旦发现显存持续上涨就能第一时间定位问题代码。这类问题不发生在训练阶段,但偏偏是现场运维最头疼的。
最后再分享几点心里话
整套系统从数据采集到可视化平台上线,前后花了一个多月,中间踩过的坑比预期多不少。如果让我重新做一次,我会把更多时间压在数据采集和标注规范上,而不是反复调模型超参数。水面垃圾这个场景太依赖真实样本了,没有足够覆盖各种光照和视角的数据,再先进的模型也白搭。
另外建议所有做这类项目的朋友,从一开始就想清楚模型和平台之间的接口问题。训练模型不比精度,更要关注可部署性、可替换性、可回滚性。系统的长期稳定性往往取决于这些工程细节,而不是某一次的mAP涨了几个点。
如果你正准备做类似的检测系统,我的建议很简单:确定场景,收集能覆盖场景全部变化的数据,标注时把类别口径定死,训练时盯住小目标和反光这两个专属难点,部署时做好版本管理。照着这个顺序走,整体过程不会太痛苦。后续这个项目我还在往垃圾密度热力图和自动巡检路径规划的方向扩展,等有新进展再单独写一篇。