
简介PCB电路板缺陷检测识别数据集专为工业质检与深度学习目标检测场景设计适用于使用YOLOv9框架进行模型训练与验证的研究者、算法工程师及产线质检人员。数据集包含1297张PCB板图片覆盖工业场景常见缺陷每张均完成YOLOv9格式的txt标注并附有yaml配置文件可直接接入训练流程宣称识别准确率达99.8%。压缩包共2000个文件其中包括1297个txt标注文件、702张jpg原始图片以及1个yaml参数配置整体大小120.79MB便于下载与离线使用。目前已有434人学习下载适合作为PCB缺陷检测算法开发、模型调优及产线落地的参考数据集。借助该数据集使用者可快速开展缺陷识别实验减少数据采集与标注成本同时为工业视觉检测方案的精度提升提供数据支撑是目标检测入门与工业项目落地的实用资源。1. PCB 缺陷检测数据集拿到手先别急着训99.8% 是什么、1297 张图能干什么拿到一份标成 YOLOv9 格式的 PCB 缺陷检测数据集——1297 张图、识别准确率标称 99.8%——多数人的第一反应是直接开训。我的建议相反先用半小时把这个黑匣子拆开再决定模型怎么训、阈值怎么定。1297 张是小样本它能否撑起 99.8 这个数字取决于缺陷分布、标注框大小、验证集切法和 99.8 的口径——是 accuracy、mAP 还是召回率。这篇笔记按我跑工业缺陷检测的顺序写先看懂 YOLOv9 标注格式再把训练跑通并对齐指标然后拆掉小样本最容易翻车的几个坑最后给出验证与落地用的置信度阈值、多帧投票和 ROI 裁剪。适合选型缺陷检测方案、手头有少量标注数据想验证能否直接上 YOLO 的从业者。2. 拆解 YOLOv9 格式标注用脚本看清 1297 张图的家底数据集到手后第一件事不是训练而是确认两件事标签格式是否真的是 YOLOv9 可读的 txt标注和图片是否对得上。YOLOv9 沿用 YOLOv5/v8 的标签约定每张图片对应一个同名 txt每行一个缺陷框。格式本身不难难的是 1297 张图里可能藏着坏标签、偏移框和异常尺寸这些在训练阶段才会暴露与其那时候返工不如先花二十分钟摸底。2.1 YOLOv9 的标签不是 XML一行一个框坐标按图宽高归一化每一行由五个数组成类别编号、框中心 x、框中心 y、框宽、框高。后四个数都除以了图片宽高归一化到 0-1。这么做的好处是换分辨率不用改标签坏处是标签文件和图片尺寸一旦不对应缺陷位置会被整体带偏。from pathlib import Path import cv2 img_dir Path(datasets/PCB_DEFECT/images) lab_dir Path(datasets/PCB_DEFECT/labels) img_path list(img_dir.glob(*.jpg))[0] # 按实际扩展名调整 lab_path lab_dir / (img_path.stem .txt) h, w cv2.imread(str(img_path)).shape[:2] for line in lab_path.read_text().strip().splitlines(): cls, cx, cy, bw, bh map(float, line.split()) x1 int((cx - bw / 2) * w) # 归一化坐标反算回像素 y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) print(fclass{int(cls)} box({x1},{y1})-({x2},{y2}) 宽{x2-x1} 高{y2-y1})这段脚本做了什么取第一张图读取对应 txt把归一化坐标按图片实际宽高反算回像素坐标并打印。为什么先做这一步标注工具导出时偶尔会把训练集和验证集标签放反或者把 resize 前的坐标当成 resize 后的坐标反算出来你会立刻发现框跑到图外或偏在角落。判断标准很简单肉眼抽十张能对上就行。两个参数要留意。cls 必须是整数txt 里出现 0.0 以外的浮点类别说明导出脚本没做取整。bw、bh 只要出现小于等于 0 或大于 1 的值这个标签基本废了直接打出来看是哪个文件。ultralytics 读到越界标签时一般会报格式错误报错信息通常带文件名别忽略。2.2 一条脚本统计类别分布与框尺寸先替训练排雷1297 张图真正有用的是两类信息每个类别有多少个框、框在像素尺度上有多大。前者决定类别均衡怎么做后者决定输入分辨率设多大。我每次拿到缺陷检测数据集都会先跑一遍统计脚本后面所有参数选择都依赖这两组数字也顺便看看有没有缺标签的图。from collections import Counter from pathlib import Path import cv2 sizes Counter() areas [] cls_counter Counter() for img_path in Path(datasets/PCB_DEFECT/images).glob(*.jpg): lab_path Path(datasets/PCB_DEFECT/labels) / (img_path.stem .txt) if not lab_path.exists(): print(缺标签:, img_path.name) continue h, w cv2.imread(str(img_path)).shape[:2] for line in lab_path.read_text().strip().splitlines(): cls, cx, cy, bw, bh map(float, line.split()) cls_counter[int(cls)] 1 pw, ph bw * w, bh * h if min(pw, ph) 16: sizes[16px] 1 elif min(pw, ph) 32: sizes[16-32px] 1 else: sizes[32px] 1 areas.append(pw * ph / (w * h)) print(类别分布:, cls_counter) print(框尺寸分档:, sizes) print(平均占图面积比:, sum(areas) / len(areas))这段脚本有三层作用。第一层检查缺标签1297 张图只要有三五张找不到同名 txt训练时这些图会被静默跳过你根本不知道数据少了一截。第二层看类别分布某个类只有十几张时它基本决定了验证集划分方式也决定了训练时要不要过采样。第三层看框尺寸分档短边小于 16 像素的缺陷在 640 分辨率下只剩两三个像素模型几乎学不出来。16 和 32 是经验分档线不是标准。如果平均占图面积比在 0.1% 以下或者小于 16px 的框占比超过 20%就别在 640 分辨率上浪费时间直接按 1280 设计训练。布匹缺陷检测、木材缺陷检测、轴承缺陷检测这类垂直数据大多也是几百上千张的规模小目标占比高是共性这个规律基本通用。统计完建议再抽 20 张图把标注框直接画出来看一眼。这一步能发现标签整体偏移、类别编号错位、框贴边不贴缺陷这类问题在训练前暴露它们能省下大量排查时间。画框代码只有几行用 OpenCV 的 rectangle 按反算坐标画输出到 check 目录人工过一遍。如果某个类别的框普遍比缺陷大一圈说明标注风格偏松后面 mAP 会虚高反过来标注太紧训练时正样本 IoU 上不去。可视化抽样花二十分钟比训练两小时后再排查划算得多。2.3 99.8% 识别准确率是什么口径训练前必须对齐标题给的 99.8 很亮眼但识别准确率在检测任务里有至少三种常见口径含义差别很大。第一种是 accuracy按图有没有缺陷分类正确的图数除以总图数缺陷区域在全图占比极低时哪怕框全部偏了accuracy 也可能很高。第二种是 mAP按预测框和标注框的 IoU 算这才是 YOLO 训练里常用的指标。第三种是召回率只看缺陷本身有没有被框出来不管框得准不准。工业验收时我优先盯两个数漏检率和误检框数。漏检率是产线最不能接受的误检框数决定复检工位扛不扛得住。准确率可以把这两个问题同时掩盖掉所以看到 99.8 时第一反应应该是去问计算方式而不是直接写进立项报告。对齐口径的实操办法是先把训练跑通再用 val 命令在同一份验证集上复算。复算结果和 99.8 对不上优先检查三件事验证集划分是否包含简单图、数据增强是否过强、测试集有没有混进训练集。这三类问题在小样本数据集里极其常见很多情况下是配置差异导致口径不一致未必是数字注水。3. 跑通 YOLOv9 训练从数据划分到参数对齐的完整流程数据看明白了就该把训练跑通。1297 张图虽然少配合预训练权重和合理的增强YOLOv9 能给出可用模型。关键是划分方式和参数不能拿默认值硬顶。下面给出一套我在小样本缺陷检测上常用的流程从分层划分开始到 data.yaml、模型选择、训练参数最后落到推理和指标复算。3.1 按缺陷类别分层划分1297 张经不起随机切分折腾直接拿 train_test_split 按图随机切在这个体量下很危险。假设某个缺陷类全数据集只有 12 张图15% 验证集期望分到 1.8 张现实里切成 0 张也不稀奇。验证集里没有这个类per-class AP 就显示不出来模型到底学没学会它你完全不知道。from pathlib import Path from sklearn.model_selection import train_test_split imgs sorted(Path(datasets/PCB_DEFECT/images).glob(*.jpg)) y [] for p in imgs: lab Path(datasets/PCB_DEFECT/labels) / (p.stem .txt) cls_list [int(line.split()[0]) for line in lab.read_text().strip().splitlines()] y.append(cls_list[0] if cls_list else -1) train_imgs, val_imgs train_test_split( imgs, test_size0.15, random_state42, stratifyy ) print(train, len(train_imgs), val, len(val_imgs))这里按每张图的第一个缺陷类别做 stratify保证每个类别在 train 和 val 里都有相对比例。两个局限要注意第一缺陷类别不等于这张图的全部缺陷一张图可能有三个类无缺陷图要用 -1 占位它们必须留在训练集里不然模型没见过没有缺陷的样子。更严格的做法是按类别集合做贪心划分先把稀有类的图均匀分到两边再把剩余图随机补进去。1297 张的体量做这个很便宜train/val/test 按 0.75/0.15/0.10 切测试集只做一次最终验收训练期间完全不碰。调参只在验证集上做不然最后汇报指标时你自己都会心虚。注意test 集从划分完成那一刻起冻结训练、选模型、调阈值都不许看它的标签最终验收只跑一次。3.2 写 data.yaml 并选择模型gelan-c 起步别一上来就 gelan-eYOLOv9 的配置文件和 YOLOv8 一致一个 yaml 指定数据集路径和类别名。唯一要注意的是 names 顺序必须和标注里的 class_id 一一对应。PCB 缺陷检测常见标注类别有缺孔、鼠咬断、开路、短路、毛刺、铜渣等具体以这份数据集的 names 文件为准不要凭经验猜编号。如果你之前用 YOLOv8 训练过自己的数据集会发现这套目录结构和 yaml 写法完全一致直接把标注文件夹切过来就能用。path: ../datasets/PCB_DEFECT # 相对 data.yaml 的路径 train: images/train val: images/val names: 0: missing_hole 1: mouse_bite 2: open_circuit 3: short 4: spur 5: copper模型选择上YOLOv9 常见规格是 gelan-c 和 gelan-egelan-e 参数多接近一倍。1297 张小样本场景我的选择顺序是先用 gelan-c 把流程跑通它学得快、调参成本低如果验证集上某类 AP 差一口气再考虑换 gelan-e 加长训练时间。一上来就用 gelan-e 的结果往往是过拟合训练 loss 降得漂亮验证集指标反而更差。另一个前提是加载预训练权重。yolov9c.pt 这类权重在大规模数据上学过通用特征对小样本迁移很关键手里拿到的权重如果叫 yolov9-c.pt按实际文件名改就行。从零开始训练 1297 张图等于让模型从空白自己摸索边缘和纹理效果和收敛速度都会差很多。3.3 训练命令与关键参数imgsz、epochs、batch 怎么配合训练命令不长但参数含义必须讲清楚。下面是在 24G 显存机器上跑这份数据集的参考命令显存小的看后面的参数说明再改。yolo detect train \ datacfg/pcb_defect.yaml \ modelyolov9c.pt \ imgsz1280 \ batch8 \ epochs300 \ patience50 \ optimizerAdamW \ lr00.001 \ workers4 \ device0 \ cacheTrue \ close_mosaic10核心参数是 imgsz 和 batch 的搭配。imgsz1280 是为了保小目标前面统计里如果小于 16px 的缺陷占两成以上640 会把它们缩到几乎不可见。显存不够时优先降到 1024而不是直接砍到 640。batch8 是 1280 分辨率下的稳妥值显存 24G 可以试 16但小样本数据 batch 过大反而容易提前过拟合。参数建议值说明imgsz1280小缺陷多时别用 640宁可缩小 batchbatch8-16显存和收敛速度的平衡点epochs200-300配 patience 早停别傻跑 600optimizerAdamW小样本收敛快后期可换 SGD 平滑close_mosaic10最后 10 个 epoch 关掉 mosaic让模型适应无拼接输入cacheTrue1297 张图内存足够时显著加快读盘epochs 配 patience50 的意思是验证指标连续 50 轮没提升就停。小样本数据集经常在 150-200 轮就到顶了硬跑 600 轮浪费时间。close_mosaic10 强烈建议开mosaic 增强在训练后期让模型大量看到拼接图像而部署时是完整图片分布不一致会掉点。最后 10 轮关闭模型重新适应真实输入。收敛判断别只看 loss。训练 loss 下降、验证 loss 上升是过拟合信号此时看验证集上的 P、R、mAP50 曲线如果 mAP 连续多轮不涨而 loss 还在降说明模型在背训练集。训练日志里出现 NaN loss优先查标签是否有越界值其次查学习率是否过大。3.4 推理与指标复算把 99.8 对齐到自己的验证集训练完进入验证阶段。val 命令会输出 mAP、每类 AP 和一系列曲线图这是复算标题 99.8 的依据。yolo detect val \ modelruns/detect/train/weights/best.pt \ datacfg/pcb_defect.yaml \ imgsz1280 \ conf0.001 \ iou0.6 \ splitvalconf0.001 是个关键细节。部署时你会用 0.2 甚至更高的置信度阈值只挑高可信框但画 PR 曲线和算 mAP 时必须用极低阈值把所有预测都统计进来不然 mAP 会被截断看不到低置信度区间的召回潜力。iou0.6 是 NMS 阈值如果标注框有大量重叠目标降到 0.5 更合理。val 结束后重点看三个产物PR_curve.png 看每个类别的精度-召回曲线形状confusion_matrix.png 看哪些缺陷类互相混淆F1_curve.png 是后面定置信度阈值的主要依据。这三张图对应哪些类学得好、哪些类互相串、部署阈值定在哪三个问题。复算 99.8 时注意如果数据集文档里的 accuracy 定义是有缺陷图被检出数除以有缺陷图总数那就不能用 mAP 直接对比得自己写脚本按框匹配统计。数字对不上先查 val 划分再查增强。数据划分不同同一个权重复算出来的结果差三五个点都很正常。4. 避坑1297 张小样本 PCB 缺陷检测最容易翻车的五个地方下面写我实际踩过的坑按出镜频率排五条。每一条都是现象、原因、解决三步走。小样本缺陷检测里这些坑几乎绕不开提前避掉能省好几天返工时间。4.1 缺陷框太小模型把背景纹理当成特征现象训练 loss 收敛正常验证 mAP 0.97但换一批板子鼠咬断这类小缺陷一个都检不出来。原因框尺寸统计早就提示了。短边小于 16 像素的缺陷在 mosaic 增强里被缩得更小模型在浅层特征图上看到的实际就是一片纹理。它学到的不是这里有个缺口而是这片区域纹理有点不一样换块板子纹理一变就失效。解决三步走。第一imgsz 提到 1280 以上从源头保住小缺陷的像素第二调低 mosaic 概率或限制 scale 增强范围避免小框被过度缩小第三对稀有且小的类别做复制粘贴增强把缺陷抠出来贴到干净板上生成的坐标写回对应 txt。如果小于 16px 的框占比超过两成先解决这个再谈其他参数。4.2 验证集随机切分稀有缺陷类可能一张都没进去现象训练完 val 的 mAP 很高但按类别拆开看某个类 AP 是 0或者验证集里根本找不到这个类的样本。原因1297 张图里某种缺陷只有 12 张15% 验证集期望只分到 1.8 张随机切分成 0 张的概率并不小。验证集缺了类模型对这个类的真实表现就完全是黑匣子。解决训练前用 3.1 的分层划分脚本把类别比例锁住保证每个类在验证集至少有两张。验证时用 per-class AP 列表逐个看不要只看均值。某个类在 val 里只有一两张时它的 AP 波动大到没有参考意义需要专门为该类补数据或者用留一法交叉验证看稳定性。4.3 训练集和验证集来自同一块拼板指标虚高现象本地验证 0.995换一个批次直接掉到 0.82。原因PCB 厂常用连板设计一张图上可能覆盖两片板相邻图之间有重叠区域。随机切分时同一块板的不同切片被分到 train 和 val 两侧模型等于提前见过验证图。数据泄漏让指标虚高上线立刻现原形。解决划分前按文件名里的板号分组同一块板的所有图进同一集合。文件名看不出板号时至少检查相邻索引图片是否内容重叠。更保险的做法是验证集直接用另一个批次、另一种光源条件下的坏板这样指标才接近真实上线表现。4.4 accuracy 高但误检多产线复检工位被打爆现象99.8% 的准确率在演示时很好看但正常板上每块平均框出三四个假缺陷复检工位根本看不过来。原因accuracy 把无缺陷区域被正确忽略也算作正确而缺陷区域占全图比例极低。框级误检在这个指标里几乎不起作用所以 99.8 和能不能用是两回事。解决验收指标换成漏检率和每板误检框数。漏检率必须压到 0误检框数按产线复检能力定一般每板 0.5 个以内比较现实。部署阈值用第 5 章的 F1 曲线方法选YOLO 默认 0.25 通常不是你的最优解。宁可多检也别漏检漏过去的缺陷会直接变成客诉。4.5 光源一变模型立刻失效小样本数据的环境依赖现象同一套权重AOI 工位换了灯的角度漏检率明显上升。原因小样本下模型会同时学缺陷特征和背景特征。1297 张图如果全在同一台设备、同一种打光下拍摄模型对光照、板色、元件遮挡的泛化能力很弱。这不是调参能解决的是数据分布问题。解决收数据时尽量覆盖不同光源角度、板色、炉前炉后状态。训练增强里加亮度、对比度、色相扰动灰度图就做 gamma 增强。上线后每天把新的坏板切片攒起来做增量训练比推倒重训效果好。传统视觉在固定打光下反而没这个问题所以很多产线方案是 OpenCV 先做差分筛大缺陷模型只看细节这个搭配在第 5 章细说。5. 验证这套方案是否可靠PR 曲线、坏板误检框与置信度阈值把关模型训练完离能用还有一步验证。验证不是看一眼 mAP 就说行而是把置信度阈值、误检来源和验收口径都过一遍。这一章讲我常用的一套把关流程也是判断这个方向和数据集值不值得继续投入的依据。5.1 用 F1-置信度曲线和混淆矩阵定阈值默认 0.25 通常不是答案YOLO 默认 conf0.25 是为 COCO 这种目标大、类别多的任务准备的。PCB 缺陷框小、误检多0.25 可能漏掉大量低置信度的真缺陷0.05 又会在背景纹理上堆假阳性。正确做法是看 val 结果里的 F1_curve.png找 F1 最高的点再对照 confusion_matrix.png 看哪些类容易混淆。import json import numpy as np preds json.load(open(runs/detect/val/predictions.json)) # gts 从 val 标签整理为 [(image, class, box)]box 为像素坐标 gts load_val_gts(datasets/PCB_DEFECT/labels/val) for conf in np.arange(0.05, 0.95, 0.05): fps tps 0 for p in preds: if p[score] conf: continue if match_any_gt(p, gts, iou_thr0.5): tps 1 else: fps 1 fn len(gts) - tps print(fconf{conf:.2f} 误检{fps} 漏检{fn})这段逻辑是扫描不同置信度阈值统计每个阈值下的误检和漏检。工厂验收时先看漏检能否压到 0再在零漏检前提下选误检最少的阈值。步长 0.05 太粗就换 0.01但要注意每个类的最优阈值可能不一样如果某类特别容易误检可以为它单独设阈值。注意 conf0.001 是画 PR 曲线用的和生产环境的 0.2-0.4 完全是两码事。把两者混在一起上线后阈值就失去参考价值。混淆矩阵里如果是 open 和 short 互混大概率是标注规范不统一不是模型问题先回去统一标签再谈收敛。5.2 用坏板误检框和混淆矩阵找假阳性模型学到的是缺陷还是背景假阳性不是玄学一定集中在某些固定位置。把误检框截下来拼成一张大图看十分钟通常会发现三类来源螺丝孔、丝印字符、板边毛刺。这些是难负样本长相和缺陷高度相似模型分不清很正常。处理办法是难负样本挖掘收集 100-200 张干净板的误检区域截图混进训练集或者单独作为无标签背景图参与 mosaic让模型学会这里像缺陷但不是。这个操作对误检率的压制非常明显比调阈值有效得多。机器视觉缺陷检测项目里传统视觉和深度学习叠着用很常见。Halcon、OpenCV 的模板差分对打光变化敏感但对大尺寸缺陷非常可靠放在模型前面做粗筛模型只负责小缺陷细节很多产线方案都是这样搭出来的。混淆矩阵还能帮你发现标注质量问题。两类缺陷反复互混先别急着加 loss 权重回去翻原始标注。我遇到过标注员把短路和开路标反的模型怎么训都学不对统一标注规范后重训一版指标立刻上来。标签质量在小样本场景里的权重远大于模型结构。顺带说选型基于 RT-DETR 的手机屏幕缺陷检测这类方案也有但 1297 张体量下 YOLOv9 配套的 val 工具链更成熟PR 曲线、混淆矩阵、F1 曲线开箱即用。轻量部署再谈换模型不迟。如果板子在图像里旋转角度大再考虑 mmrotate 那一类旋转框方向水平框在轻微旋转时靠数据增强就能兜住。5.3 保留一份 test 集当标尺一个数据集只允许一次最终验证反复在同一份验证集上调阈值和增强本质是把验证集变成训练集的延伸。你把阈值调到 val 上 F1 最高这个最优很可能只对这一份数据成立。我的习惯是训练前就留出 10% 的 test 集约 130 张放独立目录训练期间任何人不能碰。调参、选模型、定阈值都在 val 上进行全部定稿后用 test 做一次最终验收。test 结果不管好坏都作为交付依据。验收指标建议按下面这张表别只用 mAP 一个数下结论指标达标线说明漏检率0标准缺陷必须全框出来每板误检框数小于 0.5按复检工位能力可放宽mAP50参考不作为唯一放行线最差类别 AP记录稀有类才是风险点注意验收指标里漏检率优先级最高。误检可以靠复检工位兜底漏检直接变客诉。这套验证流程跑完值不值得投入才有答案漏检为 0、误检在产线接受范围内就继续投否则回头补数据、统一标注比硬调模型结构靠谱。我见过太多项目在参数上反复横跳最后发现是验证口径一直没对齐。6. 产线落地前的最后一公里多帧投票和 ROI 裁剪守住检出率阈值定好、验收通过剩下的都是部署工程问题。有两个技巧对 PCB 缺陷检测特别有用值得先做。第一个是多帧投票。AOI 相机停板拍照时连续拍 3 帧同一位置的缺陷至少 2 帧都检出才判为真缺陷。微距下相机和板子有轻微震动真实缺陷每帧出现在同一位置灰尘和噪声造成的假阳性只会闪现。实现上记录每个缺陷框的轨迹新框与已有轨迹 IoU 大于 0.5 就并入轨迹投票数达到 2 才输出。def vote(dets_frames, iou_thr0.5, min_votes2): tracks [] for frame in dets_frames: for det in frame: # 在已有轨迹里找 IoU 最高的目标 best max(tracks, keylambda t: box_iou(t[box], det[box]), defaultNone) # 超过阈值就并入该轨迹并累加票数 if best and box_iou(best[box], det[box]) iou_thr: best[box] det[box] best[votes] 1 else: tracks.append({box: det[box], votes: 1}) return [t for t in tracks if t[votes] min_votes]两个关键参数iou_thr0.5 的依据是连续帧间同一缺陷的位移通常小于框宽的一半min_votes2 在三帧拍摄下既滤掉单帧噪声又不漏真缺陷。代价是推理量变三倍所以通常配合 ROI 裁剪一起用。第二个技巧是 ROI 裁剪。先用定位标记或模板匹配找到板面位置只对板面区域做 1280 分辨率推理而不是缩放整张原图。这等价于把小目标放大检出率直接提升板外背景的误检也顺手被裁掉。部署时还有个容易踩的坑换 ONNX 或 TensorRT 加速后推理结果和 PyTorch 不完全一致。先固化 imgsz 和置信度阈值再谈帧率。要从 gelan-e 退到 gelan-t 压帧率必须重新跑一遍验收流程旧阈值不能直接搬。我最后的习惯是给每版方案按日期和阈值命名旧权重不急着删。现场效果波动时还能切回去后悔药在手试错成本就低。1297 张图的意义不只是训出一个模型而是让你把验证和部署的边界彻底看清楚。希望帮到你。本文还有配套的精品资源点击获取