简介:这份资源是面向工业质检与计算机视觉方向的焊接件表面缺陷检测数据集,适合从事目标检测算法训练、缺陷识别模型调优的研究者与工程师使用。数据同时提供Pascal VOC与YOLO两种标注格式,包含jpg原图及一一对应的xml与txt标注文件,可直接接入主流检测框架,省去格式转换环节。压缩包为7z格式,共约2000个文件,以1999个xml标注文件和1个说明txt为主,整体约256.38MB,标注类别覆盖crease、crescent_gap、inclusion、oil_spot、punching、rolled_pit、silk_spot、waist_folding、water_spot、welding_line共10类焊接缺陷。目前已有913人学习下载,说明其在同类工业缺陷数据中具备一定参考价值。读者可借助该数据集完成从数据加载、类别分布分析到模型训练与评估的完整流程,尤其适合需要多类别细粒度缺陷样本的检测任务,为焊接质量智能检测提供扎实的数据基础。
1. 焊接件表面缺陷检测数据集:2292 张 VOC+YOLO 双格式的 10 类缺陷到底怎么用
拿到一个标注好的焊接件表面缺陷数据集,第一反应往往不是兴奋,而是怀疑:2292 张够不够训一个能上产线的模型?10 个类别里有没有长尾到几乎没样本的?VOC 和 YOLO 两套格式是不是同一批图的两份标注,还是各标各的?这些问题不搞清楚,直接train.py一把梭,最后大概率得到一个 mAP 看着还行、一上真实焊缝就疯狂漏检的黑匣子。
这个数据集的价值在于它把「表面缺陷检测」这个工业质检里最刚需的场景,压缩成了一个可以直接跑通的规模:2292 张图、10 类缺陷、同时给 VOC XML 和 YOLO TXT 两种标注。它适合两类人——一类是想验证某个检测改进(比如注意力、损失函数、neck 结构)在工业缺陷上到底有没有用的算法同学,另一类是要快速搭一个焊接质检 demo、先跑通再谈精度的工程同学。下面我按「先看清数据长什么样,再决定怎么切、怎么训、怎么避坑」的顺序,把这条链路讲透。
2. 先摸清数据底细:VOC 与 YOLO 双格式的对应关系和 10 类分布
2.1 VOC XML 和 YOLO TXT 到底差在哪,为什么两个都要留
VOC 格式是每张图一个同名 XML,缺陷框写在<object>里,坐标是绝对像素值xmin/ymin/xmax/ymax,还带<name>类别名。YOLO 格式是每张图一个同名 TXT,每行一个框,格式是class_id x_center y_center width height,全部归一化到 0~1。两者描述的是同一批框,只是坐标系和存储方式不同。
保留双格式的实际意义在于:VOC 方便你用 labelImg 打开复核、也方便转成 COCO 或做可视化;YOLO 格式是 ultralytics 系列(YOLOv5/v8/v11)直接吃的输入。很多人拿到只有 VOC 的数据集,第一步就得写转换脚本,而这个数据集省掉了这一步。但要注意,双格式不等于双份标注,转换过程如果有精度损失(比如归一化时四舍五入),两边框可能对不齐,所以第一步必须做一致性校验。
2.2 用脚本统计类别分布和框尺寸,别靠肉眼
在训之前,先跑一段统计脚本,把每个类别的实例数、每张图的平均框数、框的宽高分布打出来。这一步决定了你后面要不要做重采样、要不要改 anchor。
import os, glob import xml.etree.ElementTree as ET from collections import Counter voc_dir = "annotations" # VOC XML 目录 cls_counter = Counter() box_wh = [] img_box_num = [] for xml_path in glob.glob(os.path.join(voc_dir, "*.xml")): tree = ET.parse(xml_path) root = tree.getroot() objs = root.findall("object") img_box_num.append(len(objs)) for obj in objs: name = obj.find("name").text.strip() cls_counter[name] += 1 bnd = obj.find("bndbox") w = float(bnd.find("xmax").text) - float(bnd.find("xmin").text) h = float(bnd.find("ymax").text) - float(bnd.find("ymin").text) box_wh.append((w, h)) print("类别实例数:", cls_counter.most_common()) print("平均每图框数:", sum(img_box_num) / len(img_box_num)) print("最大框数:", max(img_box_num), "最小框数:", min(img_box_num)) ws = [b[0] for b in box_wh]; hs = [b[1] for b in box_wh] print("框宽 min/mean/max:", min(ws), sum(ws)/len(ws), max(ws)) print("框高 min/mean/max:", min(hs), sum(hs)/len(hs), max(hs))逻辑说明:遍历所有 XML,用Counter统计类别频次,同时收集每个框的宽高。参数上,voc_dir指向解压后的 VOC 标注目录;如果你的 XML 和图片混在一起,改成对应路径即可。跑完重点看三件事——有没有某个类别实例数低于 50(长尾,需要过采样或专门增强)、平均每图框数是不是远大于 1(密集缺陷,anchor 和小目标策略要调)、框宽高最小值是不是只有几个像素(小目标,输入分辨率不能压太低)。
2.3 校验 VOC 与 YOLO 是否一一对应
双格式最容易翻车的地方是「图有、XML 有、TXT 没有」或者类别 id 对不上。写个校验脚本,把两边框数对齐检查一遍。
import os, glob img_dir = "images" voc_dir = "annotations" yolo_dir = "labels" imgs = {os.path.splitext(os.path.basename(p))[0] for p in glob.glob(f"{img_dir}/*.jpg")} xmls = {os.path.splitext(os.path.basename(p))[0] for p in glob.glob(f"{voc_dir}/*.xml")} txts = {os.path.splitext(os.path.basename(p))[0] for p in glob.glob(f"{yolo_dir}/*.txt")} print("图-XML 差集:", imgs - xmls) print("图-TXT 差集:", imgs - txts) print("XML-TXT 差集:", xmls - txts) # 抽查框数是否一致 import xml.etree.ElementTree as ET mismatch = [] for stem in list(imgs & xmls & txts)[:200]: n_xml = len(ET.parse(f"{voc_dir}/{stem}.xml").getroot().findall("object")) with open(f"{yolo_dir}/{stem}.txt") as f: n_txt = len([l for l in f if l.strip()]) if n_xml != n_txt: mismatch.append((stem, n_xml, n_txt)) print("框数不一致样本:", mismatch[:10], "共", len(mismatch))逻辑说明:三个集合做差集,任何非空差集都说明有文件缺失。抽查部分样本比对框数,不一致的样本要单独拎出来看是标注错误还是转换 bug。参数上imgs & xmls & txts取交集保证只比对三边都存在的样本,避免差集干扰。这一步做完,你才对「2292 张」这个数字有底——它到底是图片数还是标注数,有没有空标注图混在里面。
3. 从 2292 张到可训练集:划分、增强与 YOLO 目录组织
3.1 训练/验证/测试怎么切才不泄漏
工业缺陷数据最常见的泄漏是「同一焊缝的连续帧被分到训练和验证两边」,导致验证集 mAP 虚高。2292 张如果来自若干条焊缝或若干次拍摄,正确做法是按「拍摄批次/工件编号」分组切分,而不是随机按图切。如果数据里没有批次信息,退而求其次用感知哈希或图像相似度做聚类,把相似图分到同一侧。
import os, glob, shutil, random from sklearn.model_selection import train_test_split random.seed(42) stems = [os.path.splitext(os.path.basename(p))[0] for p in glob.glob("images/*.jpg")] # 若有批次信息,替换为按批次分组;这里演示按 8:1:1 随机切 train, temp = train_test_split(stems, test_size=0.2, random_state=42) val, test = train_test_split(temp, test_size=0.5, random_state=42) for split, items in [("train", train), ("val", val), ("test", test)]: for sub in ["images", "labels"]: os.makedirs(f"dataset/{split}/{sub}", exist_ok=True) for s in items: shutil.copy(f"images/{s}.jpg", f"dataset/{split}/images/{s}.jpg") shutil.copy(f"labels/{s}.txt", f"dataset/{split}/labels/{s}.txt") print(len(train), len(val), len(test))逻辑说明:train_test_split两次调用实现 8:1:1。参数random_state=42保证可复现。关键提醒——如果你的数据有批次字段,把stems换成按批次聚合后的列表再切,否则验证集不可信。切完检查三个 split 的类别分布是否接近,长尾类如果只在训练集出现,验证指标会失真。
3.2 针对焊接缺陷的增强策略,别照搬 COCO 那套
焊接件表面缺陷的成像特点是:金属反光强、缺陷与背景对比度低、缺陷形态细长(裂纹)或成片(气孔、夹渣)。通用增强里,颜色抖动要慎用,因为缺陷判据很大程度依赖灰度对比;随机裁剪要控制幅度,避免把细长裂纹裁断。
我一般会开这几项:mosaic(提升小目标召回,但焊接缺陷密集时可能引入不合理拼接,建议 0.5 而非默认 1.0)、hsv_h/s调小到 0.01 左右、degrees限制在 10 以内(工件旋转角度有限)、flipud/fliplr视拍摄方向决定。下面是对应的 YOLO 数据配置。
# dataset/weld.yaml path: ./dataset train: train/images val: val/images test: test/images nc: 10 names: ['crack', 'porosity', 'slag', 'undercut', 'overlap', 'spatter', 'burn_through', 'incomplete_fusion', 'crater', 'surface_scratch']逻辑说明:nc必须和实际类别数一致,names顺序必须和 YOLO TXT 里的class_id严格对应,错一位整个训练就废了。path用相对路径方便迁移。这份 yaml 是 ultralytics 系列的标准入口,训练时data=dataset/weld.yaml即可。
3.3 类别 id 映射表要单独存一份
10 个类别的 id 映射一旦定下就不要改。建议单独存一个classes.txt,每行一个类名,行号即 id。转换脚本、可视化脚本、推理后处理都读同一份,避免「训练时 id 是 A,推理时 id 是 B」这种血泪事故。
| id | 类别名 | 典型形态 | 标注难点 |
|---|---|---|---|
| 0 | crack | 细长线状 | 边界模糊,易与划痕混 |
| 1 | porosity | 圆形小孔 | 小目标,密集时易漏 |
| 2 | slag | 不规则块状 | 与背景对比低 |
| 3 | undercut | 沿焊缝边缘凹槽 | 形态随焊缝走向变化 |
| 4 | overlap | 焊瘤堆积 | 与正常余高易混 |
| 5 | spatter | 飞溅颗粒 | 极小,标注一致性差 |
| 6 | burn_through | 烧穿孔洞 | 样本少,长尾 |
| 7 | incomplete_fusion | 未熔合条带 | 内部缺陷,表面不明显 |
| 8 | crater | 弧坑 | 圆形,尺寸小 |
| 9 | surface_scratch | 表面划痕 | 与裂纹难区分 |
这张表不是摆设——标注一致性差(比如 spatter 和 surface_scratch 边界模糊)的类别,训练时可以考虑合并或加权,具体看你的业务能不能接受。
4. 训练参数怎么设:从 yolov8n 起步到 10 类收敛
4.1 先用小模型跑通,再换大模型
2292 张、10 类,属于中小规模。直接上 yolov8x 大概率过拟合,而且调参周期长。我的习惯是先用 yolov8n 或 yolov8s 跑一个 baseline,确认数据管线和类别映射没问题,再决定要不要换大模型。
yolo detect train \ data=dataset/weld.yaml \ model=yolov8n.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ warmup_epochs=3 \ cos_lr=True \ patience=30 \ project=runs/weld \ name=baseline_n逻辑说明:model=yolov8n.pt用预训练权重起步,小数据集上预训练能显著加速收敛。imgsz=640是默认值,如果统计发现小目标多(框宽高最小值小于 16 像素),提到 960 或 1280 再试。batch=16按显存调,显存不够就降 batch 并等比调 lr。patience=30早停,避免无效训练。cos_lr=True余弦退火在中小数据集上通常比阶梯下降稳。
4.2 关键参数:anchor、损失权重、类别不平衡
YOLOv8 是 anchor-free,不用手动设 anchor,但损失里的分类和框回归权重仍会影响长尾类。如果统计发现 burn_through 这类样本极少,可以在训练时对稀有类做复制过采样,或者用cls损失权重调整。ultralytics 默认不直接暴露每类权重,常见做法是在数据集层面过采样稀有类图片。
另一个容易忽略的是overlap_mask和mask_ratio(分割任务才用),检测任务不用管。检测里真正要盯的是box和cls的 loss 曲线:如果 cls loss 一直不降,多半是类别标注有噪声或 id 错位;如果 box loss 震荡,检查框坐标有没有越界(归一化后应在 0~1)。
4.3 训练中该看哪些指标,别只盯 mAP
mAP50 是主指标,但工业质检更关心召回率——漏检一个裂纹的代价远大于误报。训练时重点看metrics/recall和每类的 P-R 曲线。如果整体 mAP 不错但某类召回极低,去混淆矩阵里看它被误判成了哪一类。焊接缺陷里 crack 和 surface_scratch、porosity 和 crater 是最容易互相混淆的两对,混淆矩阵能直接暴露。
# 训练后加载模型看混淆矩阵(ultralytics 会在 runs 目录自动生成) from ultralytics import YOLO model = YOLO("runs/weld/baseline_n/weights/best.pt") metrics = model.val(data="dataset/weld.yaml", split="val") print("mAP50:", metrics.box.map50) print("每类 AP:", metrics.box.ap50)逻辑说明:model.val会输出每类 AP 和混淆矩阵图。参数split="val"指定验证集。拿到每类 AP 后,低于 0.5 的类别要单独分析——是样本太少、标注太乱,还是该类本身视觉特征就不明显。
5. 避坑与排查:焊接缺陷数据集训练中最容易翻车的 5 个点
5.1 现象:训练 loss 正常下降,但验证 mAP 始终在 0.1 以下
原因:类别 id 映射错位。YOLO TXT 里的class_id从 0 开始,而你的names列表如果顺序和标注时不一致,模型学到的就是错位映射。另一个可能是图片和标签文件名不匹配(比如图片是001.jpg,标签是001_1.txt),导致大量样本被当成背景。
解决:跑一遍 2.3 的校验脚本,确认每个 TXT 的 class_id 都在[0, nc-1]范围内,且文件名与图片一一对应。再抽查几张图用可视化脚本把框画出来,肉眼确认类别名和框位置对得上。
5.2 现象:小目标(spatter、crater)召回率极低
原因:输入分辨率 640 下,几个像素的缺陷在特征图上几乎消失。或者 mosaic 增强把本就不多的小目标进一步缩小。
解决:把imgsz提到 960 或 1280;降低 mosaic 概率到 0.3 以下;在数据层面统计小目标占比,如果超过 30%,考虑用切片推理(SAHI)或专门的小目标检测头。别指望不改任何东西就能召回小飞溅。
5.3 现象:验证集 mAP 很高,但拿真实产线图推理全是误报
原因:数据泄漏或域偏移。2292 张如果都来自同一批工件、同一光照,验证集和训练集分布几乎一样,mAP 虚高。真实产线的光照、角度、工件材质一变,模型就崩。
解决:切分时按批次分组(见 3.1);如果条件允许,留一批完全不同工况的图做测试集,哪怕只有几十张。域偏移严重时,优先补数据而不是调模型。
5.4 现象:训练到一半 loss 突然变 NaN
原因:学习率过大、batch 里有异常框(坐标越界或宽高为 0)、或者某张图标注了不存在的类别 id。
解决:先降lr0到 0.001 重跑;再用脚本扫一遍所有 TXT,过滤掉宽高为 0 或坐标超出[0,1]的行;检查是否有 class_id 大于等于nc的脏标注。焊接数据集里手工标注的越界框并不少见。
5.5 现象:同一类别在不同图里标注粒度不一致
原因:多人标注或标注规范不统一。比如 spatter 有人标单个颗粒,有人标一整片。
解决:这是数据质量问题,不是训练能救的。抽检每个类别的 20 张图,统一标注规范后重标争议样本。如果重标成本太高,考虑把粒度不一致的类合并,或者用cls损失权重降低噪声类影响。
提示:以上 5 条里,5.1 和 5.3 是最致命的,前者让训练直接无效,后者让指标失去意义。拿到任何新数据集,先做这两项检查再开训。
6. 把 2292 张用出 2 万张的效果:过采样、切片推理与模型集成
数据量固定时,提升空间主要在「怎么用」而不是「换多大模型」。第一个技巧是稀有类过采样:统计出实例数最少的 2~3 个类,在训练集里把这些图复制 2~3 份,同时保持验证集不变。这比调损失权重更直接,代价是过拟合风险,所以配合早停和强增强使用。
第二个技巧是切片推理(SAHI)。焊接缺陷里小目标多,整图推理时小目标在降采样后信息丢失。把大图切成带重叠的小块分别推理再合并,小目标召回能明显提升。代价是推理变慢,适合离线质检或对召回要求极高的场景。
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model = AutoDetectionModel.from_pretrained( model_type="yolov8", model_path="runs/weld/baseline_n/weights/best.pt", confidence_threshold=0.25, device="cuda:0" ) result = get_sliced_prediction( "test_weld.jpg", model, slice_height=640, slice_width=640, overlap_height_ratio=0.2, overlap_width_ratio=0.2 ) result.export_visuals(export_dir="sahi_out/")逻辑说明:slice_height/width是切片尺寸,overlap_*_ratio控制切片重叠避免边缘目标被切断。参数confidence_threshold按你的误报容忍度调,工业场景通常设 0.25~0.4。切片推理对小目标提升明显,但整图大缺陷可能被切碎,所以最终结果要和整图推理做融合。
第三个技巧是模型集成:用 yolov8n/s/m 各训一个,推理时做 WBF(加权框融合)。2292 张训三个小模型的总成本低于训一个超大模型,且集成对小样本类更稳。我一般用 n 和 s 两个就够,m 加进来提升有限但推理翻倍。
最后一个习惯:每次训完,把best.pt、对应的weld.yaml、classes.txt和这次的数据切分脚本一起归档到一个带日期的目录。焊接缺陷数据集我前后迭代过十几版,没有一次能靠记忆复原「当时用的是哪版切分」。这个习惯帮我省下的返工时间,比任何调参技巧都值。希望帮到你。
本文还有配套的精品资源,点击获取