拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PCD表面元器件缺陷检测数据集:600张图YOLOv8训练与避坑指南

PCD表面元器件缺陷检测数据集:600张图YOLOv8训练与避坑指南

简介:这份PCD表面元器件缺陷检测数据集面向从事工业质检、电子制造与目标检测算法实践的开发者与研究者,用于训练和验证PCB表面元器件缺陷识别模型。数据集包含超过600张标注图像,已统一处理为YOLO格式并完成数据增强,可直接用于YOLO全系列网络训练,覆盖Miss、mask defect、defect等7类缺陷,适合从入门练手到模型调优的多种场景。压缩包共1219个文件,以608个jpg图像、609个txt标签为主,另含1个py可视化脚本与1个png说明图,整体约57.41MB,训练集、验证集与class类别文件齐备。配套show脚本可将预测或标注的边界框绘制在图像上,便于直观核对缺陷位置、调试模型与评估检测效果。目前已有391人学习下载,可作为缺陷检测项目的数据基础与排错参考。

1. PCD 表面元器件缺陷检测数据集:600 张图能撑起一条产线质检链路吗

PCB 贴片产线上,AOI 设备每秒钟吐出好几张板面图,但真正让算法工程师头疼的往往不是模型结构,而是手里那批标注数据够不够“脏得真实”。PCD 表面元器件缺陷检测数据集,就是冲着这个场景来的:超过 600 张图片和对应标签,覆盖元器件贴装环节常见的缺件、偏移、立碑、极性反、异物等表面缺陷。它解决的不是“从零造数据”的问题,而是让你在目标检测这条链路上,用一份规模不大但结构完整的样本,快速验证从标注格式转换、训练配置到推理部署的全流程。适合两类人:一类是刚接手 AOI 质检项目、需要一份能跑通 baseline 的数据集练手的目标检测新手;另一类是已经在做 yolov8训练自己的数据集、想找工业小样本场景验证模型泛化边界的老手。600 张不算多,但工业缺陷检测本来就不是靠堆量取胜,关键在于缺陷形态的覆盖度和标注质量。

2. 拆开这份数据集:图片、标签与缺陷类别到底长什么样

2.1 目录结构与标注格式的常见形态

拿到一份目标检测数据集,第一件事不是急着写训练脚本,而是把目录翻一遍,搞清楚图片和标签怎么对应。PCD 表面元器件缺陷检测数据集通常按以下结构组织:

PCD_Dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── classes.txt

图片以 jpg 或 png 为主,分辨率大概率在 640×640 到 1920×1080 之间,工业相机拍板面图不会太小。标签目录里每个 txt 文件对应一张图,文件名相同、扩展名不同。classes.txt 里逐行列出缺陷类别名称,行号即类别索引。

标注格式大概率是 YOLO 格式,每行class_id x_center y_center width height,坐标归一化到 0~1。也有可能是 VOC 的 XML 格式,那就需要转换。判断方法很简单:打开 labels 下任意一个 txt,如果每行是 5 个浮点数,就是 YOLO;如果看到<object>标签,就是 VOC。

注意:有些数据集会把 images 和 labels 混在同一目录,图片和同名 txt 并排存放。这种结构在 YOLO 训练时需要用脚本重新组织,不能直接指向目录。

2.2 缺陷类别的分布与长尾问题

工业缺陷检测数据集的类别分布几乎不可能均衡。PCD 表面元器件缺陷检测数据集里,缺件和偏移这类高频缺陷可能占 60% 以上,而立碑、极性反、异物这些低频缺陷可能每类只有几十个样本。这不是数据集做得不好,而是产线实际情况就这样——有些缺陷本来就少见。

缺陷类型典型占比检测难度标注要点
缺件25%~35%低框住本该有元件的位置
偏移20%~30%中框住元件实际位置,注意与相邻元件区分
立碑10%~15%高元件一端翘起,框要包含整个倾斜体
极性反5%~10%高需要结合丝印或元件方向判断
异物5%~10%中框住异物区域,注意与焊锡球区分

长尾类别是训练时翻车的高发区。如果直接拿全量数据训练,模型会对高频类别过拟合,低频类别召回率极低。常见做法是在损失函数里加类别权重,或者对低频类别做过采样。我一般会先跑一版不加权的 baseline,看每个类别的 AP,再决定要不要动采样策略。

2.3 用脚本做一次数据体检

在训练之前,写一个体检脚本,把类别分布、框大小分布、图片尺寸分布都过一遍。这一步花不了十分钟,但能帮你避开后面几小时的无效训练。

