☰
PCB缺陷检测数据集全解析:693张到6930张增强与YOLOv8训练实战
2026/10/11 14:18:39 网站建设 项目流程

简介:面向PCB工业质检与计算机视觉研究的一份标准缺陷检测数据集,源自北京大学,将693张原始图像通过数据增强扩展至6930张,可支撑目标检测、图像分类、语义分割等模型的训练与验证。包内共1346个文件,以jpg图像、xml标注和txt标签文件为主体,xml采用VOC格式记录缺陷框位置与类别,txt对应类别或坐标信息,另附npy与cache格式的数据缓存,便于深度学习框架直接读取和加速训练。整体压缩包约584MB,目录组织清晰,图像与标注一一对应,适合刚入门缺陷检测的研究者,也适合需要标准数据验证算法的工程师。目前已有2223人学习使用,数据规模适中,涵盖多余铜箔等典型PCB缺陷类别。借助这份数据集,可快速完成从数据加载、模型训练到缺陷识别的完整流程,并对比不同检测网络在真实质检场景下的表现。

1. PCB板缺陷检测数据集:一份693张扩充到6930张的标注资源能解决什么问题

做PCB外观检测的同行应该都有共鸣:产线上缺陷样本永远不够用,漏孔、鼠咬、短路这类典型缺陷各拍几十张就没了,模型还没训明白就过拟合。这份数据集把原始693张PCB缺陷图像,通过数据增强扩充到6930张,每一张都带目标检测标注,覆盖漏孔、鼠咬、断路、短路、毛刺、多余铜箔六类典型缺陷。它适合正在做AOI视觉方案选型的算法工程师、需要一份带标签缺陷数据来跑通检测管线的开发者,以及做质检设备集成的工程师。拿到手可以直接进YOLOv8训练,也可以作为自研数据增强流程的验证基准。

2. 数据集里有什么:693张到6930张的构成与标注格式

2.1 六类缺陷是PCB AOI的标配分类

PCB裸板在制造过程中,典型的缺陷类型比较固定。拆这份数据集时,第一步就是按类别归档所有缺陷切片,因为类别分布直接决定后续增强策略怎么定。数据集覆盖的六类缺陷如下:

缺陷类别英文标识典型形态与成因
漏孔missing_hole钻孔工序漏钻,焊盘处该有的孔位缺失
鼠咬mouse_bite走线边缘出现凹口,多由蚀刻过度或机械损伤造成
断路open_circuit铜箔走线中间断开,蚀刻或应力导致
短路short两条不该连通的走线粘连,蚀刻不足
毛刺spur走线边缘突起的尖刺状铜箔,蚀刻残留
多余铜箔copper基板上残留的孤立铜块,工艺清洁不到位

六类缺陷的视觉特征差异很大。short和open_circuit这类缺陷在图像上通常表现为线宽或连通性异常,模型学起来相对容易;而mouse_bite和spur这类边缘型缺陷,缺陷区域可能只占整张图的几个像素,检测器对小目标的敏感性直接决定召回率。我在拿到数据集后做的第一件事,就是写脚本统计每个类别的图片数和框数,后面所有增强参数都围绕这个分布来定。

原数据693张并不是六类平均分配。公开PCB缺陷数据的常见现状是少数类别样本量偏少,比如missing_hole因为工艺控制严格,缺陷出现频率本身就低。如果拿到的版本里某一类只有几十张,训练时该类AP大概率会挂零。这一点后面避坑章节会专门展开。

2.2 标注格式与目录组织

PCB缺陷检测数据集的标注格式,公开版本里最常见的是VOC XML和YOLO TXT两种。这份数据集两种都提供,省去了格式转换的中间步骤。VOC适合可视化排查,YOLO格式可以直接喂给Ultralytics框架训练。目录结构通常长这样:

├── images/ │ ├── train/ # 增强后训练图 │ └── val/ # 验证图 ├── annotations/ │ ├── xml/ # VOC格式标注 │ └── yolo/ # YOLO格式标注 └── classes.txt # 6个类别名,每行一个

