1297张PCB数据集如何训练yolov9达到99.8%准确率?全流程避坑指南
简介PCB电路板缺陷检测识别数据集面向质检算法工程师、计算机视觉学习者及工业检测项目开发者可用于电路板表面缺陷的目标检测、模型训练与效果验证。数据基于yolov9格式完成标注1297张图片均配有对应txt标签文件另含yaml配置文件便于直接接入YOLO系列训练流程无需额外格式转换。压缩包共2000个文件包含1297个txt标注、702张jpg原图及1个yaml配置文件整体120.79MB目录结构清晰可按图片与标签一一对应检索。已有434人学习下载适用于小样本缺陷检测场景的基准测试、算法调优及教学演示。使用者可获得统一格式的标注数据、图注对应关系与简洁配置能快速完成环境配置、启动训练并评估模型精度也可配合可视化工具检查标注质量对快速搭建检测原型很有帮助。1. 一张1297张图的PCB数据集凭什么敢标99.8%准确率做PCB缺陷检测的同行应该都有体会产线上漏一块缺焊、少一个过孔后面贴片、波峰焊全得跟着遭殃所以在AOI自动光学检测环节把缺陷拦下来是PCB制造里性价比最高的一道关卡。但现实情况是很多工厂手里攒了几万张图跑出来的模型却连95%都上不去而这个标题给出的组合是“1297张图 yolov9 99.8%”这个数字一出来懂行的人第一反应肯定是要么是特定板卡上的有限几类缺陷要么是个很挑场景的测试集。我可以直接说结论小数据集跑到99.8%是可信的但前提是缺陷类别少、板卡类型单一、光照环境固定。这篇文章不吹这个数字我直接把yolov9格式标注的PCB缺陷数据集从目录结构、标签格式到训练验证、踩坑边界全部拆开讲把1297张图的极限在哪里、怎么榨干它的价值一次说清楚。2. yolov9格式标注的PCB数据集目录结构、标签文件与第一道校验2.1 拿到数据集后先做的事检查目录和标签格式标题里说得明白用的是yolov9格式标注。yolov9的标签格式和yolov5、yolov8完全一致都是归一化的中心点坐标加宽高每行一个目标格式是class x_center y_center width height。注意里面的坐标值全是0到1之间的小数x_center和y_center是目标中心点相对于图片宽度和高度的比例width和height是目标宽高相对于图片宽高的大小不是像素值。很多新手第一次打开标签文件看到0.5、0.6这种小数就懵了不知道这是相对于整张图的比例做归一化是为了适配不同分辨率输入。拿到数据集第一件事不是直接开训而是把目录结构捋清楚。常见做法是pcb_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── data.yaml └── classes.txtimages和labels必须是这种镜像对应的结构训练时yolov9会按照data.yaml里给的路径去images文件夹找图然后自动用同名文件去labels文件夹找标签。这里有个硬性规定图片和标注文件的文件名必须完全一致包括后缀名对应的部分——比如图片是100.jpg标签就得是100.txt不能是100.png.txt或者搞个前缀后缀。如果是从网上下载的数据集拿到的是一堆散装文件夹自己用脚本重建目录是常有的事。2.2 用脚本核对标签别让脏数据浪费一晚上训练时间拿到数据集后的第一步我一般写一个小脚本把每张图和对应标签都检查一遍重点核三件事一是标签文件是否和图片文件一一对应二是标签文件里有没有空文件三是坐标有没有越界小于0或大于1的情况。这一步看着不起眼但能挡住大部分脏数据问题。import os from pathlib import Path img_dir Path(pcb_dataset/images/train) label_dir Path(pcb_dataset/labels/train) imgs list(img_dir.glob(*.jpg)) list(img_dir.glob(*.png)) no_label [] empty_label [] out_of_range [] for img in imgs: label_path label_dir / (img.stem .txt) if not label_path.exists(): no_label.append(img.name) continue lines label_path.read_text().strip().splitlines() if len(lines) 0: empty_label.append(img.name) continue for line in lines: parts line.split() # 第一个数是class id后面四个是归一化坐标 vals list(map(float, parts[1:])) if len(vals) ! 4: out_of_range.append(img.name) break if any(v 0 or v 1 for v in vals): out_of_range.append(img.name) break print(f无标签图片: {len(no_label)} {no_label[:5]}) print(f空标签图片: {len(empty_label)} {empty_label[:5]}) print(f坐标越界图片: {len(out_of_range)} {out_of_range[:5]})这段逻辑很直白遍历图片目录拼出对应标签路径先检查文件在不在再检查是否为空文件最后逐行解析坐标范围。有一点要注意某些数据集的标签里会给稍微超出一点边界的框有些标注意外框场景下不裁边所以v 0 or v 1的判断可以根据实际情况放宽到v -0.05 or v 1.05否则误杀很多有效标注。另外空标签文件不一定是坏事对于缺陷检测来说如果一张图确实没有缺陷空标签是合法的但你要心里有数如果空标签占了很大比例训练出来的模型会偏向于无缺陷输出这就是正负样本严重不平衡的隐患。3. 把1297张图的潜力全榨出来数据划分、类别分析与训练准备3.1 1297张图的正确切法不要简单随机划分1297张图属于典型的小规模数据集这个量级下训练集、验证集、测试集的划分会直接影响99.8%这个数字可信不可信。很多人在小数据集上翻车就是用了sklearn的train_test_split一梭子随机分结果验证集里某个缺陷类别只有个位数的样本模型在这个类别上完全没学到东西val指标虚高或虚低都说不准。正确的做法是按类别分布做分层采样。先看每张图的标签里有哪些类然后保证划分出来的训练集和验证集里每个类别的样本占比和整体数据集一致。具体到1297张图的体量我建议训练集1000张左右、验证集150张、测试集留150张左右比例大约8:1:1。测试集必须是从头到尾完全没参与训练和验证过程的数据最后拿来模拟“没见过的板子”去评估。从数据划分角度缺陷检测和分类还不太一样一张图里可能有多个缺陷有的缺陷类别在整张图里只有一两个框。分层采样按图来分还不够保险最好是先把图片按“包含的缺陷类别组合”做一个分组尽量让训练集和验证集里包含的类别组合分布接近。实际操作时可以简单一点按主缺陷类别分层保证每个类别在训练集里至少有几十个实例不然这个类别基本等于没标注。3.2 data.yaml和类别序号出错率最高的地方yolov9训练时靠data.yaml告诉模型“我要学什么、去哪里找图”。这个文件的写法有固定格式而且有个特别容易搞错的地方类别名字在里面的顺序必须和标签文件里class id的数字序号一一对应。# data.yaml train: pcb_dataset/images/train val: pcb_dataset/images/val test: pcb_dataset/images/test nc: 6 # 注意这个顺序就是class id的映射下标从0开始 names: 0: missing_hole 1: mouse_bite 2: open_circuit 3: short 4: spur 5: spurious_copper这段配置里nc是类别总数names是一个有序列表第0行对应class id为0的类别第1行对应class id为1的类别第i行对应class id为i的类别。如果你用官方公开的PCB瑕疵数据集里面通常是6个类别missing_hole漏孔、mouse_bite鼠咬痕、open_circuit断路、short短路、spur毛刺、spurious_copper多余铜这是业界最常用的PCB缺陷检测基准。如果标题里的1297张图是自定义数据集的子集那类别可能更少比如只有缺焊、连锡、短路这几类所以拿到数据集时先cat classes.txt看一眼类别清单。另外yolov9的仓库里data.yaml中的train和val路径建议用绝对路径尤其在Windows上如果用相对路径会遇到一个常见的坑训练指令在某个目录下执行能跑换个目录就报FileNotFoundError。还有一个常见问题是中文路径模型和代码本身不太支持一旦数据路径里带中文图片加载阶段就会出错。我在Windows上踩过这个坑把数据集放到带中文名的目录下yolov9直接报“No such file or directory”排查半天才发现是路径编码问题。3.3 类别不平衡把缺陷类别的样本数打出来小数据集的另一个特点是类别严重不平衡。比如1297张图里缺焊占了600张短路只有50张那模型对短路的检测能力大概率是名义上的。提前看清每个类别的样本量做到心里有数比后面训练完发现“这个类别老是漏检”再回头调参数要高效得多。from collections import Counter from pathlib import Path label_dir Path(pcb_dataset/labels/train) class_counter Counter() instance_counter Counter() for label_file in label_dir.glob(*.txt): lines label_file.read_text().strip().splitlines() if not lines: continue classes_in_file set() for line in lines: class_id int(line.split()[0]) instance_counter[class_id] 1 classes_in_file.add(class_id) for c in classes_in_file: class_counter[c] 1 for class_id in range(6): print(f类别 {class_id}: 图片数 {class_counter[class_id]}, 实例数 {instance_counter[class_id]})一个小技巧上面的脚本同时统计了“包含该类别的图片数”和“该类别所有标注框实例数”两个指标结合看才有价值。如果一个类别在300张图里出现、但每张图只框了一个小目标和另一个类别在150张图里出现、每张图平均有三个框模型学到的特征密度是完全不同的。对实例数极少的类别往后的训练里你可能要考虑做类别加权或者复制粘贴增强不能指望模型对这个类别无师自通。4. 用yolov9跑通PCB缺陷检测训练命令、关键参数与推理验证4.1 训练命令与参数设置小数据集怎么调yolov9yolov9官方仓库是基于之前的yolov5架构改的训练入口是train.py。如果你的环境是Linux或者Windows下的GPU机器先保证CUDA和PyTorch版本匹配yolov9在PyTorch 1.8以上的环境里跑比较稳。训练命令一般长这样python train.py \ --data pcb_dataset/data.yaml \ --weights yolov9-c.pt \ --img 640 \ --batch-size 16 \ --epochs 200 \ --project runs/pcb_detect \ --name yolov9c_pcb \ --hyp hyp.scratch-low.yaml这段命令的每个参数都值得展开说。--weights yolov9-c.pt的意思是加载COCO预训练权重做迁移学习这是小数据集训练的核心操作——1297张图从零训练基本不可能收敛但基于预训练权重微调模型已经具备通用的特征提取能力只需要把注意力转到PCB缺陷的纹理和形状上。--img 640表示训练时把图片缩放到640x640如果数据集中缺陷都很小比如几像素的毛刺可以考虑--img 1280但代价是显存翻倍、训练时间翻好几倍。--batch-size在单卡上建议用显存能承受的最大值16或者32都行batch越大每个epoch的梯度更新越稳定。--epochs 200对1297张图来说是一个合理的起步值配合早停策略足够了没必要一上来就500个epoch烧时间。--hyp hyp.scratch-low.yaml是数据增强的超参数配置文件小数据集推荐用这个低增强配置增强太猛的话会把本来就少的样本特征完全改歪典型表现是训练loss正常下降但val指标乱跳。4.2 训练过程中的关键观察点loss曲线和验证集指标训练启动后不要干等结束。yolov9会在终端里实时打印每个epoch的box_loss、cls_loss、dfl_loss和验证集的mAP0.5、mAP0.5:0.95。对PCB缺陷检测来说最需要盯的是mAP0.5这个指标。缺陷检测任务里IoU阈值0.5下能稳定到0.98以上基本就接近标题里说的99.8%了。而mAP0.5:0.95是一个更严苛的指标它综合考量了不同IoU阈值下的检测能力小缺陷在这个指标上会明显掉分因为目标太小、检测框稍微偏一点IoU就直线下降这也是为什么很多模型在0.5阈值下指标漂亮、在0.5:0.95下惨不忍睹的原因。训练过程中还要注意过拟合的信号如果train_loss持续下降而val_loss在某个epoch之后开始反弹上升那说明模型开始死记训练集中的板卡特征而不是泛化地学习缺陷模式。1297张图特别容易触发这个问题因为训练图数量本来就少模型转个几十轮就把图片“背”下来了。解决办法有几个一是用yolov9自带的早停机制--patience 30参数表示验证集指标连续30个epoch没有提升就自动终止训练二是降低数据增强强度去除色调变化、旋转角度过大的增强策略三是适当增大--weight_decay让权重衰减更明显一点抑制过拟合。4.3 推理验证用detect.py跑一批测试图并输出可视化训练结束后模型权重保存在runs/pcb_detect/yolov9c_pcb/weights/best.pt这个best.pt是验证集上mAP最高的权重别选last.pt做后续验证。跑一次推理就清楚模型的实际检测能力了python detect.py \ --weights runs/pcb_detect/yolov9c_pcb/weights/best.pt \ --source pcb_dataset/images/test/ \ --conf 0.25 \ --iou 0.45 \ --save-txt \ --save-conf \ --project runs/pcb_detect_infer--source指向测试集图片文件夹--conf 0.25表示置信度低于0.25的检测结果会被过滤掉--iou 0.45是NMS的IoU阈值。PCB缺陷检测里如果生产环境漏检的影响远大于误报就要把这个conf调低到0.1甚至0.05——宁可多给几个误报框让人工复查也不能让缺陷漏过去。--save-txt会额外保存检测结果的txt文件每行也是class x_center y_center width height conf的格式这个文件可以拿来做后分析和统计。推理完成后在runs/pcb_detect_infer目录下查看可视化结果图。此时要重点看两类情况一是漏检测试图上明明有缺陷但模型没框出来这是致命问题二是误检把正常的走线、过孔边缘、铜箔反光当成了缺陷。这两类问题在PCB场景里的根源往往不同下一步就是针对性地做数据补充或者后处理优化。5. PCB缺陷检测避坑指南小数据集yolov9最容易翻车的四个场景5.1 准确的99.8%是纸上谈兵评估方式错位导致的高指标幻觉现象模型在验证集上mAP达到99%以上看起来完美无缺数据集的99.8%准确率好像随手就能复现。但一旦放到没有预处理过的新板卡图片上性能断崖式下跌。原因小数据集很容易出现数据泄露。最常见的是划分验证集时把同一块板子不同角度的多张图片一部分放训练集、一部分放验证集模型在训练时已经见过同一个缺陷的“近亲”验证时只是把记忆的输出复述了一遍。如果数据集制作时不严谨图片里包含重复区域或者同一板卡的多张连续扫描图指标虚高的可能性极大。解决用数据集的原始划分之前要检查图片之间的相似度至少做到两点第一同一种板卡型号的图片要么全在训练集、要么全在验证集不能让同一型号的两张图被切到两边第二建立一个独立于数据集的“留出板卡”测试集也就是拿一块从未出现在数据集的板卡单独拍一组图做最终测试得到的指标才是生产环境里的真实水平。5.2 缺陷在图里占的面积太小小目标缺陷让mAP整体失真现象训练过程正常收敛但可视化推理结果后发现毛刺、鼠咬痕这种缺陷偶尔能检测到偶尔完全丢失同一张图多次推理的结果都不稳定。mAP0.5在0.98以上mAP0.5:0.95却低到0.7左右。原因PCB上的很多缺陷比如几毫米甚至亚毫米级的毛刺、短路铜丝在640x640的输入分辨率下对应区域可能只有几个像素到十几个像素宽。yolov9-C这类模型的颈部网络下采样多次之后小目标的特征在深层特征图里几乎消失了。mAP0.5:0.95对检测框的定位精度非常敏感小目标预测框轻微偏移IoU就跌破0.75被算作错误检测。解决把训练和推理的--img从640增大到1280这能显著提升小目标召回率代价是推理时间增加。另外可以打开yolov9的SAHI切片推理方案先对大图做切片推理再拼回检测结果是工业视觉里处理小目标和超大图的惯用手段。如果显存实在撑不住1280分辨率另一个思路是只对缺陷高发区域做局部放大识别比如让AOI软件先框出可疑区域再用PCB缺陷模型判断该区域是否为真实缺陷。5.3 训练loss不正常下降数据增强太猛导致小数据集被“洗”坏现象loss前几十个epoch下降到正常水平但后半段train_loss微微下降、val_loss开始不规则跳变每一轮的mAP忽高忽低。检查增强配置发现用了一个高强度的增强参数组合包括大角度旋转、大幅hsv色彩扰动、大范围平移缩放。原因yolov9自带的hyp.scratch-high.yaml针对的是大规模数据集比如COCO一百多万张图增强强度很大。1297张图的数据量根本经不起这种折腾过强的随机旋转和透视变换会让原本就稀缺的缺陷特征被“洗”成各种扭曲形态模型学到的特征被带偏。解决小数据集训练时使用hyp.scratch-low.yaml这个文件把旋转角度限制在小范围、饱和度调整幅度削半、关闭mosaic或降低mosaic的概率保留基本的平移和缩放。还有一个保守做法直接关闭mosaic增强在hyp配置文件里把mosaic: 0.0对小目标缺陷来说mosaic拼接出的图片里缺陷会被裁到很小效果反而适得其反。5.4 类别不均衡导致模型“挑食”只在样本多的缺陷上表现好现象训练结束发现样本量大的类别比如缺焊精确率和召回率都是0.99以上样本量少的类别比如短路精确率勉强0.8、召回率只有0.5模型对少样本类别的目标视而不见。原因默认情况下yolov9的损失函数里每个类别的权重是一样的训练过程中少样本类别的梯度更新被多样本类别“淹没”模型把特征提取能力都倾斜给了高频类别。这在1297张小数据集上特别容易发生因为少样本类别的绝对数量少到几百个实例。解决一个直接的方法是在损失计算里去调整类别权重但yolov9官方没有像cls_weights这种傻瓜参数要改源码。更实际的做法是给少样本类别做离线增强把含短路的图片做水平翻转、小角度旋转、亮度扰动生成副本加进训练集本质上是用数据手段补偿类别不平衡。还有一种色彩空间的小技巧PCB的铜箔和基材颜色特征非常明确轻微调节亮度和对比度不影响缺陷本质却能让模型增强鲁棒性对少样本类别的泛化尤为有效。6. 让99.8%更硬的最后一公里用测试集难样本分析反向优化模型到这一步我们有了一个在测试集上跑出高指标的模型也完成了基础的避坑排查。但我习惯在最后做一次“难样本分析”——把模型在测试集上预测错的所有图片集中起来看一遍找到漏检和误检的共性规律然后用一种低成本方式把模型性能再往上抬一档。import os from pathlib import Path # detect.py的--save-txt输出目录 pred_dir Path(runs/pcb_detect_infer/labels) gt_dir Path(pcb_dataset/labels/test) # 统计每张图的置信度分布 low_conf_files [] for pred_file in pred_dir.glob(*.txt): preds [] for line in pred_file.read_text().strip().splitlines(): parts line.split() conf float(parts[5]) preds.append(conf) if preds: min_conf min(preds) if min_conf 0.4: low_conf_files.append((pred_file.stem, min_conf)) loss_sorted sorted(low_conf_files, keylambda x: x[1]) print(f存在低置信度检测的图片数: {len(low_conf_files)}) for name, conf in loss_sorted[:10]: print(f{name}: 最低置信度 {conf:.3f})这段脚本的输入是推理时保存的txt标签输出是模型中所有低置信度检测对应的图片名。目的很简单找出那些“勉强被识别出来”的缺陷样本。这些样本再往下降一点置信度就会变成漏检是最有优化价值的切入点。针对这些低置信度样本最常见的共性是两类一是缺陷目标和背景对比度太低比如浅色基材上的浅色残铜模型的特征提取器在这个样本上没有激发足够强的响应二是缺陷形态和正常走线非常接近模型在特征空间里难以明确区分边界。针对这两类情况最省力的优化不是在网络结构上折腾而是在训练数据里做针对性补偿——把低置信度样本中漏检的图单独挑出来用roboflow这类标注工具重新检查标签看是不是原始标注的框本身不准确比如框偏了半个身位或者把背景框进去了这种“脏标注”会让模型在该位置上无所适从。如果标签没问题则可以把低置信度样本离线多复制几份配合轻微的亮度对比度扰动加进训练集让模型对这个难片段落“多看一眼”。这个方法听着很土但在小数据集上是真有效。另一个习惯是调完一轮再用同样的难样本分析脚本跑一遍对比同一批图片的置信度有没有普遍提升。如果连续两轮难样本集没有明显变化说明模型在这个数据分布下已经接近天花板了继续堆时间不如回到数据侧补图。从我自己的经验看拿1297张图做PCB缺陷检测是可行的但前提是你清楚这个数据集的能力边界它适合验证方案、跑通流程、快速预研但要是想覆盖一个车间所有板卡型号的产线级检测这个规模远远不够。我的习惯是在项目初期用这类小数据集把模型结构、训练pipeline和评估标准全部跑通确认缺陷可检性之后再有针对性地去产线上补拍难样本数据每补一批数据就做一轮增量训练效果比一开始就堆几万张杂图好得多。这套难样本分析的流程有条件下建议直接沉淀成自动评测脚本从数据准备到模型产出做到接近一键化后面换板卡换代工厂的时候能省下大量来回试错的精力。希望帮到你。本文还有配套的精品资源点击获取