PCB缺陷检测数据集实战:9666张图、8类标注与YOLO训练全流程
简介这份资源是面向工业视觉检测与深度学习实践者的电路板PCB缺陷检测数据集采用Pascal VOC与YOLO双格式标注适合目标检测模型训练、算法验证及课程项目使用。压缩包内共1个docx文件约3.38MB文档中提供百度云下载地址数据集本身包含9666张jpg图片及对应的xml与txt标注文件覆盖falsecopper、missinghole、mousebite、opencircuit、pinhole、scratch、shortcircuit、spur共8类缺陷总标注框数达51549个使用labelImg按矩形框规则完成标注。数据集内含较多椒盐噪声增强图片可提升模型鲁棒性。目前已有223人浏览学习。需要留意的是该资源原设为0积分若页面显示非0积分请谨慎下载作者无法处理积分退还。整体而言这份数据适合直接投入PCB缺陷检测训练与对比实验省去自行采集与标注成本。1. 9666 张 PCB 缺陷图8 类标注VOC 和 YOLO 双格式到底怎么用板子打样回来AOI 设备还没到位老板让你先搞个能跑起来的缺陷检测 demo。你翻遍开源数据集要么是钢片表面、要么是轴承振动信号真正落到 PCB 裸板上的少得可怜。这份 9666 张的 PCB 缺陷检测数据集恰好卡在这个缺口上——8 个类别全是产线高频缺陷falsecopper伪铜、missinghole缺孔、mousebite鼠咬、opencircuit开路、pinhole针孔、scratch划伤、shortcircuit短路、spur毛刺总标注框 51549 个Pascal VOC 的 xml 和 YOLO 的 txt 一一对应labelImg 画的矩形框拿来就能训。它适合两类人一是刚接触缺陷检测、想用真实工业图跑通 YOLO 全流程的工程师二是手里有产线数据但标注量不够、想拿它做预训练或类别补充的团队。需要提前说清楚的是数据集里混了不少椒盐噪声增强图这不是瑕疵是作者刻意做的鲁棒性扩充后面章节会专门讲怎么处理这批图不然你的验证集指标会很难看。2. 拆开压缩包先看什么目录结构、标注格式与类别分布2.1 目录长什么样VOC 和 YOLO 怎么摆下载解压后你大概率会看到两个平行目录一个放 VOC 体系一个放 YOLO 体系图片是同一批 jpg只是标注文件不同。VOC 那边每张图配一个同名 xmlYOLO 那边每张图配一个同名 txt。注意项目说明里写得很直白不包含分割路径的 txt 文件也就是说没有 train/val 的划分清单这个得你自己切。常见做法是按 8:1:1 或 7:2:1 随机分但 PCB 缺陷有个坑——同一块板子可能被裁成多张图如果纯随机切训练集和验证集里会出现同一块板的近邻图指标虚高。我一般会先按图片文件名前缀聚类再按簇划分这样验证集才可信。先跑一段脚本确认文件配对完整别急着开训import os from pathlib import Path voc_img_dir Path(VOC/JPEGImages) voc_xml_dir Path(VOC/Annotations) yolo_img_dir Path(YOLO/images) yolo_lbl_dir Path(YOLO/labels) # 检查 VOC 图片与 xml 是否一一对应 voc_imgs {p.stem for p in voc_img_dir.glob(*.jpg)} voc_xmls {p.stem for p in voc_xml_dir.glob(*.xml)} print(VOC 图片数:, len(voc_imgs), XML 数:, len(voc_xmls)) print(VOC 缺失 xml 的图:, list(voc_imgs - voc_xmls)[:5]) print(VOC 缺失 jpg 的 xml:, list(voc_xmls - voc_imgs)[:5]) # 检查 YOLO 图片与 txt 是否一一对应 yolo_imgs {p.stem for p in yolo_img_dir.glob(*.jpg)} yolo_txts {p.stem for p in yolo_lbl_dir.glob(*.txt)} print(YOLO 图片数:, len(yolo_imgs), TXT 数:, len(yolo_txts)) print(YOLO 缺失 txt 的图:, list(yolo_imgs - yolo_txts)[:5])这段脚本干的事很简单用Path.glob把 jpg 和标注文件的 stem不带扩展名的文件名各自收成集合做差集就能看出谁缺了谁。参数上唯一要注意的是路径不同人解压后的顶层目录名可能不一样按你实际的改。如果差集非空先别往下走缺标注的图要么补要么删混进去训练就是纯噪声。2.2 8 个类别和 51549 个框分布并不均匀把类别和框数摊开看能提前判断模型会偏哪边类别名中文含义框数falsecopper伪铜4852missinghole缺孔4743mousebite鼠咬7883opencircuit开路8033pinhole针孔7453scratch划伤5543shortcircuit短路6737spur毛刺6305opencircuit 和 mousebite 最多falsecopper 和 missinghole 最少最多和最少差了将近一倍。这个差距不算极端但如果你直接开训不做任何处理falsecopper 的召回大概率垫底。常见做法有两种一是用 YOLO 自带的类别权重在 loss 里给少样本类加权二是对少样本类做定向增强但这份数据集本身已经带了椒盐噪声增强图再叠增强要小心过拟合到噪声上。我一般先跑一版 baseline看混淆矩阵里 falsecopper 和 missinghole 被误判成什么再决定要不要加权。2.3 VOC 转 YOLO 的坐标换算别在这里翻车虽然数据集同时给了两种格式但你如果拿到的是纯 VOC 版本或者想自己重新生成 YOLO 标签坐标换算这一步必须自己写。VOC 的 xml 里是xmin, ymin, xmax, ymax的绝对像素值YOLO 要的是归一化后的cx, cy, w, h中心点加宽高全部除以图片宽高。公式不复杂但边界情况多import xml.etree.ElementTree as ET from PIL import Image def voc_to_yolo(xml_path, img_path, class_list): tree ET.parse(xml_path) root tree.getroot() img Image.open(img_path) iw, ih img.size lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_list: 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) # 裁剪到图像边界防止标注越界 xmin max(0, min(xmin, iw - 1)) xmax max(0, min(xmax, iw - 1)) ymin max(0, min(ymin, ih - 1)) ymax max(0, min(ymax, ih - 1)) cx (xmin xmax) / 2.0 / iw cy (ymin ymax) / 2.0 / ih w (xmax - xmin) / iw h (ymax - ymin) / ih lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines class_list [falsecopper, missinghole, mousebite, opencircuit, pinhole, scratch, shortcircuit, spur]关键在max(0, min(..., iw-1))这几行labelImg 画框时手抖拖出图像边界是常事不裁剪的话归一化后会出现负数或大于 1 的值YOLO 训练时直接报错或者静默丢框。class_list的顺序必须和你的data.yaml里names的顺序完全一致差一位整个类别就错位了这种错误训练 loss 看着正常但推理结果全是乱的属于血泪经验。3. 用 YOLO 跑通第一版data.yaml、训练命令与椒盐噪声图的处理3.1 data.yaml 怎么写路径和类别顺序YOLO 系列v5/v8/v11 都类似靠一个 yaml 文件告诉它去哪找图、有几类、类名叫什么。这份数据集 8 类写出来是这样path: /data/pcb_defect train: images/train val: images/val test: images/test nc: 8 names: 0: falsecopper 1: missinghole 2: mousebite 3: opencircuit 4: pinhole 5: scratch 6: shortcircuit 7: spurpath是数据集根目录train/val/test是相对路径指向存放 jpg 的文件夹。YOLO 默认会去同级的labels目录找同名 txt所以你的目录结构最好是images/train配labels/train。nc和names的索引必须和 txt 里第一列的数字对上这份数据集原始类别顺序就是上面这个别自己重排。如果重排了要么改 txt要么改 names两边必须同步。3.2 椒盐噪声增强图留着还是剔掉项目说明里那句“数据集里面有很多椒盐噪声生成增强图片”是整份资源最需要认真对待的一句话。椒盐噪声就是在图像上随机撒黑白像素点作者用它做增强目的是让模型对传感器噪声、传输干扰更鲁棒。但问题在于如果训练集和验证集里都混了大量这种图你看到的 mAP 反映的是“模型在噪声图上的表现”而不是“模型在干净产线图上的表现”。我一般会分两步走第一步先按文件名或图像统计特征把噪声图识别出来打上标记第二步训练时全部保留但验证集只用干净图这样指标才对应真实场景。识别椒盐噪声图有个简单办法统计图像中纯黑0和纯白255像素的占比正常 PCB 图这两个值不会太高噪声图会明显偏高import cv2 import numpy as np from pathlib import Path def noise_ratio(img_path): img cv2.imread(str(img_path), cv2.IMREAD_GRAYSCALE) total img.size black np.sum(img 0) white np.sum(img 255) return (black white) / total img_dir Path(YOLO/images/train) ratios [(p.name, noise_ratio(p)) for p in img_dir.glob(*.jpg)] # 按噪声比例排序观察分布选一个阈值比如 0.05做标记 ratios.sort(keylambda x: x[1], reverseTrue) for name, r in ratios[:10]: print(name, round(r, 4))noise_ratio返回纯黑纯白像素占总像素的比例正常图一般在 0.01 以下椒盐噪声图能到 0.05 甚至更高。阈值不是固定的你得先打印前几十张看看分布找一个明显断层的位置。标记完之后验证集只抽干净图训练集全量保留这样既利用了增强图的鲁棒性收益又不让指标失真。3.3 训练命令和几个必调参数假设你用 YOLOv8一条命令能跑起来yolo detect train \ data/data/pcb_defect/data.yaml \ modelyolov8s.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ projectruns/pcb \ namebaselinemodel选yolov8s而不是n是因为 PCB 缺陷里 scratch、spur 这类细长目标对特征分辨率要求高n 版容易漏。imgsz640是默认值如果你显存够可以上 1024小缺陷的召回会明显好一截但 batch 要相应降到 8 或 4。patience20是早停100 轮里如果 20 轮验证指标不涨就停省时间。lr00.01是初始学习率如果你从预训练权重起步这个值可以如果从头训建议降到 0.001 并加 warmup。训练起来之后重点盯两个东西一是cls_loss有没有正常下降如果一直震荡多半是类别顺序或标签格式有问题二是验证集的mAP50和mAP50-95前者看召回后者看定位精度。PCB 缺陷检测里定位精度往往比分类更重要因为后续要引导返修框歪了工人找不到位置。4. 避坑与排查标注、类别和训练里最容易翻车的几件事4.1 现象训练 loss 正常下降但推理框全错位原因VOC 转 YOLO 时坐标没归一化或者归一化时用错了图片尺寸。labelImg 保存的 xml 里size字段有时和实际 jpg 尺寸不一致如果你信了 xml 里的宽高而不是用 PIL 重新读图换算出来的 cx/cy 就是错的。解决永远用Image.open(img_path).size拿真实宽高别信 xml 里的width/height。转换完随机抽 20 张用 OpenCV 把 YOLO 框画回图上肉眼核对这一步花五分钟能省几小时排查。4.2 现象某一类召回率极低混淆矩阵里全被预测成相邻类原因类别索引错位。比如data.yaml里shortcircuit是 6但 txt 里 6 对应的是scratch模型学到的就是错的映射。这种错误 loss 曲线看不出来只有看混淆矩阵才暴露。解决写个脚本统计所有 txt 第一列的取值分布和data.yaml的names数量对齐。8 类的话第一列应该只出现 0 到 7出现 8 或负数就是有问题。4.3 现象验证集 mAP 很高但拿产线新图一测就崩原因验证集里混了和训练集同源的椒盐噪声增强图模型学到的是噪声模式而不是缺陷特征。或者划分时同一块板的图同时进了训练和验证。解决按前面说的验证集只留干净图并且按图片前缀聚类划分。如果条件允许留一小批完全没参与训练的产线图做最终测试这个数字才是能拿去汇报的。4.4 现象训练到一半显存爆了或者速度突然变慢原因imgsz设太大加上batch没降或者 dataloader 的workers开太多导致内存碎片。YOLO 长时间运行后内存缓慢上涨也是常见问题和数据集本身无关是框架层面的缓存累积。解决显存不够就降 batch 或 imgsz别硬撑。长时间训练建议每隔几十轮存一次 checkpoint并且用--cache ram要谨慎9666 张图全缓存进内存对机器有要求磁盘缓存更稳。4.5 现象某些图标注框宽高为 0 或极小原因labelImg 画框时双击或误触生成了退化框。这种框在 YOLO 里会导致 loss 计算异常。解决转换后过滤掉 w 或 h 小于 0.001 的框或者在 xml 阶段就检查xmax xmin且ymax ymin。数据集整体标注质量不错但 9666 张里难免有几张手滑的清洗一遍不亏。5. 进阶把这份数据集用出第二层价值baseline 跑通之后这份数据集还能再榨出两层价值。第一层是做预训练。PCB 缺陷检测的公开数据不多你可以先用这 9666 张训一个 backbone再拿自己产线的少量标注图做微调收敛速度和最终精度通常都比直接从 COCO 预训练权重起步要好因为域更接近。微调时把lr0降到 0.001 甚至 0.0005冻结前几层只训 neck 和 head几十张图就能看到效果。第二层是类别补充。你产线上如果有这份数据集没覆盖的缺陷类型比如 copperresidue残铜或者 holeoffset孔偏可以把自己的图按同样格式标注和这 8 类合并训练。合并时注意类别索引要重新排data.yaml的names和所有 txt 的第一列都要同步改改完务必再跑一遍 4.2 里的分布检查。我一般会写一个merge_dataset.py把两个来源的图复制到统一目录标注统一重编号跑完打印每类框数确认没有类别塌缩再开训。验证方法上除了看 mAP建议加一个按类别拆分的召回表。PCB 产线最怕的是漏检尤其是 shortcircuit 和 opencircuit 这种致命缺陷漏一个可能整板报废。你可以在验证脚本里单独统计这两类的 recall设一个阈值比如 shortcircuit 召回低于 0.95 就不允许上线哪怕整体 mAP 再高也不行。这个习惯是我从一次翻车里养成的当时整体 mAP 0.89 看着不错结果上线后 shortcircuit 漏检率偏高返修线堆了一堆板子。从那以后我每次训完都强制走一遍分类别召回检查不达标就不推。数据集本身不保证任何模型精度这话项目说明里写了也是实话。标注质量我抽查下来是合理的框贴得比较紧类别定义也清晰但最终效果取决于你的划分策略、增强策略和阈值调优。把它当成一块靠谱的起跑板而不是终点。希望帮到你。本文还有配套的精品资源点击获取