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_objectfrom ultralytics import YOLO model YOLO(yolov8s.pt) results model.train( datapcd_defect.yaml, epochs150, imgsz640, batch16, lr00.001, lrf0.01, warmup_epochs5, weight_decay0.0005, mosaic0.5, mixup0.1, copy_paste0.1, patience30, save_period10, device0 )逐项说明关键参数。yolov8s.pt选 small 版本而不是 nano因为工业缺陷的纹理特征比较细nano 的容量可能不够。epochs150配合patience30意思是 30 轮验证集指标不提升就早停小样本训练很容易过拟合早停是必须的。lr00.001比默认的 0.01 小一个量级小样本场景下学习率太大会导致 loss 震荡。mosaic0.5而不是默认的 1.0因为工业图的背景相对固定过度拼接会引入不真实的上下文。copy_paste0.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_sampleslen(sample_weights), replacementTrue)class_weights是类别频率的倒数稀有类别权重高。sample_weights是每个样本包含的所有类别权重的均值。replacementTrue表示有放回采样低频样本会被重复抽到。这个采样器传给 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 支持冻结 backbonemodel.train(freeze10)冻结前 10 层。5.4 现象模型把正常焊盘误检为缺件原因标注时把空焊盘标成了缺件或者训练集里缺件的负样本不足。模型没学会区分“本该有元件但缺失”和“本来就没有元件的位置”。解决检查标注规范。缺件的定义是“BOM 里有但实际没有”空焊盘如果本来就不该有元件不能标缺件。另外在训练集里加入一些正常板的图片作为负样本让模型看到没有缺陷的板子长什么样。负样本比例控制在 10%~20%。5.5 现象训练到 50 epoch 后验证 loss 开始上升原因过拟合。600 张图对 YOLOv8s 来说太少了模型开始记住训练集的噪声。解决先看早停有没有生效。如果patience30验证 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(ffold_{fold_idx}.yaml, w) as f: yaml.dump(data_cfg, f) model YOLO(yolov8s.pt) model.train(dataffold_{fold_idx}.yaml, epochs150, imgsz640, batch16)跑完 5 折后你会得到 5 个模型。推理时把 5 个模型的预测框做加权框融合WBF比单个模型的 mAP 通常能高 2~3 个点。WBF 的实现可以用ensemble-boxes这个库或者自己写 NMS 的变体。集成带来的收益在低频类别上尤其明显——单个模型漏检的极性反可能被另一个模型检出来。最后一个习惯每次训练完把配置文件、数据集划分、随机种子、最终指标记在一个 markdown 文件里。我吃过亏三个月后回头想复现某个结果发现忘了当时用的哪版数据划分。现在每次训练完花两分钟记一笔省的是未来的后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表