ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

焊接缺陷检测数据集详解:VOC转YOLO格式与增强避坑指南

焊接缺陷检测数据集详解:VOC转YOLO格式与增强避坑指南

简介:面向焊接质检与工业视觉目标检测场景,这套数据集覆盖烧穿、污染、未完全融合、未完全渗透、错位等常见焊接缺陷,并包含正常状态样本,共4137张已增强图片,矩形框标注可供YOLO/VOC系列模型训练和验证。压缩包共2000个文件,以XML标注文件与TXT标签说明为主,整体约153.88MB;解压后按JPEGImages、Annotations、labels三类目录组织,分别存放图片、VOC格式XML标注与YOLO格式TXT标签。数据集标签格式规范,六类合计4214个标注框,且各框数分布相对均衡,便于分类别评估模型表现;图片清晰、已做增强,可支持迁移学习、缺陷检测算法验证或教学实验。目前已有203人学习浏览。需要说明的是,作者特别声明不对训练产生的模型或权重文件精度作保证,使用时建议结合自有数据进一步验证。

1. 焊接缺陷检测数据集:为什么4137张增强图能替代上万张原始图

质检员拿着一支手电、一块放大镜,在焊件上来回扫那条焊缝,看有没有气孔、裂纹、咬边。这套目检流程在工厂里跑了几十年,效果不差,但说到底也靠点经验玄学,人一疲劳,漏检率就上来了。把这事交给目标检测模型,第一前提就是数据:6类焊接缺陷、4137张已增强图、YOLO+VOC双格式,这类焊接缺陷检测数据集的目标,就是让做工业视觉的团队不必从零开始采集和标注,直接拿一份接近产线分布的标注数据把基线打通。它适合三类人:焊缝质检方案工程师、做缺陷检测的算法同学,以及想快速验证YOLO在工业场景能不能落地的从业者。拿到手先别急着跑训练,先把类别定义和坐标格式核对清楚,这篇文章就是把从拆包到验证的整条路径讲透。

2. 6类缺陷与两种标注格式的本质:像素坐标和归一化坐标差在哪

2.1 六类焊接缺陷的形态与检测难点

焊接缺陷数据集的品类划分,行业里基本沿用探伤和目检的习惯,最常见的是这六类:气孔、夹渣、未熔合、未焊透、咬边、裂纹。每一类在图像上的形态差异很大,检测难点也完全不同。

缺陷类别典型形态检测难点
气孔圆形或椭圆暗点,常成群出现尺寸小,单孔可能只有几个像素
夹渣不规则块状,灰度不均边界不清晰,容易和背景反光混在一起
未熔合条状或片状,沿焊缝方向分布边缘模糊,对比度低
未焊透根部线状暗带细长,几乎贴着母材,对比度最差
咬边焊缝边缘的沟槽,细长条状宽度窄,且紧贴焊缝边缘易被忽略
裂纹细线状,方向随机,可能有分叉形态细,标注难度大,增强易断裂

我会在拿到数据集后优先做一件事:把标注里的类别名全部列出来,和这六类对一遍。不同数据集命名习惯不一样,有的用中文标签,有的用porosity、slag这种英文缩写,还有的会用crack代替所有细线缺陷。类别名对不上,后面所有转换脚本都会跟着错。

2.2 VOC格式的XML结构:像素坐标与标注语义

VOC格式指的是Pascal VOC的标注结构,解压后一般会看到JPEGImages、Annotations、ImageSets/Main三个目录。JPEGImages放原图,Annotations放同名XML,ImageSets/Main里则是train.txt、val.txt这些划分文件。

XML里的核心字段是object节点下的name和bndbox。name定义缺陷类别,bndbox给出xmin、ymin、xmax、ymax四个像素坐标。注意,这四个值是绝对像素坐标,基于原图分辨率,不随缩放变化。还有size节点记录图像的宽和高,这个字段在格式转换时是关键分母,很多脚本跑挂了就是因为它丢了或者填了0。

XML字段含义使用时的注意点
size/width原图宽度像素数转换时分母,缺失会导致除零或坐标爆炸
size/height原图高度像素数同上
object/name缺陷类别名必须和转换脚本的类别映射一致
bndbox/xmin等左上右下像素坐标转换后需验证是否落在0-1区间

2.3 YOLO格式的txt文件:归一化坐标与类别索引