VOC格式的XML文件长这样:

<annotation> <filename>001_missing_hole.jpg</filename> <size> <width>640</width> <height>640</height> </size> <object> <name>missing_hole</name> <bndbox> <xmin>212</xmin> <ymin>308</ymin> <xmax>302</xmax> <ymax>398</ymax> </bndbox> </object> </annotation>

VOC格式的好处是直观,xmin、ymin、xmax、ymax一眼能看出框在图像上的位置,适合做可视化排查。注意XML里的filename必须和实际图片名严格一致,很多人改文件名之后忘记同步XML,导致后续格式转换时丢标,这是一个很典型的低级错误。

对应的YOLO格式是归一化坐标,每一行代表一个目标:

4 0.401562 0.551562 0.140625 0.140625

第一个整数是类别ID,对应classes.txt中的行号,从0开始计数。后面四个浮点数依次是归一化后的中心点x、中心点y、框宽w、框高h,全部除以原始图像宽高后取值范围在0到1之间。训练脚本读取时不关心原始分辨率,所以换分辨率不需要重新标注。

这里有一个关键点:类别顺序以classes.txt为准。有的版本把missing_hole排在第一行,有的把open_circuit排在第一行,如果你要重新排列类别顺序,必须同步修改所有txt文件里的ID,否则训练出来的模型类别全是错位的。我习惯先打开classes.txt确认行号,再抽查几个txt标注验证ID对应关系,这一步比预想中更容易出事。

2.3 693到6930的扩充逻辑

原始693张图按10倍增强变成6930张,是开源PCB缺陷数据集里比较常见的做法。增强对象是真实缺陷样本,不是背景图,这样做的价值在于每张增强图都保留了至少一个真实缺陷框,不会出现空白负样本干扰训练。

增强后的6930张,按8:2划分训练集和验证集,训练集大约5500张。对YOLOv8s这类轻量检测模型来说,这个量级足够完成一轮收敛良好的训练。但如果业务场景对某个缺陷要求极高召回,比如漏检率要低于1%,那这个数据量仍然不够,需要对少数类别做额外增强或采集真实缺陷样本。

从693到6930,数量级跨越的核心不是图变多了,而是每个类别在train/val中都有了相对充分的正样本覆盖。但这里埋了一个隐患:如果增强是在划分train/val之前做的,且增强脚本把原始集和增强集混合存放,同源的图片可能同时出现在训练集和验证集里,造成val指标虚高。这个现象在第五章会给出具体的排查和解决手段。

3. 数据增强怎么做:albumentations增强策略、脚本与参数边界

3.1 增强策略为什么优先选几何变换

把693张扩成6930张,核心问题不是用什么库,而是怎么组合变换。PCB缺陷图像和自然图像有本质区别:背景是规则的基板纹理,前景是铜箔走线和焊盘,缺陷的形态与方向受物理工艺约束。我制作增强方案时遵循三条原则。

第一,几何变换优先。水平翻转、垂直翻转、旋转90度对PCB图像天然友好,因为铜箔走线翻转后依然属于同一类缺陷,标注框在几何变换下的数学关系简单可靠。第二,像素级扰动做辅助,幅度必须克制。亮度、对比度、噪声能提升模型鲁棒性,但PCB基板颜色相对稳定,扰动过大会破坏缺陷边缘的清晰度,尤其是mouse_bite和spur这类边缘型缺陷,边缘一旦糊掉,模型基本学不到有效特征。第三,所有变换必须标注同步。几何变换时框的坐标要跟着图一起变,像素扰动不影响框,但实际操作中容易因为代码顺序写错导致图和标注脱节。

还需要考虑增强的边界。短路和断路这类缺陷与走线连通性相关,翻转后依然是短路或断路,可以放心增强;鼠咬和毛刺是边缘缺陷,翻转旋转也不改变其本质。但如果某个缺陷在工艺上有明确的方向性,比如特定方向的划伤,那旋转角度就要收敛,避免生成现实中不存在的样本。

