
简介面向YOLO系列目标检测下水道缺陷识别任务这份压缩包提供了2364张图像对应的标注数据覆盖关节偏移、障碍物、裂纹、带扣、洞、公用设施入侵、碎片等典型缺陷类别并可适配yolov5、yolov7、yolov8、yolov9、yolov10与yolo11等常用算法。包内共2000个文件以1636个xml和364个txt为主分别对应VOC与YOLO两种标签格式txt按类别 中心点x 中心点y 宽 高的归一化坐标存储适合直接输入模型训练xml则便于在LabelImg等工具中二次检查和转换。资源还提供了已划分好的数据集配置data.yaml可降低预处理成本。压缩包整体约7.43MB目前已吸引66人学习下载。对从事市政管道检测、智慧城市维护或目标检测入门实践的研究者拿到后可直接训练与验证省去从零构建标注集的流程尤其适合快速验证下水道缺陷检测方案。1. 下水道缺陷检测不是玄学这份 2364 张带标签数据集到底能干什么做市政管道检测的朋友应该都有体会下水道缺陷识别的难点从来不是模型选型而是数据。CCTV 检测机器人一趟跑下来几个小时视频几百段真正能用的缺陷图像可能就几十张更别提逐张标注框的工时成本。这也是为什么当我看到这份 YOLO 算法下水道缺陷数据集时第一反应是确认它的标签格式和类别覆盖度——它直接决定了你能不能拿来就训还是要先折腾两三天做格式转换。这份数据集一共 2364 张图像覆盖了关节偏移joint offset、障碍物obstacle、裂纹crack、带扣strap buckle、洞hole、公用设施入侵utility入侵、碎片debris这七类典型缺陷同时提供了 YOLO 格式的 txt 标签和 VOC 格式的 xml 标签还附带了划分好的数据集结构和 data.yaml 配置文件。对于正在做市政管网智能化检测、或者刚接触 YOLO 系列目标检测想找一份带标签数据练手的开发者这份资源的性价比相当高。接下来我从目录结构、标签格式、训练配置到实际踩坑完整拆一遍。2. 压缩包里到底有什么从解压到目录划分的完整审视2.1 先看目录结构别急着跑训练拿到 zip 压缩包第一步不是解压完直接扔给 YOLO 训练脚本而是先理清目录组织方式。我解压后的结构大致是这样sewer_defect_dataset/ ├── images/ │ ├── train/ │ │ ├── img_0319_1048.jpg │ │ ├── img_0319_1557.jpg │ │ └── ... │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ │ ├── img_0319_1048.txt │ │ ├── img_0319_1557.txt │ │ └── ... │ ├── val/ │ └── test/ ├── annotations_xml/ │ ├── train/ │ │ ├── img_0319_1048.xml │ │ └── ... │ ├── val/ │ └── test/ └── data.yaml这里有个细节值得注意images、labels、annotations_xml 三个目录下都按 train/val/test 划分好了而且文件名是对应的——img_0319_1048.jpg对应img_0319_1048.txt和img_0319_1048.xml。这意味着你完全不需要自己写脚本做数据集切分省掉了最容易出错的环节切分时图像和标签没对上。我一般会先跑一个简单命令核对图像和标签文件是否一一对应# 检查 train 目录下图像和标签数量是否一致 ls images/train/*.jpg | wc -l ls labels/train/*.txt | wc -l # 检查是否有图像没有对应标签 for img in images/train/*.jpg; do base$(basename $img .jpg) if [ ! -f labels/train/$base.txt ]; then echo Missing label: $base fi done这段脚本的思路是先统计数量再遍历每张图像检查同名 txt 是否存在。数量一致不代表文件名对得上所以第二步的遍历检查很有必要。如果发现有缺失优先检查是不是解压时文件损坏或者路径大小写问题而不是直接重新标注——那成本太高了。2.2 从文件命名反推采集场景压缩包里的文件名是img_0319_1048.txt这种格式0319大概率代表采集日期或者检测路段编号1048应该是图像序号。这种命名约定在真实的下水道 CCTV 检测项目中非常常见说明这批数据不是合成的而是从实际管道检测视频里抽帧标注的。这一点很重要因为真实场景数据带来的挑战和合成数据完全不同光照不均匀、管壁反光、污水遮挡、镜头畸变、缺陷形态多样。如果你之前只用公开的 COCO 或 VOC 数据集训练过 YOLO第一次跑这个数据集时 mAP 可能会低于预期——这不是模型的问题而是数据分布差异导致的正常现象。理解了这一点后面调参时就不会慌。2.3 硬件评估这份数据集的训练门槛2364 张图像的训练集规模说实话不大也不小属于典型的中小型工程数据集。用 YOLOv8s 在单张 RTX 3060 上训练 100 个 epoch大概需要 1.5 到 2 小时如果用 CPU 纯硬跑时间会拉长到十几小时不建议。显存方面batch size 设为 16 时YOLOv8s 大约需要 6-8GB 显存8GB 显存的卡可以比较舒服地跑完整个训练。如果你的机器配置不高两个方案可以选一是用 YOLOv8n 这种更小的模型二是降低输入尺寸。比如把 imgsz 从 640 降到 512训练速度能提升约 30%代价是 mAP 可能下降 1-2 个点。对于缺陷检测这种对细粒度特征有要求的场景我还是建议保持 640除非真的显存吃紧。3. 两种标签格式的底层逻辑YOLO 坐标归一化与 VOC 的边界框语义3.1 YOLO 格式的坐标到底怎么读数据集的 labels 目录下是 YOLO 格式的 txt 文件每行代表一个目标格式为class x_center y_center width height。关键点在于这四个坐标值都是相对于图像宽度和高度的比例范围在 0 到 1 之间。举个例子打开img_0319_1048.txt看到这样一行2 0.4523 0.6712 0.1834 0.1267含义是类别索引为 2对应裂纹 crack目标框中心点在图像水平方向 45.23% 的位置、垂直方向 67.12% 的位置框的宽度占图像宽度的 18.34%高度占图像高度的 12.67%。这种归一化设计的优势在于不管图像是 640×480 还是 1920×1080标签文件不需要做任何调整模型在预处理时按实际图像尺寸缩放即可。我通常用一小段 Python 把归一化坐标还原成像素坐标用来快速验证标签是否标注准确import cv2 # 读取图像和对应的 txt 标签 img cv2.imread(images/train/img_0319_1048.jpg) h, w img.shape[:2] with open(labels/train/img_0319_1048.txt, r) as f: for line in f.readlines(): cls, x_center, y_center, bw, bh map(float, line.strip().split()) # 还原为像素坐标 x1 int((x_center - bw / 2) * w) y1 int((y_center - bh / 2) * h) x2 int((x_center bw / 2) * w) y2 int((y_center bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite(check_annotation.jpg, img)这段代码的作用是把归一化坐标还原成图像上的像素框并画出来方便你直观检查标注质量。我拿到任何数据集的第一件事就是做这个可视化验证——只看坐标数字永远无法发现标签和图像不对应、类别标错这类问题画出来一眼就能看出异常。这是我在数据标注项目里养成的习惯能省掉后面排查的很多时间。3.2 VOC 格式的 XML 与 YOLO 格式的换算关系annotations_xml 目录下是 VOC 格式的 xml 文件结构上每个文件包含图像尺寸信息和多个object节点每个节点里有类别名和bndbox边界框坐标用的是绝对像素值比如xmin、ymin、xmax、ymax。这两种格式的换算关系很直接x_center (xmin xmax) / 2 / image_widthy_center (ymin ymax) / 2 / image_heightwidth (xmax - xmin) / image_widthheight (ymax - ymin) / image_height如果数据集只提供其中一种格式你需要自己写转换脚本。好在这份资源两种格式都给了省去了这个步骤。不过这里要提醒一句当你用 Ultralytics YOLOv8 训练时默认只读 YOLO 格式的 txt 标签xml 文件不会被自动加载。我一般会把 xml 当作留档和交叉验证用训练时直接走 txt。3.3 类别体系与标注一致性七类缺陷的中英文对应关系和 YOLO labels 里的类别索引如下类别索引类别名称英文标识典型外观0关节偏移Joint offset管道接口处错位1障碍物Obstacle管道内的阻挡物2裂纹Crack管壁线状裂缝3带扣Strap buckle管道接口的固定带脱落4洞Hole管壁穿孔5公用设施入侵Utility intrusion其他管线穿入6碎片Debris管道内散落的杂物这里有一个初学者容易踩的坑data.yaml 中的类别顺序必须和 txt 标签里的 class 索引严格对应。如果 data.yaml 里把类别顺序写错模型训练时不会报错但预测结果的类别全都会错位——裂纹被识别成洞碎片被识别成障碍物而且 mAP 数值看起来还正常。这种错误极其隐蔽属于那种能让你排查一整天的问题。4. data.yaml 与训练参数从配置文件到 YOLOv8 实跑4.1 看懂 data.yaml 里的每一项配置数据集自带的 data.yaml 是关键配置它告诉 YOLO 训练脚本数据在哪里、有哪些类别。典型内容如下train: images/train val: images/val test: images/test nc: 7 names: 0: joint_offset 1: obstacle 2: crack 3: strap_buckle 4: hole 5: utility_intrusion 6: debris这里需要特别注意的是路径写法。train: images/train是相对路径它相对于你执行训练命令时的当前工作目录。如果你在数据集根目录下运行yolo train命令这个路径能正常工作但如果你把 data.yaml 的路径改成了绝对路径换机器训练时就必须同步修改——这是最容易在团队协作时出现的问题。我个人的习惯是把 data.yaml 里的路径改成绝对路径并且在训练脚本里用变量统一管理。这样虽然灵活性差一点但可复现性更好团队里任何一个人拿到项目都能直接跑起来。4.2 YOLOv8 训练命令的完整拆解确认数据和配置没问题后直接用 Ultralytics 框架训练。假设你用的是 YOLOv8yolo train \ modelyolov8s.pt \ datadata.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectsewer_defect \ nameyolov8s_run1 \ patience20几个关键参数的含义和调法modelyolov8s.pt加载 COCO 预训练权重做迁移学习。对于中小型数据集迁移学习比从零训练收敛更快、精度更高这是 YOLO 系列的标准玩法。epochs100初始训练轮数。如果 100 轮还没收敛loss 曲线还在下降可以加到 150 甚至 200。patience20早停机制连续 20 个 epoch 验证集指标没有提升就自动停止。这个参数很有用能避免过拟合也省时间。imgsz640输入图像尺寸。下水道图像通常细节丰富太低会丢失小目标特征太高会显著增加显存占用和训练时间。4.3 训练过程中的关键监控指标训练启动后不要盯着终端发呆。Ultralytics 会在project/name目录下生成results.png和results.csv这两个文件才是你该关注的核心。results.png里包含 train_loss、val_loss、mAP50、mAP50-95 等曲线的变化趋势。我关注的几个关键信号train_loss和val_loss同时下降说明模型在学习如果train_loss下降但val_loss上升基本可以判断过拟合此时应该减少 epoch 数或者增大数据增强强度。mAP50在训练集只有两千多张的情况下如果能到 0.7 以上说明模型已经学到了有效的缺陷特征低于 0.5 就要回头检查标签质量和类别分布。如果某个类别的 AP 明显低于其他类别大概率是这一类别的样本数太少或者标注框不准确。4.4 训练产物与推理验证训练结束后runs/segment/train/weights/目录下会生成best.pt和last.pt。best.pt是验证集上表现最好的权重部署和测试都用它last.pt是最后一个 epoch 的权重一般只用于断点续训。验证模型效果时我一般会挑几张 val 集里没有参与训练的图来跑推理from ultralytics import YOLO # 加载训练得到的最佳权重 model YOLO(sewer_defect/yolov8s_run1/weights/best.pt) # 对验证集图像进行推理 results model.predict( sourceimages/val/img_0319_573.jpg, conf0.25, saveTrue, projectinference_output, nameval_check )推理结果会保存带标注框的图像直接看框的位置和类别对不对就够了。常见的问题是框的位置准但类别错说明类别映射有问题类别对但框偏大或偏小说明 anchor 或回归头没学好可以尝试调整模型架构或增加训练轮数。5. 避坑指南七个容易翻车的细节与排查思路5.1 解压后文件损坏导致图像加载失败现象训练到一半报Image is corrupted或者Cannot identify image file错误训练中断。原因zip 包在传输或解压过程中个别文件损坏图像文件头被破坏。这种问题在网盘下载的大文件中并不少见。解决用完整校验后的解压工具重新解压或者用下面这段脚本找出损坏的图像文件python -c from PIL import Image import os for root, dirs, files in os.walk(images): for f in files: if f.endswith(.jpg): path os.path.join(root, f) try: Image.open(path).verify() except Exception as e: print(fCorrupted: {path} - {e}) 这段代码逐个打开图像文件并调用verify()校验完整性有问题的文件会被打印出来。我在拿到任何图像数据集时都会先跑一遍这个脚本避免训练中途才因为几张坏图崩溃。5.2 标签类别索引与 data.yaml 不匹配现象训练能正常进行loss 也在下降但用验证集做预测时裂纹被识别成洞碎片被识别成障碍物mAP 却不低。原因txt 标签里的类别索引和 data.yaml 中 names 的顺序不一致。如果数据集作者用的类别顺序是[crack, debris, hole, ...]而你写的 data.yaml 里 names 顺序是[hole, crack, debris, ...]模型训练的其实是错位的类别映射。解决打开几个 txt 文件统计每个类别索引出现的频次再和 data.yaml 里的 names 逐一核对。我一般写个极简脚本做交叉验证import os from collections import Counter counter Counter() for fname in os.listdir(labels/train): with open(os.path.join(labels/train, fname), r) as f: for line in f.readlines(): cls int(line.strip().split()[0]) counter[cls] 1 print(类别索引分布:, sorted(counter.items()))如果发现索引分布和你理解的类别体系对不上优先检查 data.yaml 的 names 顺序而不是怀疑标签文件本身。5.3 训练时数据增强过度导致缺陷特征失真现象验证集 mAP 只有 0.4 左右但训练集上模型表现很好损失函数也正常下降。原因Ultralytics 默认开启了 Mosaic、MixUp 等数据增强对常规目标检测很有效但对下水道缺陷这类背景纹理复杂、目标占比较小的图像过度增强可能让缺陷特征被破坏。比如 Mosaic 会把四张图拼接在一起某些小裂纹在拼接边界处可能被截断模型学到的是残缺模式。解决在训练命令中显式降低增强强度yolo train \ modelyolov8s.pt \ datadata.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ mosaic0.5 \ mixup0.2 \ fliplr0.5mosaic0.5表示只用 50% 的概率触发 Mosaic 增强mixup0.2同理。对于缺陷检测场景我建议先按这个配置跑一轮看验证集指标是否符合预期再决定要不要逐步回调增强强度。5.4 图像分辨率不一致导致训练报错现象训练刚开始就报shape mismatch或size of input image does not match。原因数据集里的图像来源于不同检测设备或不同时间段采集分辨率可能从 640×480 到 1920×1080 不等。YOLOv8 虽然会自动缩放但如果缩放后某些图像尺寸不满足 stride 对齐要求就会报错。解决检查数据集中图像尺寸分布然后在训练前统一处理python -c from PIL import Image import os from collections import Counter sizes Counter() for root, dirs, files in os.walk(images): for f in files: if f.endswith(.jpg): path os.path.join(root, f) img Image.open(path) sizes[img.size] 1 print(图像尺寸分布:, sizes.most_common(10)) 如果发现尺寸差异过大直接用脚本统一缩放到 640 或 1280 再训练。虽然 YOLO 内部会做 letterbox 处理但统一尺寸能让数据加载更稳定batch 训练时不容易出幺蛾子。5.5 类别极度不均衡某类缺陷 AP 接近零现象训练结束后查看每类的 AP发现utility_intrusion或joint_offset的 AP 只有 0.05 左右几乎等于没学会。原因下水道缺陷天然存在长尾分布裂纹和碎片最常见而公用设施入侵和带扣很少见。如果某类样本只有几十张模型很难学到有效特征。解决先把每类样本数摸清python -c import os from collections import Counter counter Counter() for split in [train, val, test]: label_dir flabels/{split} if not os.path.exists(label_dir): continue for fname in os.listdir(label_dir): with open(os.path.join(label_dir, fname), r) as f: for line in f.readlines(): cls int(line.strip().split()[0]) counter[(split, cls)] 1 for k, v in sorted(counter.items()): print(k, v) 如果确实是样本数悬殊优先做两类操作一类是类别权重调整在 loss 计算时给稀少类别更高的权重另一类是针对性增强对稀少类别的图像做复制粘贴增强。Ultralytics YOLOv8 里可以用class weights参数但更常见的是先跑一轮看分布再决定是否用训练脚本里的损失函数加权。5.6 预训练权重选错导致收敛慢现象训练了 50 个 epochloss 下降非常缓慢mAP 一直在 0.2 以下徘徊。原因yolov8s.pt是在 COCO 数据集上预训练的COCO 是自然图像和下水道管道内壁图像的分布差异很大。虽然迁移学习仍然有效但特征提取器需要更多 epoch 去适配新域。解决调大 epoch 数到 200同时把学习率调低一些yolo train \ modelyolov8s.pt \ datadata.yaml \ epochs200 \ imgsz640 \ batch16 \ device0 \ lr00.005 \ lrf0.01lr00.005是初始学习率比默认的 0.01 低一半让模型在新域上更稳定地收敛。我到目前踩过不少类似坑基本都是这个思路降低学习率、增加轮数、配合早停效果比盲目调大模型要实在。5.7 验证集和训练集数据泄漏导致指标虚高现象训练时 mAP 高达 0.9但拿到真实 CCTV 检测视频上跑效果惨不忍睹完全比不上验证集的表现。原因数据泄漏。如果同一段管道视频抽帧后既进了训练集又进了验证集相邻帧之间内容高度相似模型相当于见了原题验证指标自然虚高。解决这份数据集的斗分已经做了 train/val/test 划分但保险起见我会抽查验证集里图像的文件名序号范围确认和训练集没有明显重叠区间。如果发现泄漏建议按采集视频片段而不是单帧来重新划分——把同一段视频的帧全部放进同一个数据子集。这类问题的排查思路很简单如果你发现验证集指标好得不正常先怀疑数据泄漏再怀疑是不是正常的模型性能。6. 把模型推到工程落地的最后一步类别权重与置信度阈值的配合调优训练完拿到 best.pt只是完成了第一步。下水道缺陷检测的一个特殊之处在于不同缺陷的后果严重程度不同——一个洞或者一处关节偏移可能意味着管道泄漏甚至坍塌而碎片只是轻微堵塞。因此在实际部署时不能对所有类别用同一个置信度阈值。我的做法是针对风险等级设置不同的置信度阈值。比如洞和关节偏移属于高风险缺陷置信度阈值设为 0.15宁可多报几个假阳性也不能漏掉真缺陷碎片和障碍物阈值设为 0.35减少低价值报警。在 Ultralytics 中推理时可以通过传入类别级别的 conf 参数来做或者在检测后处理阶段做一步过滤from ultralytics import YOLO model YOLO(sewer_defect/yolov8s_run1/weights/best.pt) # 高风险类别洞(4)、关节偏移(0) high_risk_classes {0, 4} # 低风险类别碎片(6)、障碍物(1) low_risk_classes {1, 6} results model.predict(sourcetest_images/, conf0.1, saveFalse) for r in results: boxes r.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) if cls_id in high_risk_classes: if conf 0.15: print(f高风险缺陷: 类别{cls_id}, 置信度{conf:.2f}) elif cls_id in low_risk_classes: if conf 0.35: print(f低风险缺陷: 类别{cls_id}, 置信度{conf:.2f})推理时把 conf 降到 0.1保证高置信度的结果至少不会被低阈值卡掉然后再在后处理里按类别分别过滤。这比直接统一调高阈值要合理得多——统一调高会导致低置信度的高风险缺陷被漏掉统一调低又会收到大量碎片和障碍物的误报。这套双阈值策略是我在管网检测项目里反复调整后跑通的后面基本上所有缺陷检测部署都沿用这个模式。最后再提醒一个容易被忽视的细节模型推理输入尺寸要和训练时保持一致。训练时用的是 640推理时如果直接喂原图而不做 letterbox 预处理检测框位置会明显偏移。Ultralytics 的 predict 接口会自动处理这件事但如果你自己写 C 或 TensorRT 部署这一点必须手工处理——边界框坐标还原时要把 letterbox 的 padding 减掉否则坐标全部偏移。从那以后我每次做这类缺陷检测项目都强制走一遍「解压校验 → 标签可视化 → data.yaml 核对 → 分层阈值推理」的流程中间每一步都有明确的检查点省下的时间远比多花的时间多。希望这份拆解能帮到你少走一点我当年走过的弯路。本文还有配套的精品资源点击获取