import os import glob from collections import Counter label_dir = "PCD_Dataset/labels/train" class_names = open("PCD_Dataset/classes.txt").read().strip().split("\n") class_counter = Counter() box_sizes = [] for txt_path in glob.glob(os.path.join(label_dir, "*.txt")): with open(txt_path) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) w, h = float(parts[3]), float(parts[4]) class_counter[cls_id] += 1 box_sizes.append((w, h)) print("类别分布:") for cid, cnt in sorted(class_counter.items()): print(f" {class_names[cid]}: {cnt}") # 统计小目标占比(宽高都小于 0.05 的框) small = sum(1 for w, h in box_sizes if w < 0.05 and h < 0.05) print(f"\n小目标框占比: {small / len(box_sizes) * 100:.1f}%")

这段脚本做三件事:统计每个类别的框数量、收集所有框的宽高、计算小目标占比。class_counter用类别索引做 key,最后映射回类别名。小目标占比超过 30% 就要注意了,默认的 YOLO 锚框可能匹配不上,需要重新聚类锚框或者调小检测头的最小特征图步长。

参数方面,0.05这个阈值不是固定的。如果你的图片分辨率是 1920×1080,一个 96×54 像素的元件在归一化后大约是 0.05×0.05,所以这个阈值对应的是“小于 100 像素见方”的目标。根据你的实际分辨率调整。

3. 从原始标注到 YOLO 训练:格式转换与配置落地

3.1 VOC 转 YOLO 的转换脚本与四个边界坑

如果拿到的标签是 VOC XML 格式,需要转成 YOLO 的 txt。转换逻辑不复杂,但边界情况特别多,我踩过的坑至少有四个:图片尺寸读错、类别名大小写不一致、框坐标越界、空标注文件。

import xml.etree.ElementTree as ET import os from PIL import Image def voc_to_yolo(xml_path, img_path, class_list, output_dir): tree = ET.parse(xml_path) root = tree.getroot() # 坑1:从 XML 里读尺寸可能不准,直接从图片读 img = Image.open(img_path) img_w, img_h = img.size lines = [] for obj in root.findall("object"): # 坑2:类别名统一转小写并去空格 cls_name = obj.find("name").text.strip().lower() if cls_name not in class_list: print(f"未知类别 {cls_name},跳过 {xml_path}") continue cls_id = class_list.index(cls_name) bbox = obj.find("bndbox") xmin = float(bbox.find("xmin").text) ymin = float(bbox.find("ymin").text) xmax = float(bbox.find("xmax").text) ymax = float(bbox.find("ymax").text) # 坑3:坐标越界裁剪 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)) # 坑4:宽高为0的框直接丢弃 if xmax <= xmin or ymax <= ymin: continue x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") # 空标注也要生成文件,否则训练时找不到对应标签 out_path = os.path.join(output_dir, os.path.basename(xml_path).replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines))

四个坑对应四行关键代码:尺寸从图片读而不是从 XML 读、类别名统一小写、坐标裁剪到图片范围内、丢弃无效框。class_list是类别名列表,顺序要和 classes.txt 一致。输出路径保持和原 XML 同名,只是扩展名换成 txt。

转换完之后,随机抽 5 张图,用可视化脚本把框画出来,肉眼确认一遍。这一步不能省,我见过太多因为坐标归一化搞反导致框全跑到左上角的案例。

3.2 YOLOv8 训练配置:小样本场景的参数怎么调

600 张图在目标检测里属于小样本,训练配置和常规的 COCO 级别数据集不一样。以下是一份我常用的 YOLOv8 训练配置:

# pcd_defect.yaml path: ./PCD_Dataset train: images/train val: images/val test: images/test names: 0: missing 1: offset 2: tombstone 3: polarity_reverse 4: foreign_object
from ultralytics import YOLO model = YOLO("yolov8s.pt") results = model.train( data="pcd_defect.yaml", epochs=150, imgsz=640, batch=16, lr0=0.001, lrf=0.01, warmup_epochs=5, weight_decay=0.0005, mosaic=0.5, mixup=0.1, copy_paste=0.1, patience=30, save_period=10, device=0 )

逐项说明关键参数。yolov8s.pt选 small 版本而不是 nano,因为工业缺陷的纹理特征比较细,nano 的容量可能不够。epochs=150配合patience=30,意思是 30 轮验证集指标不提升就早停,小样本训练很容易过拟合,早停是必须的。lr0=0.001比默认的 0.01 小一个量级,小样本场景下学习率太大会导致 loss 震荡。mosaic=0.5而不是默认的 1.0,因为工业图的背景相对固定,过度拼接会引入不真实的上下文。copy_paste=0.1对缺陷检测有帮助,能把缺陷实例复制到其他图上,增加低频类别的出现次数。

注意:copy_paste在 YOLOv8 里需要分割掩码才能生效,如果只有检测框,这个参数实际不起作用。替代方案是用mixup或者手动做过采样。

3.3 训练过程中的指标怎么看