3.2 一个可复现的albumentations增强脚本

albumentations的bbox变换机制比较完善,自动处理旋转、翻转后的框坐标同步,我一般优先选它。下面这个脚本演示如何从原始训练集生成10倍增强数据:

import albumentations as A import cv2 import glob import os transform = A.Compose([ A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.Rotate(limit=90, p=0.7), A.RandomBrightnessContrast( brightness_limit=0.2, contrast_limit=0.2, p=0.5 ), A.GaussNoise(var_limit=(10.0, 50.0), p=0.3), ], bbox_params=A.BboxParams( format="yolo", label_fields=["labels"], )) img_dir = "images/train" ann_dir = "annotations/yolo" out_img_dir = "augmented/images" out_ann_dir = "augmented/annotations" os.makedirs(out_img_dir, exist_ok=True) os.makedirs(out_ann_dir, exist_ok=True) for img_path in glob.glob(f"{img_dir}/*.jpg"): img = cv2.imread(img_path) txt_path = os.path.join( ann_dir, os.path.basename(img_path).replace(".jpg", ".txt") ) with open(txt_path) as f: boxes, labels = [], [] for line in f: parts = line.strip().split() boxes.append([float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4])]) labels.append(int(parts[0])) for i in range(10): # 每张原图生成10张增强图 augmented = transform(image=img, bboxes=boxes, labels=labels) base = os.path.basename(img_path).replace(".jpg", f"_{i}.jpg") out_img = os.path.join(out_img_dir, base) out_txt = out_img.replace(".jpg", ".txt") cv2.imwrite(out_img, augmented["image"]) with open(out_txt, "w") as f: for bbox, label in zip(augmented["bboxes"], augmented["labels"]): f.write(f"{label} " + " ".join([str(x) for x in bbox]) + "\n")

这段代码的逻辑:读取原始训练图像和YOLO格式标注,逐张循环生成10张增强图,同时通过bbox_params把标注框同步变换,并写入新的txt文件。

几个参数值得细说。bbox_params里format="yolo"告诉albumentations输入的框是归一化中心点格式,它在旋转、翻转时会自动完成坐标变换。label_fields=["labels"]把类别标签和框绑定在一起,避免框和类别错位。A.Rotate的limit=90表示随机旋转角度在正负90度之间,对PCB这类方向无关的纹理可以放开;但如果目标有明确上下方向,limit要收窄到15到30度。GaussNoise的var_limit是高斯噪声方差范围,PCB背景相对干净,var_limit上限超过50会让图像看起来明显的噪,模型被迫去学降噪而不是学缺陷特征。每张图生成10张,正好对应693到6930的10倍关系。

3.3 增强参数的边界条件与翻车案例

增强参数不是越大越好。我见过有人把亮度扰动拉到0.5,结果增强后的图像里铜箔和基板的对比度反转,模型把整块基板当成了缺陷。PCB图像的光照相对均匀,brightness_limit在0.2左右是安全区间,超过0.3就要人工抽图确认。

旋转角度也要结合缺陷形态。spur这类尖刺状缺陷,旋转90度后形态特征依然成立;但如果旋转22度,畸变会让细小的尖刺发生锯齿化,反而改变了缺陷本来的模样。我一般习惯用90的整倍数旋转配合小角度旋转(±10度)混合,这样既有方向多样性,又不破坏小目标结构。

增强后的命名规则同样重要。脚本里用原始文件名加后缀的方式命名,比如001.jpg生成001_0.jpg到001_9.jpg,这样始终能追溯到每张增强图的原始样本。如果你重新组织数据集时用了不同的命名规则,建议保留一份映射表,方便排查问题。

4. 用YOLOv8训练这份数据集:data.yaml配置、超参数与评估指标

4.1 整理数据集目录与data.yaml

YOLOv8训练的第一步是把数据集整理成标准结构,然后写一个data.yaml告诉训练器数据和类别的信息。标准目录结构如下:

