ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

187张管道漏水检测数据集:YOLO+VOC双格式目标检测实战指南

187张管道漏水检测数据集:YOLO+VOC双格式目标检测实战指南 简介面向目标检测与智慧水务场景这份管道漏水数据集收录了家庭管道、下水管道等多种真实环境的现场图像统一标注单一类别leak并以矩形框精确标记323处漏水区域可直接用于YOLO、SSD等模型的训练与漏水检测算法验证。压缩包共563个文件体积仅10.97MB内部包含187张jpg原图、187个xml标注文件VOC格式和189个txt标注文件YOLO格式目录按JPEGImages、Annotations、labels三层组织图片与标注一一对应且原图清晰、未做增强处理方便研究者快速加载并开展实验。数据集适合有一定目标检测基础的学生与算法工程师作为补充训练集或入门实践数据能够免去自行采集、清洗与标注的繁琐环节。目前已有307人学习浏览对于管道漏水这类细分场景而言是一份轻量且可直接上手的标注数据资源。1. 管道漏水检测数据集187张YOLOVOC双格式样本的实战价值做目标检测的同行应该都有过这种经历接到一个漏水检测的活脑子里第一反应是先找数据集验证可行性结果翻遍开源市场要么是通用物体检测集要么是规模大但场景不符的工业集训出来的模型拿到现场完全不是那么回事。这个管道漏水数据集一共187张图是围绕管道漏水这一具体场景整理的小样本集同时给了YOLO和VOC两种标注格式拿来就能直接喂给训练器省掉一大部分格式折腾的时间。它解决的核心问题很简单在真实场景数据还没攒够之前用一套小而精的样本把漏检路线先跑通验证yolo目标检测在这个场景下的可行性。适合三类人一是刚接触yolo、想找一个带标注的实物数据集练手的开发者二是做工业巡检、水务监测、管道质检项目需要快速做预演的技术人员三是想对比VOC格式和YOLO格式差异、练习格式转换脚本的学生。187张图不算多但作为「先跑通再扩量」的起步集够用了。2. 先看清家底187张图里的XML与TXT标注到底怎么组织2.1 双格式并存的真实意义不是炫技是省事很多第一次拿到这份数据集的读者会问既然训练yolo只需要txt标注为什么还要附带VOC的xml这个问题问到了点子上。VOC格式本身是计算机视觉领域的老牌标准labelimg这类常用标注工具默认输出的就是VOC格式的xml文件而YOLO官方仓库以及衍生出的yolov5、yolov8等训练框架读取的是归一化后的txt标注。两者并存意味着你既可以在labelimg里直接打开继续编辑标注也可以把txt交给训练器直接开训不用先做一轮格式转换。如果你有自己积累的历史标注数据大概率是VOC格式居多。这份资源等于给了一个「桥接样本」同一批图两种格式对照着看能帮你把坐标系、归一化方式这些最核心的换算关系彻底搞清楚之后再处理自己的数据时不容易翻车。2.2 逐行拆解一个VOC xml文件和一个YOLO txt标注行打开数据集里的Annotations目录随便挑一个xml文件结构长这样annotation folderleak_data/folder filenameleak_001.jpg/filename size width1280/width height720/height depth3/depth /size object nameleak/name bndbox xmin342/xmin ymin188/ymin xmax521/xmax ymax297/ymax /bndbox /object object nameleak/name bndbox xmin810/xmin ymin240/ymin xmax958/xmax ymax356/ymax /bndbox /object /annotation这段xml里最关键的是size里的宽高和object里的name、bndbox四个值。xmin/ymin/xmax/ymax是漏水区域在原始图片上的绝对像素坐标单位是像素左上角为原点。这个文件里出现了两个object节点说明这张图有两个漏水区域训练时就会产生两个目标框。再看labels目录里同名txt文件的内容0 0.337109375 0.3368055556 0.13984375 0.1513888889 0 0.69140625 0.4138888889 0.115625 0.1611111111每一行对应xml里的一个object。四个数字依次是类别id、归一化后的中心点x坐标、中心点y坐标、归一化后的目标宽、目标高。计算逻辑不复杂中心点x(xminxmax)/(2*width)宽(xmax-xmin)/width中心点和高度同理。上面第一行是第一个框换算的结果你可以拿计算器验证一下。2.3 三方核对脚本图片、xml、txt缺一不可拿到数据集第一件事不是直接开训而是先做一次完整性核对。我一般会写一个几十行的检查脚本把图片、VOC标注、YOLO标注三者对齐找出缺文件或坐标异常的样本避免训练时某个epoch突然报错。import os import xml.etree.ElementTree as ET from pathlib import Path def check_dataset(img_dir: str, xml_dir: str, txt_dir: str, class_file: str): # 读取类别清单类别顺序决定了yolo txt里的id编号 with open(class_file, r, encodingutf-8) as f: classes [line.strip() for line in f if line.strip()] print(f类别清单: {classes}) imgs {p.stem: p for p in Path(img_dir).glob(*.jpg)} xmls {p.stem: p for p in Path(xml_dir).glob(*.xml)} txts {p.stem: p for p in Path(txt_dir).glob(*.txt)} missing_img xmls.keys() - imgs.keys() missing_xml imgs.keys() - xmls.keys() missing_txt imgs.keys() - txts.keys() print(f有xml但缺图片: {len(missing_img)}) print(f有图片但缺xml: {len(missing_xml)}) print(f有图片但缺txt: {len(missing_txt)}) # 逐个xml检查name是否在类别清单里、坐标是否越界 for name, xml_path in xmls.items(): tree ET.parse(xml_path) root tree.getroot() size root.find(size) w, h int(size.find(width).text), int(size.find(height).text) for obj in root.iter(object): cls obj.find(name).text if cls not in classes: print(f{name}: 类别 {cls} 不在清单中) 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) if not (0 xmin xmax w and 0 ymin ymax h): print(f{name}: 坐标越界 ({xmin}, {ymin}, {xmax}, {ymax})) if __name__ __main__: check_dataset( img_dirleak_data/images, xml_dirleak_data/annotations, txt_dirleak_data/labels, class_fileleak_data/classes.txt )这段脚本做了三件事先统计三类文件名的交集差集确保每一张图都有对应标注然后解析每个xml里的目标框验证类别名和坐标范围。注意classes.txt的读取顺序很关键——YOLO训练时类别的id完全依赖清单的顺序比如清单第一行是leak那这个类在txt里就得是0次序错了整个训练都白费。跑完脚本如果输出全是0说明这个数据集的完整度不错可以进入下一步。我通常在拿到任何标注数据集时都会先干一遍这个检查既为了安全也为后面训练排掉最容易出现的低级错误。3. 把VOC转成YOLO格式转换脚本与四个边界坑3.1 为什么非转不可YOLO训练器只认txtVOC格式虽然看完xml让人心里踏实但yolov5、yolov8这些框架的数据加载器读的是txt标注你给它xml它不认。所以如果你的数据只有VOC格式转格式是绕不开的一步。反过来如果你想把数据拿回labelimg里继续标注txt又不如xml直观。这就是为什么这份187张的数据集给出了两套标注——你不需要自己写转换脚本但建议手动跑一遍转换逻辑真正理解坐标换算因为自己攒的数据迟早要面对同样的问题。3.2 转换脚本坐标换算与容错处理假设你现在手头只有一份VOC格式的数据要转成YOLO训练用的txt常见做法是写这样一个脚本import os import cv2 import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo_one(xml_path: Path, img_path: Path, out_path: Path, class_map: dict): tree ET.parse(xml_path) root tree.getroot() # 优先用opencv读原图实际尺寸xml里的size有时并不可靠 img cv2.imread(str(img_path)) if img is None: print(f图片读取失败: {img_path}) return img_h, img_w img.shape[:2] lines [] for obj in root.iter(object): name obj.find(name).text if name not in class_map: print(f未知类别 {name}跳过) continue cls_id class_map[name] 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) # 过滤掉宽或高为0的异常框这类框会导致训练loss变成nan if xmax xmin or ymax ymin: print(f{xml_path.stem}: 出现无效框跳过) continue # 坐标裁剪到图片范围内防止归一化后越界 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) # 核心换算绝对像素 - 归一化中心点宽高 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / 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}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 类别到id的映射顺序必须和你训练时的yaml一致 class_map {leak: 0} voc_dir Path(data/voc_annotations) img_dir Path(data/images) yolo_dir Path(data/labels) yolo_dir.mkdir(exist_okTrue) for xml_path in voc_dir.glob(*.xml): img_path img_dir / f{xml_path.stem}.jpg out_path yolo_dir / f{xml_path.stem}.txt voc_to_yolo_one(xml_path, img_path, out_path, class_map) print(f转换完成: {xml_path.name})这个脚本里我刻意做了三件容易被忽略的事。第一用cv2.imread读真实图片尺寸而不是直接信任xml里的size节点——很多标注工具在图片被压缩或裁剪后不会同步更新size这会导致归一化坐标整体偏移第二过滤掉宽高为0的异常框训练时遇到这类框loss会直接变成nan整轮训练报废第三把坐标裁剪到图片范围内防止标注时手滑超出边界的点影响损失计算。转换完成后建议随机抽几张图把txt里的坐标换算回像素值画框验证。常见做法是写个几行的可视化脚本或者直接用labelimg打开一张图再打开对应txt看框是否贴合漏水区域。这一步别看简单能省掉训练完才发现数据标错的后悔药。3.3 四个边界坑坐标、类别、编码、尺寸第一个坑是类别id从0还是从1开始。VOC里类别是字符串YOLO里是整数id且必须从0开始。很多人习惯了VOC的编号方式转格式时写成从1开始结果训练完发现所有预测框的类别都错位一位。第二个坑是xml里的bndbox坐标可能是小数也可能出现xmin大于xmax这种倒挂情况。常见原因是标注工具版本差异或人工标注时误操作。转换脚本里最好做一次排序校正至少要做有效性判断。第三个坑是图片尺寸的获取方式。如果你依赖xml里的size而图片在标注后又经过压缩处理那size和实际图片宽高对不上生成的归一化坐标必然出错。这是我在实际转换里踩过最实在的坑所以脚本里直接改用cv2读取真实尺寸。第四个坑是Windows下的编码问题。很多VOC xml文件里的中文类别名是用GBK编码保存的用encodingutf-8去解析会直接抛异常。处理这类文件时要么指定GBK读取要么在标注阶段就统一用英文类别名。这个坑在工业现场数据里很常见尤其是供应商提供的旧标注数据。4. 小样本怎么训yolov8n在187张图上的训练配置4.1 为什么选yolov8n而不是从零训练187张图对于深度学习目标检测来说属于典型的小样本。如果你从零初始化网络开训几乎不可能收敛因为模型需要学习的特征量远超样本能提供的信息量。正确做法是使用在COCO上预训练过的yolov8n权重作为起点利用它已经学会的通用视觉特征——边缘、纹理、形状——再在漏水数据上微调。这个策略业内叫迁移学习在小样本场景下属于基本操作。网络选yolov8n而不是yolov8s或yolov8m原因有两个。一是187张图支撑不起大模型的训练参数量越多越容易过拟合训练集loss很好看验证集mAP一塌糊涂二是nano版本推理速度快后续如果要部署到边缘设备或者工业相机上做实时检测nano的算力需求更现实。等数据量扩充到几千张再换更大的模型不迟。4.2 数据划分与超参数先解决过拟合再谈精度187张图的数据集划分要格外小心。我常用的比例是训练集147张、验证集40张如果有条件再留20张当测试集。如果你的漏水场景和这张数据集的图片风格差别较大建议把验证集比例提高到30%因为小数据下验证集的作用不只是评估精度更重要的是帮你判断模型有没有过拟合。对应的yaml配置文件这样写# leak_data.yaml train: ./leak_data/train/images val: ./leak_data/val/images nc: 1 names: [leak]超参数方面最关键的几个依次是epochs、imgsz、batch、lr0、patience。187张图配yolov8nepochs从100开始比较稳太少模型还没学好就停了太多又容易过拟合imgsz推荐640这是预训练权重最熟悉的输入尺寸改大或改小都会引入额外的分布偏移batch根据显存来定8到16之间是安全区学习率lr0不要用默认的0.01小数据集通常降到0.005左右太大容易出现loss震荡。这里有个很多新手容易犯的错误看到验证集mAP不高就疯狂加大epochs结果train loss继续降、val loss反而反弹。小样本训练本身就是半玄学正确做法是盯着val loss曲线一旦开始回升就停而不是等mAP自己变好。4.3 训练命令与参数说明下面是一条实际可用的训练命令以yolov8官方仓库为例yolo detect train \ dataleak_data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ optimizerAdamW \ lr00.005 \ patience20 \ cos_lrTrue \ augmentTrue逐项解释modelyolov8n.pt会先下载COCO预训练权重然后自动裁剪检测头你不用手动改nc参数optimizerAdamW在小数据集上比SGD收敛更稳SGD更适合大数据集从零训练cos_lrTrue让学习率按余弦曲线衰减后半程微调效果更平滑不容易在最优解附近震荡augmentTrue开启数据增强对187张图来说这个开关建议保持开启后面会讲为什么。训练过程中要重点观察两个指标训练结束后输出的mAP50和mAP50-95。对漏水检测来说mAP50是你最先要保证的指标它衡量的是框的位置大致对了就算对mAP50-95更严格要求框和真实标注的重合度很高。小样本数据集上mAP50-95偏低是正常的不要因为这个就急着调参先看mAP50过不过得去。4.4 小样本下的数据增强策略别把mosaic开太猛yolov8默认开启mosaic增强把四张图拼成一张训练。这个增强对中大型目标效果很好但对漏水这种可能只占图片几个百分点的细长目标mosaic会把目标切碎、缩小让模型学到错误的目标形态。在187张图的小样本场景下我的做法是保留mosaic但把概率降低同时把hsv扰动、随机翻转、平移旋转这些增强开到一个偏激进的水平——色域类增强不会破坏目标形状对小样本泛化帮助更明显。实际操作中你在训练命令里可以用mosaic0.5这种参数单独控制各项增强的强度不需要写额外代码。记住一条原则小样本训练的目标是让模型见到尽量多样的背景和光照而不是让它在变形的目标上反复挣扎。5. 避坑排查187张漏水图集最容易翻车的五个问题5.1 mAP一直为0但训练loss看起来正常现象训练过程顺顺利利loss曲线也正常下降但验证阶段mAP输出全是0没有一条预测框命中。原因最常见的是类别id错位。数据集txt里的类别id和训练yaml里的names列表顺序对不上模型预测的类别编号在训练时被当成背景或其他类无法正确匹配。解决重新核对labels目录下txt里的数字编号对照classes.txt或训练yaml里的names顺序。用第二节的核对脚本跑一遍把标注文件里的id全部打印出来一一比对。5.2 训练中途loss突然变成nan现象某几个epoch后loss输出为nan训练进程直接卡死。原因数据集中存在宽或高为0的异常框或者某个坐标值极端大/极端小导致损失函数计算时出现除零或溢出。另一个常见来源是图像读取失败——有的jpg文件损坏或者格式异常cv2读到空数组。解决先用5.1里的核对脚本把坐标越界和无效框过滤掉再检查图片文件完整性读取失败的图片直接从训练集里剔除。转换脚本里那两个if判断不是可有可无的冗余代码是防nan的保险丝。5.3 模型把漏水区域和暗色阴影混为一谈现象训练完成后可视化预测结果发现模型在管道阴影、锈迹、水渍上框出一堆假正例真正的细小漏水反而漏检。原因正样本太少模型学到的是「颜色偏深、纹理偏暗的区域就是漏水」这种粗糙特征。187张图如果分布不均匀比如大部分漏水都发生在暗色背景上模型就会把背景特征当成目标特征。解决先统计数据集中目标框的尺寸分布和背景复杂度。如果背景干扰太严重优先收集带负样本的图片加入训练集让模型看到「像漏水但不是漏水」的区域。其次把验证时的置信度阈值从默认0.25提到0.35过滤掉低置信度的误检框。注意不要为了消除误检把阈值加太高否则小目标漏水也一起被滤掉了。5.4 验证时预测框坐标大量越界现象预测阶段输出的框坐标有的跑到图片外面有的宽高比异常离谱。原因训练集里存在尺寸不一致的图片。187张数据集可能包含横图、竖图、不同分辨率的截图模型在训练时见到多种宽高比但yolov8会把输入统一resize到640x640导致小分辨率图片里的目标在resize后标注框相对位置失真。解决训练前统一图片尺寸常见做法是把图片长边缩放到1280或固定为640x640并做letterbox填充转换脚本里用到的实际尺寸也一起更新。对于resize后目标变得过小的样本要么补采高清图要么在标注阶段把这类样本单独用更大分辨率训练。5.5 训练提前被patience机制打断现象epochs设了100但跑到40轮左右训练就自动停了明明验证loss还在下降。原因yolov8的patience20意思是验证指标连续20轮没有提升就早停。小样本数据特征波动大可能连续十几轮mAP都没有变化但后面还会继续涨。我遇到过最典型的情况是前30轮都在适应新的域分布指标纹丝不动正要上升时被patience掐断了。解决小样本训练把patience设到50甚至100或者干脆设成0关闭早停。代价是训练时间变长但对187张图来说本来就是分钟级的事没必要为了省时间牺牲收敛质量。从那以后我每次在小数据集上训练第一件事就是先把patience关掉观察完整训练曲线再决定要不要早停。6. 进阶用法用混淆矩阵验证漏水检测再导ONNX瘦身上边缘设备训练完别急着收工先看验证输出里的混淆矩阵。yolov8的val结果会生成confusion_matrix.png重点关注漏检——也就是真实目标是leak但模型没检出来的样本数。如果漏检集中在某些特定场景比如夜间光照下的细小漏水口说明训练集在这类条件下样本不足下一步补数据就优先补这个方向。需要注意的是混淆矩阵各格子的总和并不必然等于验证集总数因为不同的置信度阈值和NMS参数会影响分类结果所以看分布趋势就好。模型验证没问题后导出ONNX做推理加速yolo export modelruns/detect/train/weights/best.pt formatonnx imgsz640 opset12导出的onnx可以直接用onnxruntime加载在Jetson Nano、瑞芯微这类边缘设备上跑实时推理。如果你的部署设备对内存和带宽敏感可以进一步做int8量化常见做法是先把onnx转成fp16验证精度损失可接受后再考虑int8。对小目标漏水检测int8量化后mAP下降超过5%就不要强行用边缘设备算力不够的情况下优先降低输入分辨率而不是牺牲精度。最后说一个我踩过好几次的坑导出的onnx如果在不同版本的onnxruntime下推理结果不一致先检查opset版本新版runtime对旧版本模型兼容性经常出问题。从那以后我每次部署前都强制走一遍「验证集推理对比」用导出前后的模型跑同一批验证图片对比框坐标和置信度偏差超过阈值就回到导出步骤排查。这个习惯帮我省掉了不少现场部署时的翻车希望帮到你。本文还有配套的精品资源点击获取
返回列表