简介:本资源为排水管道缺陷检测数据集,面向从事计算机视觉目标检测的开发者与研究人员,可用于训练和验证管道破损识别模型。数据集包含770张清晰排水管道图片,提供VOC与YOLO两种标注格式,压缩包内分设JPEGImages、Annotations、labels三个文件夹,分别存放jpg图片、xml文件与txt标签,共约2000个文件,包体大小14.53MB。标注涵盖clean、crack、hole、obstacle四类,总框数1812个,其中crack框数最多达1010,obstacle为539,hole为153,clean为110,均为矩形框,适用于目标检测任务。目前已有1012人学习下载。读者可直接获得图片与双格式标注,省去自行采集与标注成本,快速搭建管道缺陷检测训练流程,用于模型对比、算法验证或课程实践,也可作为数据增强与格式转换的基准素材。
1. 排水管道缺陷检测数据集:770 张 YOLO+VOC 双格式到底能干什么
手里只有 770 张标注图,能不能训出一个能用的排水管道缺陷检测模型?这是我在市政管网巡检项目里被问得最多的问题。排水管道缺陷检测的典型场景是 CCTV 爬行器回传视频,需要自动识别破裂、变形、腐蚀、错口、渗漏这几类结构性缺陷,人工判读一段 300 米管段要花两三个小时,而且不同判读员对同一帧的结论经常打架。这个数据集的价值就在于它把 YOLO 和 VOC 两套标注格式都打包好了,省掉了最耗时的格式转换环节,770 张虽然不算大,但对于验证一条检测链路是否跑得通、评估某个 backbone 在管壁纹理上的表现,完全够用。适合谁?适合刚接手管网巡检算法、需要快速搭出 baseline 的工程师,也适合想拿真实工业缺陷数据练手 YOLO 微调的人。别指望它直接产出生产级模型,它的定位是让你在半天内跑通训练并看到 mAP 曲线。
2. 先搞清楚 770 张双格式数据集里到底装了什么
2.1 YOLO 格式与 VOC 格式的目录差异
拿到一个压缩包,第一件事不是急着解压训练,而是先摸清目录结构。YOLO 格式和 VOC 格式对同一批图的组织方式完全不同,混用会导致标签读不到、类别对不上。常见做法是解压后先tree一遍,把两类目录分开看。
YOLO 格式的典型结构是 images 和 labels 平行存放,每张图对应一个同名 txt,txt 里每行是class_id cx cy w h,坐标全部归一化到 0 到 1 之间。VOC 格式则是 JPEGImages 放图、Annotations 放 XML、ImageSets/Main 放 train/val 的划分文件,XML 里用绝对像素坐标记录 xmin、ymin、xmax、ymax。
# 解压后先看目录层级,别急着写训练脚本 unzip 排水管道缺陷检测数据集yolo+voc格式770张.zip -d pipe_defect cd pipe_defect find . -maxdepth 2 -type d | sort # 统计图片数量,确认是不是 770 张 find . -name "*.jpg" -o -name "*.png" | wc -l # 看 YOLO 标签和 VOC 标注各有多少 find . -path "*labels*" -name "*.txt" | wc -l find . -path "*Annotations*" -name "*.xml" | wc -l这段命令的逻辑是先建立全局认知:目录层级决定你后面写 data.yaml 时路径怎么填,图片计数用来核对压缩包是否完整,标签计数用来判断 YOLO 和 VOC 两套是否一一对应。参数上-maxdepth 2避免递归太深刷屏,-o在 find 里是逻辑或,统计图片时把 jpg 和 png 都算上。如果发现 txt 数量和 xml 数量对不上,说明有一侧标注缺失,得先补齐再谈训练。
2.2 类别定义与标注质量初判
排水管道缺陷的类别体系直接决定模型能不能落地。常见做法是打开几个 YOLO 的 txt 和对应的 VOC XML,把 class_id 和类别名对照出来,确认没有把「破裂」和「变形」标混。VOC 的 XML 里类别名是明文,YOLO 的 txt 里是数字,两者必须能对上。
import os, glob, collections # 统计 YOLO 标签里每个 class_id 出现的次数 label_dir = "pipe_defect/labels" counter = collections.Counter() for txt in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt) as f: for line in f: line = line.strip() if line: counter[int(line.split()[0])] += 1 print("YOLO 类别分布:", dict(sorted(counter.items()))) # 从 VOC XML 里抽出类别名,和上面的 id 对齐 import xml.etree.ElementTree as ET name_set = set() for xml in glob.glob("pipe_defect/Annotations/*.xml"): tree = ET.parse(xml) for obj in tree.findall("object"): name_set.add(obj.find("name").text) print("VOC 类别名:", sorted(name_set))逻辑说明:第一段用 Counter 统计每个 class_id 的框数量,能立刻看出类别是否严重不均衡——如果某一类只有个位数框,训练时几乎必然被淹没。第二段从 XML 抽类别名,和 id 对齐后你才能正确写 data.yaml 的 names 列表。参数上注意 txt 每行按空格切分,第一个字段就是 class_id;XML 用 ElementTree 解析,object 节点下的 name 是类别。如果发现类别名有拼写不一致(比如 crack 和 cracks 混用),必须在转格式前统一,否则会被当成两个类。
提示:770 张图里如果某个缺陷类别少于 30 个框,建议先做类别合并或过采样,别直接硬训。
3. 用 YOLO 格式在本地跑通训练的最小闭环
3.1 写对 data.yaml 的三个关键字段
YOLO 训练翻车最常见的原因不是模型,而是 data.yaml 路径写错。三个关键字段是 train、val、names。train 和 val 指向图片目录或包含图片路径的 txt 文件,names 是类别名列表,顺序必须和 class_id 严格对应。
# data.yaml 放在数据集根目录 path: /home/user/pipe_defect # 数据集根路径,绝对路径最稳 train: images/train # 相对 path 的训练图目录 val: images/val # 相对 path 的验证图目录 nc: 5 # 类别数,按实际改 names: 0: 破裂 1: 变形 2: 腐蚀 3: 错口 4: 渗漏逻辑说明:path 用绝对路径能避免从不同工作目录启动训练时找不到文件;train 和 val 写相对路径时是相对 path 解析的,不是相对 yaml 文件位置,这点很多人搞混。nc 必须等于 names 的长度,多一个少一个都会在加载时直接报错。names 的顺序就是 class_id 的顺序,如果你把 0 号写成「变形」而标签里 0 号其实是「破裂」,模型学到的全是错的,而且 loss 还会正常下降,属于最隐蔽的坑。
3.2 从预训练权重启动训练的命令与参数
770 张图属于小数据集,从零训几乎不可能收敛到可用,必须用预训练权重微调。常见做法是拿 YOLOv8n 或 YOLOv8s 这种小模型起步,输入尺寸设 640,batch 根据显存调。
# 用 YOLOv8n 预训练权重微调,跑 100 轮 yolo detect train \ data=data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.01 \ patience=20 \ project=runs/pipe \ name=exp1逻辑说明:model 指定预训练权重,YOLO 会自动下载并加载 backbone;epochs 给 100 是因为小数据集容易过拟合,配合 patience=20 做早停,验证集 20 轮不提升就停。lr0 是初始学习率,微调场景 0.01 比从零训的 0.01 到 0.1 要保守,太大容易把预训练特征冲掉。batch=16 是 8G 显存下的稳妥值,显存不够就降到 8 并同步把 lr0 减半。imgsz=640 是通用起点,管道缺陷目标通常不大,如果发现小缺陷漏检严重,可以提到 960 再试。
注意:训练日志里重点看 mAP50 和 mAP50-95 两条曲线,如果 mAP50 涨但 mAP50-95 不涨,说明框的位置回归不准,多半是标注框偏大或偏小。
3.3 训练完怎么验证模型真的学到了缺陷
训练结束不是看最后一行 loss,而是拿验证集跑一遍推理,把预测框画出来和原图对比。这一步能暴露很多指标看不出的问题,比如模型把所有管壁反光都当成渗漏。
from ultralytics import YOLO import cv2 model = YOLO("runs/pipe/exp1/weights/best.pt") results = model.predict("pipe_defect/images/val", conf=0.25, save=True) # 统计每张图的检出框数量,异常多或异常少都值得看 for r in results: print(r.path, "检出框数:", len(r.boxes))逻辑说明:conf=0.25 是推理置信度阈值,低于它的框不输出,调高会减少误检但可能漏检,调低反之。save=True 会把画框结果存到 runs 目录,直接肉眼比对。统计每张图检出框数是为了找异常:如果某张图检出几十个框,基本是误检爆炸,得回去看这类图的标注和增强策略。参数上 conf 建议在 0.2 到 0.4 之间扫一遍,选误检和漏检平衡的点。
4. VOC 格式转 YOLO 时最容易踩的四个坑
4.1 坐标归一化与边界越界
VOC 用绝对像素坐标,YOLO 要归一化到 0 到 1,转换时用 xmin、ymin、xmax、ymax 算出中心点和宽高再除以图宽图高。坑在于标注框可能超出图片边界,归一化后出现负数或大于 1 的值,YOLO 加载时会直接报错或静默丢弃。
import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_path, class_map): tree = ET.parse(xml_path) root = tree.getroot() w = int(root.find("size/width").text) h = int(root.find("size/height").text) lines = [] for obj in root.findall("object"): cls = class_map[obj.find("name").text] b = obj.find("bndbox") xmin = max(0, float(b.find("xmin").text)) ymin = max(0, float(b.find("ymin").text)) xmax = min(w, float(b.find("xmax").text)) ymax = min(h, float(b.find("ymax").text)) cx = (xmin + xmax) / 2 / w cy = (ymin + ymax) / 2 / h bw = (xmax - xmin) / w bh = (ymax - ymin) / h lines.append(f"{cls} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") return lines逻辑说明:用 max(0, ...) 和 min(w, ...) 把框裁到图片范围内,这是防越界的关键。class_map 是类别名到 id 的字典,必须和 data.yaml 的 names 顺序一致。归一化保留 6 位小数足够,太少会丢精度。如果转换后某个框的 bw 或 bh 接近 0,说明原标注框几乎是个点,这种脏数据要单独挑出来。
4.2 图片尺寸与 XML 记录不一致
有些数据集在整理时对图片做了缩放,但 XML 里的 size 还是原始尺寸,导致归一化比例全错。现象是训练时 loss 异常高且不下降,画出来的框整体偏移。
from PIL import Image import xml.etree.ElementTree as ET img = Image.open(img_path) real_w, real_h = img.size root = ET.parse(xml_path).getroot() xml_w = int(root.find("size/width").text) xml_h = int(root.find("size/height").text) if (real_w, real_h) != (xml_w, xml_h): print(f"尺寸不一致: 图片 {real_w}x{real_h}, XML {xml_w}x{xml_h}")逻辑说明:这段是转换前的体检,用 PIL 读真实尺寸和 XML 记录比对,不一致就报警。解决方式是以图片真实尺寸为准重新归一化,而不是信 XML。参数上注意 PIL 的 size 返回的是宽高,和 XML 的 width、height 对应,别搞反。
4.3 类别名大小写与空格
VOC 的 name 字段经常有前后空格或大小写不统一,比如 "Crack" 和 "crack " 会被当成两个类。转换前必须 strip 并统一大小写。
name = obj.find("name").text.strip().lower()逻辑说明:strip 去首尾空格,lower 统一小写,这样 "Crack"、"crack "、"CRACK" 都归到 crack。如果你的类别名本身有大小写含义(一般缺陷检测没有),就别 lower,只 strip。
4.4 train/val 划分泄漏
VOC 的 ImageSets/Main 里如果 train.txt 和 val.txt 有重叠,验证集指标会虚高。现象是 mAP 高得离谱但实际推理一塌糊涂。
train_ids = set(open("ImageSets/Main/train.txt").read().split()) val_ids = set(open("ImageSets/Main/val.txt").read().split()) overlap = train_ids & val_ids print("重叠数量:", len(overlap))逻辑说明:用集合求交集,重叠不为 0 就说明划分有问题。解决方式是重新按 8:2 随机划分,并确保同一段管道的连续帧不要跨 train 和 val,否则相邻帧高度相似会造成信息泄漏。
5. 小数据集训排水管道缺陷的避坑与排查
5.1 现象:mAP 一直卡在 0.1 以下不涨
原因通常是 data.yaml 的 names 顺序和标签 class_id 对不上,或者图片路径写错导致加载的是空图。解决:先用第 2 章的统计脚本核对类别分布,再打印几张图确认能正常读取,最后检查 names 顺序。
5.2 现象:训练 loss 正常但推理全是误检
原因是管壁反光、水渍和真实渗漏在低分辨率下纹理相似,模型学到了背景捷径。解决:提高输入尺寸到 960,加入随机亮度、对比度增强,必要时对反光区域做遮挡增强。
5.3 现象:某一类缺陷完全检不出
原因是该类样本太少,被其他类淹没。解决:统计各类框数,对少于 50 框的类做过采样或复制粘贴增强,也可以在 loss 里给稀有类加权。
5.4 现象:验证集 mAP 高但换一批图就崩
原因是 train 和 val 来自同一段管道,相邻帧泄漏。解决:按管道段划分数据集,同一段的帧只进 train 或只进 val,别随机按帧分。
5.5 现象:训练中途显存爆掉
原因是 batch 太大或 imgsz 太高。解决:先把 batch 降到 8,imgsz 保持 640,还爆就降到 4,同时把 lr0 按比例调小,避免小 batch 下学习率过大导致震荡。
6. 把 770 张用到极致:增强策略与验证习惯
770 张想榨出更多价值,核心在增强和验证。我一般会开 mosaic、mixup 和随机仿射,但管道缺陷有个特殊性:缺陷的形态和方向有物理含义,比如环向裂缝和纵向裂缝不能随便旋转混淆。所以随机旋转角度我控制在正负 15 度以内,翻转只开水平不开垂直,因为管道图像上下翻转不符合爬行器视角。HSV 增强里饱和度抖动可以大一点,管壁颜色受光照影响本来就大,但亮度抖动要克制,否则暗部缺陷会被抹掉。
验证习惯上,我坚持每训完一版都拿同一组固定验证图跑推理并存档,而不是只看 mAP 数字。因为 mAP 是聚合指标,会掩盖某一类突然崩掉的情况。具体做法是固定 20 张覆盖各类缺陷的图,每次推理后把检出框数和置信度记到表格里,横向对比不同版本。
| 版本 | 破裂检出 | 变形检出 | 腐蚀检出 | 平均置信度 |
|---|---|---|---|---|
| exp1 | 18/20 | 15/20 | 12/20 | 0.61 |
| exp2 | 19/20 | 16/20 | 14/20 | 0.68 |
这张表比 mAP 更能告诉我哪一类在退步。exp2 腐蚀类从 12 涨到 14,说明提高输入尺寸对细小腐蚀有效。如果某一版平均置信度涨了但检出数掉了,多半是 conf 阈值设高了,得回去调。
还有一个习惯是永远保留一份原始标注的备份,任何格式转换和增强都在副本上做。我踩过一次坑:直接在原 labels 上做格式覆盖,结果转换脚本写错把一半标签清空了,原始标注又没备份,只能重新标。从那以后我的规矩是数据集目录只读,所有处理输出到新目录。这个方案值不值得做?如果你手头有管网巡检的标注数据,770 张足够验证链路和选型,但真要上生产,至少得补到三五千张并覆盖不同管材和光照。先从这 770 张跑通闭环,再决定要不要扩标,是最省时间的路径。希望帮到你。
本文还有配套的精品资源,点击获取