datasets/ ├── pcb/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ ├── labels/ │ │ ├── train/ │ │ └── val/ │ └── data.yaml

labels目录里的txt文件名必须和images目录里的jpg文件名一一对应。这一步看起来简单,实际容易翻车:Windows下批量复制文件时可能自动追加后缀,或者文件名大小写不一致,训练时会报"No labels found"之类的错误。我通常写一个脚本校验两边文件名是否完全匹配,不匹配的一律先处理掉。

data.yaml的写法如下:

train: ./datasets/pcb/images/train val: ./datasets/pcb/images/val nc: 6 names: ['missing_hole', 'mouse_bite', 'open_circuit', 'short', 'spur', 'copper']

train和val路径建议用绝对路径,或者固定工程根目录后用相对路径。names列表的顺序必须和classes.txt顺序一致,否则训练出来的类别标签会错位。nc是类别数量,和names列表长度必须一致,多一个少一个都会在训练启动时报错。不需要写test字段,val会在训练结束后自动承担验证功能。

4.2 训练命令与超参数的选择逻辑

环境装好之后,训练命令很简单:

yolo detect train \ data=pcb.yaml \ model=yolov8n.pt \ epochs=200 \ imgsz=640 \ batch=16 \ device=0 \ close_mosaic=20

模型权重我建议从yolov8n或yolov8s起步。这个数据集只有6930张图,轻量模型完全够用,训练速度快,迭代调试成本低。epochs设200比较稳妥,配合Ultralytics自带的早停机制,实际训练通常到120到150轮就会收敛。imgsz选640是因为PCB缺陷切片的原始尺寸不大,缩放到640不会丢失关键细节,如果切片本身只有300像素左右,可以用512。

batch大小根据显存调整,16G显存跑yolov8n用batch=16没问题,如果显存吃紧降到8。device=0指定第一张GPU,没有GPU可以去掉这个参数用CPU训练,但速度会慢一个量级。

close_mosaic=20这个参数容易被忽略。YOLOv8默认开启mosaic增强,把四张图拼接成一张输入,目的是提升小目标检测能力。但mosaic会改变缺陷的上下文关系,在训练后期让模型在接近真实分布的输入上微调是好习惯。常见做法是在最后20个epoch关闭mosaic,让模型收敛到更准确的特征表达上。

4.3 评估指标怎么看不翻车

训练完成后,重点看val输出里的mAP50和mAP50-95。mAP50衡量框在IoU 0.5下的检测精度,是最常见的单指标;mAP50-95是对更严格IoU要求的综合指标,数值通常比mAP50低不少。对于PCB缺陷检测,漏检的成本远高于误检,所以我更看重recall。如果val上的recall偏低,说明有缺陷根本没被框出来,问题大概率不在训练参数,而在数据层面。

一个容易翻车的点:精确率和召回率在类别不均衡时会出现明显分化。spur和copper这类样本多的类别P和R都高,missing_hole这类样本少的类别mAP被拉低。总体mAP看起来还行,逐类看才发现某一类几乎没检测到。正确做法是训练完逐类别看AP,而不是只看一个总指标。这个过程可以命令实现:

yolo detect val \ data=pcb.yaml \ model=runs/detect/train/weights/best.pt \ save_json=True

在生成的results.json里找到per-class的AP数值,对照六类缺陷的样本分布,判断哪些类别需要补充数据或额外增强。这一步是我每次训练完必做的动作,能快速定位数据质量和模型能力的短板。

5. 避坑与常见问题排查:标注错位、类别不平衡与指标虚高

5.1 增强后标注框错位,检测框全部偏移

现象:训练时loss正常下降,但val的mAP突然掉到0.1以下,可视化检测结果发现框整体偏移或尺寸异常。

原因:增强脚本在bbox_params里没有处理边界裁剪。旋转或平移后,部分框坐标超出图像边界,albumentations默认不会自动裁剪这些框,超出边界的坐标直接写入txt,训练时模型看到的是大量错误标注。

