尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

下水道缺陷检测:YOLO目标检测与2364张图像数据集训练实践

下水道缺陷检测:YOLO目标检测与2364张图像数据集训练实践 简介面向使用YOLO系列算法进行下水道管廊缺陷检测的开发者这份数据集覆盖关节偏移、障碍物、裂纹、带扣、洞、公用设施入侵、碎片等常见问题2364张图像均已标注并完成训练集/验证集划分内置data.yaml适配yolov5至yolo11等主流版本可直接训练与评估。资源共2000个文件包含1636个xml标注和364个txt标签YOLO与VOC两种格式分别存放既可用于YOLO系模型训练也方便与其他检测框架衔接7.43MB的压缩包体积小、结构清晰便于快速下载部署。已有66人学习使用适合作为下水道缺陷识别项目的数据基础也可用于对比不同标注格式及目标检测算法的实际效果。1. 下水道缺陷检测为什么绕不开 YOLO2364 张图能干什么城市排水管网里常年泡着污水、砂石和各类沉积物哪怕一根管道出现关节偏移或裂纹都可能引发路面塌陷。过去靠人工下井看 CCTV 视频录像逐帧判断缺陷一个熟练工看一段 10 分钟的视频要花半小时眼睛盯久了还容易漏掉小裂纹。现在主流做法是把管道 CCTV 图像切成帧用 YOLO 算法做目标检测让模型在画面上直接框出裂缝、障碍物、带扣这些缺陷位置。这份「下水道缺陷数据集-2364张图像带标签」正好覆盖了关节偏移、障碍物、裂纹、带扣、洞、公用设施入侵、碎片七类缺陷是训练和微调检测模型的实打实原料。它的核心价值在于图像数量不算多但缺陷类别贴近一线施工场景标签齐全适合用来跑通 YOLOv8 的完整训练链路也能作为预训练数据做迁移学习。适合的读者是正在做管道缺陷检测项目、手头缺标注图像或者想用 YOLO 快速验证缺陷识别效果的工程师。这个规模的数据集不可能一步训出完美模型但要摸清检测流程、定位参数问题完全够用。2. 拆解这份下水道缺陷数据集2364 张图像里到底装了什么2.1 七类缺陷的标注逻辑与检测难点拿到数据集后第一件事不是急着训练而是把 images 和 labels 两边的文件对应关系、类别编号、每张图的标注数量过一遍。常见做法的目录结构是 images 里放 JPG 原始帧labels 里放同名 txt 文件每个 txt 一行一个目标格式是class_id x_center y_center width height。我一般会先写个小脚本统计七类缺陷各自的样本数和每张图的平均目标个数这一步能直接暴露类别的数量级差异。这七类缺陷的检测难度差异很大。关节偏移和带扣都发生在管道接口处目标是环状的几何形变轮廓清晰但容易被管壁纹理干扰。裂纹是最难的一类细长、对比度低在污水背景下经常断成几截。洞和碎片特征相对独立但碎片往往密集分布且相互遮挡。障碍物是最不规则的一类可能是一块石头、一根树枝或一堆淤泥语义边界模糊。公用设施入侵通常是指管道内穿入的其他管线特征上和障碍物容易混淆。概括来说这个数据集的难点不是「有没有目标」而是「目标边界怎么定」这也直接决定了后续训练时损失函数的收敛表现。import os from collections import Counter label_dir labels # 换成实际标签目录 class_names [joint_offset, obstacle, crack, belt, hole, utility_intrusion, debris] cls_counter Counter() box_counter Counter() for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue # 跳过 YOLO 同名的 classes.txt 这类非标注文件 if fname classes.txt: continue with open(os.path.join(label_dir, fname), r) as f: lines f.readlines() cls_counter[len(lines)] 1 # 每张图的目标数量分布 for line in lines: parts line.strip().split() if not parts: continue cls_id int(parts[0]) if cls_id len(class_names): box_counter[class_names[cls_id]] 1 print(每张图目标数量分布:, dict(cls_counter)) print(各类缺陷标注数量:, dict(box_counter))这段脚本的作用有两个一是筛选出空标注的图片——如果某个图像的 txt 文件是空的训练时 YOLO 会跳过它但验证时可能引发警告二是确认类别 id 和类名是否对得上这直接关系到训练后预测结果的语义解释。请别轻视这一步我见过有人在未核对类别顺序的情况下训练了 100 轮推理时把裂纹识别成了碎片整批标注白做。2.2 标签格式YOLO 的 txt 坐标体系和归一化细节YOLO 系列的标签统一采用归一化坐标意思是 x_center、y_center、width、height 四个值都以图像宽高为分母范围在 0 到 1 之间。这套格式的好处是转换训练分辨率时不需要重新计算标注框只要训练时同比例缩放图像即可。但相应地数据集的原始标签如果是 Pascal VOC 的 XML 格式或 COCO 的 JSON 格式就必须先转换成 YOLO 的 txt转换公式是x_center (xmin xmax) / 2 / img_widthwidth (xmax - xmin) / img_widthy 方向同理。这份数据集标题里直接写了「带标签」最可靠的做法是解压后先抽查几个 txt 文件确认每行只有五个数字且第一个数字在 0-6 范围内再用 OpenCV 或 matplotlib 把框画回原图做可视化检查。画框检查这一步不是可选项它是对标注质量最直接的人工校验。如果某些框明显偏大或者框住了一大片无关背景这类样本在训练时会被当作难例但太多的话会拉偏模型的回归头。另一个隐藏细节是类别顺序。你打开数据集的 txt 文件看第一列的数字 0、1、2、3、4、5、6 分别对应什么类别必须和之后写进data.yaml的names列表严格一致。假设原数据集的 0 是「joint_offset」关节偏移你却在 yaml 里把 0 写成了「crack」模型照样能训练损失照样下降但部署时所有预测标签都会错位而且这种错误光看 mAP 根本发现不了。2.3 数据划分策略训练/验证/测试怎么切2364 张图的规模不算大划分数据时要特别注意类别分布。最简单粗暴的随机切比例比如 8:1:1有可能导致某一个类别在验证集中一个样本都没有这个类别的 mAP 直接显示为 0。更稳的做法是分层抽样先按类别分组再从每个组内按比例抽取数据保证验证集和测试集里每一类都有一定数量。我通常建议训练集验证集测试集按 7:2:1 或 8:1:1 切。因为数据量本来就少验证集比例太低会导致早停判断不稳定测试集是最终评估用的必须保证和训练数据完全独立。切分时还要注意同一个管道断面连续截帧的图像不能同时出现在训练集和测试集里否则模型会通过背景相似性「作弊」在测试集上表现虚高实际部署到新管段上立即翻车。import os import random from collections import defaultdict random.seed(42) image_dir images label_dir labels # 先找出非空标签的图片 valid_images [] for fname in os.listdir(image_dir): name, ext os.path.splitext(fname) if ext.lower() not in (.jpg, .jpeg, .png): continue if os.path.exists(os.path.join(label_dir, name .txt)): valid_images.append(fname) # 按主导类别分层 def dominant_cls(fname): name fname.rsplit(., 1)[0] with open(os.path.join(label_dir, name .txt), r) as f: rows [r.strip().split() for r in f.readlines() if r.strip()] if not rows: return -1 cls_ids [int(r[0]) for r in rows] return max(set(cls_ids), keycls_ids.count) groups defaultdict(list) for img in valid_images: groups[dominant_cls(img)].append(img) train_set, val_set, test_set [], [], [] for cls, imgs in groups.items(): random.shuffle(imgs) n_val int(len(imgs) * 0.2) n_test int(len(imgs) * 0.1) val_set imgs[:n_val] test_set imgs[n_val:n_val n_test] train_set imgs[n_val n_test:] # 写入文件供后续脚本调用 with open(train.txt, w) as f: f.write(\n.join(train_set)) with open(val.txt, w) as f: f.write(\n.join(val_set)) with open(test.txt, w) as f: f.write(\n.join(test_set))这段代码里的dominant_cls函数计算的是「整张图的主导类别」即出现次数最多的缺陷类别。一张图如果同时有裂纹和碎片它只会被归到某个主导类别下参与分层并不能做到完全严格的类别平衡但已足够避免最糟糕的「验证集缺类」情况。如果某个类别的总样本数特别少比如只有 30 张那就索性不要单独设测试集把它并入验证集测试时采用 k 折交叉验证来评估模型稳定性。类别绝对数量不够时强行三份切分只会让每个子集的标注分布更稀疏。3. 用 YOLOv8 训练下水道缺陷模型从解压到出模型的完整命令3.1 目录组织与数据配置让 YOLO 认识你的标注很多人在拿到图像数据集后犯的第一个错误是目录结构不统一YOLO 官方预训练模型的数据加载逻辑依赖严格的目录约定最好安排成images/train、images/val、labels/train、labels/val四件套结构每张图像和它的 txt 标注文件名完全一致。zip 压缩包解压后通常是一堆散落的 JPG 和 txt常见做法是先列出解压后的目录树判断是否需要重新整理。Linux 环境用unzip解压Windows 环境用 7-Zip 或 WinRAR 解压zip 文件名是英文时一般没有问题如果压缩包内含中文文件名Windows 解压可能出现编码乱码这种坑在真实项目里很常见。解开后立刻用一个脚本把文件按训练/验证划分移动到对应的目录里同时顺手剔除那些没有对应标签的图片和空标签文件。准备好数据目录后写data.yaml是下一步它告诉 YOLO 训练集、验证集的路径和类别名。路径可以是相对路径也可以是绝对路径但不同机器之间迁移时绝对路径容易失效我建议统一用相对路径并把data.yaml放在项目根目录这样整份配置能跟代码一起走。# data.yaml # 相对路径基于项目根目录解析 path: . # 项目根目录留空则使用 yaml 所在目录 train: images/train val: images/val test: images/test names: 0: joint_offset # 关节偏移 1: obstacle # 障碍物 2: crack # 裂纹 3: belt # 带扣 4: hole # 洞 5: utility_intrusion # 公用设施入侵 6: debris # 碎片这里path: .的意思是所有相对路径都从当前工作目录解析。如果训练命令是在项目根目录下执行的就这么写如果你把data.yaml放在 config 子目录里就要改成path: ..否则 YOLO 会报Dataset not found。另一个重点是names的顺序必须和标签 txt 文件里第一列的 id 一一对应。顺序错了的话训练过程不会报错但验证阶段输出的混淆矩阵和 PR 曲线会完全错位这个错误非常隐蔽。3.2 训练命令与关键参数batch、epoch、imgsz 怎么取舍YOLOv8 是当前训练自己的数据集时用得最顺手的版本命令参数直观、日志输出友好。基础训练命令如下先别急着上大模型YOLOv8n 是 nano 版本显存占用小能快速验证数据链路是否正常。# 安装 ultralytics 后在项目根目录执行 yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs100 \ batch16 \ imgsz640 \ patience20 \ projectsewer_defect \ nameexp_nano \ seed42这段命令里modelyolov8n.pt表示加载 COCO 预训练的 nano 权重。为什么要用预训练权重而不是从零训练因为 2364 张图的规模远不足以让骨干网络收敛到好的特征表达预训练权重提供了边缘、纹理、形状等底层特征的初始值迁移到下水道缺陷上时收敛速度快很多。epochs100是最大训练轮数配合patience20意思是验证集指标连续 20 轮不提升就早停避免无效训练浪费 GPU 时间。batch16取决于显存大小8GB 显存跑 YOLOv8n 没问题如果换成 YOLOv8l 或者加大 imgszbatch 就要相应减小。imgsz640是输入分辨率。下水道图像里裂纹这类目标偏细长提高 imgsz 能保留更多细节但显存和训练时间都会成倍增长。一个折中做法是用 640 跑通流程确认没问题后再试imgsz1024对比验证集的 mAP 涨幅是否值得那多出来的训练时间。如果数据集的原始图像分辨率远大于 640注意 YOLO 在训练时会自动缩放图像到指定尺寸长宽比会做 letterbox 处理不会直接拉伸变形。3.3 先跑一次 10 轮的冒烟实验再上全量我每次拿到新数据集都会先跑一个非常短的训练比如 10 轮、batch4、不做任何数据增强目的是确认整个流程链路没有断点数据加载能不能读到图、标签能不能对上、损失函数有没有下降趋势。yolo detect train \ datadata.yaml \ modelyolov8n.pt \ epochs10 \ batch8 \ imgsz640 \ workers4 \ projectsewer_defect \ namesmoke_test \ verboseTrue冒烟实验期间重点看两部分日志一是每个 epoch 结束输出的box_loss、cls_loss、dfl_loss三个数值如果 loss 在前几轮没有任何下降迹象说明学习率设置有问题或者数据路径有误二是训练结束后自动在runs/目录下生成的混淆矩阵和验证集预测样例图快速扫一眼就能发现标签是否错位、类别分布是否失衡。冒烟实验跑完后再把上一节的正式命令拿出来此时把epochs改回 100 或 150project和name换成一个新目录避免覆盖冒烟实验的权重。这里有个经验值缺陷检测任务通常比通用目标检测需要更多的轮数才能稳定因为细长裂纹和不规则障碍物的正负样本边界比 COCO 里的猫狗要模糊得多。如果 100 轮训完验证集 mAP50 还在持续上升可以考虑再加 50 轮反之如果 40 轮就早停了也不要硬撑先去看数据质量。4. 下水道缺陷训练避坑指南五个真实踩坑记录4.1 关节偏移和裂纹几乎分不清不是模型问题是标注边界问题现象模型训练后关节偏移类别的精确率很高但召回率始终在 50% 上下浮动观察预测结果发现很多关节偏移被漏检而裂纹附近出现了大量低置信度的关节偏移误检框。原因关节偏移和裂纹在视觉特征上有重叠关节偏移处的管壁缝隙在低分辨率下看起来就像裂纹。更关键的是原数据集里这两类的标注边界不统一有的标注框覆盖整个接口环形区域有的只框住缝隙主体。标注框大小差异大模型学到的是「哪里有长条暗色线」而不是「哪里是接口结构特征」。解决把这两类样本的可视化结果逐张拉出来看筛选出标注框尺度差异极大的样本用标注工具重新修正边界如果修正成本太高更务实的做法是把这两类合并成一个joint_issue类别先保证检测得准再谈细分类。这个取舍在工业项目里很常见——检测率和误报率往往比类别粒度更重要。4.2 障碍物大面积遮挡让召回率暴跌现象验证集里含大面积障碍物的图像召回率接近 40%远低于平均值模型输出的置信度普遍低于 0.5如果推理阈值设成 0.5几乎全部漏检。原因障碍物类别的目标面积大、形状极不规则YOLO 的 anchor-based 头在回归这种极端宽高比的框时表现不稳定。同时大面积障碍物与背景的对比度低模型难以确认目标边界预测框位置偏移导致 IoU 不达标。解决一是调低推理时的置信度阈值从默认的 0.25 降到 0.15二是训练时把mosaic数据增强的概率调低因为大面积障碍物在 mosaic 拼接时会被切碎反而干扰特征学习三是针对性地把障碍物类别的损失权重调高。4.3 碎片类小目标在缩小分辨率后直接消失现象使用 imgsz416 训练时碎片类别的 mAP50 接近 0预测图上一片空白把 imgsz 提高到 640 后指标明显回升但训练时间增加了近一倍。原因碎片是小目标在 416 分辨率下可能只占十几个像素特征图上一层卷积后信息基本丢失。原图分辨率越高这种问题越严重。解决先用脚本统计每个类别标注框的像素面积与图像面积之比如果发现碎片类别的框普遍小于 0.5%就把 imgsz 提升到 768 或 1024。显存不够时考虑把图像切成多个 patch 分别推理或者把碎片类别单独提出来训练一个小模型与大模型的结果做融合。4.4 zip 文件解压乱码与标签路径错位现象在 Windows 上用系统自带解压工具解压 zip解压后发现部分 txt 文件名变成乱码导致 images 和 labels 对不上训练时报Image not found或标签读取为空。原因zip 内部文件名的编码不一致造成的Windows 默认按 ANSI 解析而压缩时文件名用了 UTF-8 编码中文文件名尤其容易出问题。解决在 Windows 上优先用 7-Zip 解压并手动选择「按 UTF-8 解压」选项如果是命令行操作用 PowerShell 的Expand-Archive之前先检查文件名编码。解压后立刻用一个脚本统计 images 和 labels 两边文件名差集确认无遗漏再进入训练流程。这个检查 10 秒钟就能做完能省下后面排查数据加载问题的几个小时。4.5 公用设施入侵和障碍物语义重叠导致误检现象预测结果里公用设施入侵的框频繁出现在障碍物上混淆矩阵显示两者的误分类率都在 15% 以上。原因这两类在语义上天然重叠一条穿入管道的电缆既是「设施入侵」又可以被视为「障碍物」。原数据集在标注时也没有统一标准同样一根管线在不同图像里可能被标成不同类别。解决明确标注规范规定管壁之外的异物统一标注为障碍物只有在确认是人工设施管线、电缆时才标注为公用设施入侵。如果数据集里这类样本数量不多考虑合并为intrusion_obstacle单类牺牲细粒度换取检测稳定性。标注规范不是训练阶段的事而是数据准备阶段必须确定的口径。5. 验证模型而不是相信损失函数mAP、PR 曲线和漏检分析5.1 mAP50 和 mAP50-95 都看不能只看一个训练结束后YOLO 会在runs/detect/exp*/目录下输出results.png、confusion_matrix.png、PR_curve.png等文件。许多人只盯着 mAP50 这一个数字觉得 0.8 以上就是好模型这个习惯在下水道缺陷场景里很危险。mAP50 只计算 IoU0.5 的预测框对细长裂纹这种目标来说IoU 0.5 的门槛太低预测框偏大或偏小都能轻易满足条件mAP50-95 从 0.5 到 0.95 每隔 0.05 取一次 IoU 阈值再取平均能更严格地反映定位精度。我建议用验证集跑一次正式评估记录三个指标并对比mAP50、mAP50-95、per-class mAP50。如果 mAP50 和 mAP50-95 差距过大比如 0.85 vs 0.45说明模型存在「框得不准但能框中」的问题需要检查标注框质量或调整回归损失权重。如果某个类别的 per-class mAP50 与其他类别差距超大回到第 4 章的对应问题去排查而不是盲目调超参数。yolo detect val \ datadata.yaml \ modelruns/detect/sewer_defect/exp/weights/best.pt \ imgsz640 \ conf0.25 \ splitvalsplitval指定在验证集上评估conf0.25是置信度阈值评估时它会直接影响 precision 和 recall 的平衡。如果是为了对比不同训练轮次的模型每次评估都要用相同的 conf 和 IoU 阈值否则数字之间没有可比性。比如 A 模型在 conf0.25 下 mAP50 是 0.82B 模型在 conf0.1 下是 0.80这不能直接判定 A 优于 B因为阈值设置本身已经倾斜了比较结果。5.2 漏检分析三板斧置信度阈值、类别分布、错标回查评估指标只能告诉你「多少分」不能告诉你「错在哪」。我的习惯是看三样东西第一是 PR 曲线中每个类别的召回率拐点这决定了推理时调低置信度阈值有没有意义第二是预测结果的可视化图YOLO 在验证时默认在val_batch*.jpg里画出部分预测框直接看比看数字更直观第三是错标回查从混淆矩阵里找出误分类最严重的两个类别把对应样本大量打印出来看。一个实用的分析脚本遍历验证集每张图用训练好的模型做推理输出「图名、真实类别、预测类别、置信度、IoU」到 CSV 文件。然后按置信度升序排列置信度最低的那些样本就是模型最不确定的难例。挑出 20 到 50 张难例图人工逐张翻看通常能发现两类问题一是原图里目标太小且模糊人工都难以判断这种样本应该从训练集中剔除二是目标被严重遮挡标注框本身就不准确这类需要修正标注。做完这一轮再继续训练两三个增量轮次用已经训好的权重做续训往往比从头调参更有效。import csv from ultralytics import YOLO model YOLO(runs/detect/sewer_defect/exp/weights/best.pt) results model.predict(sourceimages/val, saveFalse, conf0.1, iou0.5) rows [] for r in results: img_name r.path boxes r.boxes for bi, box in enumerate(boxes): cls_id int(box.cls) conf float(box.conf) rows.append([img_name, cls_id, conf]) rows.sort(keylambda x: x[2]) with open(low_conf_predictions.csv, w, newline) as f: writer csv.writer(f) writer.writerow([image, class_id, confidence]) writer.writerows(rows)脚本把置信度最低的预测结果全部筛出来便于按图抽查难例。注意conf0.1是故意比默认低目的是不漏掉低置信度但正确的预测。人工看这些低置信度框时如果发现相当一部分其实是标注错误导致的「假漏检」优先修正标注而不是加强模型硬训只会让模型去拟合错误的边界。6. 部署到管道检测机器人时的三个实战技巧6.1 裁剪与拼接高分辨率图上的小目标处理管道检测机器人采集的图像分辨率通常在 1080p 甚至更高YOLO 训练时为了适应显存会缩放到 640 或 1024这让小目标在缩放过程中丢失细节。部署时的补救方法是按重叠滑动窗口把原图切成若干子图分别推理再合并结果。切块尺寸在 640 到 1024 之间、重叠率 10% 到 20% 是常见取值重叠区域能避免缺陷正好落在切块边缘被截断。合并时对重叠区域里的检测框按置信度做非极大值抑制置信度低的框如果和置信度高的框高度重叠直接丢弃。6.2 类别合并策略按部署场景裁剪输出如果这个模型最终只用于管道维护巡检而不是缺陷精细分类把易混类别合并反而能提高可用性。实际项目中运维方最关心的是「有没有问题」和「问题大概是什么类型」不是缺陷学术分类。把关节偏移和带扣合并成joint_issue把障碍物和公用设施入侵合并成intrusion输出类别从 7 个降到 5 个检测率通常比 7 类版本更稳。合并的操作很简单把标签 txt 里对应类别 id 改写成新 id再改data.yaml的names重新训练或微调一小段。6.3 置信度阈值动态调整按管段场景卡线固定一个置信度阈值表面省事实际部署时发现不同管段的误报率差异很大。干净管段的误检框置信度普遍偏低而沉积物多的管段里误检框置信度高。一个简单的做法是在推理脚本里加入基于图像熵或灰度方差的场景判断方差低的管段使用较高阈值如 0.4方差高的管段降低到 0.2通过一次现场数据的网格搜索确定两个阈值。实际尝试后发现这个做法能把干净管段的误报数量减半同时不显著降低多杂质管段的召回率。这种针对现场数据的阈值微调比在训练阶段死磕损失函数更立竿见影。做下水道缺陷检测项目以来我最大的教训就是别把数据集当成品每一份标注数据都要先花半小时做可视化检查再进训练管线。2364 张图的体量刚好适合跑通一套标准的 YOLO 流程也足够让新手理解数据、模型和部署三者之间的制约关系。希望这份从解压到部署的完整笔记能帮你少走几步弯路把更多时间留给真正需要人工判断的缺陷边界问题。本文还有配套的精品资源点击获取
返回列表