简介:这是一份面向计算机视觉目标检测任务的焊接表面缺陷图像数据集,采用YOLOV5标准目录结构,省去繁琐的格式转换,适合算法工程师、科研人员直接用于模型训练与效果验证。数据集覆盖穿孔、折痕、压痕、内含物等10个常见焊接缺陷类别,图像均为2048×1000分辨率的RGB图片,并已划分出训练集1836张、验证集458张,每张图片均对应一个txt标签文件,类别信息一目了然,可直接对接主流检测框架。压缩包大小约918MB,共2000个文件,以txt标注标签为主体,另附一个开箱即用的show.py可视化脚本——随机传入一张图片即可绘制并保存边界框,方便快速检查标注位置与类别是否正确,同时也有助于初学者理解标签格式。目前已有234人浏览学习,适合用于焊接缺陷检测相关的实验教学、模型训练或算法对比评测。
1. 焊接表面缺陷图像目标检测数据集:YOLOV5目录格式没理清,训练再快也白搭
做焊接质检的工程师拿到目标检测数据集,第一反应往往是先跑模型,结果在 YOLOV5 目录格式上翻车:图像在 images 里,标签却用了别的命名;验证集分布和训练集重叠;类别编号从 1 开始导致损失直接爆掉。这个标题里的数据集合计 10 类别,训练集、验证集都已就位,看起来“开箱即用”,但如果不先把目录规则、类别映射和标签坐标确认一遍,训练流程会在最无聊的地方卡住。它适合搞工业视觉、焊缝缺陷筛查,以及想用 YOLOV5 把质检模型真正训练起来的人。下面就是我做过几轮后的整理。
2. 焊接表面缺陷的10类别与YOLOV5目录格式:先弄清数据形态再动手
2.1 焊接缺陷10个类别:缺陷命名、形态特征与标注框的边界
焊接表面缺陷这 10 类,在图像里长得并不像 MNIST 那样干净,同一类缺陷在不同焊缝、不同光照、不同打磨程度下,形态差异非常大。按我接触过的工业质检数据集,常见组合是气孔、夹渣、未熔合、未焊透、咬边、焊瘤、飞溅、裂纹、凹陷、烧穿。这里的难点不在“认不认得出来”,在于几类缺陷之间视觉边界模糊:未熔合和未焊透都呈长条状,咬边和凹陷都表现为轮廓中断,飞溅和小气孔远看都是碎点。标注规范不一致,模型就会在这个数据集上展现出明显的“玄学”行为——训练 loss 掉得很漂亮,验证集 AP 却始终上不去。
标注框的边界尤其影响训练结果。我一般给标注员定三条规矩:第一,气孔、夹渣这类近圆形缺陷用紧凑外接矩形,框住可见缺陷主体即可,不要包进大片背景;第二,裂纹按整条连续缺陷走,长边覆盖裂纹两端,短边紧贴裂缝,留 2 到 3 个像素余量就够;第三,飞溅必须单独标,绝不能为了省事把一片飞溅点合并成一个框。你可能会觉得这些细节和模型无关,实际上 YOLOV5 的 anchor 分配逻辑对框的宽高比很敏感,框里背景一多,模型学到的特征是“背景+缺陷”的混合体,换一条新焊缝立刻露馅。
还有一个容易被忽略的点:类别之间的目标尺度极不平衡。气孔可能只有 10×10 像素,裂纹却往往横跨几百像素且长宽比奇大。这种尺度差异直接决定了你后面训练时要不要把输入分辨率从 640 提到 1280,也决定了 mosaic 增强要不要关。换句话说,拿到数据集的第一件事不是跑训练,而是打开 labels 目录,把每个类别的框数量、宽高分布统计一遍,搞清楚你面对的是“容易检的大目标”还是“必须抠细节的小目标”。
2.2 YOLOV5目录格式:images、labels、train、val的硬性结构
标题里专门强调“YOLOV5目录格式”,说明目录结构本身就是一个强约束。标准形态是下面这棵树:
dataset/ ├── images/ │ ├── train/ │ │ ├── weld_00001.jpg │ │ └── weld_00002.jpg │ └── val/ │ ├── weld_00101.jpg │ └── weld_00102.jpg └── labels/ ├── train/ │ ├── weld_00001.txt │ └── weld_00002.txt └── val/ ├── weld_00101.txt └── weld_00102.txt图像在 images 目录下按 train/val 分开,标签则放到 labels 目录下同样结构的子目录里。最关键的一条:图片文件名和标签文件名必须完全一致,扩展名除外。weld_00001.jpg对应的只能是weld_00001.txt,不能多前缀,不能改后缀名。如果一个数据集里出现weld_00001.txt和weld_00001.jpg.txt混放的情况,那训练时一半图像找不到标签,程序静默跳过,损失曲线照样往下走,但 mAP 就是上不去。
每个 txt 文件的内容是纯文本,一行标注一个目标,每行五个字段:
| 字段顺序 | 含义 | 取值说明 |
|---|---|---|
| 1 | 类别ID | 整数,从 0 开始,最大为 9 |
| 2 | 目标中心点 X 坐标 | 归一化到 0~1,除以图像宽度 |
| 3 | 目标中心点 Y 坐标 | 归一化到 0~1,除以图像高度 |
| 4 | 目标宽度 | 归一化到 0~1,除以图像宽度 |
| 5 | 目标高度 | 归一化到 0~1,除以图像高度 |
注意这里全是归一化浮点数,不是像素值。有人会把绝对像素中心直接写进 txt,YOLOV5 加载时不会立刻报错,但坐标全部超出有效范围,训练出来的模型基本是废的。另一个常见问题是把类别 ID 从 1 开始写,导致第 10 类缺陷的 ID 变成 10,超过nc=10的合法范围 0~9,训练跑到一半直接抛异常。
提示:检查数据集时先统计所有 txt 里第一个字段的最大值和最小值。最大值只要大于 9,基本就是转换脚本没做“类别减 1”。
2.3 训练集和验证集为什么这么分:加载器按路径找同名文件,错一点就漏一个
YOLOV5 的数据加载逻辑并不聪明,它只是机械地遍历images/train下的所有图片,然后去labels/train找同名 txt。问题在于,YOLOV5 的 Dataset 实现里,遇到缺失标签的图像,默认是直接跳过而不是报错。这就形成了最隐蔽的坑:images/train有 1000 张,labels/train只有 900 个 txt,训练过程完全正常,没有任何红色提示,但那 100 张图实际上根本没参与训练。等模型上线,遇到那 100 张图里出现的缺陷形态,表现自然一塌糊涂。
验证集的作用同样容易被低估。很多人把 val 当成一个“事后评分的工具”,实际上在 YOLOV5 训练流程里,验证集参与最佳权重选择。每一次 epoch 结束,程序都会跑一次 val,用验证集的 mAP 决定要不要保留当前权重。如果验证集里有大量未标注图片,或者验证图像和训练图像来自同一条焊缝,mAP 就失真了。焊接数据常见做法是把一条长焊缝切成多张训练图,如果不按焊缝编号而是直接随机切分,同一根焊缝的相邻切片会同时进入 train 和 val。训练时模型相当于“见过”同一场景的不同裁切版本,验证指标虚高,部署到全新工件上立刻现原形。
正确的切分逻辑是按来源分组:先按焊缝编号或工件编号把样本分成互不相交的组,再整组划入训练集或验证集。这样虽然看起来“浪费”了一些可训练样本,但验证结果才有参考价值。后续调整增强参数、比较不同模型结构时,才不会让数据泄漏干扰判断。
3. 整理焊接缺陷数据:把标注框转成YOLOV5格式并划分训练集验证集
3.1 按缺陷类别分层切分训练集与验证集:写一个 split 脚本
拿到原始数据后,第一步不是转格式,而是先把训练集和验证集切好。纯随机切分在焊接数据上不好用,因为气孔这类常见缺陷样本多,裂纹、烧穿这类少见缺陷样本少,随机切分很容易让某一类缺陷在验证集里一个样本都没有。我用的是按类别组合分层的切分方式。
import random import shutil from pathlib import Path from collections import defaultdict random.seed(42) # 固定随机种子,保证每次切分结果一致 src_imgs = Path("raw_images") # 原始图像目录 src_lbls = Path("raw_labels") # 原始标签目录,txt 格式 train_imgs = Path("dataset/images/train") val_imgs = Path("dataset/images/val") train_lbls = Path("dataset/labels/train") val_lbls = Path("dataset/labels/val") for d in [train_imgs, val_imgs, train_lbls, val_lbls]: d.mkdir(parents=True, exist_ok=True) # 只匹配 jpg 和 png,如果你的数据里有 jpeg/bmp,自行补上 img_files = list(src_imgs.glob("*.jpg")) + list(src_imgs.glob("*.png")) def read_classes(img_path): """返回一张图对应的类别ID集合,用于分层""" txt = src_lbls / (img_path.stem + ".txt") if not txt.exists(): return {"none"} with open(txt) as f: return {line.split()[0] for line in f if line.strip()} # 按类别组合分组,同组合的图放进同一个桶 buckets = defaultdict(list) for img in img_files: classes = tuple(sorted(read_classes(img))) buckets[classes].append(img) val_ratio = 0.15 for classes, files in buckets.items(): random.shuffle(files) n_val = max(1, int(len(files) * val_ratio)) if len(files) >= 5 else 0 val_set = set(files[:n_val]) for img in files: lbl = src_lbls / (img.stem + ".txt") if img in val_set: shutil.copy(img, val_imgs / img.name) if lbl.exists(): shutil.copy(lbl, val_lbls / lbl.name) else: shutil.copy(img, train_imgs / img.name) if lbl.exists(): shutil.copy(lbl, train_lbls / lbl.name)这段脚本的逻辑是:先统计每张图包含哪些类别,把类别组合完全相同的图放进同一组,再在组内按比例切分。这样做的原因是保证验证集覆盖到所有类别组合,而不是只保证总体数量比例。代码里有几个参数你要重点理解:seed=42是固定随机种子,没有它你每次运行切分结果都不同,后面做对比实验时无法复现;val_ratio=0.15表示 15% 的数据进验证集,工业小数据集我建议留 15%~20% 之间,别太少,否则 mAP 波动会大得让你怀疑人生;当某个组合总数少于 5 张时,脚本会把它们全部留在训练集,避免验证集只有一两张图导致评估方差过大。
这个脚本还有一个检查项没做:它没有打印每个类别在训练集和验证集中的具体数量。实际使用时我会在切分完成后加一段统计代码,输出每个类别在两侧的样本数。如果发现某个类别验证集数量为 0,说明这个类别在数据集中本身就极度稀少,后续训练需要针对它做特殊处理,而不是盲目开训。
3.2 把VOC框转成YOLOV5的归一化txt:五个字段一个都不能错
焊接缺陷数据集里最常见的标注格式是 PASCAL VOC 的 XML,也就是每个目标给xmin, ymin, xmax, ymax绝对像素坐标。转换到 YOLOV5 格式要做两步:坐标改为中心点加宽高、全部归一化到 0~1。这里最容易出错的点是忘了除以图像宽高,以及类别 ID 没有减 1。
import xml.etree.ElementTree as ET from pathlib import Path from PIL import Image # 类别映射表:键是XML里的name,值是YOLO类别ID,0~9 class_map = { "porosity": 0, "slag_inclusion": 1, "lack_of_fusion": 2, "incomplete_penetration": 3, "undercut": 4, "overlap": 5, "spatter": 6, "crack": 7, "sagging": 8, "burn_through": 9, } xml_dir = Path("voc_labels") out_dir = Path("labels") out_dir.mkdir(exist_ok=True) for xml_file in xml_dir.glob("*.xml"): # 这里假设图像和XML同名,且为jpg;实际以你的文件名为准 img = Image.open(xml_file.with_suffix(".jpg")) W, H = img.size lines = [] root = ET.parse(xml_file).getroot() for obj in root.findall("object"): name = obj.find("name").text if name not in class_map: # 打印未知类别名,防止标注拼写不一致被静默跳过 print(f"unknown class: {name}") continue bbox = obj.find("bndbox") x1 = float(bbox.find("xmin").text) y1 = float(bbox.find("ymin").text) x2 = float(bbox.find("xmax").text) y2 = float(bbox.find("ymax").text) # 越界纠正和过小框过滤,防止负坐标/零宽高直接拖崩训练 x1 = min(max(x1, 0), W - 1) y1 = min(max(y1, 0), H - 1) x2 = min(max(x2, 0), W - 1) y2 = min(max(y2, 0), H - 1) if x2 - x1 < 2 or y2 - y1 < 2: continue cx = (x1 + x2) / 2 / W cy = (y1 + y2) / 2 / H bw = (x2 - x1) / W bh = (y2 - y1) / H lines.append(f"{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") if lines: (out_dir / (xml_file.stem + ".txt")).write_text("\n".join(lines))转换脚本的核心逻辑就一句话:把绝对坐标变成相对坐标。class_map是整个流程里必须严格对照 data.yaml 的地方,两边顺序一旦错位,训练不会报错,但模型学出来的类别全乱套。比如 data.yaml 里names[0]是porosity,而这里的class_map里porosity必须也是 0,不能出现一边从 0 开始、另一边从 1 开始的情况。
越界纠正不是可有可无的防御代码。标注软件里经常出现标注框的一角稍微超出图像边界的情况,如果不把坐标 clip 回[0, W-1]和[0, H-1],后续损失计算可能产生 NaN。过小框过滤则是在处理飞溅这类细碎目标时必要的清洗手段:宽度或高度小于 2 像素的框,要么是误标,要么小到模型根本无法学习,留着只会给 loss 增加噪点。
3.3 写data.yaml:路径、类别名、编号必须和labels对得上
YOLOV5 训练时真正读取的配置文件是 data.yaml。很多数据集包不会帮你写好这个文件,需要你自己生成。一个最小可用的焊接缺陷 data.yaml 长这样:
# welding_defect.yaml path: /data/welding_dataset # 数据集根目录,建议写绝对路径 train: images/train # 相对 path 的训练集图像目录 val: images/val # 相对 path 的验证集图像目录 names: 0: porosity 1: slag_inclusion 2: lack_of_fusion 3: incomplete_penetration 4: undercut 5: overlap 6: spatter 7: crack 8: sagging 9: burn_through注意这里没有写nc,YOLOV5 会通过names的条目数自动推断。但如果你显式写了nc: 10,必须确保和names数量一致。path字段我强烈建议用绝对路径,因为不同机器的相对路径基准不同,今天能跑明天换个目录就报文件找不到;如果数据集在 Windows 上,路径分隔符要统一用正斜杠,YOLOV5 在 Windows 下用反斜杠会出莫名其妙的路径拼接问题。
names的顺序是硬约束,它和标签 txt 里的第一列一一对应。也就是说,names[0]对应的必须是你转换脚本里class_map中值为 0 的那个类别。最稳妥的做法是先跑一个检查脚本,遍历所有 labels 文件,统计每个类别 ID 出现的次数,再和 data.yaml 里的names对一遍。这一步能过滤掉 90% 的“训练正常但结果完全不对”的问题。
4. 焊接缺陷数据集训练的5个常见问题:一条一条排查再跑训练
4.1 训练中断报错“class id 超出范围”:编号从0还是从1开始
现象:训练在某个 epoch 中途直接崩掉,报错信息里出现类似Label class 10 exceeds nc=10的提示。
原因:标注工具导出时类别编号从 1 开始,转换脚本没有减 1,气孔对应 1、夹渣对应 2……最后一类烧穿就变成了 10,超出合法范围。这个坑在人工标注的数据集里极其常见,因为普通人计数自然从 1 开始,而 YOLOV5 的类别 ID 从 0 开始。
解决:先扫描整个 labels 目录,找出所有超出范围的标签文件,再决定是修脚本还是修数据。
from pathlib import Path max_class_id = 9 # 因为 nc=10,合法ID范围是0~9 for label_file in Path("labels").rglob("*.txt"): with open(label_file) as f: for line in f: cls_id = int(line.split()[0]) if cls_id > max_class_id or cls_id < 0: print(f"{label_file}: bad class {cls_id}")这个扫描脚本的作用是把所有异常标签一次性揪出来。修复时不要手工改,直接在转换脚本里把class_map[name]改成从 0 开始输出,然后重新转换一遍,比事后改 txt 靠谱得多。另外,某些标注工具导出的类别 ID 可能乱序,比如气孔给 5、裂纹给 2,这时你要做的是建立“标注名 → 0~9 统一编号”的映射表,而不是沿用原始 ID。
4.2 验证集mAP一直为0:图像里没有目标,或者空标签没过滤
现象:训练损失在降,验证集每个类别的 AP 全是 0,预测结果里没有任何框。
原因:常见的是验证集里混入了一批没有标签的图像。YOLOV5 在验证阶段遇到空标签图像时,不会报错,只是把它作为负样本处理。如果这类图像占比太高,模型倾向保守输出,验证集上几乎不产生检测框。另一种原因是标签文件扩展名与图像不一致,比如图像是.jpeg、标签是.txt,加载器按.jpg去找标签,一个都找不到。
解决:把验证集里所有图像和标签逐一对应检查,生成空标签列表。
python - <<'EOF' from pathlib import Path img_set = {p.stem for p in Path("images/val").glob("*")} lbl_set = {p.stem for p in Path("labels/val").glob("*.txt")} print("images with no label:", len(img_set - lbl_set)) print("labels with no image:", len(lbl_set - img_set)) for name in sorted(img_set - lbl_set): print("missing:", name) EOF这个检查必须在训练前做,不要等训练完再回头查。我的习惯是任何数据集到手,先跑两个统计脚本:一个统计类别分布,一个统计“有图没标签、有标签没图”的差值。全部通过后才允许进入训练流程。
4.3 气孔、裂纹这种小目标漏检率高:YOLOV5默认增强把缺陷越放越小
现象:训练完成后,气孔、飞溅这类小目标的召回率明显偏低,裂纹检出一段断一段。
原因:YOLOV5 默认开启 mosaic 增强,它会把四张训练图拼成一张再随机裁剪缩放。焊接小目标本身只有十几个像素,经过 mosaic 的随机缩放后可能缩到 3~5 像素,模型根本学不到有效特征。除此之外,随机仿射变换、HSV 扰动对焊缝这种纹理变化不大的场景影响较小,但尺度缩放的影响非常直接。
解决:训练的第一个版本关掉 mosaic 和部分增强,先用原始尺度把数据链路跑通,再逐步打开增强对比。
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 1280 \ --epochs 30 \ --batch 16 \ --device 0 \ --no-mosaic--no-mosaic是 YOLOV5 训练脚本里现成的开关。焊接缺陷检测场景下,我一般先用这个配置跑一版作为 baseline,然后单独打开 mosaic 再跑一版,对比 mAP。如果打开 mosaic 后指标反而下降,说明你当前的缺陷尺度经受不住这种增强,直接保持关闭,或者把 mosaic 的缩放范围改小。输入分辨率从默认的 640 提到 1280 也是专门针对小目标的调整,代价是显存占用明显上升,batch 16 在 24G 显存上比较稳,16G 显存建议 batch 8 或者输入降到 960。
4.4 气孔太多、未焊透太少:类别不均衡导致训练偏向
现象:气孔类别的 precision 和 recall 都很高,未焊透、烧穿这类稀有缺陷的 AP 在 0 附近徘徊。
原因:焊接缺陷分布天然不均衡,气孔可能占了 60% 的标注框,而烧穿只有几十个样本。YOLOV5 默认按图像采样,没有内置类别加权机制,多数类对梯度贡献大,模型倾向于把不确定目标都预测成常见类。
解决:最简单的做法是在训练前对稀有类做图像级过采样,也就是把包含稀有类的图像在训练目录里复制几份。复制不会引入新信息,但能提高这些图像被采样到的概率。更细一点的做法是调整--image-weights参数,YOLOV5 支持按训练损失动态加权采样,能缓解部分不均衡问题,不过实际效果因数据集而异。评估时不要只看整体 mAP,要看每个类别的 AP,尤其关注稀有类。如果稀有类样本量确实少到不够训练,优先考虑补充数据,而不是指望模型结构变强。
4.5 训练集验证集图像内容重叠:同一条焊缝既在train又在val
现象:训练过程中验证 mAP 极高,甚至在第一个 epoch 就超过 0.8,但模型部署到现场新焊缝上完全不可用。
原因:数据集中一张长焊缝被切成多张图像,切分时没有按来源分组,导致同一根焊缝的相邻切片同时出现在训练集和验证集。模型在训练时已经见过验证图像的“邻居”,等于把部分验证样本的记忆当成了泛化能力。
解决:切分数据时必须以焊缝编号或工件编号为单位,而不是以图像为单位。如果原始数据里没有提供编号信息,可以按文件名前缀分组:weld_001_01.jpg、weld_001_02.jpg属于同一条焊缝,那么这两张图必须同时进训练集或同时进验证集。3.1 节的脚本在数据文件命名规范的前提下,把分组字段加到buckets的 key 里就能实现,一个小的改动就是天壤之别。
5. 用最小训练配置先验证可用性:YOLOV5第一次跑通焊接缺陷数据的技巧
5.1 先关增强跑一个20轮的短实验,验证数据链路和损失
拿到清洗好的焊接缺陷数据集,别急着上完整训练配置。我的习惯是先关掉所有影响尺度的增强,用短训练把数据链路验证通。这个环节的目的不是追求精度,而是确认三件事:图像能正常加载、标签坐标都落在有效范围、损失能稳定下降。
python train.py \ --data data.yaml \ --weights yolov5s.pt \ --img 1280 \ --epochs 20 \ --batch 16 \ --workers 4 \ --device 0 \ --cache ram \ --no-mosaic--cache ram会把整个数据集缓存到内存里,小数据集下训练速度快很多,前提是你的内存够大。--workers 4是数据加载线程数,Windows 上有时要降到 0 或 2,否则会出现 DataLoader worker 崩掉的情况。跑完 20 轮后,看runs/train/exp/results.csv里的train/box_loss和val/box_loss:如果两者都在下降,说明数据链路没问题;如果 train loss 下降但 val/box_loss 横盘,优先怀疑数据泄漏或标签噪声,而不是急着加增强。
5.2 用验证集的每个类别AP和PR曲线判断数据集能不能用
短训练跑完后,真正要花时间看的是每个类别的单独结果。YOLOV5 训练结束后会自动跑验证并保存confusion_matrix.png和PR_curve.png,不要把目光停在整体 mAP 上,要看每个类别自己的 PR 曲线。曲线右上角越饱满,说明这个类别的特征越清晰。如果某个类别在 PR 曲线上几乎没有面积,先查验证集里这个类别的样本数量,很可能验证集压根没有它;确认有样本但 AP 极低,那问题出在标签一致性或者目标尺度上,需要回看标注框有没有框错位置。
我的收尾习惯是:把验证集预测结果可视化出来,用训练好的权重对 val 目录跑一遍预测,然后把带框图片挑出几十张人工扫一遍。这一眼能看出“标注框是否偏低”“小目标是否漏检”“相近类别是否乱标”。说实话,这个步骤看起来原始,但对焊接缺陷这类标注一致性要求高的任务,比任何超参数调优都有效。这个习惯帮我少走了很多弯路,省下的时间远比投入的多。希望帮到你。
本文还有配套的精品资源,点击获取