解决:在BboxParams里加上clip=True,让超出边界的坐标被裁剪到图像范围内。同时建议在增强后做一个随机抽检,画5到10张增强图和标注框,人工确认没有错位再进训练管线。

5.2 少数类AP挂零,多数类AP很高

现象:spur和copper的AP都超过0.8,missing_hole的AP只有0.2甚至为0。

原因:原始数据里少数类本身样本量就少,增强后虽然总量到了6930张,但missing_hole可能只有两三百张,正样本绝对数量不足。模型在训练时几乎没有见过足够多的missing_hole实例,自然学不到特征。

解决:先统计每个类别的框数量,对少数类别单独做额外增强。常见做法是写一个类别的条件增强脚本,只对包含指定类别的图片做更多的翻转变换,同时保持图片总数不变或适当调整。也可以用类别加权采样,让少数类在每轮训练中的出现频率更高。

5.3 验证集指标虚高,现场表现明显不符

现象:训练集loss不低,val指标却很好,模型上线后在实际切片上表现远不如验证结果。

原因:train/val划分发生在数据增强之前,增强后同源图片同时出现在训练集和验证集里。验证集中包含和训练集同源的图片,模型相当于做过一遍类似的题,val指标虚高是必然结果。

解决:把原始693张按类别比例先划分成train和val,再做增强。增强只作用于train部分,val直接使用原始图片。这样能保证验证集的独立性,指标反映的是真实泛化能力。我在拆数据集时习惯在划分后给每个文件记录它属于原始集还是增强集,方便追溯。

5.4 训练时报图像尺寸错误

现象:训练启动时报错Image size should be greater than 32,训练中断。

原因:增强后的部分图片在裁剪和缩放后被缩到极小尺寸,尤其是旋转后带黑边的图,缩放填充时发生了尺寸坍缩。检查对应图片会发现长边只有几十像素。

解决:在增强脚本里加一个尺寸过滤,把短边小于64的图片直接丢弃。更稳妥的做法是在训练前统一做一次preprocess,把所有图片resize到imgsz的整数倍附近,避免训练器在加载阶段发现异常尺寸。参数里imgsz设640,图片最小边最好不要小于160,过小的输入会严重损失小缺陷的细节。

6. 拿到数据先做验收:分布检查、一致性校验与分类别AP验证

数据集的验收环节很多人会跳过,直接进训练。我的习惯是花几分钟先跑一个快速校验脚本,确认这份数据值得投入训练时间。第一步是统计每个类别的框数量分布,第二步检查txt标注和图片的对应关系,第三步抽查几张图可视化标注效果。

import os from collections import Counter img_dir = "datasets/pcb/images/train" ann_dir = "datasets/pcb/labels/train" cls_count = Counter() for ann_file in os.listdir(ann_dir): img_file = ann_file.replace(".txt", ".jpg") if not os.path.exists(os.path.join(img_dir, img_file)): print(f"missing image: {img_file}") continue with open(os.path.join(ann_dir, ann_file)) as f: for line in f: cls_id = line.split()[0] cls_count[cls_id] += 1 for cls_id, count in sorted(cls_count.items()): print(f"class {cls_id}: {count} boxes")

这段脚本能快速暴露三个问题:缺失图片的对应关系、空的标注文件、类别极端不平衡。跑完这一步,我还会对比增强前后的类别分布,如果某个类别增强后占比仍然低于5%,基本可以预判这个类别的AP不会好看。

训练完之后,逐类AP是另一个验收维度。把验证集按类别拆分计算AP,如果某类AP明显低于均值,优先怀疑该类的样本量和标注质量,而不是急着调超参数。我遇到过标签错标的情况,一个框标注内容张冠李戴,模型在验证集上对这个类的AP直接掉了10个点。用混淆矩阵能快速定位是哪些类别互相混淆,检查对应样本的标注是否准确。

从那以后,我每次拿到新数据集都会强制走一遍校验流程:先跑分布统计,再跑一致性检查,训练完再看分类别AP。多花五分钟做数据验收,能省下后面几小时的排错时间。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询