YOLO格式的标注是纯文本txt,每行对应一个目标框,格式是:class_id cx cy w h。class_id是从0开始的类别索引,cx、cy是目标中心点的归一化横纵坐标,w、h是归一化框宽高。归一化的含义是除以图像宽高,所以所有值都在0到1之间。

为什么YOLO要归一化而不是直接用像素坐标?核心原因是模型训练时图像会被resize到固定尺寸,比如640x640,归一化坐标在resize前后不需要做任何换算,直接参与训练。而VOC的像素坐标在图像resize后必须同步缩放,否则框就错位。

从VOC到YOLO的映射关系其实就一句话:像素坐标除以图像宽高得到归一化坐标。但这里有个容易被忽略的点:类别名到索引的映射表(classes.txt)一旦确定,训练过程中就不能改,训练集和验证集必须共用同一个顺序。我之前见过有人训练集按字母序排、验证集按数据集自带顺序排,结果mAP直接对不上。

3. 把VOC转成YOLO格式:转换脚本与三个边界坑

3.1 用Python把XML转成YOLO训练的txt

拿到VOC格式的标注后,第一步就是转成YOLO的txt。下面这个脚本是我常用的最小可用版本,逻辑清晰,适合作为起点再按自己数据调整。

import os import xml.etree.ElementTree as ET # 类别顺序一旦确定,训练过程中不要再改动 # 训练集和验证集必须共用同一个映射表 CLASS_MAP = { 'porosity': 0, # 气孔 'slag': 1, # 夹渣 'lack_of_fusion': 2, # 未熔合 'incomplete_penetration': 3, # 未焊透 'undercut': 4, # 咬边 'crack': 5 # 裂纹 } voc_dir = 'Annotations' yolo_dir = 'labels' os.makedirs(yolo_dir, exist_ok=True) def convert_voc_to_yolo(xml_path, image_w, image_h): tree = ET.parse(xml_path) root = tree.getroot() lines = [] for obj in root.findall('object'): name = obj.findtext('name') if name not in CLASS_MAP: # 遇到没定义的类别名先打印出来确认,不要直接跳过 print(f'unknown class in {xml_path}: {name}') continue box = obj.find('bndbox') xmin = float(box.findtext('xmin')) ymin = float(box.findtext('ymin')) xmax = float(box.findtext('xmax')) ymax = float(box.findtext('ymax')) # 像素坐标转归一化坐标:计算中心点和宽高后除以图像尺寸 cx = (xmin + xmax) / 2.0 / image_w cy = (ymin + ymax) / 2.0 / image_h w = (xmax - xmin) / image_w h = (ymax - ymin) / image_h # 裁剪到[0,1],防止标注越界导致训练时框坐标溢出 cx = max(0.0, min(1.0, cx)) cy = max(0.0, min(1.0, cy)) w = max(0.0, min(1.0, w)) h = max(0.0, min(1.0, h)) lines.append(f'{CLASS_MAP[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}') return lines for xml_file in os.listdir(voc_dir): if not xml_file.endswith('.xml'): continue xml_path = os.path.join(voc_dir, xml_file) tree = ET.parse(xml_path) size = tree.getroot().find('size') if size is None or size.findtext('width') in (None, '0'): # 常见做法:从XML读宽高,缺失时用PIL读真实图像兜底 from PIL import Image img_path = os.path.join('JPEGImages', xml_file.replace('.xml', '.jpg')) image_w, image_h = Image.open(img_path).size else: image_w = float(size.findtext('width')) image_h = float(size.findtext('height')) lines = convert_voc_to_yolo(xml_path, image_w, image_h) if lines: out_path = os.path.join(yolo_dir, xml_file.replace('.xml', '.txt')) with open(out_path, 'w') as f: f.write('\n'.join(lines) + '\n')

这段代码的流程很直接:遍历Annotations目录下所有XML,解析出每个object的name和bndbox,按CLASS_MAP映射成索引,再除以图像宽高得到归一化坐标。图像宽高优先从XML的size节点读取,因为标注规范里size就是原图尺寸;但如果size节点缺失或为0,就改用PIL读JPEG图像的实际尺寸兜底。

CLASS_MAP的顺序是整个脚本的灵魂。它决定了每个类别在训练时对应的class_id。如果数据集自带的标签说明里的顺序和这个映射不一样,以数据集实际标注为准,不要先入为主按行业习惯写死。转换完成后建议打印一遍每个类别的框数量,确认没有类别消失。

