做市政管网检测这一行,最让人头疼的往往不是现场下井,而是回来之后面对几百个小时的CCTV巡检录像。你得逐帧去看哪段管道有裂缝、哪段有沉积、哪段被树根堵了大半。纯靠人眼盯屏,看久了真的会麻木,漏检基本不可避免。所以我一直觉得,如果能有一套“下水管道排水管道缺陷检测数据集”,把缺陷、损伤、异物、沉积、腐蚀这些典型问题整理成可以直接训练的格式,整个流程就能往前走一大截。这篇文章就围绕一套我经手整理的24318张、17类别的管道缺陷数据集展开,它是VOC和YOLO双格式,目标检测模型拿过去就能用。无论你是正在找训练数据的算法工程师,还是做管网检测的现场技术员,这篇都值得看看。
1. 项目背景与问题拆解
1.1 排水管道检测为什么难自动化
排水管道检测和普通工业质检不太一样。普通质检有固定工位、固定光源、固定角度,模型要学的特征相对稳定。但管道内部是一个很极端的环境:管径从三四百毫米到两三米都有,材质包括混凝土、PVC、HDPE、陶土等,光源来自机器人自带的LED灯,镜头到缺陷的距离一直在变,再加上泥水、油污、雾气、树根、钢筋外露等因素叠加,同一个“裂缝”在不同视频里可能长得完全不一样。
人工判读的问题在于一致性。同一个缺陷,上午看是三级破损,下午看可能就觉得是四级;老手和新手对模糊图像的判断也不一样。CCTV检测每小时产出的视频量很大,一个项目少则几十公里管道,多则上百公里,逐帧看下来非常耗时。所以行业内越来越倾向于先让目标检测模型把“疑似缺陷帧”筛出来,再由检测员快速复核,效率和漏检率都能明显改善。
这里的关键不是模型选了哪个框架,而是有没有一套高质量的训练数据。缺陷样本不够、类别定义混乱、标签框不准确,再好的YOLOv8也白搭。我接触过的几个团队,多数时间都耗在数据清洗和标注规范化上,真正调模型的时间反而很少。这也是这个数据集存在的核心原因:把原始视频整理成可直接训练的VOC+YOLO格式,让算法人员把精力放在模型和业务上。
1.2 数据集在这个环节里的定位
这套数据集的定位不是替代人工,而是做“自动预筛”和“缺陷分类”的底座。拿到一张管道内壁图像,模型输出是否存在缺陷、属于哪一类、大致在什么位置,检测员之后只对有目标框的帧做二次确认。这样可以省掉大量无缺陷视频的人工观看时间。
另一个用途是缺陷统计和评分。管网健康度评估里,裂缝、变形、腐蚀、沉积这些缺陷类别出现的频次和密度直接影响修复优先级。如果检测模型只输出“有缺陷”而没有类别,评分系统就没法按权重计算。所以17个类别并不是越多越好,而是和实际养护需求一一对应。
对算法工程师来说,最直接的价值是省了数据准备的脏活。很多人拿到原始视频后,要先抽帧、去模糊、挑样本、写标注规则、找工具、培训标注员,整套下来一两周就没了。这个数据集把图像、XML标注、YOLO格式txt、类别定义都整理好了,下载或者复用之后,直接划分训练集和验证集就能开始训练。
同时它也适合用来做模型评测。不同算法在这类数据上的表现和通用目标检测数据集差别很大,因为管道缺陷普遍目标小、边缘模糊、背景复杂。用这个数据集跑出来的mAP,比用COCO预训练权重直接测更贴近实际项目。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据集结构与17个类别设计
2.1 图像来源与筛选过程
数据集的24318张图像并不是简单地把视频按帧导出来就算完。原始视频里大量连续帧的差异很小,如果全部保留,训练集和验证集之间会出现很强的相关性,模型分数虚高,到了新视频上立刻露馅。我处理的第一步是按视频为单元抽帧,通常每秒抽1到2帧,然后剔除严重模糊、镜头快速移动、机器人停留原地拍摄的画面。
不同管道环境的比例也要控制。我见过有的数据集几乎全是干净、光照好的样本,训练出来的模型一到真实污水管道里就抓瞎。这个数据集在整理时有意保留了暗光、逆光、泥水遮挡、雾气附着等困难样本,占比大约有三成。管材方面也尽量覆盖混凝土管、波纹管、PVC管和砖砌管,类别特征才会更泛化。
筛选之后还要做去重。有些同一管段的画面会被不同项目重复拍摄,视觉上几乎一样,放到训练集里会放大某些特征。我当时的做法是对图像计算感知哈希,相似度超过阈值的只保留一张,再把连续视频段分组,确保同一视频的帧不会被同时分到训练集和验证集。
2.2 17个类别到底怎么定义
17个类别参考了行业常见的结构性缺陷和功能性缺陷分类,但并不是死搬规范。做目标检测的数据集,类别标签必须满足两个条件:第一,视觉上可区分;第二,边界定义对标注员来说清晰。比如“腐蚀”和“剥落”有时候肉眼看着很像,就需要在标注规则里写清楚:腐蚀以表面变色、疏松为主,剥落是表层材料成块脱落后露出内部基底。
这里我把17个类别放在一个表格里,方便对照:
| 序号 | 英文标签 | 中文含义 | 典型表现 |
|---|---|---|---|
| 0 | Crack | 裂缝 | 管壁表面线状裂痕,长短粗细不一 |
| 1 | Fracture | 破裂 | 管体断开或贯穿性破裂 |
| 2 | Hole | 孔洞 | 局部缺失,穿透或近穿透 |
| 3 | Corrosion | 腐蚀 | 化学侵蚀导致的表面疏松、变色 |
| 4 | Deformation | 变形 | 管口或管体椭圆化、凹陷 |
| 5 | Spalling | 剥落 | 表层混凝土脱落 |
| 6 | Abrasion | 磨损 | 长期水流冲刷造成的表面磨痕 |
| 7 | Reinforcement Exposure | 露筋 | 钢筋外露或锈蚀外露 |
| 8 | Deposit | 沉积物 | 泥沙杂物在管道底部堆积 |
| 9 | Scale | 结垢 | 管壁硬质附着物,呈层状或颗粒状 |
| 10 | Grease | 油脂 | 油脂或油污聚积,常见于餐饮管网 |
| 11 | Root Intrusion | 树根侵入 | 树根透过接口或裂缝伸入管道 |
| 12 | Foreign Object | 异物 | 非管道组成部分的物体 |
| 13 | Ponding | 积水 | 管道内有明显积水 |
| 14 | Infiltration | 渗漏 | 外来水或地下水从接口/裂缝渗入 |
| 15 | Joint Displacement | 接口错位 | 相邻管节在轴向或径向上偏差 |
| 16 | Joint Opening | 接口脱节 | 接口张开或脱离 |
从这个表能看出,类别标签用英文单词,中文含义只是辅助理解。YOLO格式需要按索引编号,所以这个顺序一旦确定就不再更改,否则标注txt里的数字和类别映射会对不上。
2.3 双格式目录与标注文件长什么样
数据集同时提供了VOC和YOLO两种格式,本质上标注信息是一样的,只是存储方式不同。VOC格式是每张图像对应一个XML文件,里面记录文件名、图像尺寸、每个目标框的左上角和右下角坐标,以及类别名称。YOLO格式则每一行代表一个目标,内容是“类别索引 x_center y_center width height”,四个坐标值都做了归一化,范围是0到1。
一个典型的目录结构可以这样组织:
text复制PipelineDefectDataset/
├── VOC/
│ ├── JPEGImages/
│ │ ├── pipe_0001.jpg
│ │ └── ...
│ ├── Annotations/
│ │ ├── pipe_0001.xml
│ │ └── ...
│ └── ImageSets/
│ └── Main/
│ ├── train.txt
│ ├── val.txt
│ └── test.txt
└── YOLO/
├── images/
│ ├── train/
│ ├── val/
│ └── test/
└── labels/
├── train/
├── val/
└── test/
VOC的XML里关键的object标签内容类似:
xml复制<annotation>
<folder>JPEGImages</folder>
<filename>pipe_0001.jpg</filename>
<size>
<width>1280</width>
<height>720</height>
<depth>3</depth>
</size>
<object>
<name>Crack</name>
<bndbox>
<xmin>100</xmin>
<ymin>200</ymin>
<xmax>300</xmax>
<ymax>260</ymax>
</bndbox>
</object>
</annotation>
对应的YOLO txt内容就是:
text复制0 0.15625 0.31944 0.15625 0.08333
这里0是Crack的索引,后面四个值依次是归一化中心点x、中心点y、宽度、高度。保留双格式最直接的好处是接不同训练框架时不用重写解析脚本,PyTorch、YOLOv5、YOLOv8、MMDetection这些工具都能直接吃。
3. 标注流程与质量把控
3.1 标注工具选型
标注管道缺陷图像,不需要太复杂的工具,但要满足三点:支持多边形还是矩形框、能导出VOC XML、能把类别列表固定下来。大部分缺陷检测用水平矩形框就够了,因为目标是局部区域,矩形框能覆盖缺陷主体。细长裂缝的水平框会包含比较多背景,但胜在一致性好,模型学起来稳定。
我用得最多的是开源工具labelImg,安装简单,直接支持PascalVOC格式导出。它支持通过预先配置的classes.txt固定类别顺序,避免标注员手打出错。输入命令:
bash复制pip install labelImg
labelImg images/ annotations/
打开后左边选择保存格式为PascalVOC,右边加载图片,画框后选类别。如果团队不习惯这个老工具,也可以用X-anyLabeling或CVAT。CVAT适合多人协作和远程标注,但需要部署服务,小团队有点杀鸡用牛刀。
不管用什么工具,流程上必须统一:先在一个小批次图片上试标一轮,所有标注员开对齐会议,把模糊案例放出来讨论,确定规则后再大规模开工。这个环节不能省,省了后面返工的成本会更大。
3.2 标注规则的制定
标注规则是数据集质量的生命线。第一个问题是框的范围。对于裂缝,规则定为“框住整条可见裂缝”,如果裂缝从画面边缘延伸到画面外,就只框可见部分,并在记录里注明“截断”。对于多个缺陷重叠的图像,比如裂缝边上同时有腐蚀,就分别画两个框,不强求互斥。
第二个问题是“无法判断”的处理。管道里经常有泥水糊住管壁,某块区域看着像缺陷但看不真切。我的建议是不要硬标,宁可把这类图归到待复核队列,也不要凭感觉画框。因为错误标签会让模型学到错误特征,比漏标更伤。
第三个问题涉及类别易混淆。比如“结垢”和“腐蚀”都是表面附着物,但一个是硬质无机盐沉淀,一个是材料本身被侵蚀。规则里写清楚:结垢通常覆盖在表面且边界相对清晰,腐蚀区域颜色发暗、发黑、有疏松颗粒感。标注员拿不准的时候要截图留档,由质检员统一裁决。
3.3 多人协作与二次质检
整个标注过程大概分三轮。第一轮由初级标注员画框和选类别,第二轮由两名有管道检测经验的老手交叉复核,第三轮是随机抽检。抽检比例不能太低,我一般按不少于20%抽。抽检时重点看类别是否误标、框是否漏掉明显缺陷、框边界是否和缺陷边缘严重不符。
交叉复核阶段,如果两名复核员意见不一致,会进入争议清单,最后按少数服从多数并记录在案。这样做看起来慢,但对最终模型收益很大。数据集的24318张图并非一次性标完,而是分了若干批次。每标完一批就做一次类别分布统计,如果发现某个类别数量明显偏少,下一批优先补充该类样本。比如露筋和接口脱节在普通污水管道里不常见,如果只按随机抽帧,这两个类别的有效样本就会严重不足,所以需要人为定向收集。
注意:不要为了让数量好看而给一张图重复画框。数据集的价值在于真实和多样,标注数量虚高对训练没有任何帮助,只会让评估指标失真。
4. VOC与YOLO格式转换和训练实战
4.1 转换脚本原理与实现
VOC格式的XML是人类可读的,但目标检测框架更常用YOLO的归一化txt。转换逻辑并不复杂:解析XML,拿到图像宽高和每个object的框坐标,再归一化计算中心点和宽高。
我写了一个简单的Python脚本,既方便自己转换,也方便拿到数据的人验证文件是否正确:
python复制import os
import xml.etree.ElementTree as ET
# 类别顺序需要和训练配置保持一致
CLASSES = [
'Crack', 'Fracture', 'Hole', 'Corrosion', 'Deformation',
'Spalling', 'Abrasion', 'Reinforcement_Exposure', 'Deposit',
'Scale', 'Grease', 'Root_Intrusion', 'Foreign_Object',
'Ponding', 'Infiltration', 'Joint_Displacement', 'Joint_Opening'
]
def convert_voc_xml_to_yolo(xml_path, out_txt_path):
tree = ET.parse(xml_path)
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
box = obj.find('bndbox')
xmin = float(box.find('xmin').text)
ymin = float(box.find('ymin').text)
xmax = float(box.find('xmax').text)
ymax = float(box.find('ymax').text)
x_center = ((xmin + xmax) / 2) / img_w
y_center = ((ymin + ymax) / 2) / img_h
width = (xmax - xmin) / img_w
height = (ymax - ymin) / img_h
# 防止越界和负数
x_center = max(0, min(1, x_center))
y_center = max(0, min(1, y_center))
width = max(0, min(1, width))
height = max(0, min(1, height))
class_idx = CLASSES.index(name)
lines.append(f"{class_idx} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}")
with open(out_txt_path, 'w') as f:
f.write('\n'.join(lines))
def convert_all(xml_dir, out_dir):
os.makedirs(out_dir, exist_ok=True)
for xml_name in os.listdir(xml_dir):
if not xml_name.endswith('.xml'):
continue
xml_path = os.path.join(xml_dir, xml_name)
out_txt = os.path.join(out_dir, xml_name.replace('.xml', '.txt'))
convert_voc_xml_to_yolo(xml_path, out_txt)
这个脚本里我特意加了坐标越界保护。真实标注过程中偶尔会出现xmax稍微超出图片宽度的情况,如果不做截断,训练时YOLO会报错或者产生无效anchor。图片名称和txt文件名必须完全一致,目录里也要保持大小写统一。
4.2 训练参数与划分策略
拿到YOLO格式数据后,训练前最需要注意的是数据集划分方式。很多人在这一步吃过亏:直接用随机划分,把同一视频里的连续帧分到了train和val,结果验证集精确度很高,一到真实场景就崩。正确的做法是按视频段分组,整段视频的帧要么都进train,要么都进val,保证验证集是模型没见过的画面。
我在这个数据集上常用的划分比例是train:val:test = 8:1:1,然后使用YOLOv8s作为基线模型。训练配置里最主要的是图像输入尺寸和batch大小:
bash复制yolo detect train \
data=PipelineDefect.yaml \
model=yolov8s.pt \
epochs=200 \
imgsz=640 \
batch=32 \
device=0
对应的PipelineDefect.yaml要写清楚路径和类别名:
yaml复制path: /path/to/PipelineDefectDataset/YOLO
train: images/train
val: images/val
test: images/test
names:
0: Crack
1: Fracture
2: Hole
3: Corrosion
4: Deformation
5: Spalling
6: Abrasion
7: Reinforcement_Exposure
8: Deposit
9: Scale
10: Grease
11: Root_Intrusion
12: Foreign_Object
13: Ponding
14: Infiltration
15: Joint_Displacement
16: Joint_Opening
训练200轮后,我在留出的test集上大致能做到mAP50在0.72左右,mAP50-95在0.42到0.48之间。这个成绩对于管道场景已经够用,因为实际部署时并不要求模型找到所有像素级缺陷,只需要把“值得人工复核的帧”找出来。如果要进一步提升精度,可以在标注更细的裂缝多边形上继续优化,或者引入更多真实困难样本。
4.3 从数据集到可落地的检测流程
训练出来的模型不能直接扔到CCTV视频流里就跑。实际部署的时候,我习惯的做法是先对视频流做了抽帧,每秒最多抽2帧,然后把图像输入模型做检测。当某个类别置信度超过0.25,并且目标框面积大于某个最小阈值时,才保存为关键帧。这些关键帧会进入复核队列,由人工最后确认是否上报告。
输入图像的分辨率要处理一下。CCTV原始画面常见1280x720,直接resize到640x640一般问题不大,但如果有大量小目标,建议用SAHI这类切片推理工具。SAHI把原图切成多个互相重叠的小块,分别检测后再合并结果,小裂缝和露筋的召回率能有明显提升,代价是推理耗时增加。
部署环境如果是边缘设备,比如管道机器人上的嵌入式板卡,建议把模型导出为TensorRT或ONNX格式。如果资源紧张,可以把输入尺寸降到480x480,或者关闭一些数据增强,但不要盲目压缩,否则小目标丢失会很严重。
提示:管道缺陷检测的输出不是终点,最好再写一个结果汇总脚本,把每个缺陷框映射到视频时间点和管道里程桩号。模型只负责找缺陷,工程人员真正关心的是“哪个桩号、哪段管道、什么缺陷、严重程度多少”。
5. 常见问题与避坑实录
5.1 类别不平衡怎么办
17个类别的样本数量天然不均衡。裂缝、沉积物、腐蚀这类常见缺陷样本多,露筋、接口脱节、孔洞样本少。类别不平衡会让模型在训练时过度偏向多数类,少数类的召回率很低。
处理方法有几条路可以走。最简单的做法是对少数类做离线复制,即过采样,把每张少数类样本复制几份再混合到训练集里。但要注意复制不能简单重复,不然模型容易过拟合到特定画面上。更好一点的方式是用数据增强,比如对少数类样本做随机亮度变换、水平翻转、轻微旋转和Mosaic增强。Copy-Paste增强也适合管道场景,可以把少数类缺陷目标从一张图裁剪下来,贴到另一张无缺陷的管道背景图上,只要粘贴位置的边缘融合处理好,模型泛化性会好不少。
Loss方面也可以调整类别权重。YOLO训练时适当提高分类损失的比例,少数类被惩罚的力度更大,网络会更重视它们。我用下来感觉,先统计每一类的样本数,再把少数类的损失权重设成多数类的2倍左右,是一个比较稳妥的起点。
5.2 模糊、过曝、鱼眼变形如何处理
管道图像最大的问题是光照极端。机器人靠近管壁时画面过亮,远离时一片漆黑,还有大量运动模糊和水雾。数据清洗时保留一部分模糊和过曝样本是有必要的,因为真实场景就是这样,模型必须学会在恶劣画质下输出低置信度结果,而不是强行猜测。
如果模糊样本太多,压制了清晰样本的学习,可以在预处理阶段做筛选或加权。训练时也可以对图像做亮度抖动,模拟不同的曝光。对过曝严重的帧,可以考虑用CLAHE自适应直方图均衡化来增强暗部纹理:
python复制import cv2
img = cv2.imread("pipe.jpg")
img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(img_lab)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
l = clahe.apply(l)
img_lab = cv2.merge((l, a, b))
img_clahe = cv2.cvtColor(img_lab, cv2.COLOR_LAB2BGR)
不过要注意,如果训练时用了CLAHE,推理时最好也用相同的预处理,否则会出现域偏移。鱼眼镜头畸变是另一个大坑。管道CCTV广角镜头的边缘畸变会让矩形框位置不准,解决办法是先用标定板做去畸变处理,再统一标注。如果数据集本身没有矫正,模型在相同镜头上训练和测试问题不大,但换一种镜头就会掉点。
5.3 小目标缺陷怎么提升召回
管道画面里真正的细小裂缝可能只占十几个像素,人眼都要凑近看,模型更难。提升小目标召回,最直接的手段是提高输入分辨率。如果显存允许,把imgsz从640提到768或896,mAP50对于小目标通常能涨两到三个点。
其次是多尺度训练。训练时开启YOLO的multi-scale,让模型在不同分辨率下都能稳定识别。推理时也可以用TTA或SAHI切片。SAHI适合那种“图像整体清楚但目标很小”的情况,比如1280x720原图中裂缝只有30像素宽,切成640x640重叠切片后,相当于让模型在更近的尺度上看图。
后处理上也可以做优化。默认NMS阈值对密集小目标不太友好,目标之间靠得近时容易误抑制。可以适当调低NMS的IoU阈值,例如默认0.7改为0.5到0.6之间,减少相邻框被剔除。不要调太低,否则一个目标会输出多个重复框。
5.4 数据划分泄露问题
这是很多自建数据集的隐藏问题。管道视频连续帧之间高度相似,如果随机打乱划分,训练集和验证集里会出现同一个场景的画面。模型在验证集上表现好,只是因为记住了类似背景,而不是真正识别了缺陷。到了新项目、新管段上,分数立刻下滑。
我在处理这个数据集时,每个原始视频都有唯一编号,标注结束后按编号把整个视频的帧分配到同一个集合。比如一个视频有150帧,要么全部进训练集,要么全部进验证集,绝不能拆开。对测试集同样如此。凡是用在公开评测或论文里的模型,都应该在数据说明里写明“按视频序列划分”,否则指标参考价值有限。
另外,图像的整理要保留原始分辨率信息。有的项目为了省存储空间把图像压成小图,训练时再放大,这会丢失细节。最好原始帧存成JPG保留质量95以上,或者用PNG保存关键帧,标注框坐标基于原始分辨率生成,训练时再统一缩放。
6. 说几句实在话
这套数据集从拆解项目需求到整理成双格式可用的状态,我踩过的坑不算少。最大的体会是:管道缺陷检测的问题重点不在算法多花哨,而在数据能不能真实反映现场。白天黑夜、晴天雨天、新管老管,各种环境下的样本都需要覆盖。如果只追求类别数量好看,忽略图像质量、类别定义和划分方式,最终训练出来的模型到项目现场大概率要返工。
如果你只是临时想跑通一个检测Demo,拿任何公开目标检测数据集也能跑,但要在真实管道视频上做预筛,强烈建议用这种专门整理过的管道缺陷数据。它好就好在“脏”样本足够多,模型见多了泥水、阴影和雾气,遇到新场景时不会轻易慌。
再分享一个小技巧:模型训练出来之后,不要只看总体mAP,按类别去统计单类AP。管道场景里最常见的裂缝和沉积物AP往往很高,真正决定项目验收能不能过的反而是那些数量少的孔洞、露筋、接口脱节。给每一个类别单独设一个验收阈值,比只看一个均值靠谱得多。
