简介:面向焊接质检与工业视觉应用的目标检测数据集,按YOLOV5标准目录组织,适合初学者及工程人员直接用于模型训练与验证。数据覆盖穿孔、折痕、压痕、内含物等10类焊接表面缺陷,图像为2048×1000的RGB图片;压缩包共2000个文件,主体为1836张训练图片及其txt标注、458张验证图片及其txt标注,并附10个类别文本文件与可视化脚本show.py,可随机读取图片绘制边界框,整体约918.87MB。已有234人学习下载,目录结构清晰,无需额外转换即可接入YOLOV5流程。拿到后可快速开展缺陷检测训练与效果验证,借助可视化脚本直观检查标注质量,适合作为焊接表面缺陷识别项目的起步数据集。
1. 焊接表面缺陷检测,为什么需要一份 YOLOv5 目录格式的数据集
拿到一份 YOLOv5 目录格式的「焊接表面缺陷图像目标检测」数据集,意味着你不用再花一个周末处理数据格式:训练集、验证集、10 个缺陷类别、归一化后的 txt 标注已经按 YOLOv5 的约定摆好,改一行 yaml 路径就能交给 train.py 开跑。焊接表面缺陷检测的难点不在模型复杂,而在缺陷本身——气孔、夹渣、未焊透、咬边、裂纹这类小目标,背景是焊缝的强反光和飞溅,漏检率往往比误检率更让人头疼。这份数据最适合两类人:产线上想快速验证 YOLOv5 缺陷判定方案的视觉工程师,以及做焊接质检相关毕设、课题的学生。接下来我把目录结构、标签体检和训练排查按落地顺序拆开讲。
2. 拆解 YOLOv5 目录格式:训练集/验证集与标签文件的对应关系
YOLOv5 的目录格式能成为工业界的默认选项,不是因为名字好听,而是因为它足够简单:标签是纯文本,每行对应一个目标;图像和标签靠文件名配对;数据配置文件用一个 yaml 写清楚。对焊接缺陷这种需要频繁迭代标注的工业场景来说,这套结构比 COCO 的 JSON 注解好维护得多,也方便在产线机器上直接拷贝部署。
2.1 YOLOv5 数据集的目录约定:images 与 labels 同名配对
标准结构是数据集根目录下四个子目录,image 和 label 一一对应,同名不同扩展名。训练集和验证集在物理上就分开,不依赖任何脚本现场划分。拿到数据集后我建议先跑一遍目录体检,别急着开训练。
# 在数据集根目录执行,先看目录结构是否完整 find . -maxdepth 2 -type d | sort # 统计训练集/验证集的图片数和标注数,应一一对应 echo "train images: $(find images/train -maxdepth 1 -type f | wc -l)" echo "train labels: $(find labels/train -maxdepth 1 -type f | wc -l)" echo "val images: $(find images/val -maxdepth 1 -type f | wc -l)" echo "val labels: $(find labels/val -maxdepth 1 -type f | wc -l)"find ... -maxdepth 1 -type f是列出该目录下的文件,配合wc -l数行数得到文件数量;-maxdepth 2限制递归深度,避免把备份文件或缓存目录也扫进来。这个体检的唯一目的就是确认四个数字两两相等。如果 train images 是 1200、train labels 是 1195,那缺少的 5 个 txt 就会让对应图片在训练时被 YOLOv5 静默跳过,最终表现为某一类缺陷学不好,而且很难溯源。
这类「目录格式正确但文件不配对」的问题,比格式完全错误更难发现。所以解压完数据集第一件事就是把上面的输出存档,后续每次新增标注样本后都重跑一遍,作为提交训练任务前的固定动作。
2.2 标签 txt 的 YOLO 格式:归一化中心点坐标,不是像素框
YOLOv5 的每个 txt 标签文件,每行代表一个缺陷目标,格式是五个字段:类别 ID、归一化后的中心点 x、中心点 y、框宽 w、框高 h。类别 ID 是整数,从 0 开始;四个坐标全是 [0,1] 之间的小数。不是左上角右下角的像素值,这一点和 COCO、VOC 的标注习惯差异最大,也是第一次从其它标注工具迁移数据时最容易翻车的地方。
一个典型的焊接缺陷标签文件内容大致长这样,每行一个目标:
3 0.4825 0.5312 0.0453 0.0321 0 0.1234 0.4578 0.0622 0.0489 1 0.8120 0.2641 0.0330 0.0217第一列的 3、0、1 在训练时对应的是数据配置文件里 names 列表的下标。想要确认坐标有没有存错,可以写一个小脚本把归一化坐标还原成像素坐标:
# 解析一条 YOLO 标注并还原为像素坐标,用于人工核对 import os img_w, img_h = 640, 640 # 图片实际宽高,以你的数据集为准 label_path = "datasets/welding_defect/labels/val/xxx_001.txt" with open(label_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue parts = line.split() class_id = int(parts[0]) x_c = float(parts[1]) y_c = float(parts[2]) box_w = float(parts[3]) box_h = float(parts[4]) # 归一化坐标越界检查:YOLO 格式要求所有值都在 [0, 1] if not (0 <= x_c <= 1 and 0 <= y_c <= 1 and 0 <= box_w <= 1 and 0 <= box_h <= 1): print("越界:", label_path, line) # 还原成像素:中心点与宽高换算到左上角和右下角 x_min = int((x_c - box_w / 2) * img_w) y_min = int((y_c - box_h / 2) * img_h) x_max = int((x_c + box_w / 2) * img_w) y_max = int((y_c + box_h / 2) * img_h) print(class_id, (x_min, y_min, x_max, y_max))这段脚本有两处值得注意的参数。第一处是img_w, img_h,必须取数据里图片的实际尺寸,而不是你训练时用的输入尺寸;如果图片是 1280×960,这里却填了 640×640,还原出的框会整体偏移。第二处是越界判定,焊接缺陷的框往往贴着焊缝边缘,归一化后轻微越界如 x 等于 1.002 是常见的,这种可以靠训练前 clamp 修正;但如果中心点出现 1.35 这类值,基本可以断定是标注工具的导出 bug,整条数据该删。
2.3 数据配置文件 welding_defect.yaml:10 类的 names 顺序决定输出顺序
YOLOv5 训练时读取的是一个 yaml 文件,它做的事情是把「目录结构」和「类别含义」绑定在一起。这份焊接缺陷数据集的 yaml 大致长这样:
# 焊接表面缺陷数据集配置,路径改成你解压后的实际位置 path: /absolute/path/to/welding_defect train: images/train val: images/val nc: 10 names: 0: porosity 1: slag 2: incomplete_penetration 3: lack_of_fusion 4: undercut 5: crack 6: overlap 7: spatter 8: burn_through 9: blowholepath是数据集根目录,train和val是相对路径;nc必须等于 10,和标题里的 10 类别对齐。names的下标顺序就是标签 txt 第一列的类别 ID。这里有一个约定俗成的设计:你训练时用什么 names 顺序,部署推理时输出张量里那个位置的类别就代表什么。这个顺序不是随便排的,一旦训练中途修改,前面所有的权重都会作废。
拿到数据集后,用一段脚本把标签里的实际类别 ID 和 names 逐位比对,这一步能拦住大多数「标签和配置对不上」的问题:
# 校验标签类别 ID 是否与 yaml 里的 names 顺序一致 import glob from collections import Counter # 以 yaml 里 names 的实际内容为准,顺序不能变 names = ["porosity", "slag", "incomplete_penetration", "lack_of_fusion", "undercut", "crack", "overlap", "spatter", "burn_through", "blowhole"] counter = Counter() for txt in glob.glob("datasets/welding_defect/labels/**/*.txt", recursive=True): with open(txt, "r", encoding="utf-8") as f: for line in f: if line.strip(): counter[int(line.split()[0])] += 1 for class_id in range(len(names)): print(f"{class_id:2d} {names[class_id]:28s} {counter.get(class_id, 0):6d}") # 打印标签里出现但 names 中不存在的非法 ID valid_ids = set(range(len(names))) actual_ids = set(counter.keys()) print("非法类别 ID:", sorted(actual_ids - valid_ids))运行后如果某个类别的统计数是 0,先别急着高兴省了一个类,多半是标签里那个类别的 ID 并不是它在 names 中的位置。非法 ID 一旦出现,训练时 yaml 解析会直接报错,而在报错之前把问题定位掉,是数据体检的意义。这一步做完,再谈训练参数才有意义。
3. 10 类焊接缺陷的标注体检:从类别分布到越界框的逐项核对
把目录结构和 yaml 对齐后,下一步是对标注内容做体检。焊接缺陷数据和行人、车辆这类数据最大的不同在于:缺陷形态小、类间相似度高、类别分布极不均匀。如果不做统计直接开训,常见的结局是模型把占比最高的气孔类学得很好,裂纹类基本不输出,验证集 mAP 看着还行,一上产线就漏检。
3.1 分训练集/验证集统计 10 类缺陷的框数:先看清类别不平衡
先分别统计训练集和验证集里每个类别的标注框数量,这一步决定后面要不要做类别平衡处理。
# 分别统计训练集和验证集中的 10 类缺陷框数 import glob from collections import Counter for split in ["train", "val"]: counter = Counter() for txt in glob.glob(f"datasets/welding_defect/labels/{split}/*.txt"): with open(txt, encoding="utf-8") as f: for line in f: line = line.strip() if line: counter[int(line.split()[0])] += 1 print(f"== {split} ==") for class_id in sorted(counter): print(f"类别 {class_id}: {counter[class_id]} 个框")这个统计信息直接指导训练策略。焊接缺陷数据常见的分布是气孔、飞溅占大头,裂纹和未融合很少;如果某个类在训练集里只有几十个框,YOLOv5 的 mosaic 增强也变不出新样本。常见做法是给小众缺陷类单独做复制粘贴增强,把缺陷区域裁剪后随机贴到其它图片上生成新样本;或者把该类别的训练图像复制几份到训练目录并同步复制同名 txt,相当于过采样。但要注意一个前提:训练集和验证集的类别分布要接近。如果 crack 类在训练集有 300 框、验证集只有 10 框,那验证时的 AP 波动会非常大,一次随机划分就能让指标差 5 个点。
另外一个容易忽略的点是「一张图里有多个框还是多个图里各有一个框」。同样是 100 个气孔框,如果集中在 20 张图上,模型见过的气孔上下文就非常有限。所以在统计框数的同时,顺手统计一下包含某类别的图片张数,更能反映真实采样广度。遇到框多图少的情况,建议按图做分层划分,而不是按框划分。
3.2 越界框与退化小框排查:焊接缺陷最容易在这里翻车
焊接缺陷的标注框普遍很小,小目标框在导来导去的过程中容易出现两类脏数据:一类是坐标越界,另一类是退化小框——框的宽或高折算成像素后不到 1 个像素。这两类数据不清理,训练时轻则 loss 异常,重则直接学出错误的边界框回归目标。
# 检查标签文件中的越界框、空文件、退化框 import glob bad_files = [] empty_files = [] for txt in glob.glob("datasets/welding_defect/labels/**/*.txt", recursive=True): lines = [ln.strip() for ln in open(txt, encoding="utf-8") if ln.strip()] if not lines: empty_files.append(txt) continue for ln in lines: parts = ln.split() if len(parts) != 5: bad_files.append((txt, "字段数不为5", ln)) continue cid, x_c, y_c, w, h = (float(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4])) if w <= 0 or h <= 0: bad_files.append((txt, "宽高非正", ln)) if x_c < 0 or x_c > 1 or y_c < 0 or y_c > 1: bad_files.append((txt, "中心点越界", ln)) # 按 640 参考分辨率判断退化框,训练分辨率不同时调整这个参数 if w * 640 < 1 or h * 640 < 1: bad_files.append((txt, "退化小框", ln)) print(f"空标注文件: {len(empty_files)}", empty_files[:10]) print(f"疑似错误标注: {len(bad_files)}") for f in bad_files[:20]: print(f)脚本里有两处参数要按照数据集实际情况调。一是 640 这个参考分辨率,YOLOv5 默认训练尺寸是 640,所以按 640 判断退化框和训练一致;如果你打算用 1280 训练,这里就要改成w * 1280 < 1。二是越界的容忍度,脚本里只要不超过 0 到 1 就判合法,实际处理时我一般允许 0.002 以内的溢出并直接 clamp,超出这个范围才标为错误。空标注文件这个分支值得多说一句:YOLOv5 对空 txt 不会报错,只是把它当没有目标的样本参与负样本学习,导致 loss 在训练初期振荡得非常厉害,这个坑我在后面的排查章里还会专门展开。
3.3 图像与标注文件同步核对:缺图、缺标签、重名的处理套路
最后一步是把 images 和 labels 两个集合做一次文件名差集比对。理论上体检到这里已经查过数量,但数量相等不代表文件一一配对——完全可能出现 A 图缺 txt、B 图多 txt,数量刚好抵消的情况。
# 检查 images 与 labels 是否一一对应(按文件名去扩展名) import os, glob img_train = {os.path.splitext(os.path.basename(p))[0] for p in glob.glob("datasets/welding_defect/images/train/*.jpg")} lab_train = {os.path.splitext(os.path.basename(p))[0] for p in glob.glob("datasets/welding_defect/labels/train/*.txt")} print("缺少标注的图片:", sorted(img_train - lab_train)[:10]) print("缺少图片的标注:", sorted(lab_train - img_train)[:10]) print("集合一致:", img_train == lab_train)这段脚本把文件名里的扩展名去掉后做集合差集,逻辑简单但非常有效。对查出来的不配对文件,我一般不会直接删除,而是先移动到一个名字带 quarantine 的目录里留证据——尤其当数据集是别人标注好传给产线使用时,直接删文件容易引发「数据被动了手脚」的纠纷,移动归档是最稳妥的。另外要注意重名问题:如果 images 目录里同时存在001.jpg和001.JPG,在 Windows 上解压会互相覆盖,在 Linux 上则会同时存在,而标注文件只有一个001.txt,这类情况差集查不出来,要靠find . -iname "*.jpg"配合小写化再比对才能暴露。所以脚本里的集合推导式最好统一转成小写再做差集,一步到位。
4. 焊接缺陷数据集训练中的 4 个常见问题排查:从类别错位到小目标漏检
数据体检全过,不代表训练就一定顺利。焊接缺陷这种小目标加类别不平衡的组合,跑 YOLOv5 训练自己的数据集时踩过的坑,往往不在网络结构上,而在数据细节里。我按现象、原因、解决的顺序写下四条最常遇到的排查记录,每一条都是真实项目中会浪费一两天时间的类型。
4.1 类别输出集体错位:names 顺序与标签 ID 对不上
现象:训练过程没有任何报错,loss 正常下降,但用训练好的权重做推理时,预测框的类别名和实际缺陷对不上——明明标的是裂纹,输出却显示气孔,而且所有类别整体错位。
原因:标注工具导出 txt 时按它自己内部的类别表排序,和数据集的 yaml names 顺序不一致。YOLOv5 的标签只存整数 ID,这个 ID 的含义完全由 yaml 的 names 顺序决定,标注工具和训练配置各说各话时,模型就把「第 5 个位置的类别」学成了另一个东西。
解决:任何数据集到手,先跑 2.3 里的类别 ID 统计脚本,把实际出现的 ID 集合和 names 列表逐位对比。发现错位后,写一个重映射脚本,把 txt 第一列的旧 ID 按映射表改成新 ID,改完再重新统计验证一次。这里的关键教训是:类别 ID 错位不会产生任何训练异常,它是最难从报错里发现、但最容易通过统计暴露的问题。
4.2 验证 mAP 虚高但现场漏检:小目标缺陷被评估指标稀释
现象:验证集 mAP@0.5 到 0.95 都很漂亮,部署到产线后细小裂纹和咬边大量漏检,现场质检员完全不认可模型。
原因:mAP 是所有类别、所有尺寸检测框的平均。焊接缺陷数据里气孔、夹渣这类框数量多、尺寸相对大、形态规整,它们把整体 mAP 拉得很高;裂纹只有几十个框,又多数是细长条,AP 再差也只对平均分造成很小的下拉。再加上验证集和现场工况存在光照、焊缝角度、钢材表面的分布偏移,模型在验证集上的表现天然偏乐观。
解决:不看整体 mAP,只看 val.py 输出的 per-class AP 表格,把按类别排名的最后三名当成短板项处理。另外把验证集按目标尺寸分成小目标、中目标、大目标三组,单独看 AP_S。焊接缺陷这类任务里 AP_S 才是真正的 KPI。如果 AP_S 低,优先把训练时的 img_size 从 640 提到 1280,并在推理时配合切片或高分辨率输入,而不是堆模型复杂度。
4.3 训练 loss 一直振荡:空 txt 标注混入训练集
现象:训练曲线里 box loss 和 obj loss 从头到尾高频抖动,不随 epoch 收敛,验证集的 mAP 也忽高忽低。
原因:labels 目录里混入了空 txt,也就是 0 字节文件。YOLOv5 遇到空标注时,把这张图作为「背景图」送入训练,不计算边界框损失,但物体损失会把所有预测都往「没有目标」的方向推。当这些空标注图和正常缺陷图交替出现时,模型相当于在同一个 batch 里被要求「这里必须检出缺陷」和「这里不许检出任何东西」,梯度方向互相打架。
解决:训练前用 3.2 的脚本把空文件全查出来。对空文件有两种处理:如果这个图确实没有缺陷,那就把它移出训练集,别让背景样本占比过高;如果图里明显有缺陷但没标,那是标注事故,需要补标而不是删除。我自己通常在 labels 目录建立时就用脚本拦截:任何新导入的 txt 必须是「非空且字段合法」才允许进入训练目录,而不是等训练跑崩了再回头查。
4.4 提高分辨率后小缺陷反而更差:标签体检阈值与训练尺寸脱节
现象:640 分辨率下训练正常,把 img_size 提到 1280 后,小目标缺陷的 AP 反而下降,甚至出现大量漏检。
原因:数据体检阶段用 640 作为退化小框的判定基准,把所有w * 640 < 1的框当脏数据清掉了。这些框在 640 下确实不到 1 像素,属于无法学习的退化样本;但在 1280 下它们放大了 2 倍,已经是可以被模型捕捉的 2 像素目标。反过来还有一种情况:标签标注本身基于低分辨率图像,边界框像素坐标取整误差在放大后变成了明显的框偏,模型学到的框位置始终有偏移。
解决:体检脚本的阈值必须和训练计划强绑定。先定 img_size,再定退化框判定标准,两个参数不要分开拍脑袋。如果计划在 640 和 1280 两档分辨率下分别训练,那就分别做两版体检,而不是一版结果包打天下。另外训练结束后用验证集里的小目标子集看一眼实际预测框和标注框的重合度,比盯着 mAP 数字靠谱得多。
5. 验证集不只是测 mAP:用每类 AP 与阈值扫描把焊接缺陷漏检找出来
训练完成后,验证集的价值远不止输出一个 mAP。焊接缺陷检测的落地瓶颈通常在后处理参数和评估粒度上,把这两件事做细,比再多调几轮超参数都有效。
5.1 用 val.py 的每类 AP 排名定位短板类别
YOLOv5 的 val.py 跑完会打印每个类别的 AP,先按类别排名找出最差的两三类。
python val.py \ --data welding_defect.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640--img要和训练时的设置一致,否则 yaml 里的 anchors 和感受野匹配关系会变化。重点关注 per-class AP 里最低的类别。焊接缺陷数据集里,裂纹、未融合这两类常年垫底,原因不外乎样本少、形态细长、分布随机。如果这个类别在验证集里的 AP 明显低于其它类,下一步不是盲目加训练 epoch,而是回到第 3 章检查该类在训练集里的框数和图数是否足够,再决定做增强还是补数据。
5.2 用 conf_thres 扫描找漏检与误检的平衡点
YOLOv5 后处理里的 conf_thres 是决定漏检率的直接旋钮,但很多人一直用默认的 0.25 不变。焊接缺陷场景里,漏检的代价通常远大于误检,阈值往低调往往比换模型收益更大。我一般会在验证集上做一轮阈值扫描,观察 precision 和 recall 的交叉点:
# 从 0.05 到 0.5 扫描置信度阈值,观察每类 recall 变化 for t in 0.05 0.1 0.15 0.2 0.25 0.3 0.4 0.5; do python val.py \ --data welding_defect.yaml \ --weights runs/train/exp/weights/best.pt \ --conf-thres $t --img 640 done--conf-thres越低,保留的候选框越多,recall 上升、precision 下降;产线场景建议把阈值选在 recall 开始饱和的位置,比如 0.1 左右,然后配合 NMS 的 IoU 阈值控制最终框数量。如果某个类别专门做漏检分析,还可以再写脚本把验证集里只包含该类别的图片挑出来单独跑 val,这样输出的 AP 更接近该类别在真实工况下的表现。
这类小目标缺陷项目的复盘,我最大的教训是:别把 val 的 mAP 当验收标准。先体检标签、再按类看 AP、最后扫一遍阈值,这个流程比调一个晚上的 backbone 结构收益大得多。如果这份焊接缺陷数据集在你这儿跑起来,建议把检查脚本都留在数据集目录里,下一份数据集来了还能直接复用,省下的时间够你再跑三轮实验。希望帮到你。
本文还有配套的精品资源,点击获取