简介:面向目标检测与工业视觉算法开发者,提供一套焊接缺陷检测数据集,可用于焊接质量监控、工艺异常排查、钢结构与汽车零部件制造质检等场景,也适合目标检测学习者进行 YOLO、SSD 等模型的训练、验证与算法对比。压缩包约 153.88MB,解压后图片、标注与标签文件共 12411 个,按 JPEGImages、Annotations、labels 三个目录组织,分别存放 4137 张 JPG 图片、4137 个 XML 标注文件和 4137 个 TXT 标签文件,图片、XML 与 TXT 一一对应,既保留 VOC 格式,又可直接按 YOLO 格式使用;另含说明文档,便于快速掌握目录结构与标注规范。图像清晰,且已经过增强处理,有助于提升模型泛化能力;标注采用矩形框,六类缺陷框数分别为 616、716、674、718、732、758,合计 4214 个,类别分布较为均衡。该数据集既可作为工业视觉算法的验证基准,也能支撑从数据读取、格式转换到模型训练与评估的完整实验流程。已有 200 余人学习。
1. 焊接缺陷检测数据集:6类4137张YOLO+VOC双格式,开箱前先想清楚三件事
焊接缺陷检测数据集,光看文件名就知道它是给目标检测模型喂样本用的:6个缺陷类别,4137张图,YOLO和VOC两套标注格式都齐,还预先做过增强。这类资源最典型的场景是焊接工艺质检的自动化改造,帮你在不上产线的情况下先把检测模型跑通。它适合刚接触YOLO的工程师快速练手,也适合工业视觉项目里缺缺陷样本的团队当预训练基础。我的建议是解压之前先想清楚三件事:这6类到底是哪6类,标注边界有没有统一,增强之后的验证集干不干净。很多翻车不是模型不行,而是数据集的这三个前提没先确认。这篇笔记就把这个数据集拆开看,给你能直接照做的校验流程和训练流程。
2. 焊接缺陷检测的6类定义与标注边界:类间相似度比你想的高
2.1 六类缺陷的成像特征与检测难点
焊接缺陷检测数据集里的“6类”,通常围绕焊缝外观和内部质量展开。以最常见的六分类方案来说,气孔、夹渣、未熔合、未焊透、裂纹、咬边基本覆盖了焊接质检的绝大部分现场问题。气孔是焊缝表面或内部的小圆孔,远看就是一个个深色圆点;夹渣是嵌在焊缝里的杂质,形状不规则;未熔合和未焊透都表现为母材与焊缝金属没结合好,一个在层间一个在根部;裂纹是线状或树枝状的暗纹;咬边是焊缝边缘被烧出的凹槽。
这六类放在目标检测里有一个共性:类间相似度极高。未熔合和未焊透在灰度图上就是一条深浅不同的暗带,夹渣和部分气孔在低分辨率下长得几乎一样。别的检测任务看整体轮廓,焊接缺陷恰恰相反,边缘细节决定模型能不能分得清。所以看这个数据集,先看两件事:一是它有没有把容易混淆的类别在说明文件里给出示例图,二是各类别数量是否均衡。
表格是这类数据集最常见的配套信息,我习惯把它整理成下面这样:
| 类别 | 典型形态 | 成像难点 |
|---|---|---|
| 气孔 | 圆点状、深色、边缘清晰 | 小目标,占像素少 |
| 夹渣 | 不规则块状,灰度不均匀 | 形状多变,容易与气孔混 |
| 未熔合 | 层间线状暗带 | 灰度和未焊透接近 |
| 未焊透 | 根部条状暗带 | 要结合位置上下文判断 |
| 裂纹 | 线状/树枝状暗纹 | 细长,容易被当成噪声 |
| 咬边 | 焊缝边缘凹槽 | 与背景对比度低 |
2.2 标注边界为什么决定模型上限
同一张图上多个缺陷重叠,比如一条裂纹尾部带着一串气孔,标注时是分开两个框还是合成一个框;小缺陷只有十几个像素,是只标一个框还是干脆不标;边界模糊的缺陷,标注是保守还是激进。这些决策直接决定模型学到的边界,而焊缺陷恰恰又是边界最模糊的一类目标。
拿到数据集后我不会先训练,而是先做一次类别分布统计,确认各类别真实数量。用一段简单的脚本就能把标注文件里每个框的类别和数量拉出来。假设数据集用的是YOLO格式的txt标注,脚本长这样:
import os import glob from collections import Counter label_dir = "labels/train" # 改成解压后的实际标注目录 class_counts = Counter() per_image = [] for txt in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt, "r") as f: lines = [line.strip() for line in f if line.strip()] per_image.append(len(lines)) for line in lines: parts = line.split() if len(parts) >= 1: class_counts[int(parts[0])] += 1 print("总目标数:", sum(class_counts.values())) print("每张图目标数均值:", round(sum(per_image) / len(per_image), 2)) print("类别分布:") for cls in sorted(class_counts): print(f" 类别 {cls}: {class_counts[cls]} 个框")逻辑说明:脚本遍历labels/train下所有txt,每一行取第一个数作为类别id累加,同时记录每张图的目标数。运行后如果发现某个类别只有几十个框,另一个类别上千,后续训练就要重点补样本或调损失权重。参数说明:label_dir要改成你解压后的实际标注路径;如果标签是VOC格式的xml,把解析逻辑改成读xml的<name>字段,统计逻辑完全一样。这个脚本只用Python标准库,不依赖第三方库,拿到任何数据集都能先用它摸底。
还有一个连带指标要算:单张图平均目标数。焊接缺陷数据集的图片往往只有一到两个缺陷,平均目标数如果低于1.5,模型在小目标上会明显乏力;如果高于3,要留意密集缺陷堆叠时的标注质量。这两个数字决定了后面的anchor设计和imgsz设置,先记录好,训练出问题时有据可查。
2.3 标注质量抽检:把框画回原图
统计只能看出数量,看不出框贴不贴目标。抽检方法:随机抽10到20张训练图,把YOLO坐标换算回像素坐标,用OpenCV把矩形框画出来,一边看图一边核对框是否贴合缺陷边缘、有没有漏标、有没有标错类别。这个步骤花不了十分钟,但后面节省的排错时间远大于十分钟。
import cv2 import glob import os import random img_dir = "images/train" label_dir = "labels/train" class_names = ["crack", "porosity", "slag", "lack_fusion", "lack_penetration", "undercut"] # 按数据集的classes文件改 os.makedirs("vis_check", exist_ok=True) img_paths = glob.glob(os.path.join(img_dir, "*.jpg")) random.shuffle(img_paths) for img_path in img_paths[:10]: img = cv2.imread(img_path) h, w = img.shape[:2] txt_path = os.path.join(label_dir, os.path.basename(img_path).rsplit(".", 1)[0] + ".txt") if not os.path.exists(txt_path): continue with open(txt_path) as f: for line in f: parts = line.split() if len(parts) < 5: continue cls = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:5]) x1, y1 = int((cx - bw / 2) * w), int((cy - bh / 2) * h) x2, y2 = int((cx + bw / 2) * w), int((cy + bh / 2) * h) color = (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) cv2.imwrite(os.path.join("vis_check", os.path.basename(img_path)), img)逻辑说明:代码把YOLO归一化坐标乘回图片宽高,转成左上角和右下角,画框并写类别名,输出到vis_check目录。这里有个细节:putText的y坐标用了max(0, y1 - 5),防止框贴顶时文字跑到图像外面报错。参数说明:class_names列表的顺序必须和数据集labels文件一致,否则画出来的名字张冠李戴;如果图片是png,glob通配符要改成*.png;随机抽10张不满意可以调大img_paths[:10]里的数字。
抽检时重点看三类问题:框是不是包住了完整缺陷还是只包了一半;有没有把一个裂纹拆成两个短框;类别名和缺陷形态对不对得上。如果十张图里超过两张有明显问题,这个数据集的标注一致性就要打个问号,后面训练出的模型精度上限会受影响。
3. YOLO与VOC双格式拆解:坐标换算、转换脚本与增强数据的坑
3.1 两套标注格式的关键差异
YOLO格式每个txt文件对应一张图,每行是“class_id cx cy w h”,四个坐标都是相对图片宽高的归一化浮点数,范围0到1。VOC格式是xml文件,<size>里写图片宽高,<bndbox>里写xmin、ymin、xmax、ymax四个像素绝对坐标。两者互相转换的核心就是一组乘除法换算,公式如下:
YOLO转VOC:xmin = (cx - w/2) × img_width,ymin = (cy - h/2) × img_height,xmax = (cx + w/2) × img_width,ymax = (cy + h/2) × img_height。
VOC转YOLO:cx = (xmin + xmax) / 2 / img_width,cy = (ymin + ymax) / 2 / img_height,w = (xmax - xmin) / img_width,h = (ymax - ymin) / img_height。
| 对比项 | YOLO txt | VOC xml |
|---|---|---|
| 坐标形式 | 归一化浮点数 | 像素绝对坐标 |
| 图片尺寸 | 不记录,推理时读取 | 记录在size标签里 |
| 类别名 | 数字索引 | 字符串标签 |
| 文件粒度 | 一图一txt | 一图一xml |
无论往哪个方向转换,都必须知道图片真实宽高。很多转换脚本翻车就翻在这里:拿不到图片尺寸,或者读到了被缩放过后的尺寸,坐标全错。这也是标题里“YOLO+VOC格式”两个格式的意义所在——你不用自己写转换,但你必须理解两套坐标之间的关系,否则校验标注、排查问题都无从下手。
3.2 格式转换脚本:YOLO转VOC的完整实现
虽然数据集已经给了双格式,但实际项目里经常要做反向操作,比如自己补标了一批图是txt格式,要合回流进VOC训练管线。我一般保留一套自己的转换脚本,避免每次现写。下面是YOLO转VOC的常用实现:
import os import glob import xml.etree.ElementTree as ET from PIL import Image label_dir = "labels/train" image_dir = "images/train" out_dir = "voc_labels/train" os.makedirs(out_dir, exist_ok=True) for txt in glob.glob(os.path.join(label_dir, "*.txt")): img_path = os.path.join(image_dir, os.path.basename(txt).rsplit(".", 1)[0] + ".jpg") if not os.path.exists(img_path): img_path = img_path.replace(".jpg", ".png") # 兼容png命名 with Image.open(img_path) as img: w_img, h_img = img.size annotation = ET.Element("annotation") size = ET.SubElement(annotation, "size") ET.SubElement(size, "width").text = str(w_img) ET.SubElement(size, "height").text = str(h_img) with open(txt) as f: for line in f: parts = line.split() if len(parts) < 5: continue cls_id = int(parts[0]) cx, cy, bw, bh = map(float, parts[1:5]) xmin = int((cx - bw / 2) * w_img) ymin = int((cy - bh / 2) * h_img) xmax = int((cx + bw / 2) * w_img) ymax = int((cy + bh / 2) * h_img) obj = ET.SubElement(annotation, "object") ET.SubElement(obj, "name").text = str(cls_id) # 可换成类名字符串 bndbox = ET.SubElement(obj, "bndbox") ET.SubElement(bndbox, "xmin").text = str(xmin) ET.SubElement(bndbox, "ymin").text = str(ymin) ET.SubElement(bndbox, "xmax").text = str(xmax) ET.SubElement(bndbox, "ymax").text = str(ymax) tree = ET.ElementTree(annotation) tree.write(os.path.join(out_dir, os.path.basename(txt).rsplit(".", 1)[0] + ".xml"))逻辑说明:脚本读每个txt对应的图片尺寸,把归一化坐标转成像素坐标,写进xml树,最后保存成VOC格式文件。注意代码里对图片命名的处理:先找jpg,找不到就尝试png,这样不会因为图片后缀不一致中断。参数说明:cls_id默认写成数字,如果下游训练要求字符串类名,把str(cls_id)改成从类名列表里取值;w_img, h_img来自PIL读取的原始尺寸,不要用缩放后的图像尺寸,这是坐标不出错的前提。
转换完必须做一致性校验:随机挑几个xml,把xmin、ymin、xmax、ymax画回原图,确认框的位置和原txt一致。这个校验步骤不能省,因为转换脚本最常见的错误是坐标方向写反,或者读错图片尺寸导致整体偏移一个固定值。校验通过的xml才能放心用。
3.3 已增强数据的正确打开方式
标题里“已增强”三个字,意味着这份数据集的训练图有一部分是原图做了数据增广得到的。常见的增强手段无非这几类:
| 增强手段 | 作用 | 风险 |
|---|---|---|
| 亮度/对比度扰动 | 模拟不同光照条件 | 过度扰动会导致纹理失真 |
| 高斯噪声/模糊 | 提升抗噪能力 | 小目标被噪声吞掉 |
| 水平翻转 | 加倍样本量 | 焊缝方向性被破坏,裂纹方向语义失效 |
| 小角度旋转 | 丰富几何形态 | 旋转后标注框不准 |
| HSV色域扰动 | 适应不同材质反光 | 颜色偏到异常区间 |
| Mosaic拼接 | 增加单图目标数 | 小目标被压缩得更小 |
增强本身不是坏事,但有两个坑必须提前排掉。第一,增强图不能混进验证集。验证集的意义是模拟真实推理分布,如果用增强图做验证,模型的指标会虚高,部署到现场立刻现原形。第二,翻转和旋转类增强对焊接缺陷有方向语义。裂纹如果只在一个方向出现,水平翻转后裂纹方向变了,模型学到的是“翻转后的纹理”而不是“裂纹的纹理”,这个语义错位很难通过加数据修正。
拿到zip后先看文件命名规律。常见的数据集会以_bright、_flip、_noise这类后缀标记增强图,也有的直接用独立子目录区分。按命名规则把增强图只放进训练集,验证集只保留原始图。如果数据集没有明确区分,我建议自己写个脚本按文件名去重划分,确保同一张原图的增强版本和原图不在验证集里同时出现。
4. 用YOLOv8跑通4137张焊接缺陷数据集的完整流程:解压到出模型
4.1 目录组织与data.yaml
解压后第一件事不是训练,而是把目录结构理顺。YOLOv8读取数据集靠data.yaml,里边只要配置好图像路径、标签路径、类别列表就行。常见做法是把数据组织成下面这种结构:
datasets/ └── weld/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── weld.yaml如果zip里本身是images和labels两个平级目录,你可以用软链接组织出上面结构,不需要实际复制文件再占一份磁盘空间:
mkdir -p datasets/weld ln -s /path/to/unzipped/images datasets/weld/images ln -s /path/to/unzipped/labels datasets/weld/labels对应的data.yaml这样写:
path: datasets/weld train: images/train val: images/val names: 0: crack 1: porosity 2: slag 3: lack_fusion 4: lack_penetration 5: undercut注意names的顺序必须和标签txt里第一列的类别索引完全一致,类别索引从0开始。很多报错“class index out of range”就是这里对不上。如果你不确定类别名,先打开数据集自带的classes.txt对照,别凭文件名猜。path建议写绝对路径,避免不同工作目录下相对路径解析出错。
4.2 最小训练命令与关键超参数
目录和配置文件就绪后,直接跑训练命令。我习惯用YOLOv8的s模型先跑通,再看资源换m或l。最小命令如下:
yolo detect train \ data=datasets/weld/weld.yaml \ model=yolov8s.pt \ epochs=80 \ imgsz=640 \ batch=16 \ project=weld_exp \ name=baseline逻辑说明:命令会加载yolov8s预训练权重,按data.yaml读取训练集和验证集,训练80个epoch并输出到weld_exp/baseline目录。跑完会在该目录下生成weights/best.pt和weights/last.pt,best按验证集mAP自动保存。参数说明:epochs先从80起步,焊接缺陷数据量不大,80个epoch足够看到收敛趋势;imgsz默认640,如果显存够且缺陷偏小,改成960或1280;batch根据显卡显存调,显存不够就减半。
这里有个容易被忽略的参数patience,默认100,意思是连续这么多轮验证集mAP不涨就早停。80个epoch下patience默认值是够的,但如果你把epochs调到200,建议把patience显式设成30或50,省得训练后半段干等。训练中断也不慌,YOLOv8会保存last.pt,可以加resume=True从断点继续。
4.3 训练日志里必须盯的三个指标
训练启动后屏幕上刷一堆进度条,不需要全看,盯三个地方就够。
第一看loss曲线。box_loss和cls_loss曲线如果在前10个epoch稳定下降,说明模型在正常学;如果中间出现平台期后突然跳高,多半是学习率策略在起作用或者数据里有坏样本,先不动,继续看后续走势。第二看验证集的mAP50,这个指标代表框和类别都判对的综合水平。焊接缺陷检测如果mAP50能到0.7以上,说明数据集质量基本过关;卡在0.5上下,优先怀疑标注问题而不是模型问题。
第三看PR曲线。训练结束后打开results.png或跑一遍验证脚本,观察PR曲线右下角有没有被“拽”下来。焊接缺陷里小目标多,PR曲线的尾巴往往会因为小裂纹漏检而下坠,这属于正常现象,关键看曲线主体是不是饱满。如果曲线整体贴着右下角,说明学习率设高了或者训练样本太少,先别调模型,回去看数据集划分。
5. 焊接缺陷检测数据集训练的避坑笔记:四类常见的翻车现场
5.1 边界框越界与归一化错误:训练中途loss不降反升
现象:训练到十几个epoch时box_loss突然波动甚至升高,验证集mAP一直没动静。
原因:标签txt里的坐标不是归一化值,或者w、h写成了像素值;也可能是VOC转YOLO时除错了图片尺寸,导致坐标超出0到1范围。YOLOv8对越界框不是直接报错,而是做了裁切,裁切后目标框位置已变,模型学到的自然是不对的。
解决:训练前先跑一遍校验脚本,把所有txt的坐标范围检查一遍。
import glob for txt in glob.glob("datasets/weld/labels/train/*.txt"): with open(txt) as f: for line in f: parts = line.split() if len(parts) < 5: print("格式错误:", txt, line) break cx, cy, w, h = map(float, parts[1:5]) if not (0 <= cx <= 1 and 0 <= cy <= 1 and 0 <= w <= 1 and 0 <= h <= 1): print("坐标越界:", txt, line)逻辑说明:逐行解析,只要cx、cy、w、h任一超出0到1就打印出来。这样能快速定位是某个文件坏了,还是整批转换出了问题。参数说明:如果数据集用的是VOC格式,先用第三章的转换脚本统一成YOLO格式再校验;校验结果为空才能开始训练。这是整套流程里最便宜的一道保险,千万别跳。
5.2 增强数据泄漏进验证集:val指标虚高但实测很差
现象:训练时验证集mAP非常高,0.8以上,但把模型拿到没有增强的新图上推理,漏检明显变多。
原因:划分训练集和验证集时随机打乱,同一张原图的增强版本和原图分别进了训练集和验证集。增强图和原图共享大部分纹理内容,模型记住了这个“近亲”关系,验证指标等于开卷考试。
解决:按文件名前缀划分,保证同一原始图像的所有增强版本只落在训练集。先提取文件名里属于同一原始图的前缀,再按前缀分组划分。例如原图叫weld_0001.jpg,增强图叫weld_0001_bright.jpg,按weld_0001分组即可。验证集只用完全没做过增强的原始图,数量少一点没关系,真实度优先。
5.3 六类样本数量悬殊:尾部类别漏检、mAP被多数类带歪
现象:训练完成后看看各类别AP,气孔、夹渣这类多的类别AP到了0.8,未焊透、裂纹却只有0.2甚至更低。整体mAP看起来还行,一细化就露馅。
原因:类别不平衡时损失被样本量大的类别主导。焊接缺陷数据集里,气孔和夹渣最容易收集,裂纹和未焊透本来就少见,如果增强只做了亮度扰动而没有针对少数类做样本扩充,类别差距会越拉越大。
解决:先跑一次默认训练拿到各类AP,作为不平衡的定量依据,不要拍脑袋调参。针对少数类做两条路:一是加样本,把少数类的原图再做几组针对性增强,比如对裂纹图做对比度增强让细线纹理更清楚;二是调损失权重,YOLOv8里可以设置cls_loss或按类别调权重,把多数类权重降一点,让少数类在loss里占更大比重。最土但有效的办法是对少数类做复制粘贴级过采样,把少数类图片在训练集里多放几份,这招在数量差10倍以内时比调参管用。
5.4 小缺陷目标在深层特征图里消失:细小裂纹检测不到
现象:带细裂纹的图,模型大概率漏检,即使检出了框也不贴合裂纹走向,AP50和AP50-95差一大截。
原因:焊接缺陷里裂纹和气孔很多只有十几像素宽,imgsz=640下经过多次下采样,小目标在深层特征图里只剩一两个像素,特征基本被池化抹掉。这不是模型笨,是分辨率喂不进去。
解决:三招可以组合用。第一招最直接,把imgsz提到960甚至1280,小目标像素数量翻倍,代价是显存和训练时间上升;第二招换用带P2检测头的模型结构,P2层保留更高的空间分辨率,专门缓解小目标丢失;第三招是推理阶段做切图,把大图切成若干重叠patch分别检测再合并结果,这招在工业场景里很常用,因为现场相机拍的是整块焊缝,不是模型训练时的裁切图。先试第一招,性价比最高。
6. 验证这个数据集的三个技巧:先看基线,再决定要不要换增强
6.1 第一次训练后只做两件事:看mAP@0.5和PR曲线
训练结束后别急着部署,先打开results.csv看两个数:metrics/mAP50(B)和metrics/mAP50-95(B)。mAP50代表框和类别判对的整体水平,焊接缺陷能做到0.7以上算及格;mAP50-95代表框和真实目标重叠度要求更严的分数,这个数低于0.4说明框不够贴目标,后续要在回归精度上下功夫。PR曲线在results.png里,看曲线主体是否饱满、尾巴有没有大幅下坠。这两样看完,才谈得上后面调什么。
6.2 用图像级误报反查标注质量问题
验证集里那些预测错了的图,我习惯按错误类型摊开分三类:数据漏标(模型检出了缺陷但标签没标)、类间混淆(把气孔判成夹渣)、背景误报(把飞溅、反光当缺陷)。漏标优先回补标签;类间混淆要看两类样本是否长得太像,必要时合并类别;背景误报说明负样本不够,去现场多拍没有缺陷的焊缝图加进训练集。这一步做完,你对这个数据集的底细就彻底摸清了。
6.3 增强策略回退:最少改动拿回性能
加过增强的数据集训练不升反降时,最有效的排查办法是把增强全部关掉,只用原图训练一次。如果原图训练表现反而更好,说明增强参数过猛或增强方案不符合焊接缺陷纹理规律,一项一项往回加,每次只加一种增强手段,看验证集指标变化再决定留不留。我自己的血泪经验是:一开始图省事直接上了全套增强,结果模型学到的全是增强痕迹,换成原图基线后mAP反而涨了几个点。现在的流程永远是先原图跑通,再逐步加增强,翻车了也知道是哪一步造成的。这个习惯在焊接缺陷这种小样本、高相似度数据集上尤其重要,希望帮到你。
本文还有配套的精品资源,点击获取