
简介这是一份面向目标检测与工业质检场景的管道焊接缝缺陷检测数据集按YOLOV5标准目录格式整理可直接投入模型训练无需额外转换。数据覆盖2个类别good、bad所有图像均为800×800的RGB图片训练集包含908张图片及对应标签验证集包含206张图片及对应标签可用于焊接质量自动判别、缺陷识别等任务。压缩包整体约93MB共1995个文件以996张jpg图像、997个txt标注文件为主另附1个Python可视化脚本和1张说明图脚本无需修改即可运行随机传入一张图片便能绘制边界框并保存到当前目录便于快速检查标注效果。目前已有196人学习下载适合目标检测入门者、工业视觉方向学生及需要快速构建焊接缺陷样本集的开发者使用。1. 管道焊缝缺陷检测数据集为什么绕不开 YOLOV5 目录格式管道焊接缝缺陷检测的项目十个里八个卡在数据整理这一关。现场能拍回来几百张焊缝图但要把它们变成能丢进 YOLOv5 训练的数据集还得按 images/ 与 labels/ 这种固定目录摆好认真做至少花一两天。所谓 YOLOV5 目录格式就是这个领域摆脱了默认共识图片放 images/train、images/val标注 txt 放 labels/train、labels/val每张图片配同名标签文件。这份管道焊接缝缺陷数据集在交付时就把这套结构搭好了并用 2 个类别组织标签常见分法是 0 代表气孔、1 代表夹渣省掉了格式转换的时间。它直接回答的问题是“一份缺陷检测数据到底该怎么摆、怎么验才能不出幺蛾子地送进训练脚本”。适合正在做燃气管道、压力容器、钢结构焊缝质检的算法工程师也适合第一次拿 yolov5 跑自己数据的毕设和项目组。下面按我拿到同类数据后的实际流程来讲。2. 拆开管道焊缝缺陷数据集的 YOLOV5 目录images、labels 与 2 个类别的配对规则一份数据集到手第一步一定是验货不是直接训练。YOLOv5 目录格式的约定非常具体图像目录必须叫 images标签目录必须叫 labels下面再按 train、val 切分同一张图的图片和标签必须同名。你拿到的可能是解压后的完整目录也可能只有 images 和 labels 两个文件夹但没有切分这两种情况走完全不同的处理路径。下面从目录层到文件层逐个拆开。2.1 目录结构与配对规则images/train 与 labels/train 一一对应标准目录长这样weld_defect_dataset/ ├── images/ │ ├── train/ │ │ ├── weld_0001.jpg │ │ ├── weld_0002.jpg │ │ └── ... │ └── val/ │ ├── weld_0101.jpg │ └── ... └── labels/ ├── train/ │ ├── weld_0001.txt │ ├── weld_0002.txt │ └── ... └── val/ ├── weld_0101.txt └── ...注意两点。第一如果只有 images 和 labels 而没有 train/val需要自己切分否则 train.py 跑不起来第二val 目录不是可有可无的YOLOv5 训练脚本会拿 val 做每轮验证缺了它要么报错要么靠训练集拆分临时顶替后者会让验证指标虚高不推荐。拿到目录后的第一件事是跑一个配对检查脚本import os data_root weld_defect_dataset for split in [train, val]: img_dir os.path.join(data_root, images, split) lbl_dir os.path.join(data_root, labels, split) if not os.path.isdir(img_dir) or not os.path.isdir(lbl_dir): print(f[!] 缺少 {split} 目录: {img_dir} 或 {lbl_dir}) continue imgs [f for f in os.listdir(img_dir) if f.lower().endswith((.jpg, .jpeg, .png, .bmp))] label_stems {os.path.splitext(f)[0] for f in os.listdir(lbl_dir) if f.endswith(.txt)} no_label [f for f in imgs if os.path.splitext(f)[0] not in label_stems] print(f{split}: 图片 {len(imgs)} 张缺标签图片 {len(no_label)} 张) if no_label: print( 缺失示例:, no_label[:5])这段逻辑很简单按 split 遍历用 set 取标签文件名再和图片名对比。如果一个图片没有同名 txt先别急着怪数据集也可能是图片后缀不统一JPG 和 jpg 混着或图片从别的目录复制过来但标签没跟上。把 no_label 打印出来看清原因再说。2.2 标签 txt 里的五列数字class_id 与归一化框随便打开一个 txt内容是这样的0 0.4823 0.5351 0.0182 0.0114 1 0.7012 0.4803 0.0120 0.0146每行一个目标框五列分别是class_id、x_center、y_center、width、height。四个坐标全部按图像宽高归一化到 01 区间这一点和 VOC 的 xmin/ymin/xmax/ymax 像素坐标完全不同。这份数据集是 2 个类别所以第一列只可能出现 0 或 1。常见映射是 0 对应气孔porosity、1 对应夹渣slag inclusion但如果对方只给了 txt 没给类别名不要盲信要自己看一眼图再确认。这里最常碰到的问题是有人直接把像素坐标写成五列坐标值大于 1。YOLOv5 训练时默认做 mosaic 和随机缩放坐标越界的框会被裁掉表现为训练不报错但 mAP 上不去。所以标签校验不能只看有没有文件还要看值域import os from collections import Counter lbl_dir weld_defect_dataset/labels/train cls_counter Counter() bad_lines [] for name in os.listdir(lbl_dir): if not name.endswith(.txt): continue path os.path.join(lbl_dir, name) with open(path, r) as f: for line in f: parts line.strip().split() if len(parts) ! 5: bad_lines.append((name, 列数不是5, line)) continue cls, cx, cy, w, h parts cls_counter[int(cls)] 1 cx, cy, w, h map(float, (cx, cy, w, h)) if not (0 cx 1 and 0 cy 1 and 0 w 1 and 0 h 1): bad_lines.append((name, 坐标越界, line)) print(类别分布:, dict(cls_counter)) print(异常行数:, len(bad_lines)) for item in bad_lines[:10]: print(item)这段会输出三个信息两个类别的样本数量比例、异常标签行数、前十条异常内容。类别分布对训练策略影响很大比如 1 类只有几十个框而 0 类有几千个训练时就要针对 1 类做额外增强不能就这么直接开训。2.3 为什么这份数据集用 YOLOV5 目录而不是 VOC/COCOtxt 带来的三个实在便利常见的 COCO2017 数据集结构是一个大 json 文件把图片路径、标注框、类别名串在一起VOC 则是一张图配一个 XML。这两种格式在标注工具导出的场景里很常见但进了 YOLOv5 训练管线反而多一层解析。YOLOv5 目录格式的好处是标签是普通 txt不需要 XML/JSON 解析器复制、移动、截断都很容易一张图配一个文件出问题时定位到具体图调试成本低用 shell 命令就能完成统计和筛选比如find labels/train -name *.txt直接数标签数量。这份管道缺陷数据集直接做成 YOLOv5 目录格式等于把“解析标签”这一步省掉了剩下的工作主要在于切分和定 data.yaml。如果后面要迁移到 yolov8 训练自己的数据集这个目录结构也能直接复用只需要换 data.yaml 的写法边界是相通的。3. 把现场管道焊缝图像整理成 YOLOV5 目录标注、转换与切分三条路如果这份数据集不是恰好需要的那份而是手上只有一堆现场焊缝图需要自己做成同样的目录格式那么有三个环节绕不开标注、格式转换、切分。这三个环节决定了下游训练会不会翻车比调参更要紧。3.1 目标检测常用标注工具怎么导出 YOLO 目录格式目标检测常用标注工具里开箱就能导出 YOLO 格式的主要是 LabelImg 和 X-AnyLabeling。LabelImg 的老版本默认保存为 VOC XML需要在保存时把格式切换到 YOLO切换入口在工具右侧工具栏新版本直接支持 YOLO 输出选定标注文件夹后会自动生成 images 和 labels 两套目录。X-AnyLabeling 更适合焊缝这种密集小目标场景可以加载一个预训练模型先做粗标注再人工修正气孔/夹渣这种边界清晰但数量多的缺陷尤其省事。导出时它同样生成和图片同名的 txt目录结构和 YOLOv5 约定一致。如果用的是其他标注平台导出的 COCO json或者从现场拿到的历史标注是 XML就需要转换脚本这也是下一小节要讲的部分。3.2 把 VOC XML 转成 YOLOv5 标签转换脚本与两个边界坑像这份数据集标题里“检测检测”是两个重复字在转手打包的数据集里很常见说明原始目录名是复制改名来的不影响标签内容但建议自己整理后把目录名统一一下免得后续脚本里到处带着两个“检测”。如果拿到的是 VOC XML转换脚本我通常自备核心是把 xmin/ymin/xmax/ymax 转成中心点归一化坐标。下面这段是能直接跑的版本import os import glob from xml.etree import ElementTree as ET CLASS_NAMES [porosity, slag] # 与 class_id 顺序保持一致 def voc_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_name root.findtext(filename) img_w float(root.findtext(size/width)) img_h float(root.findtext(size/height)) if not img_name or not img_w or not img_h: print(f[!] 跳过非标准 XML: {xml_path}) return lines [] for obj in root.findall(object): name obj.findtext(name) if name not in CLASS_NAMES: continue box obj.find(bndbox) x1 float(box.findtext(xmin)); y1 float(box.findtext(ymin)) x2 float(box.findtext(xmax)); y2 float(box.findtext(ymax)) # 边界钳位防止拖拽标注时框超出图片 x1 max(0, min(x1, img_w - 1)); x2 max(0, min(x2, img_w - 1)) y1 max(0, min(y1, img_h - 1)); y2 max(0, min(y2, img_h - 1)) if x2 x1 or y2 y1: continue cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h lines.append(f{CLASS_NAMES.index(name)} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_path os.path.join(out_dir, os.path.splitext(img_name)[0] .txt) with open(out_path, w) as f: f.write(\n.join(lines)) xml_dir Annotations out_dir labels/train os.makedirs(out_dir, exist_okTrue) for xml_path in glob.glob(os.path.join(xml_dir, *.xml)): voc_to_yolo(xml_path, out_dir)逻辑说明CLASS_NAMES 的顺序决定了 class_id 编号顺序写错就等于把两个类别对调钳位那一段是把越界像素坐标拉回图片范围内避免出现宽高为负的框。这是第一个坑。第二个坑是 XML 里的filename写的是 weld_0001.jpg而实际图片叫 weld_0001.JPG转换出来的 txt 和图片对不上。我的习惯是生产环境里先把图片后缀统一成小写再跑转换脚本。3.3 按管件分组切分 train/val别把同一个焊口的正反面拆到两边切分看着简单随机分配 8:2 就行但在焊缝缺陷检测里要专门提醒同一个焊口、同一条焊缝的照片会共享背景和光照如果 train 和 val 里各出现同一焊口的不同角度图验证集指标会被背景信息抬得很高模型其实没学到缺陷。切分应按焊口编号或管件批次分组同一组的图只能全部进 train 或全部进 val。代码上先给每张图编 group_id再对 group_id 做随机分配。import os import random import shutil random.seed(42) src_img weld_images src_lbl weld_labels # 已是 YOLO txt 的目录 out_root weld_defect_dataset for split in (train, val): os.makedirs(f{out_root}/images/{split}, exist_okTrue) os.makedirs(f{out_root}/labels/{split}, exist_okTrue) # 假设文件名形如 pipe_07_weld_001.jpggroup_id 取 pipe_07 def group_id_of(filename): parts filename.split(_) return parts[0] _ parts[1] groups {} for f in os.listdir(src_img): stem, ext os.path.splitext(f) if ext.lower() not in (.jpg, .jpeg, .png, .bmp): continue gid group_id_of(stem) groups.setdefault(gid, []).append((stem, ext)) group_list list(groups.keys()) random.shuffle(group_list) val_groups set(group_list[: max(1, int(len(group_list) * 0.2))]) def copy_images(stem, ext, split): src os.path.join(src_img, stem ext) dst os.path.join(out_root, fimages/{split}, stem ext) shutil.copy(src, dst) if os.path.exists(os.path.join(src_lbl, stem .txt)): shutil.copy(os.path.join(src_lbl, stem .txt), os.path.join(out_root, flabels/{split}, stem .txt)) for gid, items in groups.items(): split val if gid in val_groups else train for stem, ext in items: copy_images(stem, ext, split) print(val 组数:, len(val_groups), train 组数:, len(group_list) - len(val_groups))这个脚本以pipe_07_weld_001.jpg这样的命名提取前两段作为 group_id。如果你的命名不规律改成从文件列表读字段就行核心是“同一组焊口只进一个 split”。切完后建议把 val 目录里每个类别的框数量打印出来确认没有出现某类在 val 里一个框都没有的情况因为那会让 mAP 直接显示为 0。4. 用这份 2 类别焊缝缺陷数据集跑通 yolov5 训练自己的数据集data.yaml、超参数与日志判断数据整理完进入训练环节。这章的三个关键输入data.yaml、训练命令、日志判断。我按“先配 yaml、再动超参、最后盯日志”的顺序来讲这也是排查问题时的固定顺序。4.1 配置 data.yaml2 个类别与 names 的对应关系YOLOv5 训练时并不知道标注类别叫什么它只认 data.yaml。一份最小的配置如下# weld_defect_data.yaml path: /home/yourname/datasets/weld_defect_dataset train: images/train val: images/val nc: 2 names: 0: porosity 1: slagpath用绝对路径最省事尤其是 Windows 和 Linux 路径写法差异大写相对路径一旦当前目录不对就报 FileNotFoundError。nc必须和标签文件里的最大 class_id 1 相等否则训练过程会报警告或直接报错。names的顺序必须和标注时的类别编号一致——如果标的时候 0 是气孔、1 是夹渣yaml 里就不能写成 0: slag、1: porosity否则模型训练结束后预测结果的类别标签是反的这类问题后期很难排查。4.2 跑通训练的最小命令与必改超参数最短路径上先要准备 YOLOv5 的环境和代码常见做法是 clone 官方仓库并安装依赖git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt然后启动训练python train.py \ --data ../datasets/weld_defect_data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml \ --name weld_defect_run1四个关键项值得单独说。--img管口焊缝图常见两种分辨率1280×1280 以上的原图和 640 左右的缩略图。气孔这类缺陷框通常很小如果原始图长边在 2000 像素以上我一般直接开--img 1280代价是显存和训练时间明显上升但对小目标 recall 帮助很大如果显存只有 8G就用 640后续用切块推理补漏检。--batch不追求极限。24G 显存跑 1280 时 batch 4 就很稳跑 640 时给到 1616G 显存从 8 起步。报 OOM 时先减 batch不要先减 img因为后者直接改变目标尺度。--epochs缺陷检测数据集普遍不大100 轮基本够观察趋势。我习惯先跑到 100 轮看 val 目录下的 results.png 曲线若 loss 还在明显下降就续跑而不是一上来拉 300 轮浪费时间。--hypyolov5 超参数文件里hyp.scratch-low.yaml的增强比较温和适合小数据集和弱特征缺陷hyp.scratch-high.yaml增强更猛数据量够大才建议。管道焊缝背景变化不大、缺陷本身尺寸小我从 low 起步后续按效果换 medium。4.3 训练日志怎么看P、R、mAP 与 loss 的合理走势训练过程中每 10 轮会打出一段验证指标长这样Class Images Instances P R mAP.5 mAP.5:.95 all 40 87 0.814 0.726 0.793 0.586 ......判断规则很简单mAP0.5 在缺陷检测任务里能到 0.8 以上初步可部署一直在 0.3 以下多半不是训练轮数问题而是标签有问题或切分有问题先回第二章检查配对别盲目加 epoch。另外看 loss 时不要只看第一列 box_loss还要对比 obj_lossobj_loss 降不下去往往代表很多目标根本没被框中这和小目标、遮挡都有关系。这一章的另一个提醒是迁移到 yolov8 训练自己的数据集时目录格式几乎不用动data.yaml 的写法有差异比如 names 不再是列表而是一行一个但这份数据集本身不浪费。5. 管道焊缝缺陷训练里常见的 5 个翻车现场与排查这一章是把管道焊缝缺陷检测数据集训练中最常踩的坑单独拿出来说。下面每条都按“现象→原因→解决”写方便对照排查。5.1 现象loss 降到底mAP 却在 0 附近来回抖这是典型的标签和图片没对齐。很多焊缝图是手机或工业相机拍的带了 EXIF 方向信息用看图软件打开是正的但在 OpenCV 里读出来被旋转了 90 度。如果标注时用的是正图训练时读取的是旋转后的图那么框与目标位置完全对不上但损失函数照样收敛。解决方式数据预处理阶段统一用一个固定的读取逻辑比如cv2.imread后按 EXIF 方向旋转回正再保存成新图然后抽查 5 张图渲染一次边界框确认框落在缺陷上再开始训练。这个检查只花几分钟能把后面两天的调试时间省下来。5.2 现象气孔这种小缺陷recall 严重偏低管口焊缝的气孔在 1280 原图里可能只有十几个像素宽归一化后小于 0.01在 640 的训练尺度下几乎成为看不见的目标。最直接的表现是 mAP0.5 不算差但 recall 比另一类低 20 个点。解决方法优先级先用--img 1280把输入分辨率提上来再看显存够不够显存不够就做切块把原图按 512 大小滑窗切出子图训练和推理都用子图最后把框映射回原图坐标。两个方法都试过之后再把 mosaic 增强关到一半--mosaic 0.5因为 mosaic 拼接会让小目标在图上更“碎”。5.3 现象训练到一半显存 OOM进程直接崩10 次 OOM 有 8 次是 batch 设太大剩 2 次是--img 1280和 mosaic 叠加后把激活值撑爆。这种崩溃没有日志残留只有 python 进程直接退出。解决先 batch 减半重跑比如 16 改 8如果还崩把--workers从 8 降到 4数据读取进程也吃内存实在还崩换 yolov5n 或 yolov5s 这种小权重不要在 8G 卡上硬冲 yolov5x。还有一条心得训练中途崩了不要从第 0 轮重来用--resume runs/train/weld_defect_run1接着断点续训能省下不少时间。5.4 现象两个类别样本数量悬殊少数类一直学不动2 类别的数据集里如果气孔有 1200 个框夹渣只有 80 个框训练时 loss 基本被多数类主导少数类学不动val 里它的 mAP 会明显低于另一类。第二章那个类别统计脚本的输出其实已经把问题预告了。解决第一步是数据层面做类别平衡把少数类样本复制几份并加上旋转、亮度扰动注意别让同一批复制图同时进 train 和 val第二步是超参层面在 hyp 里调大cls_pw让分类损失对少数类加权如果还不行少数类样本数量低于 30 个的用预训练模型先做一轮伪标注再人工确认比自己从零开始标省力。5.5 现象验证 mAP 高得吓人现场一跑就漏检训练时 mAP 0.9、现场拿新管件一测只有 0.4这种落差很大程度出在切分上。如果切分时随机打乱了所有图片同一个焊口的正反面、同一个批次的相似背景会同时出现在 train 和 val 里模型记住了背景纹理而不是记住缺陷。解决按焊口分组切分见 3.3训练后拿完全不相关的、现场新拍的图做二次验证。二次验证的图不要进训练集单独留一个test_external/目录等模型完成一轮训练后再跑 detect看看缺陷框是否真的落在气孔和夹渣上。这也是我第一次做管道焊缝检测时翻车最狠的一次后来这条“外部验证集”就成了固定流程。6. 用混淆矩阵和现场视频给焊缝缺陷模型做最终验收再滚入第二轮标注训练结束不等于能交付。这套 2 个类别的焊缝缺陷检测模型上线前我会固定做两件验证量化验证和场景验证。6.1 用 val.py 导出混淆矩阵定两个类别的置信度阈值第一件量化验证是跑一次完整的 val并把混淆矩阵和 PR 曲线导出来python val.py \ --data ../datasets/weld_defect_data.yaml \ --weights runs/train/weld_defect_run1/weights/best.pt \ --task val \ --conf 0.25 \ --iou 0.5 \ --save-conf --save-json结果在runs/val/weld_defect_run1/下重点看confusion_matrix.png和PR_curve.png。现场漏检的代价远高于误检所以阈值选择我通常让 recall 优先如果 PR 曲线的转折点在 conf0.3 附近就把推理参数调到 0.25宁可让现场多出几个误报框也不放过一处缺陷等人工复核能稳定跟上时再把 conf 提高到 0.4 减少误报。6.2 用现场视频和批量未标注图做验收量化验证之外我留一批外部样本就是完全没参与训练、但来自同类型管件的现场图或现场视频跑一遍检测python detect.py \ --weights runs/train/weld_defect_run1/weights/best.pt \ --source external_weld_video.mp4 \ --img 1280 \ --conf 0.25 \ --save-txt --save-crop重点看两个指标缺陷在画面上出现的帧数是否和人工抽检一致误检是否集中在某一类背景比如焊渣飞溅、反光。出现误检集中时把误检图片挑出来补标注加进训练集这种“第二轮标注”比盲目堆 epoch 更有效。我现在拿到任何一份管道焊缝缺陷数据集习惯都是先花一小时验配对、跑外部验证再上训练脚本这个顺序不会变。多半的经验教训都来自“多验一次数据”而不是多调一次参。希望帮到你。本文还有配套的精品资源点击获取