ARTICLE DETAIL

资讯详情

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

管道焊缝缺陷检测数据集VOC转YOLO实战:从xml解析到YOLOv8训练避坑指南

管道焊缝缺陷检测数据集VOC转YOLO实战:从xml解析到YOLOv8训练避坑指南 简介这份资源面向从事工业质检、缺陷识别方向的目标检测学习者与算法工程师提供管道焊接缝缺陷检测的完整数据集解决焊缝图像标注与训练测试划分的准备工作。数据采用VOC标注格式图像为800×800的RGB图片共分good、bad两个类别可直接投入目标检测模型训练无需额外清洗或转换。压缩包内共2000个文件以1134个xml标注文件和864张jpg图像为主另附1个可视化py脚本与1个类别json字典整体约106.54MB。目录按train与test划分各自包含images图片与labels标注两个子文件夹训练集908张图片及对应xml测试集226张图片及对应xml结构清晰便于直接读取。附带的可视化脚本随机传入一张图片即可绘制边界框并保存到当前目录无需修改即可运行方便快速核验标注质量。目前已有1271人学习下载适合需要快速搭建焊缝缺陷检测基线、验证模型效果或开展课程实验的读者使用。1. 管道焊接缝缺陷检测数据集VOC 格式的 xml 到底怎么用起来拿到一份标注好的管道焊接缝缺陷检测数据集里面是一堆 VOC 格式的 xml 文件训练集和测试集已经划分好了这件事听起来省心实际动手时最容易卡在第一步xml 到底怎么读、怎么转、怎么喂给模型。管道焊缝的缺陷类型通常包括气孔、夹渣、未熔合、裂纹、咬边这几类它们在图像里往往对比度低、边界模糊、目标细长跟通用目标检测数据集里那些轮廓清晰的对象完全不是一个难度。这份数据集用 VOC 标注格式意味着每个 xml 里记录了文件名、图像尺寸、每个缺陷目标的类别和边界框坐标训练集和测试集目录已经分好省掉了自己切分的麻烦。适合谁用做工业质检方向、想快速验证检测模型在焊缝场景下表现的工程师以及需要一份带真实缺陷分布的标注数据来做 baseline 的团队。接下来把从 xml 解析到训练跑通的完整路径拆开讲重点放在参数怎么设、坑在哪。2. 先搞懂 VOC xml 的结构和这份数据集的目录约定2.1 一个焊缝缺陷 xml 里到底存了什么VOC 格式的标注文件本质是一个结构化 xml根节点是 annotation下面挂着 folder、filename、size、segmented以及每个目标一个 object 节点。对管道焊缝缺陷检测来说真正决定训练效果的是 object 里的 name 和 bndbox。name 是缺陷类别字符串bndbox 里是 xmin、ymin、xmax、ymax 四个整数坐标。下面是一个典型的焊缝缺陷 xml 片段我按常见结构写出来你拿到手的文件字段名基本一致。annotation foldertrain/folder filenameweld_0231.jpg/filename size width1280/width height960/height depth3/depth /size segmented0/segmented object nameporosity/name !-- 气孔 -- poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin412/xmin ymin305/ymin xmax468/xmax ymax352/ymax /bndbox /object object namecrack/name !-- 裂纹 -- poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin890/xmin ymin120/ymin xmax915/xmax ymax540/ymax /bndbox /object /annotation这段结构里size 决定坐标是否越界object 的数量决定这张图有几个缺陷。焊缝缺陷有个特点裂纹这类目标 bndbox 会非常细长宽高比可能到 1:10 以上气孔则接近正方形。解析时如果直接把坐标当浮点处理后面转 YOLO 归一化会引入不必要的精度问题建议全程用整数读、最后一步再除。2.2 训练集测试集划分已经做好先别急着信标题里说已经做了训练集和测试集划分这通常意味着目录下有两个文件夹各自带 images 和 annotations或者干脆是 VOC 标准的 JPEGImages 加 Annotations 两套。动手前先跑一段统计脚本把类别分布、每类目标数、图像尺寸分布摸清楚。焊缝数据集的类别不平衡往往很严重气孔可能几百个裂纹只有几十个这个数字直接决定后面要不要做重采样或改损失权重。import os import xml.etree.ElementTree as ET from collections import Counter def scan_voc(ann_dir): cls_counter Counter() box_per_img [] sizes Counter() for f in os.listdir(ann_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(ann_dir, f)) root tree.getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) sizes[(w, h)] 1 objs root.findall(object) box_per_img.append(len(objs)) for obj in objs: cls_counter[obj.find(name).text] 1 return cls_counter, box_per_img, sizes cls, per_img, sizes scan_voc(dataset/train/annotations) print(类别分布:, cls) print(平均每图目标数:, sum(per_img) / len(per_img)) print(图像尺寸分布:, sizes.most_common(5))这段脚本做三件事统计每个类别的实例数、统计每张图平均目标数、统计图像分辨率分布。参数上没什么可调的重点是看输出。如果某个类别实例数低于 50训练时基本要靠数据增强硬撑如果尺寸分布里出现多种分辨率后面 resize 策略要统一不能按原图直接送进网络。焊缝图像常见 1280x960 或 2448x2048 这类工业相机分辨率直接缩到 640 会丢细小缺陷这点后面避坑章节会展开。3. 把 VOC xml 转成 YOLO 格式转换脚本和四个必调参数3.1 转换逻辑归一化坐标和类别映射YOLO 格式每张图对应一个 txt每行是 class_id cx cy w h全部归一化到 0 到 1。转换的核心是把 VOC 的绝对坐标先算中心点和宽高再除以图像宽高。类别映射要固定顺序训练和推理必须用同一份否则类别全乱。下面这份脚本我用了很多次直接可跑。import os import xml.etree.ElementTree as ET CLASSES [porosity, slag, lack_of_fusion, crack, undercut] CLS2ID {c: i for i, c in enumerate(CLASSES)} def voc_to_yolo(ann_dir, img_dir, out_dir): os.makedirs(out_dir, exist_okTrue) for f in os.listdir(ann_dir): if not f.endswith(.xml): continue root ET.parse(os.path.join(ann_dir, f)).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLS2ID: continue # 跳过未定义类别 bb obj.find(bndbox) xmin float(bb.find(xmin).text) ymin float(bb.find(ymin).text) xmax float(bb.find(xmax).text) ymax float(bb.find(ymax).text) # 坐标裁剪防止越界 xmin, xmax max(0, xmin), min(w, xmax) ymin, ymax max(0, ymin), min(h, ymax) cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h if bw 0 or bh 0: continue # 跳过退化框 lines.append(f{CLS2ID[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(out_dir, f.replace(.xml, .txt)), w) as fp: fp.write(\n.join(lines)) voc_to_yolo(dataset/train/annotations, dataset/train/images, dataset/train/labels) voc_to_yolo(dataset/test/annotations, dataset/test/images, dataset/test/labels)逻辑说明先读图像宽高再对每个 object 做坐标裁剪和归一化最后按类别映射写 txt。四个必调参数分别是 CLASSES 顺序、坐标裁剪边界、归一化保留小数位、以及退化框过滤阈值。CLASSES 顺序一旦定了就不能改改了要重新训练坐标裁剪防止标注时手抖写出负值或超出图像小数位保留 6 位足够再高没意义退化框过滤是防止宽或高为 0 的脏标注把损失函数带崩。3.2 转换后必须做的三项校验转完不是就完事了焊缝数据集里脏标注比例不低。第一项校验是类别 id 是否都在 0 到 num_classes-1 之间第二项是每行五个字段、坐标都在 0 到 1 之间第三项是图像和标签文件名一一对应。下面这段校验脚本建议每次转换后都跑。import os def validate_labels(label_dir, img_dir, num_classes): img_stems {os.path.splitext(f)[0] for f in os.listdir(img_dir)} label_stems {os.path.splitext(f)[0] for f in os.listdir(label_dir)} missing img_stems - label_stems print(无标签图像数:, len(missing)) bad 0 for f in os.listdir(label_dir): with open(os.path.join(label_dir, f)) as fp: for line in fp: parts line.strip().split() if len(parts) ! 5: bad 1 continue cid int(parts[0]) vals [float(v) for v in parts[1:]] if cid 0 or cid num_classes or any(v 0 or v 1 for v in vals): bad 1 print(异常标注行数:, bad) validate_labels(dataset/train/labels, dataset/train/images, len(CLASSES))参数说明num_classes 传类别总数missing 集合里是那些有图没标注的样本这些图要么补标要么从训练集剔除不能留着当背景图否则模型会把缺陷学成背景。bad 计数大于 0 就要回去查转换脚本或原始 xml。4. 用这份数据集训练 YOLOv8配置、命令和焊缝场景的参数调整4.1 数据集 yaml 怎么写YOLOv8 训练靠一份 yaml 描述数据路径和类别。焊缝数据集目录按 train/images、train/labels、test/images、test/labels 组织yaml 里 path 指到数据集根train 和 val 指到 images 子目录names 按 CLASSES 顺序写。path: /data/weld_defect train: train/images val: test/images names: 0: porosity 1: slag 2: lack_of_fusion 3: crack 4: undercut注意 val 这里用的是测试集因为标题里只划分了训练和测试没有单独验证集。如果要做早停和调参建议从训练集里再切 10% 出来当 val测试集留到最后只跑一次否则调参调着调着就把测试集信息泄漏了这是很多人翻车的地方。4.2 训练命令和关键超参yolo detect train \ data/data/weld_defect/weld.yaml \ modelyolov8s.pt \ epochs200 \ imgsz1024 \ batch8 \ lr00.01 \ lrf0.01 \ mosaic0.5 \ mixup0.0 \ copy_paste0.1 \ patience30 \ device0参数逐个说imgsz 设 1024 而不是默认 640因为焊缝缺陷里裂纹、未熔合这类细长目标在 640 下可能只剩几个像素缩太狠直接消失batch 8 是 1024 分辨率下单卡 16G 显存的稳妥值显存够可以往上加lr0 0.01 是 SGD 的常规起点如果换 AdamW 要降到 0.001mosaic 0.5 比默认 1.0 低因为焊缝图像拼接后缺陷上下文会错乱拼太狠反而掉点copy_paste 0.1 对细长缺陷有增益但别开高开高会造出不符合焊接工艺的假样本patience 30 配合 200 epoch早停防止过拟合。4.3 焊缝场景下值得试的三个改动第一把输入分辨率按缺陷尺寸分布定如果统计出来小目标占比高imgsz 上 1280 比 1024 更稳。第二类别不平衡时在 yaml 同级加一份带权重的采样或者直接用 focal loss 替换默认分类损失。第三细长目标建议把 anchor 或回归损失里的宽高比惩罚调一调YOLOv8 用的是无锚框解耦头可以在损失配置里调 box 和 cls 的权重比裂纹类召回低时把 box 权重从 7.5 提到 10 左右试。这些改动都要靠验证集指标判断别凭感觉调。5. 避坑与排查焊缝数据集训练最容易踩的五个坑5.1 现象训练 loss 正常下降但 mAP 一直上不去原因通常是标注坐标和图像实际尺寸对不上。工业相机导出的图像可能带 EXIF 旋转信息xml 里记的宽高是旋转前的实际读进来是旋转后的坐标全偏。解决用 PIL 或 OpenCV 读图后打印实际 shape和 xml 里的 size 对比不一致就统一按实际读入尺寸重算坐标或者训练前把所有图按 EXIF 摆正并同步改 xml。5.2 现象裂纹类召回极低几乎检不出原因是裂纹 bndbox 太细长下采样后特征被抹掉。解决提高输入分辨率到 1280同时检查数据增强里的缩放范围别让随机缩放把细长目标压到几十像素以下另外确认 copy_paste 没有把裂纹贴到不合理位置导致模型学到错误上下文。5.3 现象验证集指标虚高测试集一跑就崩原因是把测试集当验证集用了反复调参等于在测试集上过拟合。解决从训练集切出独立 val测试集只在最终模型上跑一次如果标题给的数据集已经固定划分就自己从 train 里再切别动 test。5.4 现象转换后类别 id 错位模型把气孔认成夹渣原因是 CLASSES 列表顺序和训练时 yaml 里 names 顺序不一致或者中途改过类别顺序没重训。解决把类别顺序写进一个单独的 json 或 py 文件转换脚本和 yaml 都从它读杜绝两处手写。5.5 现象训练到一半显存爆了原因是 batch 和 imgsz 组合超过显存或者 dataloader 的 workers 开太多导致内存涨。解决先把 batch 降到 4 试再逐步加workers 设成 CPU 核数的 0.5 到 0.75 倍别拉满开启 AMP 混合精度能省不少显存YOLOv8 默认就开确认没被关掉。6. 进阶用混淆矩阵和 PR 曲线定位焊缝缺陷的漏检类型训练跑完别只看一个 mAP 数字焊缝缺陷检测真正要回答的是哪类缺陷漏了、漏在什么尺度上。YOLOv8 训练结束会在 runs 目录下生成混淆矩阵和 PR 曲线混淆矩阵能直接看出气孔和夹渣是否互相误判这两类在灰度图上确实容易混。PR 曲线看每个类别的曲线下面积裂纹这类如果曲线早早掉下来说明高置信度阈值下召回不够可以考虑在推理时对裂纹单独降阈值。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(data/data/weld_defect/weld.yaml, imgsz1024, conf0.001, iou0.6) for i, c in enumerate(metrics.box.ap_class_index): print(metrics.names[c], AP50:, metrics.box.ap50[i])这段代码用极低 conf 跑验证目的是看每个类别在宽松阈值下的上限表现如果某个类别 AP50 本身就低说明是特征学不出来降推理阈值没用得回去改训练如果 AP50 高但实际部署漏检多那是推理阈值设太高按类别调 conf 即可。我一般会把每个类别的 AP50 和实际产线漏检率对照着看两者差距大的类别重点查标注质量。焊缝数据集的价值不在于跑出一个漂亮数字而在于你能不能定位到具体是哪类缺陷、哪个尺度段出了问题然后针对性补数据或改增强。这套流程我踩过不少坑最深的教训是别信「划分好了就能直接训」先花半小时做统计和校验能省掉后面几天的返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表