
简介面向下水管道缺陷检测任务提供了一套包含1717张清晰现场图片的目标检测数据集覆盖穿入、错口、堆积、垃圾、裂缝、泥土、树根7类常见缺陷累计标注3401个矩形框。数据采用VOCxml与YOLOtxt双格式标注图片与标签一一对应可直接用于YOLO、Faster R-CNN、SSD等主流目标检测模型的训练、验证与部署评估。压缩包共2000个文件主要包含1717个xml标签文件和283个txt辅助文件整体约61.64MB文件夹按JPEGImages、Annotations、labels三层组织结构规范便于按路径批量加载与自定义格式转换。数据集未做增强保留了原始现场清晰度适合下水道管涵检测、市政养护、管道检测机器人视觉系统等真实工程场景。目前已有155人学习下载对需要构建管道缺陷检测样本集的开发者、学生和算法工程师来说是一份可直接上手的标注数据资源。1. 目标检测跑下水管道缺陷不是先选模型而是先拆数据集做市政管网检测的人都知道下水管道缺陷识别主要靠目标检测模型跑视频抽帧后把穿入、错口、堆积、垃圾、裂缝、泥土、树根七类问题逐个框出来。这份“目标检测-7类下水管道缺陷检测数据集1717张YOLOVOC格式”我完整拆过一遍第一判断是别急着打开JPEGImages先打开labels下的classes.txt。YOLO标签第一列是类别IDID顺序由classes.txt决定它不一定跟资料里的标签列表一致很多人翻车都在这一步。整套共1717张清晰原图、3401个矩形框图片未增强标注同时给了VOC和YOLO两种格式适合训练或验证管道缺陷检测模型。想用yolov8训练自己的数据集又缺管道数据的同学这份正好当基准。2. 数据集到底是什么目录结构、标签体系与 3401 个框的分布2.1 JPEGImages / Annotations / labels 三件套与 classes.txt 的角色解压后第一件事先把根目录结构看明白。这份数据给的是标准三件套JPEGImages 放原图Annotations 放 VOC 格式的 xml 标注labels 放 YOLO 格式的 txt 标注。xml 和 txt 是同一份标注的两种表达不是两份不同的标注这一点先确认了后面才不会被“为什么两个 label 对不上”这种问题卡住。目录 / 文件数量内容JPEGImages/1717 张 jpg原图说明标注为“清晰”Annotations/1717 个 xmlVOC 格式目标类别为字符串labels/1717 个 txt classes.txtYOLO 格式类别 ID 归一化坐标数据集信息.txt1 个标签种类、框数等统计说明xyxr_images_guandao*.txt若干打包时附带的过程文件训练不引用VOC 格式里每个目标的类别存的是字符串比如nameliefeng/name坐标是bndbox里的 xmin、ymin、xmax、ymax 绝对值。YOLO 格式则把类别换成了整数 ID坐标换成归一化后的中心点加宽高。两类文件必须严格同名训练时框架是按图片文件名去找对应标签的。打开一个 YOLO 标签内容长这样2 0.531482 0.722394 0.233271 0.148291 5 0.114263 0.368451 0.093417 0.086502每行五个数类别 ID、x_center、y_center、width、height。前四个坐标全部除以图片宽高做了归一化所以数值都在 0 到 1 之间。第 0 列那个 ID 是几取决于 classes.txt 的第几行不是你想当然的“第几个类别”。这条规则是整个数据集的命根子。2.2 七类缺陷的中文对照与框数分布哪类是多数、哪类是少数数据集信息.txt 里给了每个标签的框数统计我直接整理成表标签名中文对照框数占比chuanru穿入3289.6%cuokou错口2005.9%duiji堆积88426.0%laji垃圾61017.9%liefeng裂缝83624.6%nitu泥土1875.5%shugen树根35610.5%总框数 3401重点看分布duiji 堆积和 liefeng 裂缝占总框数一半多而 nitu 泥土和 cuokou 错口加起来才 387 框。这意味着如果不做任何类别平衡处理模型会把精力集中在堆积和裂缝上泥土、错口很容易被当成背景或者被相邻类别吃掉。后面训练章节你会看到这里必须干预。还有一行说明特别关键“yolo 格式类别顺序不和这个对应而以 labels 文件夹 classes.txt 为准”。我专门跑命令核对了一下当前 labels 里的类别 ID 使用情况awk {print $1} labels/*.txt | sort -n | uniq -c328 0 200 1 884 2 610 3 836 4 187 5 356 6说明这七类在 YOLO 侧都有样本没有空类。配合cat labels/classes.txt | nl -ba看行号就能确定每个 ID 到底对应哪个缺陷。这一步花一分钟能挡住后面一整天的“标签错位”排错。2.3 清晰度与增强策略为什么“原始不增强”反而是好事管道检测的真实场景往往低照度、有泥水反光、光源偏黄网上很多公开管道图自带一层噪声。这份数据在说明里只写了“清晰”没给具体像素值我拆包目测主体目标边缘清楚没有明显模糊或压缩痕迹。训练前用脚本把图片尺寸范围摘出来心里有个底from PIL import Image import glob sizes [] for f in glob.glob(JPEGImages/*.jpg): sizes.append(Image.open(f).size) print(样本数:, len(sizes)) print(最小尺寸:, min(sizes)) print(最大尺寸:, max(sizes))运行结果会告诉你该把 imgsz 设在哪个档位。我一般先跑一遍最小值如果最小边低于 480 像素imgsz 就用 480 或 512别硬上 640否则大量图片被强行拉伸小目标的标注框会跟着变形。“未增强”这点很多人会忽略其实是好事。拿它当基准数据时默认增强跑出来的 mAP 就是你这条数据的参考上限之后再逐步加 mosaic、翻转、色彩抖动才能对比出增强到底有没有正向收益。特别是裂缝、错口这类有方向语义的目标旋转超过 45 度之后“裂缝”特征就变了离线增强很容易制造一批错标注。把这份数据当作干净基线比直接盲调增强策略靠谱得多。3. 把 VOC 转成 YOLOxml 解析脚本与四个边界坑这包本身双格式齐全理论上拿到就能训。但我拿到任何标注数据都习惯自己重新生成一遍 YOLO 标签因为后面要拆训练验证集、要合并别的管道数据、要把标注清洗流程标准化这些操作都逃不开格式转换。与其等合并时踩坑不如先写一个通用转换脚本。3.1 通用 VOC 转 YOLO 脚本按 classes.txt 顺序硬映射脚本语言用 python依赖只有标准库不需要装额外包import xml.etree.ElementTree as ET from pathlib import Path voc_dir Path(Annotations) yolo_dir Path(labels) yolo_dir.mkdir(exist_okTrue) # 类别顺序必须从 classes.txt 读不能自己手写列表 classes [c.strip() for c in Path(labels/classes.txt).read_text().splitlines()] print(当前类别顺序:, classes) def convert_one(xml_file: Path): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) yolo_dir.joinpath(xml_file.stem .txt).write_text(\n.join(lines)) for xml_file in voc_dir.glob(*.xml): convert_one(xml_file)逻辑说明脚本先把 classes.txt 读成列表xml 里出现的类别字符串直接查列表下标得到 ID坐标系从左上右下的绝对值转成中心点加宽高的归一化值。三个关键参数要盯住分割用size/width和size/height不能用读图片得到的尺寸因为 xml 里存的才是标注工具当时用的基准坐标保留 6 位小数对 imgsz 640 的训练精度完全够如果 xml 里出现 classes.txt 之外的类别名脚本会直接跳过跑完统计一下跳过数量能快速发现脏数据。3.2 类别顺序不一致的坑classes.txt 是唯一真相VOC 侧不会出现类别错位问题因为存的是字符串YOLO 侧只存数字 ID数字语义完全由 classes.txt 的位置决定。最典型的问题你在数据集信息.txt 里看到的是 chuanru、cuokou、duiji、laji、liefeng、nitu、shugen 这个顺序于是想当然把 ID 0 当成 chuanru。但 source 里的 classes.txt 如果排的是清洗后的新顺序ID 0 可能变成 duiji——这一错轻则训练没意义重则模型上线后把“堆积”框标成“穿入”。数值本身没错错的是“你以为的对应关系”。所以转换脚本里我强制从 classes.txt 读顺序不手工维护任何列表这样不管这份数据以后被谁重新排过序脚本都能跟着 classes.txt 自适应。3.3 从 xml 生成 train/val 划分按图划分而不是按框划分转换完成后做数据划分我用固定随机种子保证可复现import random from pathlib import Path random.seed(2024) image_files sorted(Path(JPEGImages).glob(*.jpg)) random.shuffle(image_files) cut int(len(image_files) * 0.9) train_files image_files[:cut] val_files image_files[cut:] print(ftrain: {len(train_files)}, val: {len(val_files)}) for split, files in [(train, train_files), (val, val_files)]: with open(f{split}.txt, w) as f: for p in files: f.write(str(p.resolve()) \n)按图做划分意味着同一张图即使有十个框也会整体进入训练集或验证集不会出现“图在训练集、某几个框的标签在验证集”这种数据泄漏。1717 张图按 9:1 切验证集大约 172 张对这类目标数量不算大的任务足够。seed 固定成 2024多次运行结果一致方便复现实验。4. 训练前必看的避坑清单五条血泪经验4.1 现象训练日志直接报 No labels found新建项目目录、把 JPEGImages 和 labels 拷进去后训练第一行就提示找不到标签文件但实际上 labels 目录里明明有 1717 个 txt。我先怀疑目录配置再看发现图片文件名是.jpg标签文件名是.txt后缀没对上是小事真正的问题是文件名大小写不一致——视频抽帧工具生成的是IMG_20231205_0001.JPG标注工具输出后变成img_20231205_0001.txt。解决确保图片名和标签名逐字符一致。一行 bash 就能查diff \ (ls JPEGImages | sed s/\.[Jj][Pp][Gg]/.txt/) \ (ls labels | grep -v classes.txt) \ | head -20输出为空说明一一对应有输出每一行都是一个翻车点。4.2 现象验证集可视化时裂缝框被画成树根框框的位置完全正确但类别全错位。我遇到过一次可视化结果里“liefeng 裂缝”的框全部显示成“shugen 树根”位置很准类别是错的。原因就是前面反复强调的YOLO 标签第 0 列的 ID 和 classes.txt 行号之间的映射被忽略训练配置里 names 顺序和 classes.txt 不一致模型学到的类别语义整个旋转了。解决训练前先跑一遍awk {print $1} labels/*.txt | sort -n | uniq -c确认每个 ID 都在用再打开 classes.txt 一行行核对 names。最稳妥的是让外层脚本直接读 classes.txt 生成 data.yaml 的 names不要手写。4.3 现象data.yaml 用中文类别名直接 UnicodeDecodeError管道数据的标签是拼音英文本身没这个问题但我曾经合并过另一份直接写“裂缝”“树根”中文的标注在 Windows 上用记事本存成 GBKultralytics 读 yaml 时按 UTF-8 解码直接崩。解决yaml 里的 names 永远用英文或拼音中文只放注释里注释不参与解析。names:下必须对应 classes.txt 的每一行顺序错一个都不行。如果你要从别的标注工具导入中文标签先做一轮“中文→拼音/英文”映射再做格式转换顺序不要反过来。4.4 现象mAP 看着还行但 nitu 泥土一个都没检出来整体 mAP50 到了 0.5 以上打开每类 AP 一看nitu 只有 0.02。原因在数据分布里摆着nitu 187 框duiji 884 框接近 5 倍差距。模型训练到后期把网络参数都往多数类倾斜泥土类本身的特征又被背景泥水干扰信度一直压不上去。解决训练的 loss 里给少样本类加权重。按框数倒算权重每个类别权重 总框数 / (类别数 × 该类框数)最小类权重明显变大训练时小类梯度被放大。网络结构不用动只调权重项。4.5 现象模型精度低于心理预期怀疑是数据集标错有一版训练 mAP50 只有 0.42第一反应是标注质量差。但抽样了 20 张图把标注框叠上去看框的位置和类别都是合理的问题出在任务本身——下水管道缺陷目标小、背景相似、遮挡多0.42 可能就是这份数据加默认参数的真实难度另外这份数据集的特别声明里写得很明确只保证标注准确合理不对模型精度作任何保证。把它当基准跑对比实验先拿到默认配置的分数再谈优化。5. 用 YOLOv8 跑通训练从划分到 loss 输出5.1 目录组织train/val 与 images/labels 的对应关系ultralytics 训练时默认找images和labels同级目录目录名可以自定义但对应关系必须清楚。我习惯这样建目录mkdir -p datasets/pipe_defect/{train,val}/{images,labels}然后把 1717 张图按 9:1 分到 train/images 和 val/imageslabels 里的 txt 按同名分到 train/labels 和 val/labels。这里最容易忽略的一点如果原 labels 里有 classes.txt分目录时不要把它带进 train/labels 或 val/labels否则会被当成一张图的标签文件去解析直接报格式错误。5.2 data.yaml 写法与类别顺序对齐一份可用的 data.yaml 长这样path: ./datasets/pipe_defect train: train/images val: val/images names: 0: chuanru 1: cuokou 2: duiji 3: laji 4: liefeng 5: nitu 6: shugen注意上面 names 里的顺序只是按资料说明的列表写的示意不是让你直接抄。正确做法是打开这个包里的 labels/classes.txt一行行对着改。classes.txt 第一行对应 ID 0第二行对应 ID 1直到第七行对应 ID 6。这里错一位整份训练的类别语义就废了。5.3 训练命令与日志解读Box、Cls、Dfl 在看什么yolo detect train \ datadata.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ workers4 \ projectruns/detect \ namepipe_defect_v1参数说明model 我选 yolov8s1717 张图规模用 n 太吃亏用 m 又容易过拟合s 是平衡点batch16 大约需要 8GB 以上显存显存紧就降到 8不建议开了混合精度还硬撑大 batchepochs 写 100这种规模的数据通常 50 个 epoch 左右就收敛100 是留余量配合早停节省时间。日志里主要盯三行Box 是回归损失Cls 是分类损失Dfl 是分布焦点损失。训练一开始 Cls 掉得最快后面慢慢趋平如果你的 Cls 在 30 个 epoch 后还停在 0.01 以上没动静多半是某个类学不动回到第 4.4 的类别权重方案。不要死磕训练时长模型权重用 best.pt 而不是 last.pt晚停的权重几乎总是更好。5.4 类别不均衡的兜底按框数计算 class weight写一个独立脚本直接输出权重向量from pathlib import Path from collections import Counter import numpy as np cnt Counter() for txt in Path(labels).glob(*.txt): for line in txt.read_text().splitlines(): if line.strip(): cnt[int(line.split()[0])] 1 counts np.array([cnt[i] for i in range(len(cnt))]) weights counts.sum() / (len(counts) * counts.astype(float)) print(每个类别的框数:, counts) print(计算得到的权重:, weights.round(3))计算逻辑是总数除以类别数 × 每类框数框数越少的类权重越大。以前面分布为例nitu 约 187 框算出来权重接近 2.6duiji 884 框权重只有 0.55训练时少样本类每个框的贡献被放大模型不会过早放弃它。把输出的数组填进训练脚本的 loss 权重参数曲线一般会明显改善。6. 验证模型像不像话可视化、混淆矩阵与漏检复盘训练完不要只看最后一行 mAP 数字我习惯拿 best.pt 跑三件事。yolo detect predict \ modelruns/detect/pipe_defect_v1/weights/best.pt \ sourceval/images \ conf0.15 \ iou0.5 \ save_txtTrue \ save_confTrueconf 特意压到 0.15因为管道场景漏检比误检危险多框出来的目标交给人工二次核验成本很低漏检却要重新下井采集。iou 保持 0.5和训练评估口径一致。输出目录里逐张看框有没有整体偏移、有没有把同一处缺陷切成两个框、有没有把泥土背景误检成裂缝。检查项怎么看参考标准标注框复用预测框 vs 原标注框重叠程度多数目标 IoU 0.5类别语义框内目标与类别标签是否一致无明显张冠李戴小目标召回单独筛出 nitu、cuokou 两类每类至少出现 3 个命中边界稳定性同一张图多次推理框位置抖动中心点抖动不超过 5 像素第二件事是看混淆矩阵横轴真实类、纵轴预测类对角线越亮越健康。我见过一次“nitu 被大面积预测成 duiji”的情况就是类别权重加得不够混淆矩阵上一眼就暴露了。第三件事是打开 results.csv逐类看 mAP50 和 mAP50-95两类指标一起看mAP50 高但 mAP50-95 低的类说明框位置不精确需要调 anchor 或 imgsz。最后说一个我自己的习惯。拆这份数据最深的教训是所有格式问题的根都在“我以为”三个字——我以为 classes.txt 是那个顺序我以为 xml 尺寸和原图一致我以为 1717 张图一定一一对应。从那以后我每次拿到标注数据第一件事是打印 classes.txt、跑一遍 awk 统计、再画一张可视化图三步全对上了才动训练命令。这个习惯帮我避开过好几次白训一晚上的尴尬。希望帮到你。本文还有配套的精品资源点击获取