3.2 边界坑:分母为零、越界坐标、类别错位

第一个坑是分母为零。表现是转换脚本直接抛ZeroDivisionError,或者生成的txt里cx、cy全是inf。原因是XML里的size节点缺失、width字段为0,或者标注工具导出时根本没写分辨率。解决方法是脚本里必须保留PIL兜底分支,用真实图像宽高替代。习惯上我会在转换前先跑一个脚本统计所有XML的width最小值,低于阈值的直接标记出来。

第二个坑是越界坐标。表现是归一化后w或h大于1,或者cx、cy超出[0,1]。多数发生在增强后的数据上——旋转或裁剪时框坐标没有同步裁剪,导致右边界超出图像宽度。脚本里加clip到[0,1]只能防训练报错,但解决不了框实际位置错误的问题。处理办法是clip后重新算一次框面积,如果面积趋近于0,说明这个框已经被增强操作破坏,直接过滤掉比留着更安全。

第三个坑是类别错位。表现是训练能跑通,但验证集上某个类别的AP始终为0,混淆矩阵里预测结果整体偏移一位。最常见原因是训练集和验证集分别用了不同顺序的classes.txt,或者原始数据集的标签名里有隐藏空格。我一般会写一行命令把生成txt里的class_id做一次unique统计,再把类别名顺序和CLASS_MAP对比,花两分钟就能定位问题。可视化抽样画框也是有效的复核手段,这个习惯最好保留到整个数据管线里。

4. 已增强数据怎么用:增强策略、参数与三类校验

4.1 针对性增强:小目标与焊缝细长形态的约束

焊接缺陷和通用目标检测的显著差异在于:缺陷小、形态细长、局部对比度低。气孔可能只占几十个像素,裂纹宽不过两三个像素。这意味着增强策略不能照搬COCO那套通用管线,无脑随机裁剪和大幅旋转很容易把本来就小的缺陷裁掉或拉伸到认不出来。

我一般会先分析三件事:缺陷占图像面积的比例分布、缺陷的长宽比、以及缺陷和背景的灰度差。占比低的样本做随机裁剪时要限制scale下限,长宽比大的缺陷(裂纹、未焊透)不适合做90度旋转,灰度差低的样本不能大幅降饱和。这些都是增强前需要先看清楚的事实,而不是凭感觉调参数。

标题里的4137张已增强图,本质上就是从原始样本通过组合增强扩出来的。常见做法是多路线叠加:水平翻转、小幅旋转、亮度扰动、对比度调整、裁剪、拼接各来一种,最终每个原始样本生成4到8张派生图,总样本量就达到几千的量级。4137这个数字并不规整,说明不同类别的增强倍数可能不一样,这种非均匀增强在长尾分布下是合理的。

4.2 增强参数表:什么组合能凑出几千张

下面这张参数表是我在焊接缺陷数据上反复调过的经验值,适用于离线增强生成训练集,也适用于在训练时叠加在线增强。

增强项经验参数适用原因注意点
水平翻转p=0.5焊缝左右对称,标注无需变换有方向物理含义的缺陷慎用
小幅旋转±10度焊接姿态和拍摄角度差异必须同步旋转标注框
HSV色相偏移H ±10金属反光和氧化色差异色相大幅偏移会让缺陷颜色失真
饱和度偏移S ±30%打光不均导致颜色浓淡变化对比度低的缺陷别再降饱和
亮度偏移V ±40%弧光和遮挡导致明暗变化亮度太高小缺陷会融入背景
高斯模糊半径1-2对焦不准和轻微运动模糊模糊太强小目标直接消失
椒盐噪声p=0.01焊接飞溅和尘点干扰噪声分布要贴近产线实际
随机裁剪scale 0.5-1.0等效增加拍摄视角裁剪后丢失标注框的样本要过滤
Mosaic拼接4张拼接增加上下文多样性拼接时标注框要按偏移量重算

数量估算的规则很简单:最终训练集规模约等于原始样本数乘以每条增强路线的命中概率之和。如果原始样本800张,水平翻转出800张,亮度扰动出800张,旋转出800张,裁剪出800张,Mosaic再出800张,叠加去重后就是4000张上下,和4137这个量级是吻合的。如果发现增强后的总量和预期差很多,优先检查增强路线的执行概率是不是被过滤逻辑误伤了,尤其是裁剪类增强会把大量小框滤掉。

