
简介一份面向深度学习语义分割任务的焊接图像数据集专为焊接质量视觉检测场景设计包含超过六千张经预处理的原图与对应标签覆盖良好焊缝、夹渣、飞溅等典型类别适合研究人员与工程师训练分割模型。压缩包内共二千个文件以PNG和JPG图片为主并附有类别定义文本与数据处理脚本整体大小约四百一十八兆。数据已进行对比度拉伸、尺寸调整等增广处理可提升模型在不同光照和尺度下的泛化能力标签与类别清单组织清晰便于直接接入常见的语义分割网络。其中包含典型焊接缺陷样本对工业场景中的自动质量检测具有直接帮助目前已有三百六十八人学习对需要构建焊接缺陷自动识别方案或开展工业视觉实验的学习者是一份完整、可直接使用的数据资源。1. 焊接图像语义分割数据集先弄清楚这 6k 张图能干什么焊接图像语义分割数据集超过 6k 张数据和标签是专门给焊缝、焊道和焊接缺陷做像素级分类的深度学习数据集。它不输出目标框而是让语义分割算法把图像里的每一个像素归到背景、焊道、缺陷等类别所以用 YOLO 做目标检测的习惯到这里要先停一下。对做焊接质检、机器人视觉和缺陷检测的工程师来说这批数据的直接价值是省掉从零标注的几个月时间但拿到手的关键不是立刻训练而是先核对标签约定和类别分布否则 6k 张图也会因为标签不统一变成负资产。2. 焊接图像语义分割数据集的构成与标签约定拿到手先做三件事不管是自己采集的还是别人交付的焊接图像语义分割数据集的第一关不是模型而是弄清目录和标签编码。我一般会在解压之后先做三件事看目录结构、统计 mask 里的类别像素、筛掉明显漏标的图。这三件事做完后面训练才不会翻车。2.1 目录与标注格式先确认是灰度 PNG 还是调色板 PNG常见的数据集目录会分成 images 和 labels 两个文件夹外加 train.txt、val.txt 或 class_names.txt。焊接图像的原始图可能是工业相机拍的黑白灰度图也可能是伪彩色图标签图通常用 PNG 保存每个像素值代表一个类别比如 0 是背景、1 是焊道、2 是气孔、3 是裂纹。类别的具体含义一定要以 class_names.txt 或者标注文档为准不能靠猜。拿到数据后先用一条命令看两级目录find . -maxdepth 2 -type d find images -type f | head -5 find labels -type f | head -5第一段命令看数据集的顶层结构第二段和第三段分别确认图像和标签的文件名是否一一对应。这里最容易出现的问题是文件名对不上有的数据集图像是 jpg、标签是 png扩展名不同但主名相同有的则带 _mask 后缀。如果主名对不上后面写 Dataset 时会漏掉一批样本而且报错很隐蔽。确定文件结构之后再用 Python 检查标签图的 modefrom PIL import Image from pathlib import Path label_dir Path(labels) for p in sorted(label_dir.glob(*.png))[:10]: img Image.open(p) print(p.name, img.mode, img.size)打印结果里如果是 L说明是单通道灰度图可以直接作为类别 id 读取如果是 P说明是调色板图读取时要按调色板索引而不是 RGB 值如果是 RGB那就要警惕很多标注软件导出时会顺手把标签存成彩色 PNG直接np.array()读进来会得到三维数组训练时大概率直接崩。注意mask 的存储格式直接影响训练代码先花十分钟把 mode 检查完比训练时到处排查维度错误划算得多。2.2 统计灰度与类别分布用一段脚本看清标签焊接图像里背景往往占了大半张图真正属于焊道和缺陷的像素非常少。如果不先做类别统计训练时 loss 很容易被背景主导。下面这段脚本可以统计整个数据集的类别像素数和占比from PIL import Image import numpy as np from pathlib import Path label_dir Path(labels) class_counts {} for p in label_dir.glob(*.png): img Image.open(p) if img.mode not in (L, P): print(f{p.name} 不是灰度/调色板标签跳过) continue mask np.array(img) values, counts np.unique(mask, return_countsTrue) for v, c in zip(values, counts): class_counts[int(v)] class_counts.get(int(v), 0) int(c) for cls_id, total in sorted(class_counts.items()): print(fclass {cls_id}: {total} pixels)这段脚本的关键是np.unique(mask)如果标签是 P 模式np.array(img)拿到的是调色板索引恰好对应类别 id如果是 L 模式拿到的就是灰度值。统计完成后重点看两类值一是有没有 255二是缺陷类的像素占比是不是低到离谱。255 在很多语义分割任务里代表 ignore 区域也就是不参与 loss 计算的像素。如果焊接图像里有过曝、反光或者工件之外的区域被标成 255训练时要在损失函数里设置ignore_index255否则模型会强行学这些不可见区域。缺陷类像素占比如果不到 1%不要急着换模型先把类别权重或者 Dice loss 加上否则前几十个 epoch 模型可能一直输出全背景。2.3 按掩膜质量筛图哪些图该留、哪些图该删6k 张带标签的图不是每一张都值得送进训练集。焊接图像经常出现整张图都是背景的情况可能是采集到了焊前或焊后的间隙也可能是标注员漏标了。漏标图放进训练集相当于给模型喂了错误标签比少一张图危害更大。写一个简单的漏标检查脚本from PIL import Image import numpy as np from pathlib import Path label_dir Path(labels) empty_samples [] for p in label_dir.glob(*.png): img Image.open(p) if img.mode in (L, P): mask np.array(img) if len(np.unique(mask)) 1: empty_samples.append(p.name) print(f空标签数量: {len(empty_samples)}) for name in empty_samples[:20]: print(name)如果一个样本的 mask 里只有一个像素值说明它要么是全背景要么是整张图都被标成了同一个类别。全背景图是否要删除取决于业务定义如果将来在线检测时会遇到大量没有焊缝的帧保留一些全背景图有利于降低误检但如果这些图只是采集时机不对建议直接剔除。除了空标签还要看边缘质量。把 mask 和原图叠加显示如果发现标注边缘粗糙得像刷子画出来的训练出的模型边缘也会同样粗糙。对这种图我更倾向于在标注工具里用多边形修一遍或者直接把标注质量差的样本从训练集里隔离出来等后期做半自动标注时再处理。3. 用语义分割模型训练焊接图像从数据加载到收敛标签和目录确认之后才算真正进入训练阶段。焊接图像和自然场景图不一样焊道边缘细、缺陷小、背景纹理单一所以模型选择和数据加载都要针对这个特点来调。3.1 选模型U-Net 还是 DeepLabV3以及焊接图像为什么不吃大 backbone焊接图像语义分割的难点不是区分类别而是不要漏掉小缺陷。常见的语义分割算法里U-Net 结构对小目标更友好因为它的跳跃连接能把浅层边缘信息送到深层DeepLabV3 的优势是感受野大适合轮廓相对规则的场景。如果数据量只有 6k 张我一般会先用 U-Net 或 DeepLabV3 的轻量骨干跑一版而不是直接上很大的 backbone。原因很简单焊接图像的背景结构简单焊道区域在图像里通常是连续条带不需要特别深的网络去理解复杂语义。大 backbone 带来的提升往往被背景像素占比拉平反而把训练时间拖长。选择模型时还要考虑部署环境如果是工业相机旁边的工控机推理速度和显存占用比 mIoU 上零点几个点更重要。3.2 一个能直接改的 Dataset 类crop、resize 和 mask 插值训练代码的第一块硬骨头是数据加载。这里必须记住一个原则图像可以用线性插值mask 一定不能用线性插值。mask 里的 1、2、3 是类别编号线性插值会产生 1.7 这种根本不存在的类别。下面这个 Dataset 类是一个可以直接改的模板import cv2 import numpy as np import torch from torch.utils.data import Dataset from pathlib import Path class WeldSegDataset(Dataset): def __init__(self, image_dir, label_dir, samples, crop_size512, augFalse): self.image_dir Path(image_dir) self.label_dir Path(label_dir) self.samples samples self.crop_size crop_size self.aug aug def __len__(self): return len(self.samples) def __getitem__(self, idx): sample_id self.samples[idx] image cv2.imread(str(self.image_dir / f{sample_id}.jpg)) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) mask cv2.imread(str(self.label_dir / f{sample_id}.png), cv2.IMREAD_GRAYSCALE) if image.shape[0] self.crop_size or image.shape[1] self.crop_size: image cv2.resize(image, (self.crop_size, self.crop_size), interpolationcv2.INTER_LINEAR) mask cv2.resize(mask, (self.crop_size, self.crop_size), interpolationcv2.INTER_NEAREST) else: h, w image.shape[:2] top np.random.randint(0, h - self.crop_size 1) left np.random.randint(0, w - self.crop_size 1) image image[top:topself.crop_size, left:leftself.crop_size] mask mask[top:topself.crop_size, left:leftself.crop_size] if self.aug: if np.random.rand() 0.5: image image[:, ::-1].copy() mask mask[:, ::-1].copy() image image * (0.9 0.2 * np.random.rand()) image torch.from_numpy(image.transpose(2, 0, 1)).float() / 255.0 mask torch.from_numpy(mask).long() return image, mask这里 crop_size 我一般取 512焊接缺陷小切太大反而让背景占比更高切太小又会把焊道截断。随机水平翻转对大部分焊接件是安全的但我不会做随机旋转和弹性形变因为焊缝是刚性几何结构过度扭曲会让模型学到错误的形态特征。亮度扰动用 0.9 到 1.1 的系数模拟工业现场光照波动太强的亮度扰动会掩盖反光缺陷。3.3 损失函数与训练参数交叉熵、Dice 和缺陷类别 IoU焊接语义分割的类别不均衡是必然的。如果不处理模型会学成“背景预测器”。常见做法是给类别加权或者把 Dice loss 和交叉熵混合。类别权重的计算可以直接用上一章的统计结果class_counts np.array([1000000, 50000, 3000, 2000]) frequencies class_counts / class_counts.sum() class_weight torch.tensor(1.0 / np.sqrt(frequencies), dtypetorch.float) print(class_weight)这里1 / sqrt(freq)是防止权重差距过于极端。如果直接用1 / freq背景类的权重可能只有 0.001小缺陷类权重却超过几百训练初期梯度会抖动得很厉害。加上 sqrt 之后背景权重变小、缺陷权重变大但不会把训练变成缺陷类单独拟合。训练时可以这样组合损失import torch.nn.functional as F ce F.cross_entropy(logits, mask, weightclass_weight, ignore_index255) dice soft_dice_loss(logits, mask, num_classes4) loss 0.7 * ce 0.3 * dice交叉熵负责让每个像素分类尽可能准确Dice loss 负责拉高前景和缺陷的重合度。系数 0.7/0.3 是我常用的起点如果缺陷类 IoU 一直不动把 Dice 的系数加到 0.4 或 0.5。优化器用 AdamW初始学习率 1e-4batch size 8训练 80 个 epoch。前 30 个 epoch 只看 loss 下降趋势后 50 个 epoch 要盯着缺陷类别的 IoU不要只盯整体 mIoU。4. 焊接图像语义分割的 5 个典型坑标注、类不平衡与模型失效即使模型能收敛焊接图像数据集的坑也远没结束。下面这 5 个问题是我在类似项目里反复踩过的每条按现象、原因、解决三个部分说清楚。4.1 焊道反光造成的标签漂移现象训练 loss 正常下降验证 mIoU 也不错但一到新现场就出问题模型把焊道边缘的高亮反光区域预测成了气孔。原因反光区域在灰度图上表现为高亮的圆形或条状斑块和真实气孔在视觉上非常像。如果标注人员只是按亮度圈缺陷反光就会被标进缺陷类模型实际学到的是“亮度异常缺陷”而不是“形态异常缺陷”。解决先把标签里所有反光区域重新检查一遍把误标为缺陷的反光擦掉归到背景或单独的反光类。训练时不要做强直方图均衡因为均衡化会进一步放大反光和暗区对比让误判更严重。如果现场反光无法避免可以在数据增强里加随机高光模拟但前提是标签本身已经干净。4.2 类别像素极度不平衡从 loss 曲线看不出类别崩溃现象loss 从 1.2 降到 0.3训练看起来很成功但单独看缺陷类 IoU 是 0模型把所有像素都预测成背景。原因背景像素占比过高时交叉熵 loss 会被背景项主导。即使缺陷类 100% 分错背景类 95% 以上分对loss 依然在下降。解决先打印每个类别的混淆矩阵不要只看整体 loss。然后在损失函数里加上类权重或 Dice loss。我的习惯是最低类别占比低于 1% 时强制把该类的 loss 权重调到其它类的 5 到 10 倍同时用ignore_index255排除无效区域。4.3 边缘标注过粗导致 mIoU 虚低现象训练到 50 个 epoch 后验证 mIoU 在 0.85 附近卡住怎么调学习率都上不去。原因焊接掩膜的边缘如果标注得很粗模型学到的边缘和真实边缘之间永远差几个像素。评估阶段 ground truth 边缘也是错的所以模型预测越接近真实边缘和错误标签的 IoU 反而越低。解决先放大看标签边缘确认是不是标注问题。如果是用形态学腐蚀把 mask 边缘向内收缩 1 到 2 个像素让标签更贴真实边界。更彻底的方案是在标注工具里把粗画笔改成多边形精修或者用模型预测结果做边缘微调再人工确认。4.4 验证集划分不当同一工件出现在训练和验证现象验证 mIoU 高达 0.95但换到新的焊接件上效果骤降。原因焊接图像经常是按视频帧采集的同一道焊缝的相邻帧几乎一模一样。如果随机切分 train/val同一个工件会同时出现在两边模型等于提前见过验证集。解决按工件 ID 或采集时间分组切分。比如文件名是part001_frame010.jpg就以part001作为分组特征保证同一个工件的所有帧只进训练集或只进验证集。分组切分的代码在下一章给出。4.5 误把 RGB 图当作灰度标注现象训练脚本没报错但 loss 涨到几千或输出维度对不上检查时发现 mask 长成了(H, W, 3)。原因标签导出时用了彩色 PNG0、1、2 被存成 RGB 三元组而不是单通道类别值。解决加载 mask 前先打印mode。如果是 RGB优先回到标注工具重新导出如果实在没办法只能写映射表把 RGB 颜色还原成类别 id但整个过程非常痛苦。所以拿到数据集的第一件事永远是用上一章的脚本检查 mask 的 mode这个习惯能省下一整天的排查时间。5. 让 6k 数据发挥更大价值的三个方向分组划分、后处理与半自动标注如果前面的热身都做完了模型也已经能跑通接下来才是把 6k 张图真正变成可用资产的部分。数据质量比数量重要但好的方法可以让质量一般的 6k 张图翻倍。5.1 按工件分组划分 train/val/test别让同一道焊缝两边都出现焊接视频帧之间存在极强的时序相关性相邻两帧可能只差一个采集角度。随机切分会让验证集失去意义。用 GroupShuffleSplit 可以按组切分from sklearn.model_selection import GroupShuffleSplit samples [part001_frame001.jpg, part001_frame002.jpg, part002_frame001.jpg] groups [name.split(_frame)[0] for name in samples] gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(samples, groupsgroups)) train_samples [samples[i] for i in train_idx] val_samples [samples[i] for i in val_idx]这里groups是每个样本所属的工件编号。如果没有现成的工件 ID可以按文件名前缀或采集时间窗口来构造。test_size 我一般取 0.2但焊接数据里如果某个工件的缺陷形态特别特殊把它整组放进测试集就会导致测试指标异常所以最好再单独留一个包含典型缺陷的固定测试集。5.2 后处理连通域过滤比直接调阈值更稳模型输出的是每个像素的类别概率直接取 argmax 会得到很多孤立噪声点。尤其是气孔这类小目标模型经常在焊道中间吐出一连串零散小点。常见做法是加连通域过滤import cv2 import numpy as np def remove_small_components(pred_mask, min_area30): num_labels, labels, stats, _ cv2.connectedComponentsWithStats(pred_mask, 8) clean np.zeros_like(pred_mask) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] min_area: clean[labels i] 1 return cleanmin_area是像素面积阈值需要根据训练时的输入尺寸调整。如果模型输入是 512x512气孔在图上可能只有 10 到 30 个像素min_area 建议不超过 20如果是检测焊瘤或大块飞溅阈值可以放到 100 以上。这个后处理最大的坑是误删小缺陷所以上线前一定要可视化检查被过滤掉的区域。CRF 后处理也能让边界更干净但推理速度会明显变慢。工业现场如果对单帧延迟有要求我一般不用 CRF而是用连通域过滤加边缘平滑。实际效果和 CRF 差距不大速度却快得多。5.3 半自动标注先用干净数据训练再反推质量差的标签6k 张图里如果只有 4k 张标注得干净剩下 2k 张边缘很糙不要直接删掉。更高效的做法是先用 4k 干净图训练一个初版模型再对剩余图像做预测让标注员只修标签而不是从零画。挑选哪些图值得修可以用预测熵来决定。熵越高说明模型对这个区域越不确定越可能是标注本身有问题import torch def uncertainty_score(model, images): logits model(images) prob torch.softmax(logits, dim1) entropy -(prob * torch.log(prob 1e-6)).sum(dim1) return entropy.mean(dim(1, 2)).cpu().numpy()把每张图的平均熵从高到低排序优先修熵最高的一批。这个流程本质上是把人工标注资源用到模型最不确定的地方。对焊接缺陷这类小目标模型不确定的区域往往集中在缺陷边界和反光区域正好是标注最容易出错的位置。6. 上线前的验证与模型迭代用 6k 图换一个可用模型训练完成不等于可以上线。焊接图像分割模型落到现场之前我会先用一个“现场视角”的验证集做检查这个集和 train/val 的划分方式完全不同。现场样本应该包含不同光照、不同焊接速度、不同工件批次而不是从原数据集里再抽一部分相似帧。如果现场数据没有标签就临时标记 30 到 50 张只用来做通过性测试。验证时先跑整体指标再看三类细节缺陷有没有漏报、边缘有没有锯齿、反光区域有没有误报。不要只看 mIoU焊接质检更关心漏报率。缺陷漏掉了mIoU 再高也没意义边缘糙一点反而可以通过后处理修。所以我会单独算每个缺陷类别的 recall如果 recall 低于业务要求先把后处理阈值调低再考虑重训。模型迭代时一次只改一个变量。要么调损失系数要么调数据增强要么换 backbone不要同时全改。我自己的习惯是任何一次改动先看缺陷类别的 IoU再看整体 mIoU如果缺陷 IoU 没有变化就回到数据层面检查标签。这个习惯帮我挡掉过很多次看起来 mIoU 很高、实际没法用的模型。希望帮到你。本文还有配套的精品资源点击获取