简介面向焊接质检与工业视觉应用的目标检测数据集按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, encodingutf-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.yaml10 类的 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, recursiveTrue): with open(txt, r, encodingutf-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(fdatasets/welding_defect/labels/{split}/*.txt): with open(txt, encodingutf-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, recursiveTrue): lines [ln.strip() for ln in open(txt, encodingutf-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 虚高但现场漏检小目标缺陷被评估指标稀释现象验证集 mAP0.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 结构收益大得多。如果这份焊接缺陷数据集在你这儿跑起来建议把检查脚本都留在数据集目录里下一份数据集来了还能直接复用省下的时间够你再跑三轮实验。希望帮到你。本文还有配套的精品资源点击获取