4.3 增强后三类校验:文件、类别分布、可视化

增强做完,第一类校验是文件一一对应。每张增强图必须存在对应的txt或XML标注文件,不能多也不能少。用下面的脚本可以快速核对:

import os images_dir = 'images' labels_dir = 'labels' img_names = { os.path.splitext(f)[0] for f in os.listdir(images_dir) if f.endswith('.jpg') or f.endswith('.png') } label_names = { os.path.splitext(f)[0] for f in os.listdir(labels_dir) if f.endswith('.txt') } missing_labels = img_names - label_names missing_images = label_names - img_names print(f'缺标注的图: {len(missing_labels)}') print(f'缺图像的标注: {len(missing_images)}') for name in list(missing_labels)[:10]: print(f' {name}')

这段脚本的核心是构建两个文件名集合做差集。增强库如果没配对输出图像和标注,这里立刻能看出来。缺标注的图直接删掉或重新生成,缺图像的标注说明管线有严重问题,需要回去检查增强流程。我一般要求差集数量为0才会进入下一步。

第二类校验是类别分布统计。按txt里每行的class_id统计框数量,观察六类缺陷的框数是否均衡。增强如果全用全局随机,少数类(通常是裂纹和未焊透)的绝对数量可能仍然很少,这时就要对少数类做额外加权增强,否则后面训练时这一类AP大概率是0。

第三类校验是可视化抽样。随机抽九张增强图,把标注框画回去,人工确认框有没有跑偏、缺陷有没有被增强操作破坏。

import cv2 import os for img_name in ['sample_001.jpg', 'sample_009.jpg', 'sample_013.jpg']: img = cv2.imread(f'images/{img_name}') h, w = img.shape[:2] label_path = f'labels/{img_name.replace(".jpg", ".txt")}' if not os.path.exists(label_path): print(f'缺少标注: {label_path}') continue with open(label_path) as f: lines = f.readlines() for line in lines: parts = line.strip().split() cls_id = int(parts[0]) x_c, y_c, bw, bh = map(float, parts[1:]) # txt里是归一化坐标,画框前要乘回图像宽高 x1 = int((x_c - bw / 2) * w) y1 = int((y_c - bh / 2) * h) x2 = int((x_c + bw / 2) * w) y2 = int((y_c + bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cls_id), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(f'check_{img_name}', img)

画框输出后重点看两类情况:框是否还紧贴缺陷边缘,以及增强后缺陷是否仍然保持原来的可辨识形态。比如裂纹增强后如果断成几截,模型大概率学到的是碎线段而不是完整裂纹,这类样本应该调低旋转和裁剪强度后重新生成。

这三类校验跑完,数据才算真正可用。在YOLOv8训练自己的数据集时,第一条命令不是调参,而是跑通数据管线。确认txt格式无误后,用最小配置先跑一轮:

yolo train model=yolov8n.pt \ data=welding_6cls.yaml \ imgsz=640 epochs=80 batch=16

这轮训练只看两件事:loss有没有在正常下降,以及有没有报坐标或标签相关的错误。如果第一个epoch loss就是nan,优先回去查标注格式和类别索引,而不是调学习率。数据集本身已经做过离线增强,训练时在线增强建议保持默认强度但别再加额外的Mosaic,否则叠加损伤会让小缺陷目标彻底消失,这是我在实际项目里反复遇到的教训。

5. 避坑手册:焊接缺陷数据集最容易翻车的5个坑

5.1 增强导致标注错位:框跑到背景上

现象是训练时loss下降正常,但推理阶段误检特别多,预测框大量出现在背景区域。原因是增强库在处理旋转或裁剪时只变换了图像,没有同步变换标注框。特别是用OpenCV自己写旋转增强的时候,图像转了90度但标注还指向原来的像素位置,框就错位了。解决方法是统一使用带BboxParams的增强库,让图像和标注走同一条变换链路,并在增强后固定随机种子重跑可视化校验。我习惯把这类校验沉淀成一个固定脚本,每次生成新数据集都跑一遍。

5.2 小缺陷目标被缩放消失:mAP卡在低位

