
做工业视觉的同行应该都有体会找一份能直接用于目标检测训练的焊缝缺陷检测数据集比调参本身更费神。焊缝缺陷检测项目里数据集的标注质量、缺陷类别覆盖和图像来源直接决定模型上限而偏偏这块的公开资源少、格式乱、坑又深。我最近在做焊接质量在线检测的验证项目把能找到的焊缝缺陷检测数据集按可用性筛了一遍从中挑出一份标注相对规范的数据集做完整实验代号就叫WSD-01。这篇就围绕焊缝缺陷检测数据集的下载、整理、训练以及落地评测讲清楚每一步该怎么做、为什么这样做。想入局工业质检方向的同学不管是做目标检测算法还是准备数据集都可以参考这份流程。1. 焊缝缺陷检测任务到底难在哪1.1 缺陷类别不是“有无异常”这么简单焊接工艺里的缺陷类型各有各的形态目标检测要处理的是一个多类别且形态差异极大的问题。常见的是气孔、夹渣、未熔合、未焊透、裂纹、咬边这六类。气孔在X光图像上表现为封闭性暗斑边缘相对圆滑面积小但特征稳定夹渣形状不规则往往伴随明显的灰度跳变未熔合和未焊透更接近条带状的灰度不连续区域方向通常与焊缝走向一致裂纹是细线状可能只有几个像素宽是六类里视觉上最难识别、工业后果最严重的一类。如果数据集里对这几类没有清晰区分模型训练完连“漏检的是什么缺陷”都说不清现场就无法解释。更麻烦的是同一种缺陷在不同材质、不同焊接工艺下的成像差异非常大。同样是气孔在钛合金焊缝和碳钢焊缝里的对比度、大小、分布规律都不一样。所以只看单一来源的训练数据模型很容易学成“只会认这种焊机、这种材质”的专用模型换个产线就不灵了。这也引出我判断数据集好坏的第一条标准类别定义是否贴近真实标准。有些公开数据集把缺陷笼统分成内部缺陷和表面缺陷两类这种粗粒度对目标检测训练帮助有限做不了精细返修决策。WSD-01把六类分开标注虽然增加了训练难度但至少模型学出来之后能回答“这是什么缺陷”而不是只给一个“有问题”的框。1.2 焊缝图像与通用目标检测数据集的本质差异先说尺寸。COCO数据集里的目标往往占图面积的5%以上船、车、人这种大目标一眼就能看到。焊缝缺陷呢我统计过这份数据集七成以上缺陷框面积占比不到0.5%在1024分辨率下也就是几十像素见方有些裂纹框宽度只有2到3像素比普通检测任务里定义的“小目标”还要小。检测头的下采样倍数哪怕只有32倍一个宽度4像素的缺陷在特征图上的响应可能就剩1个像素甚至消失不见。再说背景。焊缝图像本身有焊纹、飞溅、氧化色和大量随机纹理这些都可能被模型误判成缺陷。X光图像的信噪比还不稳定灰度分布受工件厚度、拍片参数影响很大。相比之下自然图像里的背景类别相对简单天空就是天空路面就是路面模型很容易学到边界。焊缝图像里“正常”和“异常”之间的边界是渐变的同一个灰度区域放在焊缝根部可能是正常的放在熔池边缘就可能意味着未熔合。第三是样本不平衡。气孔和夹渣这类常见缺陷可能各有几千个实例裂纹可能只有一两百个。模型天然倾向于学多样本类别这种长尾分布如果不处理最终结果就是大类别检测得很好、关键小类别几乎全漏。这三条叠加在一起直接用通用目标检测的默认做法很容易翻车这也是为什么专门针对焊缝场景整理的数据集比通用数据集更值得重视。1.3 数据集质量直接决定项目上限我得把话放前面在焊缝缺陷检测这种小目标、强背景干扰、类别极不平衡的任务里换模型、调超参只能在小范围改进但数据集中存在漏标、错标、误标那无论怎么调模型都没法学到正确的东西。数据集的整理和清洗在焊缝缺陷检测里不是前期准备工作而是项目主体工作之一。我在验证项目里花在数据检查上的时间前后加起来占了整个项目周期的四成。这听起来很夸张但实际上非常必要。标注框偏移几个像素对自然图像检测可能无所谓对裂缝这种以像素宽度计算的目标来说就是致命的。所以后面讲到的格式转换和可视化检查我建议所有人都认真做一遍千万不要解压完就直接开训练。2. 数据集的筛选逻辑与下载前核查清单2.1 为什么我最终挑中了WSD-01先交代一个现实公开的焊缝缺陷检测数据集数量少标准不一。有些来自高校科研项目开放图像是X光底片扫描件分辨率很高但标注框质量层次不齐有些是工业相机拍的表面缺陷图对内部缺陷完全无能为力还有一部分数据集共享出来纯粹是为了论文指标标注类别只有两三类训练出来根本不满足产线需求。我这次用的WSD-01规模中等约12000张图像、超过3万个标注实例覆盖前面说的六类缺陷。图像以DR数字成像为主分辨率集中在1024到2048有清晰的train/val划分标注采用Pascal VOC格式。选它还有一个重要原因是附带说明文档比较完整数据来源、采集设备、标注规范都写清楚了。这在公开数据集中挺难得的很多数据集根本没有说明文档数据格式只能靠猜标注字段含义搞错了都没地方问。实际筛选过程也受限于可获取的渠道。公开数据集的发布页面有些提供了直链下载有些只给网盘链接还有的需要提交申请后由维护者人工发送。我个人更偏好直链文件便于脚本化下载和断点续传。网盘链接的问题在于大文件经常被限速训练数据动辄几个GB卡在下载环节非常影响节奏。2.2 下载前的五个核查点这里有一张检查清单是我每次下载数据集之前都会过的你可以直接拿来用核查项为什么重要WSD-01的结果下载渠道与授权是否允许非商业研究用途直接决定能不能写进项目报告授权注明研究用途可用标注格式VOC、COCO还是YOLO决定转换工作量Pascal VOC类别体系类别名称是否统一是否有等级需要合并六类定义清晰图像来源X光DR、底片扫描还是可见光表面图以DR数字成像为主划分与配套官方有没有train/val划分是否附带负样本有官方划分负样本另附这几个点里最容易让人心态崩的是“图像来源”和“划分协议”。我见过一个数据集说明文件里写着“全部为X光DR图像”下载解压完发现里面混着三分之一可见光表面照片灰度图、彩色图、带水印的图全都有。还有一批图像的标注框坐标参照系是底片原始分辨率但图像本身已经被统一resize成固定宽度导致画的框全部错位。这些问题在下载页面上通常不会写清楚必须在数据附带的说明文档里逐字确认。2.3 下载与解压时的完整性检查经验下载时一定要做文件完整性校验尤其数据包比较大的时候。我吃过一次亏数据包下载到三分之二的时候网络中断重新下载后解压到一半提示压缩包损坏只能全部重来白白浪费了一个下午。后来我的固定流程是先校验哈希再解压最后把原始压缩包保留在硬盘里不删。sha256sum WSD-01.zip unzip WSD-01.zip -d ./datasets/如果发布页没给哈希值那就解压后再跑一遍全量读取测试用Python把所有图片读一遍确保没有坏图import cv2 import glob bad_files [] for img_path in glob.glob(./datasets/WSD-01/JPEGImages/*.jpg): img cv2.imread(img_path, cv2.IMREAD_UNCHANGED) if img is None: bad_files.append(img_path) print(坏图数量:, len(bad_files))这一步看起来基础但真能救你一次。某个图像文件如果在复制过程中损坏训练程序可能直接崩溃或者更隐蔽——只在某次random seed下才触发异常排查起来非常头疼。顺便说一句不要解压完就删除压缩包训练过程中想重新核对原始标注时有压缩包在能少很多麻烦。3. 拿到手后第一件事目录拆解、格式转换与标注可视化3.1 WSD-01的目录结构和标注文件长什么样WSD-01是典型的Pascal VOC结构解压后的目录大概是这样的WSD-01/ ├── JPEGImages/ │ ├── 000001.jpg │ ├── 000002.jpg │ └── ... ├── Annotations/ │ ├── 000001.xml │ ├── 000002.xml │ └── ... ├── ImageSets/ │ └── Main/ │ ├── train.txt │ └── val.txt └── negative/ └── *.jpg打开一个XML结构大致如下annotation filename000001.jpg/filename size width1024/width height1024/height depth8/depth /size object name气孔/name bndbox xmin128/xmin ymin396/ymin xmax156/xmax ymax424/ymax /bndbox /object /annotation看懂结构之后先不要急着写转换脚本。先做一件事统计类别名出现的所有取值。因为很多数据集的标注文件里类别名称不统一比如“pore”和“gas_pore”同时存在或者“裂纹”和“crack”混用。如果不做归一化直接转格式训练时类别数量会凭空多出好几类模型根本学不过来。3.2 VOC坐标转YOLO格式脚本与陷阱现在主流目标检测训练基本都用YOLO格式的TXT标注转换步骤不复杂核心是把VOC的绝对坐标转成归一化的中心点坐标和宽高并映射到类别编号。我一般直接在Jupyter里跑这个脚本import os import xml.etree.ElementTree as ET CLASSES [气孔, 夹渣, 未熔合, 未焊透, 裂纹, 咬边] def voc_to_yolo(xml_path, out_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 归一化到0~1 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))这里有一个关键点img_w和img_h必须来自图像的实际尺寸而不是以XML里size字段为唯一依据。有些数据集的XML里size字段本身写错了我见过宽度写反、图像已经被resize但size标签没更新的情况。更稳妥的做法是用cv2.imread读图后取shape再传入转换函数。宁可多读一遍图也不要赌标注文件里的size字段可信。类别映射也要反复核对。YOLO的类别编号从0开始CLASSES列表的顺序一旦定下来就不要改训练和推理的配置必须完全一致。我在另一个项目里就栽过训练用的类别顺序是“气孔、裂纹、夹渣”推理脚本里写的是“气孔、夹渣、裂纹”结果模型输出的检测框全部分类错位现场测试报告完全不能用。3.3 可视化检查比mAP更早暴露问题的环节转换完成后不要直接开训先抽几百张图把标注框画上去看一遍。这一步的目标不是看框画得对不对而是看框的质量是不是有框把背景大范围包进去、是不是有目标被漏标、是不是有些类别之间框重复严重。画框代码不复杂OpenCV就能做import cv2 import random colors {} for line in open(classes.txt): cls line.strip() colors[cls] [random.randint(0, 255) for _ in range(3)] img cv2.imread(000001.jpg) for obj in bboxes: # 从标注文件读取 x1, y1, x2, y2 [int(v) for v in obj[box]] label obj[name] cv2.rectangle(img, (x1, y1), (x2, y2), colors[label], 2) cv2.putText(img, label, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[label], 1)要特别留意的几个问题有些框标注的其实是整段焊缝而非单个缺陷。这种框训练时模型会把“正常焊缝”当成目标推理时造成大面积误检。同一位置出现多个不同类别的框叠加说明缺陷被重复标注会产生训练矛盾模型两个类别都学不扎实。裂纹这类细长目标如果标注框是正方形大框背景占比过高检测头很难学准还是白白消耗训练资源。这些问题一般不会在mAP数值上立刻暴露因为平均指标把局部错误稀释掉了但后期现场测试时一定会现原形。可视化这一步我建议全量跑一遍把可疑的框按面积、长宽比等指标筛出来抽查比肉眼一图一图看高效得多。4. 训练环节的三座大山小目标、样本不平衡、增强失控4.1 先把目标尺寸分布摸清楚开始训练前我用一个统计脚本把所有缺陷框的宽度、高度和面积占比跑了一遍import cv2 import glob import xml.etree.ElementTree as ET area_ratios [] widths [] for xml_path in glob.glob(Annotations/*.xml): tree ET.parse(xml_path) root tree.getroot() img_path os.path.join(JPEGImages, root.find(filename).text) img cv2.imread(img_path) if img is None: continue img_h, img_w img.shape[:2] for obj in root.findall(object): box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) box_w xmax - xmin box_h ymax - ymin widths.append(box_w) area_ratios.append(box_w * box_h / (img_w * img_h)) print(面积占比 0.5% 的比例:, round(sum([1 for r in area_ratios if r 0.005]) / len(area_ratios), 2))结果和预期一致面积占比小于0.5%的目标超过了七成很多缺陷框宽度只有几十像素部分裂纹框宽度还不到5个像素。这意味着如果不做任何处理YOLO这类检测器的多尺度特征层对小目标本来就不敏感漏检几乎是必然的。应对方案有两种可以结合使用。一是训练和推理时做图像切片把1024以上的原图切成若干有重叠的patch相当于把小目标放大后再进模型二是训练时用高分辨率输入比如1280甚至1536但会明显增加显存占用和训练时间。我在WSD-01上用同一套模型对比过推理输入从640提高到1280裂纹这类小目标的mAP可以从0.31提高到0.55代价是单帧推理耗时大约增加一倍。工程上如果相机分辨率固定优先考虑切片方案而不是一味拉大输入尺寸。4.2 样本不平衡先调数据再调损失函数类别分布统计出来气孔和夹渣样本占了将近八成裂纹大概只有不到两百张图。如果直接训练模型极大概率把“多数类”学得很好而裂纹几乎全漏。我试过几种做法按实际效果排序第一是过采样对样本量少的类别每次epoch重复这些图像甚至可以把裂纹类图像复制4到8份混入训练集让模型每轮都能看到足够数量的裂纹样本。这种粗暴方法最简单而且在验证集上提升明显。第二是在损失函数上做文章给不同类别配不同的损失权重或者对目标框回归加入长宽比相关的惩罚项让细长缺陷的定位更稳定。这里的代价是训练配置复杂度上升超参又多了一批。第三是合成样本把裂纹实例按比例粘贴到无缺陷背景图上。这类方法在小样本类目上确实能扩充数量但在安全关键场景下要非常谨慎因为粘贴出来的位置和上下文可能不合理模型可能学到“凡是焊纹附近就是裂纹”这种假特征。我在项目中主要用前两种合成样本只用于排除极端过拟合的辅助实验。实际对比结果baseline不加任何处理的mAP0.5是0.84但裂纹AP只有0.43做过采样加类别权重之后整体mAP到0.87裂纹AP提升到0.62。看起来mAP只涨了零点零几但裂纹这个类别的单独提升才是关键因为这才是产线上最要命的缺陷。4.3 数据增强焊缝图像不是自然图像数据增强是为工业场景里最容易翻车的环节。焊缝缺陷图像有自己的特殊性不能照搬自然图像那套方案。水平翻转对大多数类别是合理的但像未熔合这类有方向性的细长缺陷翻转后的语义等于相反方向是否合理取决于缺陷定义的走向。垂直翻转则需要考虑图像本身的采集方向X光成像中工件上方和下方的物理含义通常不同贸然翻转容易把模型搞糊涂。颜色抖动对X光DR图像基本没有意义还会引入根本不存在的灰度分布亮度对比度扰动可以小幅度做但幅度要控制。工业底片的灰度分布本身就不稳定过度增强会迫使模型去适应现实中不会出现的纹理反而降低泛化能力。我的经验是先用保守策略水平翻转、小角度旋转、随机裁剪和轻微仿射跑一个baseline然后根据验证集错误case逐个加增强。不要一开始就把Mosaic、MixUp全开焊缝纹理背景的特殊结构很容易被强增强破坏出现的典型现象是训练loss很漂亮验证集准确率也还行但现场测试误检高得离谱因为模型看到的所有训练样本都已经被增强得“不像焊缝”了。4.4 负样本与背景误检的教训实时产线上误检比漏检更让人头疼。漏检可以靠人工复检兜底误检会导致合格工件被反复报警产线根本跑不起来。如果数据集中没有足够的无缺陷样本作为负样本模型在背景噪声强烈的地方会输出大量假阳性。我在训练过程中期加入约1500张无缺陷图像标注文件为空配合损失函数里对无目标区域进行抑制现场误检率下降了大约七成。负样本不需要特别精细的标注但有两个前提一是要和真实场景同分布最好来自同一台设备在产线上拍摄的不同工件二是加入时机要放在模型已经能从正样本中学到基本特征之后太早加入会让模型只顾着学“什么都不算缺陷”反而丧失对小缺陷的敏感度。5. 评测指标与真实落地之间的落差5.1 拆开看指标mAP高不等于能上线训练完第一版验证集mAP0.5到了0.84看着不错。但拆开逐类看裂纹AP只有0.43气孔AP却有0.91不同类别之间的差异大得吓人。所以看报告不能只看一个均值至少要看这几组数mAP0.5判定宽松适合确认训练流程没跑歪mAP0.75对定位精度要求高更接近产线上“框打歪了就算错”的标准per-class AP必须逐类打印均值会把短板藏起来小目标AP按COCO标准面积小于32×32像素的目标单独统计焊缝缺陷场景必看。这个经验是我被真实场景教育过之后才记住的。有一版模型mAP0.5到了0.90当时觉得已经能交付结果产线试运行一天误报率超过三成几乎没法用。后来复盘才发现问题全出在背景误检和定位精度上而这两个问题在mAP均值里几乎看不出来。5.2 部署环节的推理性能问题模型跑得再准产线节拍不允许也没用。YOLO系列权重在GPU上推理PyTorch原始实现的单帧耗时大约在120毫秒左右显然达不到在线检测的实时要求。后续把模型导出为ONNX再转成FP16精度推理引擎之后单帧耗时降到25毫秒左右基本满足实时帧率。中间踩过的坑不少推理引擎版本和显卡驱动版本不匹配编译时候不报错推理结果随机出错动态shape配置不当batch size一变就会出现“检测框整体偏移”这种极难察觉的Bug。我建议项目的第一次集成先用固定shape把输入尺寸锁定稳定跑通后再考虑动态batch。固定shape还有个好处就是可以利用编译优化做更激进的算子融合推理延迟还能再压一点。5.3 现场环境带来的三个落差卡在实验室数据上永远不知道模型在现场会翻什么车。我总结三个高频问题第一现场工件与训练图像的材质、表面处理不同灰度分布整体偏移模型置信度全面下降。比如训练数据都是打磨后的焊缝现场工件可能带着氧化皮同样的缺陷在图像里对比度差异很大。第二背景中出现训练数据里没有的夹具、支撑件、线缆产生一批假阳性。这类问题在实验室测永远测不出来因为实验室背景太干净了。第三相机拍照角度稍有变化细长缺陷的形状变化明显检测率直线下降。2到3像素宽的裂纹角度偏一点就可能消失在背景纹理里。对策是提前规划现场数据采集在项目预算里就留出现场数据回流和增量标注的环节。模型上线只是一个起点不是终点。6. 这份数据集还能怎么挖掘以及我的实际体会6.1 从检测到分割提升细长缺陷的量化能力焊缝缺陷检测数据集的深度学习价值不只是做框预测。如果标注质量允许把检测结果升级为实例分割会更有价值。尤其裂纹、咬边这类形状不规则缺陷一个包围框能表达的信息很有限——框有多大、缺陷有多宽、边界在哪全都不知道。分割结果能给返修提供精确位置和尺寸参考。大多数公开数据集没有分割标注但可以自己用检测框结合人工辅助标注做小规模分割数据。具体做法不复杂先用训练好的检测模型把缺陷框提出来再用标注工具在框内精细勾勒边界。焊缝缺陷本身数量少、面积小人工勾勒的边际成本比自然图像低很多是一个值得投入的方向。6.2 半监督、合成数据与预训练特征的潜力工业场景的数据永远不够用后续还有两条路值得探索。一是半监督产线上每天都在产生大量未标注图像虽然不能直接送入监督训练但可以用一致性正则或伪标签方式参与训练对大类别平衡很有帮助。二是合成数据针对裂纹这类极度稀缺的类别用模拟焊接缺陷的方式生成更逼真的样本但必须仔细验证合成域与真实域的差异防止模型学到过于理想化的纹理。另外如果在多个不同数据分布上做预训练能让模型对灰度变化、工件材质变化有更强的鲁棒性。不过这个方向需要足够多的算力和数据小团队可以等预训练模型迭代成熟后再迁移不一定自己从零预训练。6.3 个人实操体会整个项目走下来我最大的感受是把焊缝缺陷检测当成一个数据工程问题来处理比当成纯模型问题要有效得多。数据下载、格式转换、可视化检查、统计分析、负样本整理这些环节看着不起眼却决定了后续所有训练和部署工作能不能顺利进行。建议准备入坑的同学从构建一套可复用的数据检查工具链开始把坏图检查、类别统计、框尺寸分布、可视化抽样做成固定脚本每拿到一个新数据集就先跑一遍再决定要不要在这种数据上投入训练时间。用WSD-01跑通全链路流程之后拿到现场数据时才能真正做到不慌。