简介:这是一套面向工业视觉与深度学习目标检测的焊接件表面缺陷数据集,适合研究人员、算法工程师及目标检测学习者使用。数据源自GC10场景,原始2300张图像经标签校验与清洗后保留2294张;缺陷覆盖气孔、裂纹、未熔合、咬边、烧穿、夹渣、未焊透、焊瘤、形状缺陷及焊缝共十类,并采用COCO格式json标注,便于接入PaddlePaddle等平台。压缩包共2000个文件,含1997个jpg图像与3个json标注文件,整体约917.06MB;图像提供真实焊接表面样本,json记录类别与位置信息,可直接完成数据加载、划分与评估。数据集已划分458张作验证集,其余用于训练,适合开展缺陷识别与定位实验。目前已有397人学习下载,适合需要标准工业缺陷数据验证检测算法或开展课题研究者使用。
1. 焊接件表面缺陷检测:为什么“有数据集”只是第一步
在产线上干过质检的人都有体会:焊接件表面缺陷检测,眼睛看不过来,手电筒照得眼花,老师傅凭经验判断,新员工看了三个月还不敢下结论。气孔、咬边、未熔合、裂纹、焊瘤,每一种缺陷的光影特征都不同,而且焊接件表面还有油污、飞溅、氧化色这些干扰项。所以这两年越来越多团队想用机器视觉代替人工目检,第一步就是找一份像样的训练数据。标题里的“焊接件表面缺陷数据集-zip”就是这么个东西:把焊接件表面的原始图像和缺陷标注打包成 zip 压缩包,下载解压就能开始训练检测模型。
但这里有个容易误会的点:拿到 zip 不等于拿到能用的数据。zip 里可能是别人整理好的标准格式,也可能是一堆没清洗过的原始图;可能是 VOC 的 XML 标注,也可能是 YOLO 的 txt 坐标。解压之后先看什么、怎么转换成自己框架需要的格式、哪些缺陷类别值得单独建模、样本不平衡怎么处理,这些才是真正决定模型能不能上线的关键。这篇文章就沿着“解压 → 认识数据 → 搭训练流程 → 调参 → 踩坑 → 验证”这条路,把全过程讲清楚,给手里拿着 zip 却不知道下一步怎么走的人一条能直接落地的路径。
2. 解压与数据体检:先弄清 zip 里装的是什么
2.1 别急着跑模型,先看目录和标注格式
拿到“焊接件表面缺陷数据集-zip”这类文件,第一件事不是解压后扔给训练脚本,而是先搞清楚里面的组织方式。常见的数据集 zip 会包含 images 目录存放原始图像,annotations 目录存放标注文件,还可能附带一个 README 或 labels.txt 说明缺陷类别。有的打包者会把 train/val/test 划分也放进 zip,但也有人图省事全堆在一起,划分就要自己来。
在 Linux 或 Windows 环境下,我一般先看压缩包的文件清单,不急着全部解压。Windows 上直接双击打开 zip 看目录,Linux 下用 unzip -l 命令列出内容:
unzip -l welding_defect_dataset.zip | head -80这条命令只打印压缩包内的文件列表,不实际解压。通过列表能立刻判断几个关键信息:有没有 README、图片是什么格式(jpg 还是 png)、标注是 xml 还是 json 或 txt、数量大概多少。看清单之后再决定是完整解压还是先解压一部分试用。
解压到本地的工作目录时,注意路径里不要有中文和空格,很多框架的 DataLoader 对路径解析不友好。建议解压成这样的结构:
mkdir -p ~/datasets/welding && unzip welding_defect_dataset.zip -d ~/datasets/welding解压完成后先做一次完整性校验。zip 文件在传输过程中可能损坏,尤其是从网盘下载的大文件。用 unzip -t 命令可以测试每个文件的 CRC 校验值,别等到训练到一半发现图片损坏才返工。
unzip -t welding_defect_dataset.zip | tail -202.2 标注格式识别:VOC、COCO 还是 YOLO
焊接件表面缺陷数据集的标注格式决定了后续所有处理流程,这一步不能靠猜。解压后找几个标注文件打开看看,用 Python 快速判断格式类型:
import json, os, glob annot_dir = "path/to/annotations" print("Files:", os.listdir(annot_dir)[:10]) sample_xml = glob.glob(os.path.join(annot_dir, "*.xml")) sample_json = glob.glob(os.path.join(annot_dir, "*.json")) sample_txt = glob.glob(os.path.join(annot_dir, "*.txt")) if sample_xml: print("Detected: VOC XML format") elif sample_json: with open(sample_json[0]) as f: data = json.load(f) if "annotations" in data: print("Detected: COCO JSON format") else: print("Detected: LabelMe JSON") elif sample_txt: with open(sample_txt[0]) as f: line = f.readline().strip() parts = line.split() if len(parts) == 5 and parts[0].isdigit(): print("Detected: YOLO txt format")这段代码通过扩展名和内容结构判断标注类型。VOC 是 XML,COCO 是 JSON,YOLO 是纯文本且每行五列(类别 ID、中心点归一化坐标和宽高)。把格式认准之后,后续转换才有依据。
2.3 类别分布统计:哪些缺陷能建模,哪些是干扰
焊接件表面缺陷有个特点:不同缺陷出现频率差异极大。气孔和咬边可能成百上千个样本,未熔合只有几十张,裂纹因为形态复杂更是少得可怜。打开标注后第一件事就是统计类别分布,否则模型训出来只剩大类在跑。
import glob import xml.etree.ElementTree as ET from collections import Counter category_counter = Counter() xml_files = glob.glob("path/to/annotations/*.xml") for xml_file in xml_files: tree = ET.parse(xml_file) root = tree.getroot() for obj in root.findall("object"): name = obj.findtext("name") category_counter[name] += 1 print(category_counter)这份统计结果直接决定后续策略:类别超过 300 样本的可以考虑单独建模,少于 50 的要么做数据增强硬训,要么先合并成“其他缺陷”类。焊接件缺陷检测的落地项目里,我会把“未熔合”和“裂纹”这类小样本类别单独拎出来看标注质量,因为这两类缺陷在灰度上跟正常焊缝差异本来就小,如果标注框画得不准,模型再怎么调都学不到东西。
提示:解压后不要急着删原始 zip。后续如果发现某一张图片损坏或标注错乱,从原始压缩包里重新提取比重新下载快得多。
3. 从 zip 到可训练数据:VOC 转 YOLO 与样本划分
3.1 为什么要转成 YOLO 格式
当前焊接件表面缺陷检测的主流做法是 YOLO 系列模型,无论是 YOLOv8 还是 YOLOv5,官方训练代码默认吃 YOLO 格式的标注:每个 txt 文件一行,格式是“类别ID 中心点x 中心点y 宽度 高度”,所有坐标都是归一化到 0~1 的值。而公开的焊接缺陷数据集大概率以 VOC XML 或 COCO JSON 发布,所以第一步就是把标注转成 YOLO txt。
有人问为什么不直接用 COCO 格式训,或者直接用 Detectron2。能训,但 YOLO 生态对新手最友好,权重文件小、部署方便、工业现场跑起来要求低。做焊接质检不是发论文,是让模型在工控机上跑得动,YOLO 是性价比最高的选择。
3.2 VOC XML 转 YOLO txt 的完整转换脚本
下面是一个可以直接用的转换脚本,输入是 VOC XML 目录和一个类别列表文件,输出是 YOLO txt 标注和 images 目录的索引清单:
import os import glob import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, output_dir, class_file): with open(class_file) as f: classes = [line.strip() for line in f.readlines()] os.makedirs(output_dir, exist_ok=True) for xml_path in glob.glob(os.path.join(xml_dir, "*.xml")): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.findtext("width")) img_h = int(size.findtext("height")) if img_w == 0 or img_h == 0: print(f"[skip] {xml_path}: invalid image size") continue base_name = os.path.splitext(os.path.basename(xml_path))[0] txt_path = os.path.join(output_dir, base_name + ".txt") with open(txt_path, "w") as out: for obj in root.findall("object"): name = obj.findtext("name") if name not in classes: continue class_id = classes.index(name) bbox = obj.find("bndbox") xmin = float(bbox.findtext("xmin")) ymin = float(bbox.findtext("ymin")) xmax = float(bbox.findtext("xmax")) ymax = float(bbox.findtext("ymax")) # 坐标边界裁剪,防止标注越界造成训练崩溃 xmin = max(0, min(xmin, img_w)) xmax = max(0, min(xmax, img_w)) ymin = max(0, min(ymin, img_h)) ymax = max(0, min(ymax, img_h)) if xmax <= xmin or ymax <= ymin: print(f"[skip] {xml_path}: invalid bbox {name}") continue x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h out.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n") print(f"[done] {base_name}") if __name__ == "__main__": voc_to_yolo("annotations", "labels", "classes.txt")这段代码做了几件关键事:解析 XML 里的 width 和 height 做分母;把 xmin/xmax 裁剪到图片范围内,防止越界;过滤掉宽高为零的无效框;把绝对像素坐标转成归一化相对坐标。每个 txt 文件的文件名和对应图片文件名保持一致,这是 YOLO 能匹配上图和标注的唯一约定。
3.3 训练集、验证集、测试集的划分边界
数据集 zip 里如果没有现成的划分,就需要自己切分。焊接件表面缺陷数据集有个特殊情况:同一个焊接工件上往往拍了多张照片,这些照片背景高度相似,如果随机划分,同一工件的不同照片可能同时出现在训练集和验证集里,导致验证分数虚高。
我的做法是先按图片的文件名前缀分组,再按组划分。很多数据集命名包含工件编号,比如 weld_001_surface_01.jpg,weld_001_surface_02.jpg,那么 weld_001 就是组标识。按组划分的脚本如下:
import os, random from collections import defaultdict image_dir = "images" group_dict = defaultdict(list) for img_name in os.listdir(image_dir): prefix = img_name.split("_")[0] + "_" + img_name.split("_")[1] group_dict[prefix].append(img_name) groups = list(group_dict.keys()) random.shuffle(groups) train_groups = groups[:int(len(groups) * 0.7)] val_groups = groups[int(len(groups) * 0.7):int(len(groups) * 0.85)] test_groups = groups[int(len(groups) * 0.85):] def write_split(group_list, out_file): with open(out_file, "w") as f: for g in group_list: for img in group_dict[g]: stem = os.path.splitext(img)[0] f.write(f"images/{img} labels/{stem}.txt\n") write_split(train_groups, "train.txt") write_split(val_groups, "val.txt") write_split(test_groups, "test.txt")这里按前缀提取组标识,7:1.5:1.5 划分到训练、验证、测试。测试集独立留出来非常关键,因为模型调参过程中验证集用多了会产生隐式过拟合,焊接件缺陷的纹理细节太相似,这种过拟合很容易被忽略。
注意:划分完看一眼每个集合里的类别分布是否接近原始比例。焊接件缺陷里小样本类别本来就少,如果划分后验证集里完全没有“未熔合”样本,那验证的 mAP 就没有参考价值。
4. 搭建焊接缺陷检测的训练流程:模型选型与参数配置
4.1 模型选型:为什么要用 YOLOv8n 而不是更大的模型
焊接件表面缺陷检测是在工业现场落地的任务,模型最终要部署到工控机甚至嵌入式设备上。这些设备的算力大体是入门级独显或核显级别,还要留出余量跑图像采集和 PLC 通信。因此模型体积和推理速度比精度更优先。
YOLOv8n 是最小的 YOLOv8 变体,只有约 300 万参数,在 COCO 上 mAP 不高,但焊接件缺陷类别少(通常 5~8 类),背景相对固定(焊缝区域),小模型完全够用。如果用 YOLOv8x,单张推理时间可能从 15ms 涨到 80ms,产线上如果每秒要检测多张图,这个差距很致命。
我一般先训 YOLOv8n 出一个精度基线,如果某一类缺陷的 recall 明显不够,再尝试升级到 YOLOv8s 对比。直接上大模型不是好习惯,因为焊接件表面缺陷的图像分辨率高(相机动辄 1200 万像素),大模型输入尺寸受限,反而丢失小缺陷细节。
4.2 用 ultralytics 跑通第一个训练命令
假设标注已经转换好、train.txt 和 val.txt 已经生成,那么用 ultralytics 训练是最快路径。安装之后,一条命令就能开训:
pip install ultralytics yolo detect train data=weld.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 device=0这里 data=weld.yaml 是数据配置文件,内容是指向图片路径、标注路径和类别名称的清单。weld.yaml 的写法如下:
path: /home/user/datasets/welding train: train.txt val: val.txt test: test.txt names: 0: porosity 1: undercut 2: lack_of_fusion 3: crack 4: spatter这个 YAML 里 path 是数据集根目录,train/val 指向包含图片路径和标注路径的 txt 文件,names 里是类别 ID 到名称的映射。类别名称要和转换标注时 classes.txt 里的顺序一致,否则模型训练出的类别 ID 全部错位,这是新手最容易翻车的地方。
训练开始后,每轮迭代会输出 mAP50、mAP50-95、precision、recall 等指标。焊接件表面缺陷和自然图像不同,背景相对单一,所以 mAP50 一般能很快涨到 0.9 以上,但 mAP50-95 才是更严苛的指标,它衡量预测框和真实框在 IoU 从 0.5 到 0.95 不同严格程度下的平均表现。缺陷检测里,小缺陷只有几个像素的偏差,IoU 到不了 0.75,mAP50-95 就会卡住不上涨。
4.3 输入尺寸、batch 与训练轮数的经验参数
焊接件表面的缺陷特点是:气孔小且密集、咬边沿焊缝走向呈长条形、裂纹细长且对比度低。输入尺寸设 640 是 YOLO 默认值,但焊接件表面图像里的小气孔可能只有 10×10 像素,缩放到 640 之后只剩 5×5 像素,很难检出。我一般把 imgsz 设到 1024 或 1280,代价是显存占用上升。如果 GPU 只有 8GB,imgsz=1280 会直接 out of memory。
此时有两个变通方案:降低 batch 到 4 甚至 2;或者把大图切成小块训练。切块的做法是用滑动窗口把原始 2000×2000 图像切成 640×640 的 patch,缺陷在每个 patch 里的相对大小变大,模型更容易学到特征。代价是标注也要跟着切,这个操作有一定复杂度,但它能明显提升小缺陷的 recall,值得做。
训练轮数方面,焊接件缺陷数据集规模通常不大(几千到几万张),100 轮足够,配合 early stopping。超过 150 轮大概率开始过拟合,验证 loss 和训练 loss 开始背离。优化器直接选默认的 SGD 或 AdamW 都可以,ultralytics 默认的 lr=0.01 不用改,weight_decay 保持默认。真正需要调的是数据增强参数。
4.4 数据增强策略:焊接件缺陷的特殊性
焊接件表面和自然图像有个巨大差别:焊道纹理、飞溅颗粒、氧化色都是自然存在的“噪声”。很多数据增强手段在焊接件上不适合,比如大幅度的随机旋转——焊缝方向是有规律的,工件装夹位置决定了焊缝基本是水平或垂直走向,旋转 90 度会让模型学到错误的方向先验。随机裁剪是合理的,但裁剪比例不能太大,否则焊道边缘的上下文信息丢失。
我推荐的增强配置是用 ultralytics 的默认增强 + 关闭 mosaic 和 auto_augment。mosaic 把四张图拼成一张,适合自然图像场景,但对焊接件来说,四张不同焊缝图像拼在一起会造成焊道纹理的断层,模型学到“焊缝处纹理断裂是正常的”,这在真实检测中是严重的认知偏差。如果一定要用 mosaic,只在最后 10 轮关闭。
from ultralytics import YOLO model = YOLO("yolov8n.pt") model.train( data="weld.yaml", epochs=100, imgsz=1024, batch=8, mosaic=0.0, hsv_h=0.0, hsv_s=0.2, hsv_v=0.2, degrees=0.0, translate=0.1, scale=0.3, fliplr=0.5, flipud=0.0, )这里的参数含义值得逐个说:hsv_h 设为 0 是因为焊接件在灰度相机下颜色信息本来就没用,调色相只会引入噪声;hsv_s 和 hsv_v 可以小幅调整,模拟不同光照强度;degrees=0 关闭旋转,焊接件装夹方向固定,不需要旋转增强;translate=0.1 允许小幅平移,容忍工件在照片中的位置偏移;scale=0.3 允许 30% 的缩放变化,应对同一缺陷在不同距离下的成像尺寸差异;fliplr=0.5 水平翻转是安全的,因为焊缝左右对称性在大多数情况下成立。
提示:焊接件表面有金属反光,原始图中可能存在过曝区域。如果数据里有明显的反光干扰,可以在训练前做灰度归一化或 CLAHE 对比度增强,这类预处理比模型里塞任何模块都有效。
5. 避坑与常见问题排查:zip 数据到训练落地的真实血泪
5.1 zip 伪加密:解压工具不报错但解出空文件
现象:从网盘下载的焊接件缺陷数据集 zip 在 Windows 上一双击就能打开,但解压到一半提示“需要密码”,或者某个文件解出来是 0 字节。在 Linux 下 unzip 直接报错“incorrect password”。
原因:zip 文件有一种“伪加密”机制,文件头里的 general purpose bit flag 第 0 位被置 1,表示文件加密,但文件数据本身实际上是明文。这种 zip 要么是发布者打包时工具误设了加密标志,要么是别人故意用伪加密绕过网盘的敏感文件审查。数据集分享者自己可能都没意识到做成了伪加密。
解决:不需要跑暴力破解,也不需要任何密码移除工具。用 Python 的 zipfile 库读文件头,把加密标志位改写为 0 再另存,就能正常解压:
import struct def fix_zip_pseudo_encryption(zip_path, output_path): with open(zip_path, "rb") as f: data = bytearray(f.read()) # 搜索文件头 signature 0x04034b50 idx = 0 fixed = 0 while idx < len(data) - 4: sig = struct.unpack("<I", bytes(data[idx:idx+4]))[0] if sig == 0x04034b50: flag_offset = idx + 6 flags = struct.unpack("<H", bytes(data[flag_offset:flag_offset+2]))[0] if flags & 0x1: flags &= 0xFFFE data[flag_offset:flag_offset+2] = struct.pack("<H", flags) fixed += 1 idx += 1 with open(output_path, "wb") as f: f.write(data) print(f"Fixed {fixed} entries, saved to {output_path}") fix_zip_pseudo_encryption("welding_defect.zip", "welding_defect_fixed.zip")这段代码遍历 zip 文件的所有 local file header,检测加密标志位并清除。修复后再用 unzip -t 校验,如果所有文件都通过 CRC 校验,说明数据完整可用。这是 zip 相关的数据集分享里最常见的隐藏坑,遇到解压报密码先试这个,别去花时间找密码。
5.2 标注坐标越界导致训练 loss 变成 nan
现象:模型训练到第 20 轮左右,loss 突然变成 nan,或者验证集的 mAP 从 0.8 骤降到 0.1,看起来像是“训练崩了”。
原因:标注文件里存在越界框。有些标注工具生成的 bbox 坐标会超出图像尺寸,比如 xmin = -5 或 xmax = 1500 而图片宽度只有 1280。YOLO 训练时不做边界检查,越界框参与损失计算后导致梯度爆炸。
解决:在数据转换阶段就做边界裁剪和过滤,第 3 章的转换脚本里已经做了这件事。如果用的是 COCO JSON,需要在加载时加一个过滤逻辑,把 x < 0 或 y + h > height 的标注裁剪到边界内。不要直接丢弃这些样本,因为焊接缺陷标注本身样本就稀缺,能裁剪就裁剪,不能丢弃。
另外检查标注框是否覆盖了目标缺陷的大部分区域。有一类情况是标注框画得过大,把整条焊缝都框进去了,而不是只框缺陷区域。这种标注会让模型学到“看到焊缝就输出检测框”,推理时误报率极高,而且难以通过阈值调整解决。
5.3 训练和验证时类别顺序不一致
现象:训练集上 loss 一直在降,验证集 mAP 一直为 0,打印验证集预测结果发现所有输出的类别 ID 都比真实标签大 1。
原因:转换标注时用的类别文件顺序和训练时用的 data.yaml 里 names 顺序不一致。最常见的是 classes.txt 里按字母排序(crack, lack_of_fusion, porosity, spatter, undercut),但 data.yaml 里按手工整理顺序(porosity, undercut, lack_of_fusion, crack, spatter)。模型学习的 class ID 5 在验证集的标注里可能是 class ID 0,自然全错。
解决:训练前写一段脚本校验类别顺序的一致性,别依赖肉眼判断。一个简单方法是从 data.yaml 读取 names,从 classes.txt 读取名称列表,直接对比字符串顺序:
import yaml with open("weld.yaml") as f: cfg = yaml.safe_load(f) yaml_names = cfg["names"] with open("classes.txt") as f: txt_names = [line.strip() for line in f if line.strip()] assert yaml_names == txt_names, f"Mismatch: {yaml_names} vs {txt_names}" print("Class order OK")这个校验跑完只需要一秒钟,能省掉一整天的调试时间。
5.4 小样本缺陷类别完全检不出
现象:气孔和咬边的 mAP 都到 0.9 以上,但“未熔合”类目无论怎么调参,recall 始终在 0.2 上下徘徊。
原因:样本量严重不足。焊接件表面缺陷数据集中,未熔合可能只有几十个标注框,而气孔有几千个。YOLO 的 anchor-free 头在类别不平衡下会倾向于预测出现频率高的类别,这种偏差不是调节 loss 权重就能完全纠正的。
解决:三个手段叠加使用。第一,对未熔合样本做过采样,每个 epoch 里重复加载这部分图片,让模型每轮都看到它们;第二,用复制粘贴增强,把未熔合的缺陷区域从原图抠出来,粘贴到其他焊缝图像的随机位置,注意粘贴时要保持光照方向和焊缝纹理走向一致;第三,把未熔合和裂纹合并成“熔合缺陷”一个大类,因为实际产线上这两类缺陷的处理方式相同——都需要返工重焊,区分它们没有产线意义,只增加了模型负担。这第三招在工业落地里最实用,模型要服务的是决策,不是论文里的类别细分。
5.5 解压后图片文件损坏且无法重新下载
现象:zip 解压过程没有报错,但训练时某几张图片无法用 cv2.imread 打开,返回 None,导致 DataLoader 崩溃。
原因:网盘下载的文件在传输过程中发生了静默损坏,zip 的 CRC 校验在部分解压工具实现中被跳过,或者上传者打包时源文件本身就有问题。
解决:训练前先跑一遍图片完整性扫描,把打不开的图片和对应标注一起剔除:
import cv2, glob, os image_files = glob.glob("images/*.jpg") + glob.glob("images/*.png") corrupted = [] for img_path in image_files: img = cv2.imread(img_path) if img is None or img.size == 0: corrupted.append(img_path) label_path = img_path.replace("images", "labels").replace(".jpg", ".txt").replace(".png", ".txt") if os.path.exists(label_path): os.remove(label_path) os.remove(img_path) print(f"Removed {len(corrupted)} corrupted images")这个脚本会删掉坏图和对应的 txt 标注,保证后续训练数据百分之百可读。注意跑完这个脚本之后要重新生成 train.txt 和 val.txt 的文件索引,因为有些图片已经被删除了。
6. 验证与进阶:用混淆矩阵和 patch 推理提升小缺陷召回
模型训练结束后,不要只看终端打印的 mAP 数字。焊接件表面缺陷检测的落地场景里,mAP 高 ≠ 产线能用。把验证集的预测结果导出,逐类看混淆矩阵,这是发现模型真实缺陷最快的方式。
ultralytics 训练结束后会自动生成 confusion_matrix.png 和 results.png,但我建议用下面的方式手动导出每张测试图的预测结果,以便针对具体缺陷类别做细粒度分析:
from ultralytics import YOLO model = YOLO("best.pt") results = model.predict(source="test_images", save_txt=True, save_conf=True, imgsz=1024, conf=0.25) for r in results: boxes = r.boxes if boxes is None: continue for i in range(len(boxes)): cls = int(boxes.cls[i].item()) conf = float(boxes.conf[i].item()) xyxy = boxes.xyxy[i].cpu().numpy().tolist() print(f"class={cls} conf={conf:.2f} bbox={xyxy}")如果发现某一类缺陷在 conf=0.25 时误报特别多,就把 conf 阈值往上调,比如 0.4。在产线上,误报的代价是停线检查,漏报的代价是缺陷流到下游。焊接件缺陷场景里通常宁可误报也不漏报,因为焊缝返工比整件报废便宜。
一个更进阶的做法是 patch 推理。焊接件表面图像分辨率高,直接缩放到 1024 会丢失小目标。推理时把测试图切成多个 640×640 的 patch,分别推理,再把预测框映射回原图坐标,做一次 NMS 合并。这个方法能显著提升小气孔和细微裂纹的召回率,代价是推理时间翻几倍。如果产线检测节拍允许(比如 3 秒检测一个工件),这个代价完全值得。
import cv2 import numpy as np def patch_inference(model, image_path, patch_size=640, stride=640): img = cv2.imread(image_path) h, w = img.shape[:2] all_boxes = [] for y in range(0, h, stride): for x in range(0, w, stride): patch = img[y:y+patch_size, x:x+patch_size] results = model(patch, imgsz=640, conf=0.3) for box in results[0].boxes: xyxy = box.xyxy[0].cpu().numpy() cls = int(box.cls[0].item()) conf = float(box.conf[0].item()) all_boxes.append([xyxy[0] + x, xyxy[1] + y, xyxy[2] + x, xyxy[3] + y, conf, cls]) # 合并重叠框 boxes = np.array(all_boxes) if len(boxes) > 0: keep = cv2.dnn.NMSBoxes(boxes[:, :4].tolist(), boxes[:, 4].tolist(), 0.3, 0.45) final = boxes[keep] else: final = np.empty((0, 6)) return final这段代码在推理时用滑动窗口遍历原图,每个 patch 独立检测,然后把所有预测框映射回原图坐标,最后用 NMS 合并同一缺陷产生的多个重叠框。patch_size 和 stride 相等意味着切块之间没有重叠,不会出现同一缺陷被多个 patch 重复检测的问题;但如果缺陷恰好出现在 patch 边界上,会被切成两半各自只检出一部分。要处理这种情况,可以让 stride 小于 patch_size,例如 stride=480、patch=640,让相邻 patch 有 160 像素重叠。代价是推理次数变多,但边界漏检问题基本消除。
焊接件表面缺陷检测这件事,数据比模型重要,踩坑经验比看书重要。我最早做这个方向时也是拿到 zip 就开始跑训练,结果被伪加密、标注越界、类别错位轮番折磨。后来养成了解压后先体检、训练前先校验、推理时先看错误的习惯,项目才慢慢顺起来。这里面的每一段代码和参数都是这么磨出来的。第一次跑完整个流程后,建议你刻意把一批不合格品图片混进测试集里,看看模型能不能识别出“这个焊件不对劲”,这一步能帮你判断模型是真的理解了缺陷,还是只是记住了训练集的纹理。希望帮到你。
本文还有配套的精品资源,点击获取