简介:PCB电路板缺陷检测识别数据集,面向计算机视觉工程师、工业质检人员及科研工作者,可用于搭建基于YOLOv9的电路板缺陷识别系统,解决生产中的外观质检与缺陷分类问题。包内共2000个文件,包含702张JPG缺陷样本图片、1297个TXT标注文件及1个YAML数据配置文件。TXT文件采用YOLOv9标准格式,坐标信息与图片一一对应;YAML中存放类别配置,导入后即可直接训练。压缩包整体大小约120.79MB,规模紧凑,能够在普通显卡上完成数据预览、模型训练与指标验证。数据集图片取自真实拍摄场景,覆盖多种常见的电路板工艺缺陷,据作者说明模型可达到99.8%的识别准确率,具有较高的工程参考价值。目前已有434人学习访问,尤其适合缺少标注样本的开发者作为预训练基准,可显著节省数据采集与标注时间,加快缺陷检测方案落地。
1. PCB 电路板缺陷检测:这份 1297 张的 yolov9 数据集到底能做什么
上一轮我帮朋友做 AOI 复检项目,机器视觉等设备扫完 PCB 电路板之后会在屏幕上标一堆疑似缺陷,最后靠人工一张张复核,一天下来眼睛都花了。我当时想用目标检测把这一步自动掉,就在找现成数据集。这份 PCB 电路板缺陷检测数据集就是那时候淘到的:1297 张图片,按 yolov9 格式标注,标题写着 99.8% 的识别准确率。它适合两类人:一类是想快速跑通 YOLOv9 训练到部署全流程的工程师,另一类是正在做工业视觉缺陷检测、想拿真实板面数据验证思路的开发者。先泼盆冷水:1297 张不算多,精度数字也要实测确认,但它作为起步和验证数据,比多数教程里那些花瓶猫狗图有用得多。
2. 先盘清数据集的底细:目录结构、标注格式与数据划分
2.1 下载后先检查目录结构,别急着开训
数据集拿到手,第一件事不是训练,而是确认文件形态。YOLOv9 官方代码沿用了 YOLOv5 的目录习惯,一般解压后是这种结构:
pcb_defect/ ├── data.yaml ├── train/ │ ├── images/xxx.jpg │ └── labels/xxx.txt ├── valid/ │ ├── images/... │ └── labels/... └── test/ ├── images/... └── labels/...images 里是图片,labels 里是同名 txt。txt 文件每一行是一条标注,没有标注的图片对应空 txt 文件,或者干脆没有对应文件。我拿到手先跑一遍计数脚本,确认图片和标注数量对得上:
import os from pathlib import Path base = Path("pcb_defect") for split in ["train", "valid", "test"]: img_dir = base / split / "images" lbl_dir = base / split / "labels" imgs = sorted(img_dir.glob("*.jpg")) lbls = sorted(lbl_dir.glob("*.txt")) missing_labels = [f.stem for f in imgs if not (lbl_dir / (f.stem + ".txt")).exists()] print(f"{split}: images={len(imgs)} labels={len(lbls)} missing_labels={len(missing_labels)}")逻辑说明:Path.glob("*.jpg")列出所有 jpg 文件,再逐个检查同名 txt 是否存在。missing_labels里出现文件名,说明标注丢了,这种图训练时会被跳过,白白浪费样本。
参数说明:如果你的图片有 png 后缀,把 glob 里的*.jpg换成*.png或直接*.jpeg。1297 张图的数据集,这一步花不到一分钟,但能阻止你带病训练。
顺带检查一下有没有空 labels 文件。空 txt 文件代表负样本,即没有缺陷的正常板面,这在工业场景里很常见。如果整个数据集几乎没有空 txt,说明它全部是缺陷图,后面评估误检率时要小心。
2.2 YOLOv9 标注格式:一行的五个数字分别是什么
YOLOv9 使用的标注格式和 YOLOv5 完全一致,txt 里每一行是:
class_id x_center y_center width height其中x_center、y_center是目标框中心点的相对坐标,width、height是框的相对宽高,数值都除以了图像宽高做归一化。举个例子:
2 0.512305 0.438146 0.018750 0.013889含义是类别 id 为 2,中心点在图像横向 51.23%、纵向 43.81% 的位置,框宽占图像宽度 1.875%,框高占图像高度 1.3889%。
我写了个小脚本把归一化坐标还原成像素坐标,方便肉眼核对:
line = "2 0.512305 0.438146 0.018750 0.013889" class_id, x_c, y_c, w_norm, h_norm = map(float, line.split()) img_w, img_h = 640, 480 # 用 cv2.imread 读取真实分辨率替换 x_min = (x_c - w_norm / 2) * img_w y_min = (y_c - h_norm / 2) * img_h box_w = w_norm * img_w box_h = h_norm * img_h print(f"class={int(class_id)} x_min={x_min:.1f} y_min={y_min:.1f} w={box_w:.1f} h={box_h:.1f}")逻辑说明:归一化坐标乘回图像宽高,就得到了左上角坐标和框的宽高。x_c - w/2得到左边界相对位置,再乘img_w换成像素;y方向同理。这套换算在可视化验证标注时经常用。
参数说明:如果读到的w_norm、h_norm大于 1,或者x_c、y_c超出 [0,1],说明这个 label 没有正确归一化。我遇到这种情况会直接把这个样本挑出来,而不是硬训,因为它会污染模型的回归头学习。
2.3 从文件名反推数据来源,手动分组再划分
和其他数据集不同,这部分的文件名有规律。看几个典型:
6_PNG_jpg.rf.9d8afdf3c8af9092dd5497554d1020e0.jpg 12_PNG_jpg.rf.4535dbaedb94356501f29eba99673a8f.jpg IMG_6850_JPG_jpg.rf.58867852aab90335cf2e984959199108.jpg middle_PNG_jpg.rf.e108238c9803ac99cfc629a78a6bac0b.jpg.rf.是 Roboflow 平台导出时留下的标记,前面的字母才是原始文件名。IMG_6850这类像是相机拍摄的现场图片,middle像是从大图上裁出来的中间区域,12_PNG则可能是某批样板的编号。它们不是同一光源、同一角度下拍的,数据的分布差异很大。
这个观察直接影响数据划分。如果直接把所有图片随机分成训练集和验证集,同一块板或同一批次的图片会同时出现在两边,因为拍摄条件相似,验证集指标会虚高。换一批真正没见过的板子,精度立刻下滑。我的做法是先按文件名的前缀分组,再按组划分:
import os import random from pathlib import Path base = Path("pcb_defect") images = sorted((base / "train" / "images").glob("*.jpg")) groups = {} for img in images: prefix = img.name.split("_jpg")[0][:20] # 取原始文件名前缀作为组名 groups.setdefault(prefix, []).append(img) all_imgs = [] for prefix, imgs in groups.items(): random.Random(42).shuffle(imgs) n_test = max(1, int(len(imgs) * 0.2)) all_imgs += imgs[n_test:] # 每组留 80% 做训练 # 每组留出的 20% 单独汇总到 val逻辑说明:prefix取_jpg前的原始名,random.Random(42)固定随机种子保证可复现。这样分组后,同一前缀下的图片不会同时在训练集和验证集里“串场”。
参数说明:如果你不需要严格分组,直接用官方data.yaml里的随机划分也可以跑通流程;但后续要汇报精度或做方案选型时,分组划分的结果更有说服力。对 1297 张这种小数据集,分组划分通常会比随机划分的验证分数低几个点,那是真实的差距。
2.4 99.8% 识别准确率该怎么理解
标题里的“99.8% 识别准确率”是个营销味很浓的数字。目标检测领域,一般不用“准确率”这个词,而是看 mAP50、mAP50-95、Precision、Recall。99.8% 如果是指训练集上的分类准确率,那很可能是过拟合;如果是指某一次验证的 mAP50,还有参考价值。我拿到这类数据集的习惯是:先不管宣传指标,自己跑一遍 val,看 mAP50 是多少,再看 P-R 曲线在哪些类别上拉胯。1297 张图在 PCB 缺陷检测方向上规模不大,好在 PCB 板面本身纹理规整,类别差异也较明确,配合 YOLOv9 的预训练权重,训练集指标很容易做得好看,关键看验证集能不能立住。
3. 基于 YOLOv9 训练 PCB 缺陷检测:从环境到命令行
3.1 拉取仓库与准备环境
YOLOv9 官方仓库是 WongKinYiu/yolov9,安装依赖后用它的train.py直接开训。我本地的 Python 版本是 3.10,PyTorch 用的 2.0.1,CUDA 11.8。装完依赖后,建议先跑一个最简单的推理 demo 确认环境没问题,再进入训练。
git clone https://github.com/WongKinYiu/yolov9.git cd yolov9 pip install -r requirements.txt逻辑说明:requirements.txt会安装 torch、torchvision、opencv-python、pyyaml、tqdm 等基础库。如果机器上已经有 PyTorch,不建议用 pip 直接装,因为默认源装的往往是 CPU 版本。我一般先确认python -c "import torch; print(torch.cuda.is_available())",输出为 False 就去官网按 CUDA 版本重新装。
参数说明:YOLOv9 的train.py对 torch 版本不挑,2.0 及以上都跑得动。内存方面,1297 张图直接用--cache加载,约占用 4~8 GB 内存,普通 16 GB 内存的机器没问题。
3.2 修改数据集配置 pcb.yaml
训练之前要写一个 pcb.yaml,告诉训练脚本数据在哪、类别有几个、类别名是什么。我的配置长这样:
# pcb.yaml path: /data/pcb_defect train: train/images val: valid/images nc: 6 names: 0: missing_hole 1: mouse_bite 2: open_circuit 3: short 4: spur 5: spurious_copper逻辑说明:path是数据集根目录绝对路径,train、val是相对于path的图片目录。如果某个类没出现,也不要删掉编号,保持类别索引和标注文件里的 class_id 对齐即可。YOLOv9 会根据nc初始化检测头输出通道,改了nc后之前训练好的权重大概率没法直接用,需要重新 finetune。
参数说明:names的顺序必须和标注里的 0~5 一一对应,否则训练不报错,但推理时输出类别名全乱掉。标签里最大类别号加一就是nc,所以第一步数清楚类别数量很重要。如果这个数据集的类别编号只用了 0、1、2,那就把nc改成 3,names 也只写三条。
3.3 训练命令与关键参数
以 YOLOv9 的 c 系列模型为例,训练命令如下:
python train.py \ --batch 16 \ --epochs 200 \ --data pcb.yaml \ --weights yolov9-c.pt \ --img 640 \ --device 0逻辑说明:--weights yolov9-c.pt是从 COCO 预训练权重开始迁移学习,比从零训练收敛快得多。PCB 缺陷检测和 COCO 的域差距较大,但前几层特征提取依然有用。--img 640是训练时的输入分辨率。PCB 缺陷往往比较小,640 是起步值,显存允许的话可以试 960 或 1280。
参数说明:--batch 16在 24 GB 显存的 3090/4090 上比较稳妥,8 GB 显存降到 4~8,否则会 OOM。--epochs 200对小数据集来说有点多,我一般配--patience 50开启早停,连续 50 个 epoch 验证指标不涨就自动停。想快速验证流程,先跑 10 个 epoch 看 loss 能降,再拉长正式训练。
3.4 训练过程中看什么:loss 曲线和权重选择
训练过程中的终端输出会每轮打印 loss 和各指标变化。我更习惯重定向到日志文件,方便事后翻:
python train.py ... 2>&1 | tee train_log.txt tail -f train_log.txt逻辑说明:tee同时把输出写到文件和终端,日志里能看到每个 epoch 的 box_loss、cls_loss 和验证指标。正常的训练曲线是 loss 前 30 个 epoch 快速下降,之后缓慢震荡下行;如果 loss 一路降到很低但验证 mAP 不动,说明过拟合了。
参数说明:训练结束后看runs/train/exp/weights/下的best.pt和last.pt。best.pt是验证集上指标最高的权重,训练集 loss 最低的 epoch 不一定是验证集最优的 epoch。部署时我一般用best.pt,不用last.pt。
4. 常见问题排查:五个踩坑记录
4.1 验证集指标虚高,换新板子就崩
现象:训练时 mAP50 一路飙升到 0.95 以上,验证集也不错,但拿一批现场新拍的 PCB 板一测,漏检率特别高。
原因:很大概率是数据划分有问题。文件名前缀相同意味着图片可能来自同一块板或同一批次,随机划分时相同分布的图片同时进了训练集和验证集,模型学的其实是拍摄条件而不是缺陷本身。
解决:按 2.3 节的分组逻辑重新划分数据集,确保同一个前缀的图片只出现在一个集合里。如果分组后指标掉下来,那掉下来的幅度才是真实水平的反映。我后面做数据汇报时,一律用分组划分的结果。
4.2 标注里的归一化坐标越界
现象:训练 loss 波动剧烈,不收敛;画验证框发现有些框超出图像边界特别多,像是被人为拉过。
原因:标注导出的尺寸和实际图片尺寸不一致。比如标注工具基于模板图导出,但实际图片被重新缩放过,导致中心点和宽高的归一化数值不对。
解决:训练前用脚本逐张检查标注是否越界。判断条件是x_c - w/2 < 0、x_c + w/2 > 1这类情况;越界的标签要裁剪回 [0,1] 或者直接删除。注意裁剪后宽高会变小,如果目标被裁掉一半,不如删除整个样本,让模型少学一个脏数据。
4.3 显存 OOM,训练跑不起来
现象:启动训练没几分钟,终端报CUDA out of memory,然后进程直接退出。
原因:--batch、--img和显存不匹配。分辨率 640 时 batch 16 需要约 12 GB 显存,分辨率提到 1280 后显存需求接近翻倍,8 GB 卡必挂。
解决:优先把 batch 降到 4 或 2。如果还在报 OOM,就把--img降到 512,先跑通流程。工业场景中训练分辨率 640 和推理分辨率 1280 可以不同,训练先低一点不影响后续验证高分辨率的效果。
4.4 小目标缺陷漏检严重
现象:验证集 mAP50 还可以,但那些只有十几个像素的细小缺陷,比如开路、鼠咬,预测框总是出不来或者框偏了一大截。
原因:缺陷目标太小,在 640×640 输入下可能只占几个像素,特征图下采样到 20×20 时几乎丢失。
解决:训练和推理都用更高的分辨率。训练设--img 1280,若显存不足就降低 batch;推理时用 1280 或更高。另外可以检查 labels 里的框宽高分布,如果大量框的宽度不到图像宽度的 1%,后续做切片推理比调模型结构更划算。
4.5 缺陷类别不平衡,少数类学不到
现象:训练 200 个 epoch 之后,某几个类别 AP 始终很低,甚至 F1 曲线惨不忍睹。
原因:数据集中各类缺陷数量差异大,几百张图里可能某类只出现几十次,模型根本不够学。YOLO 框架本身对多数类有偏向,小类梯度被压制。
解决:先统计每类框数量。差的类别做离线增强——旋转、翻转、亮度扰动,并配好对应标签。另一种思路是降低置信度阈值,用低阈值把少数类捞回来,但最终要扩展数据才能根治。
5. 验证与推理:把模型输出变成可用的检测结果
5.1 用 val.py 复现指标
拿到权重后,第一步是在验证集上跑出真实的 Precision、Recall、mAP:
python val.py \ --data pcb.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --conf 0.25 \ --iou 0.45逻辑说明:val.py会把验证集图片逐张做前向推断,与标注做 IoU 匹配,输出 mAP50 和 mAP50-95。--conf 0.25表示置信度阈值 0.25 以下的预测框被过滤,--iou 0.45是 NMS 时的 IoU 阈值。
参数说明:验证时的--img尽量和训练一致。如果你想看模型在更高分辨率下的表现,可以单独用--img 1280跑一次,这个分数代表部署时放大图像后的真实收益。mAP50 达到 0.95 以上,且 mAP50-95 在 0.7 左右,说明模型在 1297 张图上效果已经不错;如果 mAP50-95 偏低,就看下一小节的可视化结果。
5.2 可视化预测结果与保存标签
val 跑完会生成runs/val/exp*/目录,里面有带框标注图、混淆矩阵和各类曲线。我一般会保留现场图片:
python val.py --data pcb.yaml --weights best.pt --img 640 --save-conf --save-txt逻辑说明:--save-txt把预测框写到 txt,和标注格式一致;--save-conf在 txt 里额外保存置信度。这样可以直接把预测结果和原始标注做差集,把差异部分找出来人工看。
参数说明:如果要统计漏检情况,重点看那些“标注有框但预测没框”的图片;如果要统计误检,看“预测有框但标注没框”的图片。PCB 板面纹理复杂,很多误检是铜箔上的字符、划痕和过孔引发,这种误检在纯数据集上很难规避,要在应用层面加规则过滤。
5.3 评估时看四张图,不只看一个数
训练完成后,我按优先级检查四张图:
| 文件 | 作用 |
|---|---|
| confusion_matrix.png | 看哪些类别互相混淆,比如毛刺被识别成余铜 |
| P_curve.png / R_curve.png | 看当前阈值下 Precision 和 Recall 的取舍 |
| F1_curve.png | 选生产环境的置信度阈值 |
| labels.jpg | 检查标注框尺寸分布,判断小目标占比 |
二维矩阵里如果某个类别的行有大量值落到了对角线之外,说明这两个类别特征太像,需要看原图去找差异特征。P-R 曲线是另一个参考维度,曲线下的面积越大越好;曲线靠近右上角,说明模型在高召回下还能保持高精确率。
6. 产线落地技巧:小目标召回与置信度阈值选择
6.1 训练后期关掉马赛克增强
YOLOv9 的增强策略里有mosaic,把四张图拼成一张训练,能提升小目标泛化。但训练后期它会让定位不稳定,模型的输出框偶尔偏移。常见做法是最后 10~20 个 epoch 把 mosaic 关掉,让模型回归正常分布。改hyp.scratch-high.yaml里的mosaic: 1.0,或在命令行指定:
python train.py --data pcb.yaml --weights yolov9-c.pt --hyp hyp.scratch-high.yaml --epochs 200如果训练脚本支持动态关闭,我习惯在总 epoch 的 90% 之后把 mosaic 调到 0.15 左右,具体看训练日志里验证集的 mAP 走向。
6.2 推理时上高分辨率,必要时加 TTA
部署阶段用小目标召回不够时,第一个动作是提升推理分辨率。训练用 640,推理用 1280,对小缺陷的提升立竿见影。TTA 也可以叠加,但时间成本高,产线节拍不够时没必要常开。
from ultralytics import YOLO model = YOLO("runs/train/exp/weights/best.pt") results = model.predict( source="test_pcb.jpg", imgsz=1280, conf=0.25, augment=True, verbose=False ) for r in results: for box in r.boxes: cls_id = int(box.cls[0]) conf = float(box.conf[0]) x1, y1, x2, y2 = map(int, box.xyxy[0]) print(f"cls={cls_id} conf={conf:.3f} bbox=({x1},{y1},{x2},{y2})")逻辑说明:imgsz=1280把输入拉伸到 1280 再做前向,augment=True启用测试时增强,用多尺度 + 翻转的平均结果作为最终预测。对 PCB 这类静态拍摄场景,TTA 通常能稳定提升 1~3 个百分点的 mAP,代价是单图推理时间从几十毫秒涨到几百毫秒。
参数说明:conf阈值在产线里要单独调。漏检成本高就把阈值调到 0.1~0.15,宁可多误检几块板,再靠人工复核;误检成本高就调到 0.4 以上。这个值没有通用的最优解,需要根据实际产线抽样标定。
6.3 置信度阈值怎么选:漏检和误检之间的平衡点
PCB 缺陷检测的最终指标不是准确率高低,而是漏检率与误检率之间的代价平衡。我一般会在验证集上画出 F1-Confidence 曲线,取曲线最高点对应的置信度,再根据产线反馈往左或往右压一点。如果一块坏板流出造成的损失很大,阈值就压低点;如果人工复核资源紧张,阈值就调高点。
最后说说我个人的习惯:每次接到新的 PCB 缺陷数据集,都强制自己走一遍三件事——标签越界检查、按组划分数据、用最小 batch 跑通全流程再加参数。这套流程看起来多花一两个小时,但能省掉后面大量翻车的“玄学排查”时间。希望这些经验对你复现这套数据集有帮助。
本文还有配套的精品资源,点击获取