简介:焊接件表面缺陷数据集面向工业质检与计算机视觉研究人员,主要用于焊缝缺陷检测模型的训练与验证。原始图像经过严格的标签校验与清洗流程,由最初的两千三百张图像修正并保留有效样本,缺陷类别涵盖气孔、裂纹、未熔合、咬边、烧穿、夹渣、未焊透、焊瘤、形状缺陷以及焊缝背景共十种目标,能够支撑目标检测、缺陷分类与定位等常见任务,帮助模型准确辨识焊接质量常见异常。压缩包共包含两千个文件,主体为一千九百九十七张JPG格式图像,另有三个JSON标注文件,整体体积约九百一十七兆字节;标注遵循COCO格式,并针对飞桨平台做了适配,可方便导入常用深度学习框架使用,减少数据格式转换成本。数据集划分了四百五十八张图像作为验证集,其余图像用于训练,便于在独立验证集上评估模型泛化表现,降低过拟合风险。目前已有三百九十七人浏览学习,这份经过修正的高质量标注数据适合作为焊接质检项目的基础训练数据,也可用于教学演示与算法对比实验。
1. 焊接件表面缺陷数据集:解压后能直接开训的带标签缺陷图集
拿到“焊接件表面缺陷数据集”这个zip包,我第一件事不是解压,而是先确认里面的标注格式和类别分布。干过焊接质检项目的人都清楚,这类数据集比模型权重难找得多——工厂里一条焊缝的照片可能就几十张,能直接用的带标签数据更是宝贝。这份资源解压后就是一个相对完整的缺陷检测数据集,覆盖了常见焊接表面缺陷类型,比如气孔、裂纹、未熔合、咬边、飞溅等,每张图都带有对应的标注文件。适合正在做缺陷检测落地验证的算法工程师、刚入门工业视觉的学生,以及需要真实数据来跑通训练流程的开发者。下面从解压开始,按结构、加载、训练、排错一条龙讲完。
2. 数据集解压与目录结构:从zip到能直接读的images和labels
2.1 为什么这类数据集偏爱zip分发
工业数据集用zip打包是最常见的分发方式。原因很实际:焊接件缺陷图片通常由工业相机拍成,一张原始图可能几MB,几百上千张合在一起体积不小;zip压缩可以把同类纹理的图片体积压掉一大截,同时保留目录结构,便于在解压后直接按images/labels这样的约定去加载。相比tar.gz,zip在Windows上双击就能看内容,对非算法背景的现场工程师更友好。相比rar,zip不需要装额外软件,Python标准库自带zipfile也能处理。
这份资源解压后,常见布局是:
welding_defects/ ├── images/ │ ├── train/ │ │ ├── weld_0001.jpg │ │ └── ... │ └── val/ ├── labels/ │ ├── train/ │ │ ├── weld_0001.txt │ │ └── ... │ └── val/ ├── classes.txt └── data.yaml(如果作者没放,自己生成也不难)我一般会先看有没有classes.txt或data.yaml。有它,标注类别顺序就定死了;没有的话,只能从txt标注文件里的类别ID反推,那就麻烦一点。classes.txt里每行一个类别名,顺序就是标签文件里第一列数字对应的类别。这份数据集如果写的是crack、porosity这类英文名,那训练时类别ID务必与之一致。
2.2 用Python做安全解压:处理路径穿越和文件名乱码
解压看起来简单,但直接双击“全部解压”有两个隐患:一个是路径穿越,某些压缩包里的文件名带../,解压时会写到你电脑的其他目录;另一个是中文文件名编码问题,后面避坑章会单独讲。我习惯把解压写成一个可复用的脚本,检查安全再解压。
import zipfile from pathlib import Path src_zip = Path("welding_defects.zip") out_dir = Path("./welding_defects_extracted").resolve() out_dir.mkdir(exist_ok=True) with zipfile.ZipFile(src_zip) as zf: for info in zf.infolist(): target = (out_dir / info.filename).resolve() # 防止路径穿越:解压目标必须位于输出目录内 if not str(target).startswith(str(out_dir)): raise RuntimeError(f"非法解压路径: {info.filename}") zf.extractall(out_dir)逻辑说明:infolist()返回压缩包内所有文件条目,每个条目包含文件名和元信息。先用resolve()把目标路径解析为绝对路径,检查它是否落在输出目录内,防止../跳出目录。这一步在数据集来源不可控时尤其重要,我所有从网上下到的zip都会过一遍。
参数说明:resolve()会解析符号链接和..,Windows和Linux行为一致;startswith(str(out_dir))比较的是路径字符串前缀,所以out_dir本身也要先resolve,避免相对路径和绝对路径混比。如果压缩包里有中文文件名且解压后乱码,别急着怪数据集,先看后面的避坑章节。
2.3 标注格式快速体检:先判断是YOLO还是COCO再动手
很多下过数据集的人都经历过“解压完发现标注是COCO json,但训练脚本按YOLO txt读”的翻车。所以第1章说的“先确认格式”就是这个意思。这份资源里最常见的是YOLO txt格式,每行五个数字:
class_id cx cy w h坐标都是相对于图片宽高的归一化值,范围在0到1之间。我写了一个小脚本,用来扫描所有标签文件,统计类别数量并检查坐标是否合法:
from pathlib import Path from collections import Counter def inspect_labels(label_dir: Path, num_classes: int): cls_counter = Counter() invalid = [] for txt_path in label_dir.rglob("*.txt"): if txt_path.name == "classes.txt": continue with open(txt_path, encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: invalid.append((txt_path, "列数不对", line)) continue cls_id, cx, cy, w, h = map(float, parts) if not (0 <= cx <= 1 and 0 <= cy <= 1 and w >= 0 and h >= 0): invalid.append((txt_path, "坐标越界", line)) if cls_id >= num_classes: invalid.append((txt_path, "类别ID超范围", line)) cls_counter[cls_id] += 1 return cls_counter, invalid逻辑说明:rglob("*.txt")递归找出所有标注文件,逐行解析。坐标越界的判断用0 <= cx <= 1这样的区间条件,类别ID必须小于类别总数。
参数说明:num_classes由classes.txt决定,如果数据集里有5类,就传5。跑完输出类别分布counter,能一眼看出哪些类样本极少——这在焊接缺陷里很常见,比如裂纹往往比飞溅少一个数量级。这里有个值得注意的细节:坐标合法性检查不能只看是否在0到1之间,还要看w和h是否为正数,0宽度的框在训练时会被直接跳过,最后表现为某个类别完全没学习到。
3. 数据加载与训练集划分:把原始图片整理成模型能吃的样本
3.1 图片统一预处理:灰度图、尺寸和路径对齐
焊接件图片里有一部分可能是灰度图,或者尺寸不统一。YOLO训练时其实不强制统一尺寸,ultralytics内部会自动letterbox,但如果你自己写Dataset类,就得先处理这些。
import cv2 from pathlib import Path def load_image(path: Path, target_size: int = 640): img = cv2.imread(str(path), cv2.IMREAD_UNCHANGED) if img is None: raise FileNotFoundError(f"无法读取图片: {path}") # 单通道灰度图转三通道,保证后续网络输入一致 if len(img.shape) == 2: img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) elif img.shape[2] == 4: img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR) h, w = img.shape[:2] # 长边缩放到target_size,短边按比例缩放,后续由网络letterbox补齐 scale = target_size / max(h, w) if scale != 1.0: img = cv2.resize(img, (int(w * scale), int(h * scale))) return img逻辑说明:单通道图和带透明通道的图统一转成三通道BGR,避免训练时通道数不匹配报错。缩放选择长边对齐,保留原始长宽比,这样标签坐标不用改。
参数说明:target_size在训练时通常与imgsz一致,检测模型吃640x640输入,实测480也能跑,但小缺陷召回率会掉。有一点要提醒:如果你自己做了resize,标签txt里的归一化坐标如果是从原图尺寸算的,不需要跟着改;只有图片尺寸变更而标签没同步重新归一化时才会出问题,这在避坑第3条里细说。
3.2 按缺陷类别做分层抽样:避免验证集里缺类
直接随机划分最坑的是某个缺陷类别在验证集里压根没出现,或者只出现了几个框,导致mAP计算失真。焊接缺陷数据集类别天然不平衡,我一般按图片主类别做分层划分。
import random from pathlib import Path from collections import defaultdict def split_by_class(image_dir: Path, label_dir: Path, val_ratio: float = 0.2, seed: int = 42): random.seed(seed) train_images, val_images = [], [] # 按标签文件名找到对应图片,并记录该图片包含哪些类别 samples = {} # img_path -> list of class_ids for txt in label_dir.rglob("*.txt"): if txt.name == "classes.txt": continue img_stem = txt.stem # 常见命名:images/train/x.jpg 与 labels/train/x.txt 同名 candidates = list(image_dir.rglob(f"{img_stem}.*")) if not candidates: continue img_path = candidates[0] cls_ids = [] for line in txt.read_text().strip().splitlines(): cls_ids.append(int(line.split()[0])) samples[img_path] = cls_ids # 按“包含的类别集合”分组,再每组内按比例划分 groups = defaultdict(list) for img_path, cls_ids in samples.items(): groups[tuple(sorted(set(cls_ids)))].append(img_path) for group, items in groups.items(): random.shuffle(items) split = int(len(items) * val_ratio) val_images.extend(items[:split]) train_images.extend(items[split:]) return train_images, val_images逻辑说明:按“同一组类别组合”的图片作为分组单位做抽样,每个组合内部再按比例划分,这样能保证验证集里尽量包含所有类别组合。比纯随机划分稳得多。
参数说明:val_ratio取0.15到0.2比较常见,样本总量小时取0.15更合适;seed固定后结果可复现。注意这个脚本只是返回图片路径列表,划分完后要自己执行shutil.copy或写相对路径索引文件,并不直接改动目录。实际操作中,我还会顺手把所有类别的框数量叠加统计一遍,避免某个类在训练集里只有三个框,这种情况后面靠增强也救不回来。
3.3 生成data.yaml并做完整性校验
YOLO训练前需要一份data.yaml,声明训练集和验证集路径、类别数量、类别名。有些数据集作者会放一份,没有的话就自己生成。
path: ./welding_defects train: images/train val: images/val nc: 5 names: 0: porosity 1: crack 2: lack_of_fusion 3: undercut 4: spatter生成之后一定要做完整性校验,最常见的问题是train/val目录里图片和标注数量对不上。一行命令统计:
ls -1 images/train | wc -l ls -1 labels/train | wc -l find labels/train -name "*.txt" -size 0 -delete # 把空标签清掉,YOLO会把空txt当背景逻辑说明:find加-size 0 -delete会把0字节的空标签删除。焊接件缺陷数据里有少量无缺陷图片是正常的,但没有txt和图片缺失同时出现,就要回头检查是不是解压丢文件了。
参数说明:wc -l在中文文件名带空格时会有偏差,因为ls默认按行输出。稳妥的做法是ls -1 images/train | wc -l强制单列输出。空标签文件建议先备份再删,别直接清。还有一个常见坑:labels目录里混入了classes.txt,find时不会匹配到txt吗?会,所以命令里可以用-name "*.txt" ! -name "classes.txt"排除,避免把类别文件误当空标签删掉。
4. 用YOLOv8训练焊接缺陷检测模型:参数设置与评估
4.1 为什么选YOLOv8:anchor-free对细密裂纹更友好
焊接缺陷检测这个场景,我默认选YOLOv8而不是更早的YOLOv5,原因是v8改成anchor-free之后,对细长裂纹、小气孔这类目标的表现更稳。anchor-based模型在小目标上要预设多个anchor尺寸,遇到标注框长宽比极端的目标会吃亏;anchor-free直接回归中心点和宽高,减少了先验配置的玄学成分。另一个现实原因是部署生态,v8的ONNX导出和TensorRT部署资料多,现场同事遇到问题能找到人问。
4.2 训练脚本与关键超参数
from ultralytics import YOLO model = YOLO("yolov8n.pt") # 从官方预训练权重开始,迁移学习收敛更快 model.train( data="welding_defects/data.yaml", epochs=120, imgsz=640, batch=16, patience=20, mosaic=1.0, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, device=0, workers=4, project="runs/weld_detect", name="yolov8n_640" )逻辑说明:从yolov8n.pt预训练权重开始,利用在COCO上学到的通用纹理特征做迁移学习,工业数据集通常只有几千张,从头训练很容易过拟合。device=0表示用第一块GPU,没有GPU时改成device="cpu",但训练时间会成倍增加。
参数说明:mosaic=1.0开启马赛克增强,对多数场景有益,但如果发现气孔这类小目标AP为0,可以调小到0.5或在最后20个epoch关掉。patience=20表示20个epoch内验证集mAP没提升就早停,工业数据集噪声多,早停阈值设大一点能避免在平台期反复震荡。hsv_h/s/v是颜色增强幅度,焊接件表面金属反光强,饱和度增强调低一点更合适,我一般用hsv_s=0.5左右。
显存不够时优先减batch,而不是减imgsz。焊接缺陷检测的目标往往在100像素以下,imgsz降到480会让小目标更难检。8GB显存跑640x640加batch=16比较紧张,可以batch=8同时把workers降到2,训练时间多花30%,但召回率比降imgsz稳。以下是几个关键参数的参考范围:
| 参数 | 我常用的值 | 说明 |
|---|---|---|
| imgsz | 640 | 低于480会明显损失小目标召回 |
| batch | 16 | 显存不足就减半,别去动学习率 |
| epochs | 120 | 配patience=20早停足够 |
| mosaic | 1.0 | 气孔等小目标多时降到0.5 |
| hsv_v | 0.4 | 金属反光强,亮度扰动尽量小 |
4.3 评估指标怎么读:别只盯着mAP50-95
训练结束后用model.val()或直接看runs/weld_detect/yolov8n_640/目录下的结果文件。
metrics = model.val(data="welding_defects/data.yaml") print(metrics.box.map50) # mAP@0.5 print(metrics.box.map) # mAP@0.5:0.95逻辑说明:map50是IoU阈值0.5时的平均精度,工业缺陷检测更看重这个值,因为现场检测框不需要像素级精准覆盖缺陷,框住就行。map是多个IoU阈值的平均,对框的精准度要求更高。
参数说明:这两个值差距大说明什么?如果map50高但map低,说明模型能框到缺陷但框偏大或偏小,属正常情况;如果两个都低,问题多半在数据本身。除了这两个指标,一定要看混淆矩阵图confusion_matrix.png——焊接件里最怕的是把裂纹认成背景,那在混淆矩阵里会直接体现为crack所在行的false negative偏高。PR曲线PR_curve.png的曲线形状比面积更有信息量,曲线右偏说明模型对正样本的置信度输出保守,需要调低conf阈值。我见过一个案例,map50有0.87但现场实测漏检严重,最后发现是PR曲线右偏严重,模型的输出置信度普遍在0.1以下,跟默认的0.25阈值完全不匹配。
5. 常见避坑:zip解压、标注错位与类别不均衡的五个实战记录
5.1 zip伪加密与损坏压缩包:CRC报错的另类解决路径
现象:解压到某个文件时报“CRC校验失败”或提示输入密码,但数据集作者明明没说加密。
原因:一部分这种情况是zip伪加密——压缩包的文件头里加密标志位被置1,但数据本身并没有真正加密。常见于用某些国产打包工具或从网盘中转过的文件。另一部分是压缩包真的在传输中损坏。
解决:先不急着找密码,用7-Zip命令行测一下:
7z t welding_defects.zip逻辑说明:t是测试完整性。如果7z能列出文件且不要求密码,说明只是伪加密标志位问题,直接解压即可:
7z e welding_defects.zip -o./welding_defects -y如果7z提示需要密码,再用Python判断是伪加密还是真加密。真加密的文件在缺密码时读取会抛出RuntimeError,伪加密则可以正常读出明文,完整脚本判断逻辑如下:
import zipfile src = "welding_defects.zip" try: with zipfile.ZipFile(src) as zf: # 尝试读出第一个文件内容;若伪加密可读出,真加密会抛异常 name = zf.namelist()[0] data = zf.read(name) print("伪加密或未加密,可正常读出", len(data), "字节") except RuntimeError as e: print("文件确实加密或数据损坏:", e)参数说明:这个脚本只读第一个文件,如果第一个文件恰好损坏而其他文件完好,结果就不准。更稳的做法是循环zf.namelist()对所有文件执行zf.read(),但遇到坏文件就会提前中断,所以脚本定位是“快速定性”,别当万能药。
5.2 中文文件名目录乱码:GBK与UTF-8的编码战争
现象:解压出来的文件夹名是閴翠宸这种乱码,或者图片名变成汉å—,但文件内容正常。
原因:这个zip是在Windows简体中文环境下创建的,压缩包子目录名按GBK编码存储。zipfile库和部分解压工具会按UTF-8去解码这些字节,于是中文全变乱码。
解决:用Python手动解压,对压缩包内文件名做编码修正:
import zipfile import shutil from pathlib import Path src = "welding_defects.zip" out = Path("./welding_defects").resolve() out.mkdir(exist_ok=True) with zipfile.ZipFile(src) as zf: for info in zf.infolist(): raw = info.filename.encode("cp437") # 先还原原始字节 try: fixed_name = raw.decode("gbk") # 按GBK解出正确中文名 except UnicodeDecodeError: fixed_name = info.filename # 解不出就保留原名 target = (out / fixed_name).resolve() if not str(target).startswith(str(out)): raise RuntimeError(f"非法路径: {fixed_name}") shutil.copyfileobj(zf.open(info), target.open("wb"))逻辑说明:zipfile读到的文件名,本质上是创建者在写入时的原始字节被按cp437错误解码的结果,所以先把字符串编码回cp437拿到原始字节,再用正确编码GBK解出中文。shutil.copyfileobj逐块拷贝,适合大文件。
参数说明:encode("cp437")这一步是逆向解码,如果原始文件名里本来就带特殊字符,可能还原失败,这时UnicodeDecodeError兜底保留原名。Windows上的另一个更省事方案是用Bandizip或7-Zip的解压界面里选“以代码页936解压”,效果等价,但Python脚本能集成到你的数据准备流程里,全自动。
5.3 训练时bbox全部越界:归一化坐标与图片尺寸的勾稽关系
现象:YOLO训练日志里刷出一堆WARNING ⚠️ Box coordinates are outside of image bounds,且训练出来的模型漏检严重。
原因:标注txt里的坐标是针对原始图片尺寸归一化的,而你的加载代码或处理流程把图片做了resize或裁剪,却没有同步修正坐标。比如数据作者的原图是2448x2048,你用OpenCV把图缩到640x640后,标签文件里那些“归一化坐标”还是基于原图的位置比例,实际上没错——真正的问题往往是你在resize后没有保留长宽比,导致目标形状被拉伸,坐标比例其实是算不准的。
解决:不要自己resize图片喂给YOLO,把原图路径直接给ultralytics,它会做letterbox并同步修正坐标。如果你一定要预处理,则要写一个同步变换函数,核心逻辑是letterbox后坐标跟着缩放比例走:
def letterbox_boxes(boxes, orig_shape, new_shape): oh, ow = orig_shape[:2] nh, nw = new_shape[:2] scale = min(nw / ow, nh / oh) # 等比例缩放系数 dw = (nw - ow * scale) / 2 # 水平填充 dh = (nh - oh * scale) / 2 # 垂直填充 new_boxes = [] for box in boxes: cx, cy, w, h = box x1 = (cx - w / 2) * scale + dw / nw y1 = (cy - h / 2) * scale + dh / nh w2 = w * scale h2 = h * scale new_boxes.append((x1 + w2 / 2, y1 + h2 / 2, w2, h2)) return new_boxes参数说明:scale取min保证图片完整放进目标尺寸内,多出的边用灰色填充;缩放后坐标再除以nw/nh归一化,注意填充偏移dw/dh也要归一化后再加。这个函数只适用于纯letterbox,如果你用了cv2.resize拉伸,对不起,标签没法救,只能重新标注了。
5.4 气孔类AP为0:类别不均衡与最小尺寸目标的处理
现象:训练完看结果,porosity这一类mAP是0,但其他类都正常;检查训练集里气孔样本不算太少,大约300个框。
原因:焊接气孔在工业相机下通常只有几十像素宽,属于小目标。YOLOv8的默认anchor-free机制对8x8特征图上的小目标有基础能力,但马赛克增强会把小目标进一步缩小——一个本来就只有32像素的气孔,经过mosaic和随机缩放后可能只剩16像素,正样本特征几乎丢失。此外类别不均衡,气孔框数量只是飞溅的十分之一,模型在loss里被大头类别带偏。
解决:先调mosaic,再把少样本类别做过采样。
model.train( ..., mosaic=0.5, # 调低马赛克概率 close_mosaic=10, # 最后10个epoch完全关闭 hsv_h=0.01, # 小目标对颜色扰动更敏感,调低 scale=0.3 # 限制随机缩放范围,防止小目标被缩小到消失 )逻辑说明:close_mosaic是ultralytics的参数,最后N个epoch关掉mosaic,让模型在接近真实分布的尺寸上做精调。scale=0.3表示缩放范围从默认的0.5收窄到0.3,减少小目标再缩小的概率。
参数说明:如果气孔占比确实极低,比如只有几十个框,光调参数不够。更有效的做法是数据层面过采样:从训练集里把包含气孔的图片复制几份放进同一训练目录,但要注意复制出来的图片不要经mosaic增强后和原图太像,否则过拟合。我见过有人简单复制5份然后训练,验证集mAP确实涨了,但现场泛化一塌糊涂——复制不是万能药,先试试只调参数。
5.5 验证集mAP高但现场漏检多:光照分布与背景干扰
现象:训练集上mAP达到0.9,验证集也有0.85,但拿到产线试跑的样张上,同一个气孔位置模型就是检测不到。
原因:绝大多数焊接件缺陷数据集是在实验室或固定工位拍摄的,打光方向、工件颜色、反光强度相对一致。现场环境是亮面金属、随机光照角度、弧光飞溅,模型从数据集学到的纹理分布和现场分布对不上。这是典型的过拟合到数据集拍摄条件,不是模型没学好。
解决:用现场抽样的方式做验证——从产线随机拍5到10张图,跑一次推理:
yolo detect predict model=runs/weld_detect/yolov8n_640/weights/best.pt source=./field_samples/ save=True逻辑说明:yolo detect predict是ultralytics的命令行入口,source指向现场图目录,save=True把检测结果图保存下来。这一步的核心价值是把“验证集mAP”和“现场可用性”两个指标分开,现场漏检率高时就先别折腾模型了,去收集更多现场图加入训练集更有用。
参数说明:conf默认0.25,现场环境建议先调低到0.1跑一遍,观察是不是目标被检出来了但置信度不足;如果调低conf就能检出来,说明模型没坏,是置信度阈值和现场光照的适配问题。到这一步,真正应该做的是把现场图的数据增强做进去,这也是下一章要讲的。
6. 进阶:针对性增强与现场抽样验证
6.1 用Albumentations做针对焊接表面的增强
如果数据集图片数量有限,但现场拍摄条件多变,可以考虑在训练前用Albumentations做一次离线增强,专门针对金属表面反光和光照变化:
import albumentations as A aug = A.Compose([ A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.7), A.HueSaturationValue(hue_shift_limit=5, sat_shift_limit=10, val_shift_limit=10, p=0.5), A.GaussNoise(var_limit=(10, 40), p=0.3), ])逻辑说明:RandomBrightnessContrast模拟现场不同光照强度,GaussNoise模拟传感器噪声,这两个增强比通用的随机旋转对焊接件更实用。参数说明:brightness_limit=0.2表示亮度最多加减20%,太大照片会发灰;HueSaturationValue的hue_shift_limit设得极小,因为金属表面色相漂移会引入不真实的颜色。
6.2 留出法验证的现场抽样习惯
我的习惯是每个焊接项目都强制留出一个“现场样本”目录,和训练集放在一起但绝不参与训练。每轮模型训完,先用现场样本跑推理,再回看验证集指标。有一条血泪经验:模型在验证集上mAP从0.85涨到0.88,但现场漏检率从5%升到15%,原因就是我加了过强的旋转增强,焊缝的细长裂纹被旋转后形状变形,模型学歪了。从那以后我每次都在增强前后分别跑一遍现场样本,记录漏检数;增强如果让现场漏检变多,哪怕验证集涨点也直接回退。这个习惯帮我少走了很多弯路,希望也能帮到你。
本文还有配套的精品资源,点击获取