训练启动后,重点关注三个指标:metrics/mAP50、metrics/mAP50-95和每个类别的AP。mAP50 到 0.8 以上算及格,但工业场景更看召回率——漏检一个缺陷比误检三个更严重。

如果发现高频类别 AP 很高但低频类别 AP 接近 0,说明模型完全忽略了低频类。这时候可以:把cls损失权重调高、对低频类别做过采样、或者用 focal loss 替代默认的 BCE loss。YOLOv8 不直接暴露 focal loss 开关,需要改源码或者用fl_gamma参数(部分版本支持)。

验证集上的可视化结果比数字更直观。跑完训练后用model.predict()在验证集上推理一批图,把预测框和真实框叠在一起看。如果发现模型把焊盘误检成缺件,说明正负样本定义有问题,需要检查标注时有没有把空焊盘也标成缺件。

4. 数据增强与类别不均衡:小样本工业缺陷的实战策略

4.1 工业场景下哪些增强有用、哪些是负优化

通用目标检测的增强策略搬到工业缺陷检测上,有一半会起反作用。翻转、旋转、缩放这些几何变换通常没问题,因为元器件的缺陷形态不依赖绝对方向。但颜色抖动要慎用——工业相机的白平衡和曝光是固定的,颜色偏移会让模型学到不存在的特征。Cutout 和 RandomErasing 也要小心,遮挡区域可能恰好是缺陷所在,把缺陷擦掉等于制造噪声。

我一般会保留的增强:水平翻转、垂直翻转、±15° 旋转、0.8~1.2 倍缩放、mosaic(概率控制在 0.5 以下)。会关掉的:HSV 色相抖动、大比例随机裁剪、cutout。copy_paste如果有分割掩码就用,没有就手动实现一个简化版。

手动过采样的做法:统计每个类别的样本数,对样本数少于中位数的类别,随机选图复制到训练集,复制时加轻微旋转和亮度扰动。注意复制后的图要重新生成标签文件,不能直接复制原图。

4.2 用加权损失和采样器处理长尾分布

YOLOv8 默认的分类损失是 BCE,每个类别的权重相同。长尾场景下,低频类别的梯度会被高频类别淹没。两种处理方式:

第一种,在 dataloader 里做加权采样。给每个样本一个权重,权重和它包含的稀有类别数量成正比。这样每个 batch 里低频类别的出现概率会提高。

from torch.utils.data import WeightedRandomSampler import numpy as np # 假设 dataset 是 YOLO 的 Dataset 对象 labels = [dataset.get_label(i) for i in range(len(dataset))] class_counts = np.bincount([l for label in labels for l in label]) class_weights = 1.0 / (class_counts + 1e-6) sample_weights = [] for label in labels: w = sum(class_weights[l] for l in label) / len(label) sample_weights.append(w) sampler = WeightedRandomSampler(sample_weights, num_samples=len(sample_weights), replacement=True)

class_weights是类别频率的倒数,稀有类别权重高。sample_weights是每个样本包含的所有类别权重的均值。replacement=True表示有放回采样,低频样本会被重复抽到。这个采样器传给 DataLoader 的sampler参数即可。

第二种,改损失函数。在 BCE 前面乘上类别权重,或者换成 focal loss。YOLOv8 的损失函数在ultralytics/utils/loss.py里,找到BCEWithLogitsLoss那一行,把pos_weight参数传进去。这个改动需要重新编译或者以源码方式运行,pip 安装的版本改不了。

4.3 验证集划分的坑:别让同一块板子同时出现在训练和验证里

工业数据集最容易犯的错误是按图片随机划分训练集和验证集。如果同一块 PCB 被拍了多张图(不同角度、不同光照),随机划分会导致训练集和验证集里有同一块板子的不同视角。模型在验证集上表现很好,但换一块新板子就崩了。

正确做法是按板子 ID 划分。如果数据集里没有板子 ID 信息,至少按拍摄批次划分——同一批次拍的图要么全在训练集,要么全在验证集。600 张图如果来自 50 块板子,按 8:2 划分就是 40 块板子训练、10 块板子验证。

验证这个划分是否合理的方法:算一下训练集和验证集的图片相似度。如果验证集里有图和训练集的某张图 SSIM 超过 0.9,说明划分有问题。这个检查用 OpenCV 就能做,花几分钟跑一遍,比训练完发现过拟合再回头查要划算得多。

5. 避坑与排查:PCD 缺陷检测训练中最容易翻车的 5 个点

5.1 现象:训练 loss 正常下降但 mAP 始终为 0

原因:标签文件的类别索引和 yaml 里的 names 顺序不一致。比如 classes.txt 里第一行是 offset,但 yaml 里 0 写的是 missing。模型学到的类别和评估时的类别对不上,mAP 自然为 0。

