多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

排水管道缺陷检测数据集770张YOLO+VOC双格式实战指南

排水管道缺陷检测数据集770张YOLO+VOC双格式实战指南 简介本资源为排水管道缺陷检测数据集面向从事计算机视觉目标检测的开发者与研究人员可用于训练和验证管道破损识别模型。数据集包含770张清晰排水管道图片提供VOC与YOLO两种标注格式压缩包内分设JPEGImages、Annotations、labels三个文件夹分别存放jpg图片、xml文件与txt标签共约2000个文件包体大小14.53MB。标注涵盖clean、crack、hole、obstacle四类总框数1812个其中crack框数最多达1010obstacle为539hole为153clean为110均为矩形框适用于目标检测任务。目前已有1012人学习下载。读者可直接获得图片与双格式标注省去自行采集与标注成本快速搭建管道缺陷检测训练流程用于模型对比、算法验证或课程实践也可作为数据增强与格式转换的基准素材。1. 排水管道缺陷检测数据集770 张 YOLOVOC 双格式到底能干什么手里只有 770 张标注图能不能训出一个能用的排水管道缺陷检测模型这是我在市政管网巡检项目里被问得最多的问题。排水管道缺陷检测的典型场景是 CCTV 爬行器回传视频需要自动识别破裂、变形、腐蚀、错口、渗漏这几类结构性缺陷人工判读一段 300 米管段要花两三个小时而且不同判读员对同一帧的结论经常打架。这个数据集的价值就在于它把 YOLO 和 VOC 两套标注格式都打包好了省掉了最耗时的格式转换环节770 张虽然不算大但对于验证一条检测链路是否跑得通、评估某个 backbone 在管壁纹理上的表现完全够用。适合谁适合刚接手管网巡检算法、需要快速搭出 baseline 的工程师也适合想拿真实工业缺陷数据练手 YOLO 微调的人。别指望它直接产出生产级模型它的定位是让你在半天内跑通训练并看到 mAP 曲线。2. 先搞清楚 770 张双格式数据集里到底装了什么2.1 YOLO 格式与 VOC 格式的目录差异拿到一个压缩包第一件事不是急着解压训练而是先摸清目录结构。YOLO 格式和 VOC 格式对同一批图的组织方式完全不同混用会导致标签读不到、类别对不上。常见做法是解压后先tree一遍把两类目录分开看。YOLO 格式的典型结构是 images 和 labels 平行存放每张图对应一个同名 txttxt 里每行是class_id cx cy w h坐标全部归一化到 0 到 1 之间。VOC 格式则是 JPEGImages 放图、Annotations 放 XML、ImageSets/Main 放 train/val 的划分文件XML 里用绝对像素坐标记录 xmin、ymin、xmax、ymax。# 解压后先看目录层级别急着写训练脚本 unzip 排水管道缺陷检测数据集yolovoc格式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_idXML 用 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 这种小模型起步输入尺寸设 640batch 根据显存调。# 用 YOLOv8n 预训练权重微调跑 100 轮 yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/pipe \ nameexp1逻辑说明model 指定预训练权重YOLO 会自动下载并加载 backboneepochs 给 100 是因为小数据集容易过拟合配合 patience20 做早停验证集 20 轮不提升就停。lr0 是初始学习率微调场景 0.01 比从零训的 0.01 到 0.1 要保守太大容易把预训练特征冲掉。batch16 是 8G 显存下的稳妥值显存不够就降到 8 并同步把 lr0 减半。imgsz640 是通用起点管道缺陷目标通常不大如果发现小缺陷漏检严重可以提到 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, conf0.25, saveTrue) # 统计每张图的检出框数量异常多或异常少都值得看 for r in results: print(r.path, 检出框数:, len(r.boxes))逻辑说明conf0.25 是推理置信度阈值低于它的框不输出调高会减少误检但可能漏检调低反之。saveTrue 会把画框结果存到 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 降到 8imgsz 保持 640还爆就降到 4同时把 lr0 按比例调小避免小 batch 下学习率过大导致震荡。6. 把 770 张用到极致增强策略与验证习惯770 张想榨出更多价值核心在增强和验证。我一般会开 mosaic、mixup 和随机仿射但管道缺陷有个特殊性缺陷的形态和方向有物理含义比如环向裂缝和纵向裂缝不能随便旋转混淆。所以随机旋转角度我控制在正负 15 度以内翻转只开水平不开垂直因为管道图像上下翻转不符合爬行器视角。HSV 增强里饱和度抖动可以大一点管壁颜色受光照影响本来就大但亮度抖动要克制否则暗部缺陷会被抹掉。验证习惯上我坚持每训完一版都拿同一组固定验证图跑推理并存档而不是只看 mAP 数字。因为 mAP 是聚合指标会掩盖某一类突然崩掉的情况。具体做法是固定 20 张覆盖各类缺陷的图每次推理后把检出框数和置信度记到表格里横向对比不同版本。版本破裂检出变形检出腐蚀检出平均置信度exp118/2015/2012/200.61exp219/2016/2014/200.68这张表比 mAP 更能告诉我哪一类在退步。exp2 腐蚀类从 12 涨到 14说明提高输入尺寸对细小腐蚀有效。如果某一版平均置信度涨了但检出数掉了多半是 conf 阈值设高了得回去调。还有一个习惯是永远保留一份原始标注的备份任何格式转换和增强都在副本上做。我踩过一次坑直接在原 labels 上做格式覆盖结果转换脚本写错把一半标签清空了原始标注又没备份只能重新标。从那以后我的规矩是数据集目录只读所有处理输出到新目录。这个方案值不值得做如果你手头有管网巡检的标注数据770 张足够验证链路和选型但真要上生产至少得补到三五千张并覆盖不同管材和光照。先从这 770 张跑通闭环再决定要不要扩标是最省时间的路径。希望帮到你。本文还有配套的精品资源点击获取
返回列表