简介:面向YOLO系列算法的下水道缺陷检测标注数据集,覆盖关节偏移、障碍物、裂纹、带扣、洞、公用设施入侵、碎片等典型缺陷类别,适合目标检测入门、算法对比与工程验证,可用于排水管道巡检、市政设施维护等场景的缺陷自动识别。压缩包共2000个文件,包含1636个VOC格式xml标注与364个YOLO格式txt标注,两种格式分别存放,配合数据集配置文件data.yaml和已划分好的训练/验证目录,可直接用于yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等模型训练与测试。YOLO格式标注采用class x_center y_center width height的归一化坐标写法,便于快速读取;xml格式则可与传统检测流程或标注工具衔接。标注文件名末尾带有缺陷类别关键词,方便按关节偏移、裂纹、碎片等目标筛选数据。整个压缩包约7.43MB,学习人数已达66人,适合需要现成下水道缺陷样本开展实验的学生和算法工程师。
1. 下水道缺陷检测的 YOLO 实战:2364 张带标签图像先把数据缺口补上
做排水管网检测的人都有同感:CCTV 检测车跑一天能拍几千张管内图像,能用来训练模型的标注数据却少得可怜。不少团队卡在 YOLO 算法训练第一步——数据不够、标签格式不统一、类别定义对不上现场缺陷标准。这份下水道缺陷数据集共 2364 张带标签图像,覆盖关节偏移、障碍物、裂纹、带扣、洞、公用设施入侵、碎片七类缺陷,YOLO 的 txt 和 VOC 的 xml 两种标签都配齐,还带 data.yaml,zip 解压后就能进 YOLOv5 到 YOLO11 的训练流程。它主要服务两类人:刚入行的算法工程师,以及做排水管网智能检测、想拿真实工业数据练手的团队——省掉的是最耗时的人工标注和格式整理阶段。下面按拆包顺序,把格式、训练、调参与踩坑逐条说清楚。
2. 数据集结构与双标注格式:txt 与 xml 各管什么用
拿到压缩包第一件事不是急着训练,而是先把目录结构和标签内容看清楚。这个数据集最讨喜的地方是同一份标注给了两种格式:YOLO 直接读取的 txt,以及 VOC 惯例的 xml。只跑训练的话用 txt 就够了,但这套数据把 xml 也一起给了,意味着你可以在 labelImg 里复查、补标注,不破坏原始数据。我先把两种格式怎么解析、怎么互相换算讲透,后面调数据、写脚本时能省不少事。
2.1 解压后的目录与文件对应关系
zip 解压后,典型的目录结构是这样的:
下水道缺陷数据集/ ├── images/ │ ├── train/ # 训练图像,jpg 格式 │ └── val/ # 验证图像,jpg 格式 ├── labels_yolo/ # YOLO txt 标签,与 images 中图像一一对应 ├── labels_voc/ # VOC xml 标签,内容与 txt 等价 └── data.yaml # YOLO 训练入口配置文件命名是 img_编号 的方式,比如 img_0319_1048.txt 对应同一编号的图像文件,txt 和 xml 描述的是同一批目标框,区别只在编码方式。train 和 val 已经划分好,这一点很实用——很多开源数据集只给一堆图和标签,划分要自己做,而这份省掉了随机抽样的环节,也避免了自己切分时把同一类缺陷图全部塞进训练集或验证集导致指标虚高的问题。
我的习惯是进训练前先抽查几个 txt 和对应 xml,确认图像大小、目标框位置能对上。具体做法是打印出 txt 里所有行,再用 labelImg 打开同编号的 xml 看框的位置,两边一致再往下走。这一步花不了五分钟,但能挡掉后面大部分"训练完发现标签错位"的返工。
2.2 YOLO txt 标签的归一化坐标含义
YOLO 格式的 txt 每一行代表一个目标框,五个值分别是:类别索引、中心点 x、中心点 y、框宽、框高。其中坐标全部做了归一化,范围在 0 到 1 之间,也就是除以了图像自身的宽和高。写个十来行的脚本就能把标签内容读出来:
# 读取一个 YOLO txt 标签文件并解析 label_file = "labels_yolo/train/img_0319_1048.txt" with open(label_file, "r", encoding="utf-8") as f: lines = f.read().strip().splitlines() for line in lines: parts = line.split() if len(parts) != 5: continue cls = int(parts[0]) x_center, y_center, w, h = map(float, parts[1:]) print(f"类别索引={cls} 中心=({x_center:.4f},{y_center:.4f}) 宽={w:.4f} 高={h:.4f}")cls 是从 0 开始的类别索引,对应 data.yaml 里 names 列表的下标。x_center 和 y_center 是目标框中心点相对图像宽高的比例,w 和 h 是框宽高占图像宽高的比例。如果图像实际是 640×480,某个框的 x_center=0.5、w=0.25,那它在像素坐标系里的中心点 x 就是 320,框宽就是 160。反过来,要把 VOC 的像素坐标转成 YOLO 格式,就是用框的像素值除以图像的宽和高。
提示:检查标签时不要只看坐标范围,还要看 w 和 h 是否接近 0。w 或 h 为 0 的框在训练时会直接让 loss 变成异常值,这类脏标签一定要在训练前清掉。
2.3 VOC xml 与类别索引的映射关系
VOC 的 xml 就直观多了,坐标是绝对值像素,类别直接用名字写在<name>标签里。一个典型的标注长这样:
<annotation> <folder>images/train</folder> <filename>img_0319_1048.jpg</filename> <size> <width>640</width> <height>480</height> <depth>3</depth> </size> <object> <name>crack</name> <bndbox> <xmin>120</xmin> <ymin>80</ymin> <xmax>340</xmax> <ymax>210</ymax> </bndbox> </object> </annotation>xml 里的 name 是字符串,YOLO txt 里是数字索引,两者靠 data.yaml 的 names 列表对应起来。比如 names 列表第 2 项是 crack,那所有 name 为 crack 的 xml 目标,在 txt 里类别索引就是 2。批量转换时最容易出错的就是 names 顺序没对上——xml 转 txt 时直接把字符串丢到字典里查索引,查不到就说明 names 列表写错了。
两套格式在这个数据集里同时存在,还有个实际好处:你可以用 AnyLabeling 或 labelImg 打开 xml 做增量标注,改完再导出成 txt,不用从头标一遍。我一般把 labels_voc 当作可编辑的工作副本,labels_yolo 当作训练用的只读目录,新标注完跑一遍转换脚本覆盖训练标签,防止两边不一致。
3. 用 YOLOv8 训练缺陷检测模型:从 data.yaml 到训练命令与断点恢复
这一章直接进入能跑的部分。数据集已经配好了 data.yaml,理论上把路径一改就能开训。但 data.yaml 里的路径、类别顺序、验证集指向这三个点,任何一个错了都会让训练白跑半天,所以我把每一步都拆开讲,顺便把超参数怎么根据现场图像调整说清楚。
3.1 环境准备与 data.yaml 核对
环境部分没有玄学,装好 ultralytics 就行:
pip install ultralytics nvidia-smi # 确认 GPU 可用,显存大小决定 batch 能开到多少之后打开 data.yaml,把它改成你本机的实际路径。原始内容应有以下关键字段:
# data.yaml 示例,路径按实际解压位置修改 path: D:/datasets/sewer_defect # 数据集根目录 train: images/train # 训练图像目录(相对 path) val: images/val # 验证图像目录(相对 path) nc: 7 # 类别数量,必须与 names 长度一致 names: 0: joint_offset # 关节偏移 1: obstacle # 障碍物 2: crack # 裂纹 3: buckle # 带扣 4: hole # 洞 5: utility_invasion # 公用设施入侵 6: debris # 碎片path 这一项经常被忽视。如果写成相对路径,YOLO 会拿它和当前工作目录拼,拼不对就报 "dataset not found"。我习惯把 path 写成绝对路径,train 和 val 保持相对 path 的写法,这样换机器时只需要改 path 一行。还要确认 val 目录里确实有图像,有些数据集的 val 目录是空的,训练时不报错,验证时直接全 0,等跑完才发现指标没法看。
类别顺序必须和 txt 里的索引一致,这一点我在第 2 章已经强调过。如果你发现自己数据里 crack 在索引 2,而 data.yaml 里写成了 3,训练不会报错,但模型学到的类别全是错的,推理时输出的标签对不上现场缺陷名,这种错误最难查。
3.2 训练命令与超参数选择
数据集配齐、yaml 改好后,用 YOLOv8 的命令行直接开训:
yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ device=0 \ project=sewer_runs \ name=exp1模型先用 yolov8n 跑通流程,n 是 nano 版,权重小、训练快,适合先验证数据有没有问题。等流程跑通、确认指标合理,再换 yolov8s 或 yolov8m 提精度。数据是 2364 张,n 模型在单卡上大概半小时到一小时能跑完 150 轮,具体看 GPU 型号。
epochs 我一般先给 150,配合早停 patience=30,也就是连续 30 轮 mAP 不涨就自动停。imgsz 默认 640,这套数据里裂纹是细长目标,后面可以试 960 甚至 1280,代价是显存占用和训练时间翻倍。batch 16 在 12GB 显存上跑 yolov8n 没问题,显存不够就降到 8,不要硬撑,否则会 OOM 中断。device=0 指第一块 GPU,没 GPU 就删掉这一行用 CPU,训练会慢很多。
提示:想看每轮训练后的验证指标,命令后面加
plots=True,训练结束会生成 results.png,内含 loss、mAP、精度、召回的变化曲线,判断收敛情况直接看这张图。
3.3 断点恢复与多版本兼容
训练中断是常态,电源波动、显存不够、远程连接断开都可能打断。YOLOv8 每轮都会把 last.pt 和 best.pt 写到输出目录,恢复训练只需要一条命令:
yolo detect train resume model=sewer_runs/exp1/weights/last.ptlast.pt 是最近一轮的权重,best.pt 是根据验证集 mAP 选出的最优权重。恢复训练时用 last.pt,因为它保证从断点继续;最终拿来推理和验证用 best.pt,因为它指标最好。这个习惯我踩过一次坑后才彻底改过来——以前直接用 best.pt 恢复,结果学习率被重置,后面几十轮的曲线完全乱掉。
这套数据同样能跑 YOLOv5 和 YOLOv7。YOLOv5 用仓库里的 train.py,命令大同小异:
python train.py --data data.yaml --weights yolov5s.pt --img 640 --batch 16 --epochs 150YOLOv9、YOLOv10 和 YOLO11 的命令行格式与 YOLOv8 一致,都是 ultralytics 系,只需要把 model 参数换成对应的预训练权重名,比如 yolo11s.pt。data.yaml 在这几个版本间通用,不用改格式,这也是这份数据集省事的地方。
4. 七类缺陷的类别边界与类别平衡:裂纹、碎片、带扣怎么区分
数据集好不好的关键,不仅在看图数量和标注格式,还在类别定义是否贴近现场。下水道缺陷的类别之间本来就容易互相混淆,比如细长的裂纹在低分辨率下看起来像碎片,带扣变形区域又容易被标成洞。这一章我讲七类缺陷的现场形态和典型混淆点,再给出类别不平衡时怎么处理。
4.1 七类缺陷的形态特征与混淆点
先把类别和现场特征对齐,我用一张表说明:
| 类别 | 现场形态 | 容易混淆的类别 |
|---|---|---|
| 关节偏移 joint_offset | 管节接口错位、位移,通常有环形痕迹 | 障碍物 |
| 障碍物 obstacle | 石块、树根、沉积物堆,体积大 | 碎片、公用设施入侵 |
| 裂纹 crack | 细长线状裂缝,方向不规则 | 碎片、关节偏移 |
| 带扣 buckle | 管道局部凹陷变形,呈扣状或波浪状 | 洞、关节偏移 |
| 洞 hole | 圆形或不规则破损开口,可见背后管壁 | 裂纹、碎片 |
| 公用设施入侵 utility_invasion | 其他管线、电缆穿入管内,边缘整齐 | 障碍物 |
| 碎片 debris | 散落砖块、泥块、杂物,形状不连续 | 障碍物、洞 |
训练前把这张表打印出来,人工抽检 50 张左右的标注,对照表看有没有标错的。我抽检时最常发现的是碎片和障碍物被混标——两者本身没有严格分界线,很多人把大块石头标成障碍物,把小砖块标成碎片,模型训练时就会学到模糊的边界,推理时同一个物体在不同帧里被识别成不同类别。
遇到这种情况,判断标准只有一个:和检测目标挂钩。如果现场验收关心的是管壁结构破损,那只看碎片和洞;如果关心的是过流断面被侵占,障碍物和公用设施入侵才是重点。标注口径统一了,模型的决策边界才稳。数据集的标签是已经标好的,如果你们项目有自己的验收口径,建议先抽检再决定要不要微调标签。
4.2 类别不平衡的统计与处理
七类缺陷在真实管网里的出现频率差很多,裂纹和碎片通常占比高,公用设施入侵可能只有几十个实例。先跑个统计脚本,看清每类的分布:
import os from collections import Counter label_dir = "labels_yolo/train" counter = Counter() for fn in os.listdir(label_dir): if not fn.endswith(".txt"): continue with open(os.path.join(label_dir, fn), "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue cls = int(line.split()[0]) counter[cls] += 1 names = ["joint_offset", "obstacle", "crack", "buckle", "hole", "utility_invasion", "debris"] for cls_id in range(7): print(f"{names[cls_id]}(索引 {cls_id}): {counter[cls_id]} 个实例")统计结果出来后,如果少数类样本明显偏少,有几个常用处理手段。第一是开 mosaic 增强,YOLOv8 默认开启 mosaic=1.0,四张图拼成一张,对缓解类别不平衡和提升小目标检测都有帮助。第二是给少数类做图像过采样,也就是把含公用设施入侵、带扣这些少样本的图像在训练集里多复制几份,简单直接。
第三是调整 loss 权重,YOLOv8 命令行里没有直接的 class_weight 参数,但可以用 ultralytics 的 Python API 改损失函数权重,或者直接替换成带 class weights 的配置。我的经验是小样本类超过 5% 就先用过采样加 mosaic,效果已经够;如果某类连 50 个实例都没有,硬训练意义不大,不如考虑把这类并到相近大类里,比如把公用设施入侵并入障碍物,保证每个类别至少有上百个实例,模型才能学到稳定特征。
5. 训练与推理避坑:五个常见问题的排查记录
这一章我整理了训练这套下水道数据时最容易遇到的五个问题,每个都按"现象、原因、解决"来写。这些坑不全是我一个人踩的,也有团队同学反复问过我的,按出现频率排序。
5.1 现象:loss 不降或震荡明显
训练跑了二三十轮,box_loss 一直在一个区间里来回跳,mAP 基本没变化。最常见的原因是学习率过大加 batch 太小,模型在损失面里跳来跳去,没法收敛。另一个高频原因是训练集里存在没有标签的图像文件,图像本身没被过滤掉,但对应的 txt 文件不存在或内容是空的,模型在空标签图像上计算出的 loss 是噪声。
解决方法是先清空标签再调参。写脚本把 images 里的图片和 labels_yolo 里的 txt 做一次差集核对,把没有对应标签的图像移出训练目录。然后检查超参数:用预训练权重时,把 lr0 从默认 0.01 降到 0.005,batch 低于 8 时更要调小学习率,否则 BN 层统计不稳定,loss 曲线会一直抖。
5.2 现象:训练过程中 loss 突然变成 NaN
loss 在某个 epoch 之后变成 nan,val 指标也全部变 nan,这种情况多半是梯度爆炸,或者输入图像里有损坏文件。下水道图像来自 CCTV 采集,偶尔会有截断损坏的 jpg,解码出来是异常像素,喂给网络后产生极大梯度。
解决分两步。先用 PIL 写个脚本扫描全部图像,把打不开的文件单独移出来:
from PIL import Image import os img_dir = "images/train" for fn in os.listdir(img_dir): path = os.path.join(img_dir, fn) try: with Image.open(path) as im: im.verify() except Exception as e: print(f"损坏图像: {fn} - {e}")跑完删掉这些损坏文件,同时把对应的 txt 和 xml 一起移走。然后再把 lr0 降一档,比如 0.01 改 0.005,weight_decay 保持默认即可。如果用的是从头训练而不是预训练权重,学习率更要保守,从头训练的收敛对 lr 敏感得多。
5.3 现象:裂纹这类细长小目标几乎检不到
训练完的模型对碎片、洞的检测没问题,但裂纹基本漏检,或者框的位置偏了一大截。本质是裂纹在 640 分辨率下只占几十个像素,经过网络下采样后在特征图里只剩一两格,信息基本丢了。
解决方向有三个。一是把 imgsz 提到 960 甚至 1280,直接增加小目标在输入图像里的像素占比,显存不够就减小 batch。二是用切片推理,把原图切成四块分别检测再合并结果,推理时间变长但小目标召回率明显提升。三是换模型结构,YOLOv8 没有原生 P2 输出层,如果裂纹是主要检测对象,可以换用 YOLOv9 或者给 YOLOv8 加自定义小目标检测头。我的建议路径是:先试 imgsz=960 加 mosaic,看裂纹的召回率变化,还不行再上切片推理。
5.4 现象:验证集 mAP 不错,现场图像效果差
验证集上 mAP50 到了 0.85,拿现场新拍的照片一测,漏检一堆。这个坑几乎是工业数据的通病:验证集和训练集来自同一批 CCTV 设备、同一批管径和光照条件,模型学到的其实是这批数据的分布特征,而现场图像的管径、镜头角度、光源强度全变了。
解决思路是让验证集更"硬"。把现场采集到的不同管径、不同光照的图像单独归一个测试文件夹,用训练好的模型在上面测,看哪些场景掉点。然后针对性做数据增强,比如扩增 HSV 色域扰动、随机旋转、模拟不同光源方向,再把现场图像少量加入训练集做微调。我在实际项目里最管用的一招是:把现场最容易漏检的类别图像挑 20 到 30 张,手工标注后加进训练集,冻结前 50 层只训后面几轮,效果立竿见影。
5.5 现象:txt 标签坐标出现大于 1 或负值
训练前检查标签时,发现某些 txt 行里出现 x_center 超出 [0,1] 范围,或者 w、h 为负。这通常是 VOC 转 YOLO 时用了错误的图像宽高做归一化,或者标注框画到了图像边界之外。
解决方法是写一个清洗脚本,把所有越界坐标 clamp 回合法范围,并剔除宽高为 0 的框。clamp 之后框边缘会紧贴图像边界,虽然不完美,但至少不会让 loss 产生异常值。这类问题必须放在训练前处理,因为 YOLO 训练时不会自动检查坐标越界,只会默默把 loss 算错。
6. 模型验证与批量推理:从混淆矩阵到检测结果落盘
训练结束不是终点,能不能把模型用起来才是。这一章我给两个最常用的动作:复现验证指标,以及批量推理并导出结果。
6.1 复现验证指标与混淆矩阵
用训练时的 best.pt 重新跑一遍验证集,确认指标是稳定的:
yolo detect val \ model=sewer_runs/exp1/weights/best.pt \ data=data.yaml \ batch=32 \ plots=True输出里会给出 mAP50、mAP50-95、精确率和召回率,plots=True 会生成混淆矩阵和样本预测图。混淆矩阵是我每次必看的图,它能直接告诉你哪两个类别在互相误判,比如裂纹被识别成碎片,这和第 4 章的类别边界问题对得上。如果混淆矩阵里某个对角元素明显偏低,回到标签抽检,大概率是标注口径问题而不是模型问题。
6.2 批量推理脚本与参数落盘
验证通过后,用 Python API 对一批现场图像做推理:
from ultralytics import YOLO model = YOLO("sewer_runs/exp1/weights/best.pt") results = model.predict( source="test_images/", conf=0.25, iou=0.45, save=True, save_txt=True, project="sewer_inference", name="field_test", ) for r in results: boxes = r.boxes print(f"{r.path}: 检出 {len(boxes)} 个目标") for box in boxes: cls_id = int(box.cls[0]) print(f" 类别={model.names[cls_id]} 置信度={float(box.conf[0]):.3f}")conf 是置信度阈值,现场图像光照差、目标模糊时,0.25 比默认的 0.25 更激进,但会把误检也放进来;如果对误检敏感,调到 0.4 以上。save_txt=True 会把检测结果按 YOLO 格式写成 txt,便于后续接报表系统。我一般先跑一轮全 conf 阈值扫描,找现场误检和漏检的平衡点,再固定最终参数。
从那以后,我每次拿到新数据集,不管来源是哪,都强制先走一遍标签合法性检查、类别分布统计、抽检三个动作,再进训练。这套流程在不少水务和管网项目上帮我省了整周的返工时间,希望帮到你。数据集的 zip 包在项目下载页可以直接拿到,里面 images、两套标签和 data.yaml 都齐了,解压改完 path 就能复现上面的全部过程。
本文还有配套的精品资源,点击获取