现象是训练了几百个epoch,mAP一直在低位徘徊,验证集上小缺陷几乎全漏。原因多半是增强管线里的随机缩放把原本只有几十像素的小框缩到几个像素,或者随机裁剪把框裁掉后被过滤逻辑直接删掉。解决方法是限制缩放尺度范围,比如scale控制在0.5到1.5之间,同时对长宽比极端的缺陷单独做拼接增强——把缺陷区域抠出来放大后贴到新背景上,而不是整体依赖随机裁剪。过滤被破坏的标注框时,优先做降权处理而不是直接删除,否则少数类样本会越滤越少。

5.3 训练验证划分泄露:验证mAP虚高

现象是验证集mAP比实际测试结果高出一大截,差距超过10个点。原因是用增强后的文件名直接随机划分训练集和验证集,同一原始样本的多个增强派生图被分到了两侧。模型等于在验证时看到了训练图的近亲版本,评估结果自然乐观。解决方法是按原始样本ID划分而不是按文件名划分:先给每个原始样本编号,增强时把派生图继承这个编号,划分时整个编号组要么全进训练集,要么全进验证集。这是焊接缺陷数据最容易踩的大坑之一,我见过不止一个项目在这里翻车。

5.4 类别不平衡被增强完全忽略:个别缺陷AP为零

现象是六类缺陷里某一类的AP始终是0,其他类都正常。原因是用全局统一的增强概率处理长尾分布,少数类得到的新样本仍然很少。比如气孔数量多、增强概率高,裂纹本身数量少、又被过滤掉一部分,最终训练时裂纹的样本量远低于模型需求。解决方法是先按txt统计每类框数量,对数量最少的1到2类单独设置更高的增强倍数,或者在数据集yaml里配合类别权重。实操上我还会在训练完成后单独看每一类的PR曲线,AP为0的类别基本都指向数据问题而不是模型问题。

5.5 评估随机性:同一张图两次结果不一致

现象是同一张图两次推理结果差别很大,验证集的mAP每次跑都有波动。原因是推理阶段开了TTA测试时增强,或者训练代码里的随机增强流程没有固定随机种子,离线增强数据集在训练时又被二次增强,输入分布每次都不同。解决方法是推理时只保留letterbox和归一化预处理,关掉一切随机增强;评估时显式设置augment=False,并固定随机种子保证结果可复现。对于已增强数据集,训练时的在线增强强度建议减半,避免二次变形破坏离线增强已经做好的分布。

6. 验证与进阶:mAP之外,焊接缺陷模型还要看什么

6.1 混淆矩阵与漏检方向

mAP能反映整体水平,但对焊接缺陷,漏检方向比总分数更有价值。YOLOv8训练完会在验证集上直接输出混淆矩阵,我习惯先看每一行:裂纹被预测成什么类、未熔合是不是被当成气孔。如果条状缺陷之间互相混淆,说明增强时要加大形态差异的样本量;如果某一类整体漏检,说明这类样本数量或标注质量还有问题。混淆矩阵比单个mAP数字诚实得多,它能告诉你要回去改数据还是改损失函数。

6.2 多尺度训练与TTA取舍

焊接缺陷尺度跨度大,气孔和裂纹可能相差几十倍。训练时加多尺度能提升小目标表现:

yolo train model=yolov8n.pt \ data=welding_6cls.yaml \ imgsz=640 scale=0.5,1.5 \ epochs=100 batch=16

scale参数让模型在每个batch随机缩放图像,相当于在训练时额外引入尺度多样性。但尺度范围不要超过0.5到1.5,否则小缺陷会缩放过度。验证阶段TTA能多涨一点mAP,但不适合作为线上推理的默认配置,我一般只在验收时开一次,确认模型上限,日常评估保持关闭。导出ONNX后也要核对预处理,训练时的归一化方式如果和部署端不一致,掉点会非常明显。

6.3 边界模糊缺陷的评估:IoU阈值与标签规范

焊接缺陷的边界本来就模糊,尤其是未熔合和裂纹,标注员之间画框差异可以超过10个像素。评估时我会同时看mAP50和mAP50-95两个指标,边界模糊严重的类别mAP50-95会偏低,但mAP50正常,这不一定代表模型差。反而说明需要统一标注规范——框紧贴缺陷可见边界还是包含过渡区域,直接影响训练和评估口径。我现在的习惯是拿到类似数据集先花半天做格式校验和可视化,再进训练,省得后面翻车再回头改数据。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表