解决:写一个校验脚本,读 labels 下所有 txt 的 class_id 最大值,确认不超过 names 的数量。再随机抽几个标签,把 class_id 映射回类别名,和图片里的实际缺陷对照。这个检查在训练前做,五分钟搞定。

5.2 现象:验证集 mAP 很高,但推理时框位置明显偏移

原因:训练时的imgsz和推理时的输入尺寸不一致,或者推理时用了 letterbox 但训练时没用。YOLO 默认在训练和推理时都做 letterbox 填充,但如果自己写了推理脚本,很容易漏掉这一步。

解决:推理脚本里直接用model.predict(),不要自己写预处理。如果必须自己写,确保 letterbox 的缩放比例和填充值(通常是 114)与训练时一致。检查方法:拿一张训练集里的图做推理,如果框位置对不上,就是预处理的问题。

5.3 现象:低频类别(如极性反)的 AP 始终低于 0.3

原因:训练样本太少,模型没有足够梯度学习这类特征。600 张图里极性反可能只有 30 个实例,分摊到每个 epoch 就是随机出现。

解决:三步走。第一步,确认标注没问题——把所有极性反的图调出来看一遍,框的位置和类别是否正确。第二步,过采样到至少 100 个实例。第三步,如果还不行,考虑用预训练模型做特征提取,只训练检测头。YOLOv8 支持冻结 backbone,model.train(freeze=10)冻结前 10 层。

5.4 现象:模型把正常焊盘误检为缺件

原因:标注时把空焊盘标成了缺件,或者训练集里缺件的负样本不足。模型没学会区分“本该有元件但缺失”和“本来就没有元件的位置”。

解决:检查标注规范。缺件的定义是“BOM 里有但实际没有”,空焊盘如果本来就不该有元件,不能标缺件。另外,在训练集里加入一些正常板的图片作为负样本,让模型看到没有缺陷的板子长什么样。负样本比例控制在 10%~20%。

5.5 现象:训练到 50 epoch 后验证 loss 开始上升

原因:过拟合。600 张图对 YOLOv8s 来说太少了,模型开始记住训练集的噪声。

解决:先看早停有没有生效。如果patience=30,验证 loss 上升 30 轮后应该自动停止。如果没有停止,检查patience参数是否被正确传入。另外可以加 dropout、减小模型容量(换 yolov8n)、或者增加数据增强强度。最根本的办法还是加数据,600 张确实紧张,能标到 1000 张以上会稳很多。

6. 把 600 张图用到极致:交叉验证与模型集成的具体做法

600 张图做单次训练验证划分,验证集只有 120 张左右,评估结果的方差很大。同样的配置跑两次,mAP 可能差 5 个点。这不是模型的问题,是验证集太小导致的评估噪声。解决办法是 K 折交叉验证:把数据分成 5 折,每次用 4 折训练、1 折验证,跑 5 次,取平均指标。这样每个样本都被验证过一次,评估结果稳定得多。

具体操作:按板子 ID 分成 5 组,每组 10 块板子。写一个循环,每次选一组做验证,其余做训练。YOLOv8 的data参数指向不同的 yaml 文件即可。5 次训练可以并行跑在不同 GPU 上,也可以串行跑,总时间大约是单次训练的 5 倍。如果时间紧张,做 3 折也行,比单次划分强。

import yaml from ultralytics import YOLO board_ids = list(range(50)) # 假设 50 块板子 folds = [board_ids[i::5] for i in range(5)] # 5 折 for fold_idx in range(5): val_boards = folds[fold_idx] train_boards = [b for b in board_ids if b not in val_boards] # 根据 board_id 生成对应的 train/val 图片列表 # 这里假设图片文件名里包含 board_id train_images = [f for f in all_images if get_board_id(f) in train_boards] val_images = [f for f in all_images if get_board_id(f) in val_boards] # 写临时 yaml data_cfg = { "path": "./PCD_Dataset", "train": train_images, "val": val_images, "names": class_names_dict } with open(f"fold_{fold_idx}.yaml", "w") as f: yaml.dump(data_cfg, f) model = YOLO("yolov8s.pt") model.train(data=f"fold_{fold_idx}.yaml", epochs=150, imgsz=640, batch=16)

跑完 5 折后,你会得到 5 个模型。推理时把 5 个模型的预测框做加权框融合(WBF),比单个模型的 mAP 通常能高 2~3 个点。WBF 的实现可以用ensemble-boxes这个库,或者自己写 NMS 的变体。集成带来的收益在低频类别上尤其明显——单个模型漏检的极性反,可能被另一个模型检出来。

最后一个习惯:每次训练完,把配置文件、数据集划分、随机种子、最终指标记在一个 markdown 文件里。我吃过亏,三个月后回头想复现某个结果,发现忘了当时用的哪版数据划分。现在每次训练完花两分钟记一笔,省的是未来的后悔药。希望帮到你。

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

返回列表