水下垃圾污染已经成为海洋生态治理中很难回避的问题。传统清理方式依赖潜水员、拖网和目视巡检,效率不高,也容易受天气、水流和深度影响。计算机视觉和深度学习成熟之后,AI 检测水下垃圾逐渐成为替代人工巡检的重要方向。这里说的 AI 检测水下垃圾,并不是让模型看一眼图片就输出“有垃圾”这么简单,而是要在水下视频帧中定位出垃圾类别和像素范围,输出可用于机械臂、AUV(自主水下航行器)或人工清理班组的坐标信息。
这篇博客会围绕“AI 检测水下垃圾”的完整工程链路展开,从水下成像特点、数据标注格式、模型选型、训练验证,到部署常见问题和持续迭代,尽量做到每一步都有可落地的代码或命令。读者如果是刚接触目标检测的开发者,可以按文章顺序搭出一套最小可运行的水下垃圾检测流程;如果已经在做相关项目,排错和最佳实践部分可以直接拿来当检查单。
1. 先理解水下垃圾检测的任务边界
1.1 它不是普通目标检测:水下成像质量是第一个约束
陆地目标检测已经比较成熟,因为图像清晰、光照稳定、类别外观相对统一。水下环境完全不同,光线随深度按指数衰减,水体对不同波长的光吸收程度也不一样,红色和黄色最先消失,图像整体偏蓝绿色,对比度低。再加上水中悬浮颗粒造成的散射和反射,画面会出现类似雾霾的效果,远处物体边缘模糊,甚至完全看不清轮廓。
这些成像特性直接影响了 AI 检测水下垃圾的难度。如果直接把陆地目标检测的模型拿到水下图片上训练和推理,通常会遇到两个问题:一是模型学到的是“清晰物体的纹理”,而不是“垃圾的轮廓和形态”;二是训练数据和水下实地拍摄数据之间的颜色分布差异太大,导致验证集 mAP 很高,到了真实视频里却漏检频繁。
所以,在设计方案时不能只把 AI 检测水下垃圾看作“目标检测框架套数据”,而是要把它当作一个“低对比度小目标检测 + 水下图像恢复/增强”的复合问题来处理。这也是后续数据增强、模型选型和训练策略都必须围绕的核心。
1.2 典型垃圾类别与检测难点
水下垃圾常见类别包括塑料瓶、塑料袋、渔网、金属罐、玻璃瓶、绳索、轮胎、橡胶制品等。不同类别的物理特性和沉底状态差异很大:
- 塑料瓶形状规则,但容易被水流推动或部分埋入泥沙。
- 渔网通常呈线状、网状,目标长宽比极端,传统锚框很难匹配。
- 金属罐尺寸不大,表面可能反光,在弱光水下反而容易造成高亮斑块。
- 玻璃瓶透明,轮廓和背景重叠严重,模型很难区分边界。
- 绳索和细长物体容易被误判为水草或线缆。
除此以外,水下垃圾还存在严重的类内差异。同样是塑料瓶,矿泉水瓶、饮料瓶、洗洁精瓶在颜色、透明度、体积上差异很大。模型的泛化能力不只是“记住类别”,还要能理解“废弃物”和“自然物”之间的功能区分。这个语义层次比陆地目标检测更高,也是数据标注时需要特别设计规则的原因。
1.3 为什么用深度学习,而不是传统图像处理
传统水下垃圾识别方案一般走边缘检测、颜色空间变换、阈值分割和形态学处理路线。这种方法在处理单一场景、固定位置、光照稳定的条件下有效,但遇到复杂背景、遮挡、光照突变就会失效。原因是水下图像的边缘信息受噪声干扰严重,单纯靠颜色阈值很难同时兼顾多类别目标。
深度学习目标检测模型通过卷积层自动学习从底层边缘到高层语义的特征表达,对光照变化、尺度变化和部分遮挡有更强的鲁棒性。更关键的是,目标检测模型输出的不只是类别概率,还有边界框坐标,这个坐标信息可以直接转成机械臂抓取点或 AUV 航点,工程价值更高。对于需要实时分析水下视频的巡检场景,单阶段检测器的推理速度也远优于传统滑动窗口加分类器的方式。
当前常用的检测框架有两类。一类是两阶段检测器,以 Faster R-CNN 为代表,精度高但速度偏慢,适合离线分析水下照片;另一类是单阶段检测器,以 YOLO 系列和 SSD 为代表,速度快且部署方便,适合水下视频实时处理。水下垃圾检测应用更看重实时性和部署成本,因此本文的示例代码以 Ultralytics YOLOv8 为基础,它同时支持检测、分类和分割任务,并且有比较完整的训练与部署生态。
2. 环境准备与数据组织
2.1 开发环境与依赖版本
在开始之前,先把环境准备好。这里给出一个经过验证的组合,但不代表所有环境都必须完全一致,落地前要先确认自己的驱动和 CUDA 版本。
| 软件 | 推荐版本 | 用途 |
|---|---|---|
| Python | 3.9 或 3.10 | 运行训练和推理脚本 |
| PyTorch | 2.1 或 2.2 | 深度学习框架 |
| CUDA | 11.8 或 12.1 | GPU 加速计算 |
| ultralytics | 8.x | YOLOv8 训练与推理 |
| OpenCV | 4.8 | 图像读取与预处理 |
| labelme / CVAT | 最新稳定版 | 数据集标注 |
在纯净环境中,可以使用如下命令安装核心依赖:
python -m venv .venv source .venv/bin/activate pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python numpy matplotlib安装完成后运行一次导入检查,确认环境没有发生底层库冲突:
python -c "import torch, ultralytics; print(torch.__version__); print(ultralytics.__version__)"如果输出两个正常版本号,说明基础环境可用。接下来准备数据。
2.2 公开数据集和自建数据集的取舍
训练水下垃圾检测模型需要带标注的图片。公开数据集中,TrashCan 是出现频率较高的水下垃圾标注数据集,包含塑料、布料、金属、橡胶等多个类别;Marine Debris Archive 则偏重于收集不同水体环境中的垃圾图像。Kaggle 上也有一些水下垃圾检测竞赛和公开图像集,类别命名和标注风格各不相同。
使用公开数据集的好处是起步快、标注统一,缺点是数据来源往往是某个海域或某种水质环境,直接用于其他水域会出现域偏差。实际项目更推荐“公开数据预训练 + 自建数据微调”的组合:
- 第一阶段:用公开数据集训练一个能够提取“垃圾通用特征”的基线模型。
- 第二阶段:采集目标水域的视频帧,挑选有代表性的样本,按统一规范重新标注。
- 第三阶段:用自建数据微调模型,并将公开数据中的部分样本与自建数据混合,防止灾难性遗忘。
如果项目预算有限,也可以先不自己采集,用公开数据先跑通整条流程,等到已经有初步推理能力后,再针对漏检样本做定向数据补充。这样能以最低成本验证技术可行性。
2.3 数据目录结构与标注格式
水下垃圾检测的数据组织可以参考 YOLO 的目录规范。下面是一个最小可用的结构:
underwater-trash/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yamlYOLO 格式的标注文件是文本文件,每一行代表一个目标,格式为:
class_id x_center y_center width height其中x_center、y_center、width、height都是相对于图像宽度和高度的归一化值,范围在 0 到 1 之间。例如一张 1920x1080 的图中有一个塑料瓶,中心坐标是 (960, 540),宽度是 200,高度是 400,那么标注行是:
0 0.5 0.5 0.1041667 0.3703704这里width=200/1920≈0.1042,height=400/1080≈0.3704。类别编号从 0 开始,如果后续想增加“塑料瓶、渔网、金属罐、玻璃瓶”四个类别,编号对应为:
| 类别 | 编号 |
|---|---|
| plastic_bottle | 0 |
| fishing_net | 1 |
| metal_can | 2 |
| glass_bottle | 3 |
如果原始标注是 COCO 格式的 JSON,需要转换成 YOLO txt 格式。转换时最容易犯的错是忘记做归一化,导致训练时报“box坐标越界”或者模型完全无法收敛。下面是一个简单转换脚本,用于说明思路:
import json import os def coco_to_yolo(json_path, output_dir): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) categories = {c['id']: i for i, c in enumerate(data['categories'])} for img in data['images']: img_id = img['id'] file_name = img['file_name'] img_w = img['width'] img_h = img['height'] label_lines = [] for ann in data['annotations']: if ann['image_id'] != img_id: continue cat_id = categories[ann['category_id']] bbox = ann['bbox'] # [x, y, width, height] x, y, w, h = bbox cx = (x + w / 2) / img_w cy = (y + h / 2) / img_h nw = w / img_w nh = h / img_h # 防止浮点误差导致越界 cx = min(max(cx, 0), 1) cy = min(max(cy, 0), 1) nw = min(max(nw, 0), 1) nh = min(max(nh, 0), 1) label_lines.append(f"{cat_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") txt_name = os.path.splitext(file_name)[0] + '.txt' with open(os.path.join(output_dir, txt_name), 'w') as f: f.write('\n'.join(label_lines))转换之后要检查标注框是否越界。YOLO 训练一般会忽略越界标注,但越界会产生空标注文件,导致该图片被跳过,数据量白白流失。
2.4 数据增强要模拟水下环境
通用目标检测训练里,翻转、缩放、旋转和 HSV 扰动是标配。水下场景光在水中的吸收和散射特性与陆地不同,所以数据增强还应该包含以下策略:
- 色偏模拟:随机把图像向蓝绿色调偏移,模拟不同水深的颜色吸收。
- 模糊增强:使用高斯模糊模拟悬浮颗粒造成的散射。
- 亮度抖动:模拟光照突变和水面波纹造成的高亮区。
- 低对比度增强:模拟浑浊水体中的对比度下降。
- Cutout/随机遮挡:模拟鱼群、气泡和悬浮物遮挡目标。
Ultralytics YOLOv8 可以通过data.yaml和训练参数中的增强配置控制。例如在训练脚本中开启 HSV 扰动和翻转,并设置合理的degrees旋转范围,避免大角度旋转改变物体“沉底”的物理语义。水下目标通常是自由漂浮或躺在海底的,过大的旋转不一定合理。
下面的data.yaml是一个基础配置:
path: /data/underwater-trash train: images/train val: images/val names: 0: plastic_bottle 1: fishing_net 2: metal_can 3: glass_bottle数据校验这一步不建议跳过。在训练前先写一个脚本检查图片能否被 OpenCV 读取、标注文件是否存在、类别编号是否在合法范围内。这样可以提前暴露路径错误和数据损坏问题,省下大量调试时间。
import cv2 import os from pathlib import Path def validate_dataset(data_root): train_img_dir = Path(data_root) / "images" / "train" train_label_dir = Path(data_root) / "labels" / "train" bad_files = [] for img_path in train_img_dir.glob("*.jpg"): img = cv2.imread(str(img_path)) if img is None: bad_files.append((str(img_path), "image read failed")) continue label_path = train_label_dir / (img_path.stem + ".txt") if not label_path.exists(): bad_files.append((str(label_path), "label missing")) continue with open(label_path, "r") as f: for line in f: parts = line.strip().split() if len(parts) != 5: bad_files.append((str(label_path), "bad line format")) break cls_id = int(parts[0]) if cls_id < 0 or cls_id > 3: bad_files.append((str(label_path), f"invalid class {cls_id}")) break if bad_files: print("Found %d problems:" % len(bad_files)) for path, reason in bad_files[:20]: print(path, reason) else: print("Dataset validation passed.") if __name__ == "__main__": validate_dataset("/data/underwater-trash")3. 用 PyTorch + YOLOv8 搭建最小检测流程
3.1 为什么选择 YOLOv8 而不是从零写网络结构
水下垃圾检测项目的核心目标通常是把技术路线跑通,并在真实场景中看清模型的不足。如果从零实现 Faster R-CNN 或自注意力网络,会花费大量时间在数据加载、训练管线和后处理上,不利于快速验证。YOLOv8 在速度和精度之间提供了一个比较平衡的默认值,训练代码、预训练权重和部署工具都相对完整,适合作为基线模型。
如果你的项目最终需要部署到嵌入式设备,YOLOv8 也支持导出 ONNX、TensorRT 和 NCNN 格式。先跑通标准流程,再针对设备做剪枝、量化和算子替换,是比较稳妥的路径。如果使用的是更新版本的 YOLO 系列,配置方式大同小异,只需根据官方文档调整参数名。
3.2 训练前的数据划分与验证集
训练前要把图片划分为训练集、验证集和测试集,最好不要直接使用公开数据集自带的划分,因为不同来源的图片可能重复,或者某类目标过于集中在某段连续帧中。下面是一段基于随机种子划分数据的脚本:
import random from pathlib import Path import shutil random.seed(42) src_img_dir = Path("/data/underwater-trash/images") src_label_dir = Path("/data/underwater-trash/labels") train_ratio = 0.8 val_ratio = 0.1 img_files = list(src_img_dir.glob("*.jpg")) + list(src_img_dir.glob("*.png")) random.shuffle(img_files) train_files = img_files[:int(len(img_files) * train_ratio)] val_files = img_files[int(len(img_files) * train_ratio):int(len(img_files) * (train_ratio + val_ratio))] test_files = img_files[int(len(img_files) * (train_ratio + val_ratio)):] for split, files in [("train", train_files), ("val", val_files), ("test", test_files)]: split_img_dir = src_img_dir.parent / "images" / split split_label_dir = src_img_dir.parent / "labels" / split split_img_dir.mkdir(parents=True, exist_ok=True) split_label_dir.mkdir(parents=True, exist_ok=True) for img_path in files: label_path = src_label_dir / (img_path.stem + ".txt") if not label_path.exists(): continue shutil.copy(img_path, split_img_dir / img_path.name) shutil.copy(label_path, split_label_dir / label_path.name)这里有一个常见的坑:同一个物体的连续帧必须放在同一个划分里,否则训练集和验证集会非常相似,验证曲线虚高,真实场景一测就崩。建议在划分前对连续帧做去重或按视频 ID 分组。
3.3 训练命令与关键参数
数据准备好后,可以用 Ultralytics 的 Python API 或命令行启动训练。下面是一个 Python 训练脚本:
from ultralytics import YOLO model = YOLO("yolov8s.pt") model.train( data="/data/underwater-trash/data.yaml", epochs=100, imgsz=640, batch=16, device=0, workers=4, cos_lr=True, lr0=0.001, augment=True, patience=20 )参数说明如下:
| 参数 | 含义 | 调大/调小影响 | 推荐设置 |
|---|---|---|---|
| imgsz | 训练输入尺寸 | 调大能提升小目标检测精度,但显存占用和训练时间增加 | 640 或 1280 |
| batch | 每 GPU 的批大小 | 调大加速稳定,但显存不足会中断 | 按显存决定,16 或 32 |
| epochs | 训练轮次 | 过小欠拟合,过大没有明显收益 | 100 到 200 |
| lr0 | 初始学习率 | 过大 Loss 震荡,过小收敛慢 | 0.001 左右 |
| patience | 早停耐心值 | 小则过早停止,大则浪费时间 | 20 到 30 |
| cos_lr | 余弦退火调度 | 让学习率平缓下降,收敛更稳 | 推荐开启 |
训练时如果使用预训练权重yolov8s.pt,模型会加载在 COCO 上学习的通用特征,再迁移到水下垃圾目标上,收敛速度会比随机初始化快很多。第一次训练不建议直接使用最大分辨率,先用 640 跑通,确认数据没有问题,再提高分辨率看是否有提升。
命令行训练方式更加简洁:
yolo detect train data=/data/underwater-trash/data.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0训练结束后,runs/detect/train/weights/目录下会生成best.pt和last.pt。best.pt是在验证集上表现最好的权重,last.pt是最后一个 epoch 的权重,一般部署时选择best.pt。
3.4 推理与结果可视化
训练完成后,先用单张测试图片验证推理流程:
yolo detect predict model=/data/underwater-trash/runs/detect/train/weights/best.pt source=/data/underwater-trash/images/test/trash_001.jpg conf=0.25如果需要在 Python 脚本中集成推理,并输出检测框坐标用于后续机械臂控制,可以参考下面的代码:
from ultralytics import YOLO model = YOLO("/data/underwater-trash/runs/detect/train/weights/best.pt") results = model.predict( source="underwater_video.mp4", conf=0.25, iou=0.45, stream=True ) for frame_id, result in enumerate(results): boxes = result.boxes if boxes is None: continue for box in boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) x1, y1, x2, y2 = box.xyxy[0].tolist() print(frame_id, cls_id, round(conf, 3), [round(v, 1) for v in (x1, y1, x2, y2)])推理时conf=0.25表示置信度阈值,低于该值的框会被过滤;iou=0.45用于 NMS 去除重叠框。水下图像模糊、目标边缘不清晰,阈值设置过高会漏检,过低会造成大量误检。建议根据实际场景做小范围搜索,不必拘泥于默认值。
4. 验证模型是否真的能用
4.1 用 mAP、Precision 和 Recall 判断模型质量
训练过程中,Ultralytics 会在验证集上实时计算一组指标,其中最重要的是:
- Precision:预测为正例的框中有多少是真正的垃圾。
- Recall:真实垃圾中有多少被模型找了出来。
- mAP50:IoU 阈值为 0.5 时的平均精度均值。
- mAP50-95:IoU 阈值从 0.5 到 0.95 的平均值,指标更严格。
水下垃圾检测中,Recall往往比Precision更值得关注。漏检一个垃圾比误检一个石块更容易造成清理遗漏。但也不能无限提高 Recall,否则系统会对鱼、珊瑚、水草产生大量误报,影响自动化设备决策。
评估时不要只看平均值,要按类别查看单类指标。很多项目整体 mAP 不错,但渔网、绳索等细长物体类别的 AP 非常低。出现这种情况时,需要专门为这些类别增加训练样本或调整锚框策略。
4.2 离线评估和真实视频验证为什么不等价
离线评估使用静态图片测试集,反映的是模型在“单帧清晰图像”上的能力。真实水下巡检通常是视频流,模型要面对连续帧的动态变化、相机运动模糊、悬浮物遮挡和水流造成的目标形变。因此,只报告测试集 mAP 不够,还需要在真实视频或模拟水下环境中做一次端到端验证。
验证步骤建议按照以下顺序:
- 挑选一段包含多个垃圾类别的水下视频,提取 300 到 500 帧。
- 用训练好的模型逐帧推理,统计检出数量、误检数量和丢帧情况。
- 人工抽查置信度在 0.3 到 0.6 之间的检测框,分析是漏检还是误检。
- 记录常见失败场景,例如“绿色渔网在绿色水草背景中被漏检”“透明玻璃瓶被背景吸收”。
这一步得到的结果比 mAP 更接近真实业务价值,也为后续数据补充指明了方向。
4.3 保存错误样本,建立难例池
第一次训练很难直接达到可用状态。更高效的方式是建立难例池:
- 将验证和实测中的漏检图片保存到
hard_examples/目录。 - 将误检图片保存到
false_positive/目录。 - 定期人工复核难例池,修正错误标注后补充到训练集。
难例池的价值在于,它让后续迭代不是盲目增加数据,而是针对当前模型最薄弱的环节定向补数据。这个做法在真实项目中比反复调超参数有效得多。
5. 水下垃圾检测落地的常见问题与排查链路
5.1 训练 Loss 不下降或震荡
现象:训练刚开始 Loss 很大,训练多轮后 Loss 仍然没有明显下降,或者验证集 Loss 出现剧烈震荡。
可能原因:
- 学习率设置过大或过小。
- 数据集存在大量错误标注。
- 标注文件类别 ID 和
data.yaml不一致。 - 图片尺寸和标注框不匹配,导致边界框始终是错误位置。
- 训练集和验证集划分不均匀,验证集包含过多难样本。
检查方式:
python validate_dataset.py先确认数据校验脚本通过,再查看训练日志中box_loss和cls_loss的变化。如果box_loss不下降,重点检查标注框;如果cls_loss不下降,重点检查类别定义和类别比例。
处理建议:先用小规模数据跑 20 个 epoch 做冒烟测试,确认 Loss 能下降,再启动完整训练;同时把初始学习率降到 0.0005 左右,观察是否更稳定。
5.2 小目标和遮挡目标严重漏检
现象:塑料瓶等小型垃圾在近距离视频中能检出,但在远景帧或目标被水草部分遮挡时完全漏掉。
可能原因:
- 输入分辨率过小,小目标在特征图上只占几个像素。
- 锚框设计不适合水下小目标。
- NMS 阈值设置不当,把相邻目标合并。
- 训练数据中缺少小目标样本。
处理建议:
- 将
imgsz从 640 提升到 1280,但要注意显存占用和推理耗时。 - 在训练数据中增加“裁剪放大”增强,模拟靠近目标的状态。
- 对检测结果做分块推理,例如把 1920x1080 图像切分成多个重叠 patch,分别检测后再聚合。
- 如果模型结构允许,调整检测头的 anchor 尺寸或使用解耦头,使小目标更容易匹配。
这类问题不是一天能解决的,最好的路径是建立一套“小目标测试集”,每次改动后都能快速比较基线模型和新模型的召回率。
5.3 训练集和真实环境差异过大
现象:离线测试 mAP 很高,但在目标海域的真实视频上出现大面积漏检或误检。
可能原因:
- 训练数据来自公开数据集,成像水质和研究海域差异大。
- 光照、季节、水深、悬浮物浓度变化导致颜色分布偏移。
- 模型学到的是“数据集特有纹理”,而不是“垃圾通用语义”。
处理建议:
- 采集目标海域的视频帧,作为微调数据。
- 在数据增强中加入更随机的水下颜色变换。
- 使用域适应方法,例如将源域和目标域图片在特征空间对齐,但这也增加了实现复杂度。
- 先用少量目标域数据做测试,人工观察明显漏检,再决定是否需要从头训还是微调。
实际项目中最有效的仍是“数据回灌”:把真实场景拍摄的视频加入到训练集,反复做几轮迭代。模型对特定水质的适应能力会明显提升。
5.4 显存不足、训练中断和依赖版本冲突
显存不足通常是 batch 或 imgsz 设置过大,可以按顺序降低batch、imgsz,或使用梯度累积模拟更大 batch。训练中断则优先检查本地磁盘空间、GPU 温度和日志输出。依赖版本冲突多见于torch和ultralytics的版本组合不匹配,简单做法是在虚拟环境中固定版本重新安装,不要直接覆盖全局环境。
下面整理一份常见问题速查表:
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 训练 Loss 一直不降 | 标注错误、类别 ID 错乱 | 数据校验脚本 | 修复标注,启用余弦退火 |
| 验证 mAP 高但实测漏检 | 训练集分布偏差 | 真实验证集评测 | 补充目标域数据微调 |
| 小目标漏检 | 输入分辨率低、锚框不匹配 | 查看小目标类别 AP | 提高 imgsz,使用分块推理 |
| 推理时误检大量水草 | 背景和目标外观相近 | 查看误检样本 | 增加负样本,降低置信度阈值 |
| GPU 内存溢出 | batch 或 imgsz 过大 | nvidia-smi | 降低 batch,开启梯度累积 |
| 训练中途停止 | 磁盘满、进程被杀 | 查看日志 | 清理空间,使用 nohup 或 tmux 运行 |
6. 从实验到生产:部署、监控和迭代
6.1 模型导出与推理加速
实验环境跑通后,下一步是把模型部署到巡检设备或后端服务。YOLOv8 支持多种导出格式:
yolo export model=/data/underwater-trash/runs/detect/train/weights/best.pt format=onnx dynamic=True导出 ONNX 后,可以继续转成 TensorRT 引擎(需要 NVIDIA 设备)以提升推理速度;在边缘设备上也可以转成 NCNN 或 OpenVINO。转换过程中要注意输入尺寸是否固定,动态尺寸虽然灵活,但在某些推理引擎中性能会打折扣。
部署时不要直接使用 PyTorch 模型加载方式,除非是离线分析任务。生产环境中优先使用 TensorRT、ONNX Runtime 或专门推理框架,它们的内存峰值更低,单帧延迟也更稳定。
6.2 监控模型推理质量
上线后,模型会遇到训练时没见过的场景。需要建立基础的监控机制:
- 记录每帧检测到的类别、数量、置信度分布。
- 定时抽取一定比例的检测结果,人工复核。
- 将置信度大于阈值但实际是误检的样本,以及置信度低于阈值但实际是目标的样本,回流到难例池。
- 当某个类别检出数量突然剧烈变化时,触发告警并检查是否出现水质变化或模型退化。
这些监控不一定要做得很复杂,但至少要让模型输出“可追溯”。如果检测结果只是在屏幕上闪一下,没有日志,后续想定位问题会非常困难。
6.3 持续迭代的检查清单
水下垃圾检测项目不会只有一次训练。下面是一份可以直接复用的检查清单,每次新增数据或调整模型时都可以按清单走:
- 检查图片是否能正常读取,图像分辨率是否统一。
- 检查标注文件是否存在、类别编号是否合法、框坐标是否越界。
- 划分训练集、验证集、测试集时,按视频 ID 分组,避免泄漏。
- 使用预训练权重初始化,观察前 20 个 epoch 的 Loss 走势。
- 比较
best.pt和last.pt在固定测试集上的 mAP。 - 在真实视频上做逐帧验证,记录漏检和误检样本。
- 将错误样本人工复核后加入难例池,再进入下一轮训练。
- 部署前导出 ONNX 或 TensorRT,确认推理时间和显存占用在可接受范围内。
- 部署后保留每个版本的模型备份和参数配置,方便回滚。
这个清单看起来简单,但很多项目在数据路径、类别定义和验证集划分上反复出问题,提前固化流程能节省大量沟通成本。
7. 从检测结果到业务价值:清理决策与后续方向
7.1 让检测结果辅助清理决策
检测模型输出的是“哪里有垃圾、是什么垃圾、置信度多少”,但真正的业务目标是“如何高效清理”。这一步需要把检测结果和空间信息结合起来。
例如,对 AUV 巡检测得的每一帧结果做时间戳和 GPS 位置同步,得到垃圾分布热力图。清理团队可以按热力图优先处理高密度区域。对于机械臂抓取,检测框的中心点可以转换到机械臂坐标系,但要注意水下折射和目标移动的影响,最终抓取时往往还需要结合深度相机做二次定位。
检测结果还可以用于长时间趋势分析:某个海域的塑料垃圾数量是否在上升,哪些季节漂浮垃圾增多,这些统计信息对环境保护和市政决策更有价值。模型输出的边界框和类别是基础数据,后续分析都是在这些原始结果之上叠加的。
7.2 可扩展的方向
从目标检测出发,可以沿几个方向扩展:
- 语义分割:把“垃圾区域”按像素分割出来,适合渔网、大面积薄膜等不规则目标。
- 视频检测:用时间上下文消除单帧误检,连续多帧稳定出现的物体才判定为垃圾。
- 多光谱融合:结合水下可见光和声呐数据,提高浑浊水域的检测能力。
- 轻量化部署:用剪枝、量化和知识蒸馏压缩模型,跑在低功耗水下机器人上。
- 主动学习:让模型选择最不确定的样本交给人工标注,降低标注成本。
这些方向不是同时都要做,而是根据项目实际瓶颈来选择。如果当前最大问题是小目标漏检,优先做高分辨率输入和分块推理;如果是连续帧误报太多,再引入时序滤波。
回到最开始的问题,AI 检测水下垃圾之所以有工程价值,不是因为模型能“认出”一张图片里有瓶子,而是因为它能把不完整的、复杂的水下视觉信息转化为可执行的位置和类别数据,支撑后续的清理、统计和监测工作。对开发者来说,先跑通一条最小流程,再把真实场景的失败样本持续回灌,比追求某一个指标更高更有意义。扎实做好数据校验、指标分析和难例迭代,这个方向才能真正从实验室走向海洋。