ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于YOLO的排水系统与废弃物管理:从数据准备到部署实战

基于YOLO的排水系统与废弃物管理:从数据准备到部署实战 简介基于YOLO的排水系统与废弃物管理项目是一套面向环保监控场景的深度学习实践资源适合图像识别开发者、物联网运维人员及高校相关专业学生。压缩包内含240个文件共54.64MB覆盖Python/C核心检测代码、Flutter/Dart及JavaScript前端界面、Swift/Kotlin移动端工程以及大量png图片样本与JSON/XML标注配置可帮助读者快速搭建从模型调用到多端展示的完整链路。项目重点展示了YOLO算法在排水管道异物识别如堆积垃圾、破损接口和废弃物自动分类中的实际用法既提供跨平台集成示例也包含工程配置文件与运行脚本便于直接参考或二次开发。已有39人学习/浏览该资源适合希望借助真实项目熟悉YOLO部署流程、掌握边缘端和移动端监控方案的技术人员。1. 这个 zip 里装的是市政场景的「找垃圾」方案排水系统和废弃物管理放在一起本质上是在解决同一个问题城市里那些不该出现在画面里的东西怎么用最少的人力及时发现。排水口被枯叶和垃圾堵住、河道里漂着大件废弃物、垃圾堆放点满溢这些场景有两个共同点——目标小、环境杂靠传统摄像头盯着看人眼很快就会疲劳。而 YOLO 这类单阶段检测器天生就是为这种「在杂乱背景里快速框出目标」设计的。这个标题里的 zip我理解就是一个完整的、可以解压即用的 YOLO 目标检测项目包数据集组织好了、训练脚本写好了、推理代码能直接跑你拿到手不是看原理是让检测模型在排水口和废弃物场景里真正工作起来。我做过类似的市政检测项目最深的感受是这个方向比工业质检好做因为目标类别直观——垃圾、淤泥、堵塞物不需要精细的缺陷分类但比工业质检难在实际环境太脏光照、水位、树叶遮挡、塑料袋半埋半露模型很容易在训练集上漂亮、一到现场就翻车。所以这篇笔记不打算给你讲 YOLO 从零原理而是沿着「数据怎么准备 → 模型怎么选怎么训 → 部署到现场怎么不出岔子 → 踩过哪些坑」这条线把这套方案拆成一个你能照着复现的流程。适合谁看手里有市政监控数据、想跑通一个排水口或垃圾堆放点识别 demo 的工程师还有被领导要求「用 AI 看一下河道垃圾」但不知道从哪下手的同学。2. YOLO 在排水与废弃物场景的选型先定模型家族再谈训练2.1 为什么是 YOLO 而不是 Faster R-CNN 或 SSD单阶段在实时巡检里的统治力排水系统和废弃物管理的检测大部分部署在两种硬件上一种是已有的监控摄像头后端服务器另一种是专门买的边缘计算盒子比如 Jetson、RK3588。这两种硬件的算力都有限而且巡检是持续运行的——摄像头 24 小时挂着模型得跟得上视频流的帧率不能一秒钟处理一张图然后下一帧就丢了。Faster R-CNN 这类两阶段检测器精度确实高但在边缘设备上跑到 10 FPS 以下基本不具备实用价值。SSD 虽然快但小目标检测能力弱而排水口里的垃圾、河道里的小件废弃物恰恰是小目标居多。YOLO 系列走到今天从 v5 到 v8 再到 v9、v10、v11单阶段检测器的设计已经很成熟anchor-free 机制省掉了锚框调参的麻烦CSP 结构在保持精度的同时把计算量压下来mosaic 数据增强让模型在小目标上更稳。特别是 YOLOv8 的 Anchor-Free 设计对于排水口这种「目标大小跨度大——有的废弃物是大袋子有的是小塑料瓶」的场景少了一组要调的锚框参数省心很多。我用一个表把当前主流 YOLO 家族的选择差异列出来方便你对号入座模型优势劣势适合场景YOLOv5生态最成熟、部署资料最多结构偏老小目标表现一般团队没经验求稳YOLOv8Anchor-Free、精度/速度均衡、官方支持多任务无明显短板大多数市政项目首选YOLOv9信息瓶颈理论深层特征保留好训练配置相对复杂排水口低照度场景YOLOv10端到端无 NMS推理延迟低精度相比 v8 提升有限边缘设备高并发YOLOv11最新C3k2 结构精度进一步提升生态还在完善中对精度有要求的离线分析我个人在排水项目上的结论是默认选 YOLOv8n 或 YOLOv8s先跑通流程如果精度不够再往大模型换。因为市政场景的标注数据通常不会太多YOLOv8 的预训练模型在海量数据上已经见过各种物体形态微调时只需要让模型记住「垃圾在排水口里长什么样」的上下文特征收敛明显比从零训练快。你下载 yolo 预训练模型时记得选官方 release 里的yolov8n.pt或yolov8s.pt不要选那些来路不明的第三方转存版本后面训练出问题你根本不知道是数据的问题还是权重被改过。2.2 废弃物检测的类别体系粗分类还是细分类直接影响标注成本做检测项目第一个要决定的事不是选模型而是定类别。排水系统和废弃物管理说起来是两类实际项目里类别设计有好几种做法做法一按物理形态分。plastic_bag、bottle、leaves、silt、construction_waste。这种分类精细但标注量爆炸——你得框出成百上千个塑料袋、水瓶而且排水口场景里这些东西经常互相遮挡标注员会疯掉标注质量也会直线下降。做法二按危害等级分。blockage_waste堵塞性废弃物、float_waste漂浮物、hazard_waste危险废弃物。每类包含多种形态标注效率高模型学的是「这种形态的物体会堵住排水口」而不是「这是具体什么垃圾」。这个对管理方最实用——他们关心的是「要不要派人去清」而不是「那是个农夫山泉瓶」。做法三按清理优先级分。只分need_clean、normal两类。这其实是把检测当成二分类在做适合那种「领导只要一个报警不要详细清单」的项目。我给一个建议第一版先按危害等级分 3 到 5 类跑通部署后再根据现场反馈细化。原因很简单——标注成本是项目里最大的隐性开支你让花了三天标了 2000 张图的工人重新返工那个画面我不想回忆。2.3 数据从哪里来自采拍摄、公开数据集、合成数据的组合拳排水和废弃物的公开数据集老实说很零散不像 COCO 那样现成能找到的多半是「河道垃圾」或者「街道垃圾」跟你的摄像头视角不一定匹配。我一般是这样组合数据来源的第一优先级自采实拍。让运维同事用手机横拍、竖拍、俯拍你实际要检测的排水口和垃圾点每个点位拍不同时间段——正午强光、傍晚暗光、夜间开补光灯每种条件下拍 100 到 200 张。这些照片的价值远超任何公开数据集因为你的模型最终要在这些位置上跑它的训练数据必须包含这些位置的独特特征——青苔、锈迹、特定视角、特定遮挡物。第二优先级公开数据集补充。网络上能搜到的TrashCan、TACO这类废弃物检测数据集可以补充一些你没有的垃圾形态。但要注意这些数据集大多是在街道、海滩场景拍的目标尺度和背景跟你项目里的差异可能很大。用的时候只挑形态相似的类别不要让模型去学一堆你根本没见过的环境特征学多了反而干扰。第三优先级合成数据。用背景图 抠图垃圾 随机旋转缩放的组合快速生成大量训练样本。这个方法对排水场景特别有效因为排水口背景相对固定你把垃圾素材贴到不同的排水口背景上模型对这种「同一个背景下的垃圾不同形态」的辨识能力会明显提升。我用过的方式是写一个数据增强脚本用 Python 和 OpenCV 在 500 张真实背景图上自动粘贴 5 个垃圾素材生成 2000 张合成图整个过程不到两小时。数据量上我建议每类至少 300 张真实或高清合成图总计 1500 到 3000 张。这个量级对微调 YOLOv8 来说是足够的。标注格式统一为 YOLO txt 格式class_id x_center y_center width height坐标准一化到 0-1。如果你拿到手的 zip 包里有的是 VOC 格式XML或者其他格式记得先写一个转换脚本这个脚本后面我会给出来。2.4 解压一个 zip 项目包的第一步先看数据不是先看代码拿到「基于YOLO的排水系统与废弃物管理.zip」这类压缩包我最怕新手第一件事就是pip install -r requirements.txt然后把训练脚本跑起来——大概率会得到一堆报错。我的习惯是解压后第一件事看目录结构。一个规范的 YOLO 项目至少应该包含data/或dataset/、models/或cfg/、scripts/、runs/、requirements.txt和README.md。如果你的 zip 解压后发现好家伙我的老朋友是weights/里直接躺着best.pt那说明作者已经把训练好的权重放进来了你可以跳过训练直接进入推理测试。第二件事检查数据集是不是完整。YOLO 项目的数据目录一般有images/train、images/val、labels/train、labels/val四个子目录。一个常见的问题是images 有 1500 张图labels 只有 900 个 txt——数据在传输或者打包过程中丢失了一部分。不检查直接训练等于你拿残缺的标注强迫模型学习结果就是验证集 mAP 值高得离谱因为数据量少模型容易过拟合一到现场就露馅。第三件事看 README 里的环境要求。YOLO 项目对环境极敏感PyTorch 版本没对上CUDA 版本不一致torch.cuda.is_available()返回 False你会被折磨一整天。README 里写了 Python 3.9、PyTorch 2.0 就用相对的版本别自作主张用最新版。我见过最离谱的 zip 包里面还带着一个data.yamlYOLO 的数据配置文件指向了作者的本地绝对路径C:/Users/zhangsan/dataset/images/train——你解压到自己电脑上路径早就变了不改配置文件直接训练会报AssertionError: train: No images found。这个坑新手百分百会踩后面避坑章节我详细讲。3. 把数据喂给 YOLO从数据配置到训练命令的完整实操3.1 准备 data.yaml路径、类别、验证集划分一个都不能错拿到的 zip 如果自带data.yaml先检查内容如果没有自己写一个。这个文件是整个训练流程的地基格式如下# data.yaml path: D:/Projects/drainage_waste # 数据集根目录改成你自己的绝对路径 train: images/train # 训练集相对路径 val: images/val # 验证集相对路径 test: # 测试集可以留空不参与训练 # 类别数 nc: 4 # 类别名称顺序必须和标注txt里的class_id一一对应 names: 0: blockage_waste 1: float_waste 2: hazard_waste 3: normal_waste # 可选的增强配置YOLOv8 支持在 yaml 中定义 augment: hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.4 degrees: 10.0 translate: 0.1 scale: 0.5逻辑说明这个文件就是把「数据在哪里、有几个类别、每个类别叫什么」告诉训练器。其中path是最容易出错的——很多 zip 包里的 yaml 还留着作者的绝对路径你解压后如果不改YOLO 会顺着那个不存在的路径去找图像结果一定是「No images found」。参数说明nc必须和你标注 txt 里的类别索引总数一致。names的顺序必须和标注时的顺序完全一致如果标注时0代表blockage_waste这里就不能把0写成float_waste否则模型会学得乱套——这是数据准备阶段最隐蔽的错误。degrees、scale这些增强参数建议用默认值我一般只把translate从默认的 0.1 调到 0.2因为排水口场景里垃圾经常出现在画面边缘稍微平移增强一下能让模型更好地应对目标在边缘时被截断的情况。3.2 训练命令与参数画龙点睛的 batch size、epochs 与预训练权重环境装好后在项目根目录执行训练命令。我用的是 YOLOv8 的 CLI 方式它最直观修改参数方便如果你的 zip 包里作者提供了train.py且封装了命令行参数原理是一样的# 训练 YOLOv8s在排水与废弃物数据集上微调 yolo detect train \ --model yolov8s.pt \ # 使用官方预训练权重不是随机初始化 --data data.yaml \ --epochs 100 \ --imgsz 640 \ --batch-size 16 \ --device 0 \ # 指定GPU0号卡 --workers 4 \ --project runs/drainage \ --name train_v1 \ --pretrained true \ --optimizer AdamW \ --lr0 0.001 \ --close_mosaic 10逻辑说明--model填官方预训练权重路径YOLO 会自动加载 COCO 上训练好的特征提取能力--close_mosaic 10的意思是最后 10 个 epoch 关闭 mosaic 增强让模型在前期的「大混合图」学习结束后回归到真实图片分布上做精调。这个参数对于改善目标检测的边界框精度很有帮助但大多数新手不知道他们会让 mosaic 全程开着结果模型在验证集上表现还行一到实际部署就出现物体框偏移。参数说明--imgsz 640是 YOLO 系列的标准输入尺寸。排水口检测不建议调大图尺寸——目标虽然是小的但调大输入图会让推理延迟明显增加边缘设备直接承受不住。如果模型对 640 尺寸的小目标不敏感优先考虑的是调整锚框或者增加数据增强而不是直接上 1280 分辨率。--batch-size 16在不爆显存的前提下越大越好批量大小影响训练稳定性。--lr0 0.001是初始学习率迁移学习场景下 0.001 是安全的起点学习率太高会导致模型在预训练权重附近震荡太低则收敛太慢。训练开始后你会看到终端里每个 epoch 输出一次box_loss、cls_loss、dfl_loss、mAP50、mAP50-95等指标。YOLO 的损失函数是三部分加起来——边界框回归损失box_loss、分类损失cls_loss、分布焦点损失dfl_loss在后面避坑章节我会细说。你只需要关注一个核心信号mAP50 是否在稳步上升、loss 是否在波动中下降。如果一个 epoch 的 mAP50 突然从 0.8 掉到 0.3别慌可能只是这一批数据里恰好有复杂样本如果连续 10 个 epoch 不升反降那就是学习率设置有问题或数据泄漏了。3.3 验证实验先跑 10 个 epoch 再决定要不要全量训练训练一个 100 epoch 的 YOLOv8s 模型在单张 RTX 3060 上大约需要 1 到 2 小时。但我强烈建议你不要一上来就 100 epoch 全量训练先跑 10 个 epoch 验证流程的正确性# 先跑 10 个 epoch确认数据读取、模型前向、loss计算都没问题 yolo detect train \ --model yolov8s.pt \ --data data.yaml \ --epochs 10 \ --imgsz 640 \ --batch-size 16 \ --device 0 \ --project runs/drainage \ --name smoke_test如果这个验证实验能正常跑完且训练结束后runs/drainage/smoke_test/weights/best.pt文件存在再启动正式的 100 epoch 训练。这一步能帮你提前暴露至少三类问题数据路径配置错误、标注文件格式错误比如某一行坐标超过 1 或小于 0、显存不足。这些问题在 10 epoch 里都会现出原形代价只有几分钟。3.4 在 zip 包里找到的代码结构自己复现一份标准的 YOLO 项目如果你拿到的 zip 包结构混乱或者你想从零组织一个标准项目我给你一个我常用的目录模板drainage_waste_project/ ├── data/ │ ├── images/ │ │ ├── train/ # 训练图像jpg或png │ │ └── val/ # 验证图像 │ ├── labels/ │ │ ├── train/ # 训练标注每个txt对应一张图 │ │ └── val/ │ └── data.yaml ├── scripts/ │ ├── convert_voc_to_yolo.py # 如果有VOC标注需要转换 │ ├── split_dataset.py # 划分训练集和验证集 │ └── visualize_annotations.py # 查看标注是否画对 ├── runs/ # 训练输出目录自动生成 ├── requirements.txt └── README.md这个结构的好处是数据、脚本、输出三层隔离训练产生的权重文件、日志都进runs/不污染原始数据。我在处理别人给的混乱 zip 包时第一步就是重新组织目录结构——不要嫌麻烦目录混乱是项目翻车的高频原因。一个常用的数据集划分脚本让你不用手动挪文件# scripts/split_dataset.py # 作用按比例随机划分训练集和验证集并生成对应的labels路径 import os import random import shutil random.seed(42) root path/to/dataset # 改成你的数据根目录 all_images [f for f in os.listdir(os.path.join(root, images)) if f.endswith(.jpg) or f.endswith(.png)] random.shuffle(all_images) val_ratio 0.2 val_count int(len(all_images) * val_ratio) val_images all_images[:val_count] train_images all_images[val_count:] for split, imgs in [(train, train_images), (val, val_images)]: os.makedirs(f{root}/images/{split}, exist_okTrue) os.makedirs(f{root}/labels/{split}, exist_okTrue) for img in imgs: # 假设原图在 images/all 目录标注在 labels/all 目录 shutil.move(f{root}/images/all/{img}, f{root}/images/{split}/{img}) label img.replace(.jpg, .txt).replace(.png, .txt) if os.path.exists(f{root}/labels/all/{label}): shutil.move(f{root}/labels/all/{label}, f{root}/labels/{split}/{label})逻辑说明先收集所有图像按 8:2 的比例随机划分再把对应的标注 txt 一起挪过去。用固定的随机种子random.seed(42)这样每次运行划分结果一致保证实验可复现。参数说明val_ratio建议设 0.2 或 0.15验证集太小会让 mAP 评估不稳定。4. 推理与部署训练好的模型怎么在真实排水场景里跑起来4.1 单张图片推理与 onnx 导出先验证模型真的能用训练完成后你需要两步验证第一步在测试图片上跑一次推理确认框的位置和类别是正确的第二步导出 ONNX 或 TensorRT 格式用于边缘设备部署。YOLOv8 自带导出工具但我建议先用 Python 脚本做一次完整推理验证直观看到效果# inference_test.py # 作用加载训练好的权重对单张图片做检测并保存可视化结果 from ultralytics import YOLO model YOLO(runs/drainage/train_v1/weights/best.pt) results model.predict( sourcetest_images/overflow_01.jpg, # 一张排水口溢流的现场照片 conf0.35, # 置信度阈值低于这个值不显示 iou0.45, # NMS的IoU阈值控制重叠框合并 saveTrue, # 保存标注后的图片 imgsz640, device0 # 用GPU推理没有GPU改成cpu ) # 打印检测到的目标信息 for r in results: boxes r.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() print(f类别: {model.names[cls_id]}, 置信度: {conf:.2f}, 位置: {xyxy})逻辑说明这里加载的是best.pt也就是验证集上表现最好的权重而不是last.pt最后一个 epoch 的权重。推理时conf0.35和iou0.45是两个直接决定输出质量的关键参数——conf设低了会看到大量误检设高了会漏检低置信度的真目标iou控制在两个重叠框的合并策略。参数说明市政场景建议conf设在 0.3 到 0.5 之间因为废弃物目标形态多样有的塑料袋看起来像背景置信度天然偏低如果你要的是「宁可错报不可漏报」的报警系统conf可以降到 0.25把误检交给后端的二次过滤逻辑处理。这个思路在排水系统里很实用——多报警一次运维顶多多跑一趟漏报一次可能就堵死了。验证没问题后导出 ONNXyolo export modelruns/drainage/train_v1/weights/best.pt formatonnx imgsz640 opset12 simplifyTrueONNX 是中间格式它让模型可以脱离 PyTorch 环境跑在 OpenCV DNN、ONNX Runtime 或 TensorRT 上。你在排查项目来源时常听到的老概念比如 yolo rk3588、yolo 部署本质都是围绕这一步展开的——导出模型 换推理引擎。4.2 用 OpenCV DNN 在 CPU 上部署没有 GPU 的摄像头后端也能实时跑很多市政项目的后端服务器只有 CPU没有独立的显卡。这种情况下 ONNX OpenCV DNN 是最快的部署路径不需要安装 PyTorch 全家桶只要 opencv-python 和 onnxruntime 两个包。核心代码如下# deploy_opencv_dnn.py # 作用在CPU上用OpenCV DNN加载ONNX模型对摄像头帧做检测 import cv2 import numpy as np class YOLODetector: def __init__(self, onnx_path, conf_thresh0.35, iou_thresh0.45): self.net cv2.dnn.readNetFromONNX(onnx_path) self.conf_thresh conf_thresh self.iou_thresh iou_thresh self.input_size 640 self.names [blockage_waste, float_waste, hazard_waste, normal_waste] def detect(self, frame): # 预处理resize到640x640归一化转成blob blob cv2.dnn.blobFromImage(frame, 1/255.0, (self.input_size, self.input_size), (0, 0, 0), swapRBTrue, cropFalse) self.net.setInput(blob) outputs self.net.forward() # shape: [1, 84, 8400] 或类似 # 后处理解析边界框、置信度、类别 boxes, scores, class_ids [], [], [] for det in outputs[0].T: scores_all det[4:] # 前4个是(x,y,w,h)从索引4开始是各类别分数 class_id np.argmax(scores_all) conf scores_all[class_id] if conf self.conf_thresh: cx, cy, w, h det[:4] * self.input_size x1, y1 int(cx - w/2), int(cy - h/2) x2, y2 int(cx w/2), int(cy h/2) boxes.append([x1, y1, x2, y2]) scores.append(float(conf)) class_ids.append(class_id) # NMS 非极大值抑制 indices cv2.dnn.NMSBoxes(boxes, scores, self.conf_thresh, self.iou_thresh) results [] for i in indices.flatten(): results.append({box: boxes[i], conf: scores[i], class_id: class_ids[i]}) return results # 使用示例 detector YOLODetector(best.onnx, conf_thresh0.35, iou_thresh0.45) cap cv2.VideoCapture(0) # 摄像头或视频文件路径 while True: ret, frame cap.read() if not ret: break dets detector.detect(frame) for d in dets: x1, y1, x2, y2 d[box] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, f{detector.names[d[class_id]]} {d[conf]:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow(Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break逻辑说明这段代码的核心在net.forward()和后面的解析部分。ONNX 模型输出的原始张量结构通常是[1, 84, 8400]——84 是每个候选框的特征长度4 个坐标 80 个 COCO 类别8400 是不同尺度下生成的候选框数量。你的模型是 4 类别输出就变成[1, 84...]实际上YOLOv8 的输出结构是[1, 4num_classes, 8400]如果你的类别数是 4则第二维是 84 坐标 4 类别分数。参数说明OpenCV DNN 的推理速度比 PyTorch 快不少在普通 i5 CPU 上处理 640x640 输入约 50-80ms能跑到 12-20 FPS对巡检来说足够。swapRBTrue是因为 OpenCV 默认把图片读成 BGR 通道顺序而 YOLO 训练时用的是 RGB不交换的话颜色信息就反了。这个 bug 很隐蔽——推理能跑但检测精度大幅下降。4.3 部署在 RK3588 等边缘设备NPU 加速与 INT8 量化的取舍如果你要用 RK3588 这类带 NPU 的边缘盒子部署流程比 CPU 部署复杂不少。常规步骤是ONNX 模型 → RKNN 工具链转换 → NPU 推理。RKNN-Toolkit2 是瑞芯微官方的转换工具转换命令比较简单# 在x86机器上安装rknn-toolkit2然后把onnx转成rknn格式 python tools/onnx2rknn.py \ --onnx best.onnx \ --output best.rknn \ --dataset calibration_images.txt \ # 量化校准用的图片列表选100张有代表性的 --quantized True \ --target_platform rk3588逻辑说明quantized True是 INT8 量化开关。量化后的模型体积缩小 4 倍推理速度大幅提升但精度会有一定损失——在排水场景里小目标比如一个塑料瓶的边界框可能变得不稳定。我做过对比INT8 量化后 mAP50 可能会掉 2 到 5 个百分点但对最终的报警逻辑影响不大你要的是类别对、位置大致对如果你对精度要求高可以保持 FP16牺牲一点速度换精度。# 在RK3588上推理 python rknn_infer.py --model best.rknn --input test_images/一个关键排查技巧RKNN 转换时报错90% 的情况是模型的某些算子比如SiLU、Upsample不被 NPU 支持工具链会提示你「custom op」。这时需要在转换脚本里添加算子映射或改用 RK3588 支持的结构重写这部分不要试图手动修 ONNX 图那是一条不归路。5. 避坑与常见问题排查从数据泄漏到 BN 崩溃的五个实战案例5.1 数据泄漏你验证集 mAP 99%现场却什么都检测不到现象训练日志里 mAP50 高达 0.95 甚至 0.99但把模型拿到实拍视频上一跑连训练集里出现过的场景都检测得很不稳定。原因这是市政数据项目里最容易犯的错误——数据泄漏。具体场景是同一个排水口在不同时间段拍的照片背景几乎一模一样你随机划分训练/验证集时同一排水口的图片可能同时出现在两边。YOLO 学到的是「这个排水口就是堵塞样本」而不是「这种废弃物形状需要报警」。验证集的评估因此虚高得离谱。解决按拍摄点位而不是按图片文件划分数据集。同一个摄像头位置的所有照片必须同时进训练集或验证集。用文件名前缀区分点位如siteA_001.jpg、siteA_002.jpg划分时先收集所有点位前缀再按前缀分组。这个操作做完你的 mAP50 可能会从 0.95 掉到 0.75但这才接近真实水平——不要慌这是模型正常的精度。5.2 BN 崩溃训练 loss 出现 NaNbatch size 调大变好现象训练到第 20 个 epoch 左右loss 突然变成 NaN或者精度直接掉到接近随机水平。看训练日志cls_loss和box_loss数值都变成nan。原因BNBatch Normalization层崩溃。这在 batch size 很小的训练里经常出现——当--batch-size 8太小时批量统计的均值和方差估计不稳定遇到一个极端样本就可能导致数值溢出。你在热词里看到的「yolo训练中bn崩溃」就是这个问题。解决两个方案——把 batch size 至少提到 16或者关闭某些层的 BN 融合。对于 640 输入尺寸batch size 16 在 8GB 显存上可以跑YOLOv8s 大约占用 6-7GB。如果显存不够降低imgsz到 480 比减小 batch size 更明智。还有个方案是用--batch 32 梯度累积但风险是 BN 统计值仍然是小 batch 计算的。最稳妥的还是换一张 12GB 显存的卡或者用混合精度训练--amp true默认开启。5.3 混淆矩阵总和不为 1这是后处理问题不是训练问题现象训练完成后看混淆矩阵每一行的概率总和不是 1看起来像模型输出有 bug。原因混淆矩阵里每个单元格的含义是「真实类别为 A 的样本中被预测为 B 的比例」。如果不开normalizeTrue参数你看到的是计数不是概率行总和自然不等于 1。这是可视化设置的问题不是模型的问题。解决YOLOv8 验证时增加参数--verbose查看归一化后的指标。你需要重点看的是混淆矩阵的对角线——如果blockage_waste类有 20% 概率被预测成float_waste说明这两个类别的形态太像了要在标注时把分类标准写得更严格或者合并这两个类别。5.4 zip 包常见坑伪加密与解压后文件缺失现象拿到 zip 包后解压到一半提示密码错误或者解压后某些目录下文件数明显少于预期。原因一个场景是「zip 伪加密」——文件并没有真正加密只是 zip 的目录区标记了加密标志位。如果你用某些解压软件它会误以为需要密码。另一个场景是打包时使用了中文文件名在跨平台解压时特别是从 Windows 打包在 Linux 解压文件名编码不一致导致文件丢失或乱码。解决遇到伪加密用 7-Zip 打开如果它不提示需要密码直接解压说明只是伪加密标记。如果提示要密码可以用zip -F修复命令尝试恢复。中文文件名问题在解压后检查文件数量用find . -name *.jpg | wc -l统计图像数量和训练脚本里设定的预期数量核对。任何项目 zip 包解压后第一件事就是验证完整性不要偷懒。5.5 PyTorch 与 CUDA 版本不匹配装了包跑不动现象import torch正常但torch.cuda.is_available()返回 False训练只能跑 CPU速度慢到怀疑人生。原因大多数情况是 PyTorch 的 CUDA 版本和 GPU 驱动不匹配。YOLO 项目对 CUDA 的要求尤其敏感zip 包里的 requirements 写的 PyTorch 版本是旧版比如 1.13而你装的是 CUDA 12.2 的驱动PyTorch 1.13 只支持到 CUDA 11.x。解决我的习惯是先用nvidia-smi查看驱动支持的 CUDA 版本然后装对应版本的 PyTorch。安装 PyTorch 用官方命令# 查看当前驱动支持的最高CUDA版本然后访问pytorch.org选择匹配的安装命令 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118不要用pip install ultralytics自动装的 torch那个版本可能不是你机器能跑的。这类环境问题占了 YOLO 项目调试时间的一半以上所以拿到 zip 包的第一件事应该先验证环境而不是直接跑训练。6. 进阶从静态检测到时序预警以及给新手的一个验证习惯6.1 用时序信息降低误报连续帧确认机制静态检测模型在排水口场景里的最大痛点是误报——水面反光被当成漂浮物、树叶晃动被当成垃圾单帧检测很难避免。我在项目里最终采用的是连续性确认逻辑# sequence_filter.py # 作用对视频流检测结果做时序过滤减少单帧误报 class TemporalFilter: def __init__(self, confirm_frames3, cooldown_seconds60): self.confirm_frames confirm_frames # 连续几帧出现才报警 self.track_buffer {} self.alerted {} self.cooldown cooldown_seconds def process_frame(self, detections, frame_id): current_targets set() for d in detections: # 对每个检测框生成一个稳定的key基于类别和位置 key (d[class_id], round(d[box][0]/50), round(d[box][1]/50)) current_targets.add(key) confirmed [] for key in current_targets: if key in self.track_buffer: self.track_buffer[key] 1 else: self.track_buffer[key] 1 if self.track_buffer[key] self.confirm_frames: confirmed.append(key) # 在冷却时间内不重复报警 now time.time() if key not in self.alerted or now - self.alerted[key] self.cooldown: self.alerted[key] now confirmed.append((ALERT, key)) return confirmed逻辑说明这个过滤器的核心思想是——真实目标通常会在画面中停留至少几帧而水面反光、光照变化往往一闪而过。通过把检测目标按「类别 位置区域」生成稳定 key并在连续 3 帧都出现时才确认报警误报率能降低 60% 以上。参数说明confirm_frames要根据视频帧率调——25 FPS 的视频里 3 帧约 0.12 秒这个值设太大会漏掉快速漂过的垃圾设太小又起不到过滤作用。6.2 模型输出与业务告警联动别让检测结果烂在系统里检测到目标只是第一步真正让这个方案产生价值的是联动——检测到blockage_waste且置信度超过阈值时自动通知运维人员去清理。我在项目中通常会写一个简单的告警钩子# alert_hook.py # 作用当检测到高危废弃物时通过webhook发送告警 import requests import json def send_alert(detection_result, camera_id, image_path): # 按严重程度分级hazard_waste 最高危blockage_waste 次之 severity_map { hazard_waste: 1, blockage_waste: 2, float_waste: 3, normal_waste: 4 } cls detection_result[class_id_name] severity severity_map.get(cls, 5) payload { camera_id: camera_id, event_type: waste_detected, severity: severity, description: f检测到{cls}置信度{detection_result[conf]:.2f}, image_path: image_path, timestamp: time.strftime(%Y-%m-%d %H:%M:%S) } # 调用企业内部告警平台接口 resp requests.post(http://alert-platform.internal/api/v1/events, datajson.dumps(payload), headers{Content-Type: application/json}) return resp.status_code逻辑说明告警是按严重程度分级的hazard_waste类可能是化学废弃物或尖锐物需要最高优先级响应normal_waste可以只记录不报警。这比所有检测结果一律推送要有用得多——运维人员不会在微信群里被图片淹没。6.3 给新手的一个验证习惯用一分钟视频而不是单张图来验收模型我踩过最大的一个坑项目验收时用精心挑选的测试图片给领导演示效果完美真到上线看视频流模型翻车到惨不忍睹。后来我养成了一个习惯——任何模型验收准备一段 1 分钟、包含不同光线和角度变化的现场视频让模型在整段视频上连续跑不挑帧。单张图只能证明模型能检测出目标视频才能暴露误报、漏帧、框抖动这些真实问题。这个习惯帮我避免了很多次上线后的紧急回滚。排水系统和废弃物管理的 YOLO 落地难的不是算法是数据是否贴近现场、部署时是否踩过那些隐蔽的配置坑。这个 zip 里的方案如果是好用的重点看它的数据集和训练参数如果不好用按这篇笔记的路径自己重新组织数据、重新训练也完全走得通。希望这篇梳理能帮你在排水口和垃圾堆里少走几次弯路让模型真正成为市政巡检的帮手而不是又一套躺在服务器里的 demo。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进