拿到一套带标注的PCB缺陷检测数据集第一反应是省了大功夫但真正训练起来才发现从“有数据”到“模型能上线”之间还隔着格式校验、类别不均衡、小目标漏检、训练配置这些坑。这篇就围绕deepPCB电路板缺陷检测数据集VOCYOLO双格式、1500张、6类缺陷展开完整记录我从拿到数据到训练出可用模型的整个过程包括每一步为什么这么做、哪些参数值得调、哪些坑千万别踩。适合刚入手目标检测、想拿工业数据集练手或者在产线上做AOI视觉检测的朋友参考。1. 先弄明白这套数据集到底给了什么1.1 数据来源与目录结构deepPCB数据集本身是公开的PCB缺陷检测benchmark原始图片来自一套真实PCB板卡扫描/拍摄样本图像分辨率不算高但胜在缺陷真实、背景相对统一。市面流通的版本经过二次整理常常同时提供VOCxml标注和YOLOtxt标注两种格式。我这套1500张是基本版划分上大约1050张训练、300张验证、150张测试不同分发版会有出入。拿到数据后第一步不是急着开训而是先看目录dataset/ ├── images/ │ ├── train/ # 训练图片 │ ├── val/ # 验证图片 │ └── test/ # 测试图片 ├── annotations/ │ ├── xml/ # VOC格式标注 │ └── yolo/ # YOLO格式标注 └── classes.txt # 类别清单注意VOC和YOLO目录里通常都不带类别映射文件classes.txt是单独放的。不要想当然地按顺序数类别务必先打开classes.txt确认每一行对应哪个类。我见过有人没看类别文件直接把“0”当成缺孔类结果训练出来模型完全不可用——不是模型坏了是标签和类别对不上。1.2 六个类别不是随便分的背后对应PCB制程的真实缺陷deepPCB的6类缺陷分别对应PCB生产中最常见的制程异常类别ID英文名中文含义形态特征0missing_hole漏孔本应钻孔的位置没有孔1mouse_bite鼠咬焊盘或导线边缘出现半圆凹缺2open_circuit开路导线断裂不通3short短路不相连的导线/焊盘之间异常连接4spur毛刺/突刺导线边缘多余的尖状铜箔5spurious_copper残余铜非设计区域的微小铜皮残留这6个类别有个共同点它们多数是小目标或极细长目标。比如open_circuit可能是一条2像素宽的断线缺口spur可能只有几个像素的毛刺。这是后续所有调优动作的根源——数据集本身没问题问题是目标尺寸分布决定了必须用小anchor、大分辨率、多尺度训练否则检测器学到的是“大目标拟合”对小缺陷完全无感。1.3 为什么说“VOCYOLO双格式”是加分项很多老数据集只给VOC格式训练YOLO还得自己写转换脚本。这套数据直接给了两种格式方便了不少。但要注意双格式意味着标签来源一致但不代表能互相信任。我打开几张xml后发现有一些框的坐标甚至越界了目标超出图片边界几个像素而对应的yolo txt里算出的中心坐标也可能因归一化出现负值或大于1的情况。这类脏数据在工业数据里太常见了。所以“自带双格式”的真正意义不是省掉转换而是给了你一个交叉校验的手段用脚本同时读xml和txt解析同一张图的同一条标注对比坐标是否一致不一致的地方手工修正。这个动作看起来冗余但对后续训练稳定性帮助极大。2. 训练前的准备与数据体检比训练本身更重要2.1 工具链选择我为什么选了YOLOv5这条路训练目标检测模型YOLOv8/9/11也有但我选YOLOv5具体小版本建议用v5.0或v7.0官方仓库的原因有4个生态成熟教程多遇到问题容易搜到解法。部署导出onnx/tensorrt方便产线设备通常用CPU或老显卡v5的老实架构延迟可控。deepPCB的标注是VOC/YOLO格式v5原生支持yolo txt不需要额外改dataset类。v5的anchor自动计算逻辑很稳对中小目标友好v8的动态head对小目标也强但超参敏感工业场景下我要的是确定性。如果你本身熟悉v8完全可以用v8。但下面的数据体检、训练策略、后处理思路是通用的不绑框架。环境方面我建议用Python 3.9 PyTorch 1.13/2.x CUDA 11.8即可。不用追求最新版本稳定优先。显卡哪怕是6G显存的也可以跑batch size调到8img size从640开始。2.2 训练前必须回答的3个问题第一图片和标注能不能对上把每张图的名字和对应xml/yolo文件做一一映射多一个文件少一个文件都记录。deepPCB这套数据里有极少数的xml文件内容为空图片还在标注文件是空的。这种情况要么删掉对应的图片要么单独标注。我选择直接剔除因为它会让训练时某个batch没目标影响损失回传。第二类别数量是否均衡用脚本统计每个类别的实例数。我这套数据中“残余铜(spurious_copper)”类别数量明显少于“短路(short)”而“开路”的实例往往非常稀疏。类别不均衡直接导致模型对少数类召回率低。后面专门处理。第三目标尺寸分布如何统计所有标注框的宽度和高度占图片宽高的比例。deepPCB很多目标框宽度只有图片宽度的2%-6%属于典型中小目标。如果直接用默认的anchor小目标的anchor匹配数会很少。这也是为什么很多人拿这个数据集直接trainmAP不差但小类检不出。2.3 VOC与YOLO互转以及校验脚本建议直接落地虽然数据给了双格式但我还是写了个20行的py脚本来核对。核心思路对同一张图读xml中的bndbox转成yolo格式再用diff与txt内容比对。import xml.etree.ElementTree as ET import os def xml_to_yolo(xml_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() boxes [] for obj in root.findall(object): cls_name obj.find(name).text bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # clamp to image boundaries x1 max(0, min(x1, img_w)) x2 max(0, min(x2, img_w)) y1 max(0, min(y1, img_h)) y2 max(0, min(y2, img_h)) x_center ((x1 x2) / 2) / img_w y_center ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h boxes.append(f{cls_name} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return boxes注意两点一是cls_name要和classes.txt顺序对应这里需要额外做一个name→id映射二是坐标必须clamp到图片范围内否则会造成训练loss异常。实操后的结论这套1500张的deepPCB数据我总共发现12张图有边界越界、3张图标签空、1张图xml和txt类别不一致。修完再训练val mAP稳定上涨0.02左右。这个提升不是玄学是脏数据清理的直接收益。3. 训练YOLOv5/v8时的配置细节与实操记录3.1 数据配置文件的写法如果用YOLOv5data.yaml长这样train: dataset/images/train val: dataset/images/val test: dataset/images/test nc: 6 names: 0: missing_hole 1: mouse_bite 2: open_circuit 3: short 4: spur 5: spurious_copper这里有一个容易忽视的点train/val的路径指向的是图片目录而不是标注目录。YOLOv5会默认在图片目录的同级创建labels目录来寻找txt文件。也就是说如果你把图片放在dataset/images/train/那么YOLOv5会到dataset/labels/train/找对应的标注txt。如果标注txt放在另一个地方必须手动整理目录结构或者在数据加载类里改路径变量。我第一次跑的时候把txt放在annotations/yolo/train/结果训练日志里每一张图都显示0 labels损失不下降。正确的目录整理方式dataset/ ├── images/ │ ├── train/*.jpg │ ├── val/*.jpg │ └── test/*.jpg └── labels/ ├── train/*.txt ├── val/*.txt └── test/*.txt图片文件名和txt文件名必须完全一致后缀不同jpg vs txt没关系。检查一遍有没有前缀空格、大小写不一致这类低级错误会让训练集有效样本直接少一半。3.2 超参数怎么选以及为什么最基础的一轮训练我用的配置参数数值原因img_size640原始图本身不大640够用显存不够可降到416但小目标会更难batch_size166G显存用168G用32显存量力而行epochs1501500张小数据集150轮足够收敛多了容易过拟合lr00.01v5默认SGD跑工业小数据集很稳lrf0.01余弦退火末期降到万分之一微调细节anchorauto让v5基于训练集重新聚类不要用默认coco anchormosaic1.0前100轮开mosaic增强小目标受益明显hsv_h/v/s0PCB图颜色单一不需要颜色抖动反而会引入伪阴影特别强调anchor。deepPCB的默认anchor是coco的针对80类通用目标里面很多大目标scale。PCB缺陷的框普遍小于10x10像素直接沿用coco anchor会导致正样本匹配少导致模型直接摆烂loss下降很慢val mAP很低。在yolov5里只要设置--autoanchor它会自动计算anchor聚类建议开。另外mosaic增强对这个数据集是把双刃剑。mosaic能把4张图拼在一起对小目标的效果好但它带来的拼接边会让PCB边缘线条错乱如果目标正好在拼接边界会有脏样本。所以我建议mosaic设为0.9而不是1.0留一点原始样本避免全部训练样本都被mosaic“污染”。3.3 训练过程中的观测点训练不是启动完就万事大吉我习惯每隔一段时间就看一眼几个关键输出。第一labels.jpg图片。YOLOv5会在训练集上生成标注可视化图一张全图叠上所有框。如果框和缺陷重合度差说明标注坐标有系统性偏移比如xml转txt时x/y反了。第二Batch_i.jpg增强结果图。看mosaic和随机裁剪是否把缺陷裁出去太多如果很多图上目标被裁掉一半会严重影响训练。第三loss曲线。正常情况是train_loss持续下降val_loss先降后平稳。如果val_loss在20轮后就开始反升说明过拟合。1500张数据量不大epoch拉太长没有意义反而浪费时间早停更聪明。我的做法是设置patience30超过30个epoch没涨就自动停。训练过程中还有一个经验torch的AMP混合精度可以开在不掉精度的情况下训练速度提升约15%。如果卡是10系老显卡开AMP反而可能出nan老老实实用fp32。4. 模型评估与针对性调优4.1 看指标别只看mAP训练结束官方仓库会输出一张混淆矩阵、P曲线、R曲线以及各类别的AP。deepPCB数据集类别少但每类难度差异极大。我会先把每个类别的AP单独列出来missing_hole通常AP最高因为漏孔特征明显框又比较规整。mouse_bite / spur边缘缺陷目标小AP中等。open_circuit细长断口只有几个像素最容易漏。spurious_copper残余铜大小不一、对比度低AP往往最低。所以最终结论不是“mAP 0.78完成”而是要追问“残余铜这一类AP是不是0.6以下短路是不是还凑合”如果小类不够好下一步调优才有方向。4.2 针对小目标的优化数据增强、切片、重训练遇到小类漏检首先尝试的其实不是换模型而是降低anchor的置信度阈值同时增加小尺度目标在训练中的权重。但最有效的手段我实测下来是SAHI切片辅助推理。在推理时把原图切成416x416的patch对每个patch做检测再把结果合并回原坐标。deepPCB的图分辨率不高但直接对全图推理时小目标只有几个像素特征损失严重。切patch后相当于放大了缺陷区域AP提升非常明显。具体做法推理时设置slice_size320overlap_ratio0.2。合并NMS时IOU阈值设0.45避免拼接框处的重复检测。置信度阈值从0.45降到0.25因为小目标置信度天然偏低。SAHI的使用不需要重新训练模型只是推理阶段的后处理。实测下来mAP50提升0.03-0.05特别是spur和spurious_copper这两类。4.3 类别不均衡的应对策略deepPCB的类别不均衡不像日常物品检测那么夸张但残余铜等类确实占比低。对付这个问题我没有傻到去逐类采样而是做两部分一是给loss加类别权重。YOLOv5本身没有内置per-class loss weight接口但可以通过修改loss函数把分类loss里的bcecls按类别权重加权。我在代码里将类别权重设置为该类别在训练集中占比的倒数再归一化让少数类的梯度贡献增加。这一步能让小类mAP提升约0.02。二是过采样少数类图片。从训练集中把包含open_circuit和spurious_copper的图片全部挑出来在每次epoch里多重复一遍这些样本路径。具体可以用os.path重写dataset的img文件列表来实现图片级复制。但注意不要复制到让同一张图在一个batch里出现两次否则模型会发生部分记忆验证集上表现反而变差。4.4 若想进一步提升更细的分类策略与后处理PCB缺陷检测有一个特殊性不同缺陷的判定标准会受到后道工艺的影响。比如spur很小可能在波峰焊后被锡覆盖不再是缺陷open_circuit则无论多小都必须检出。因此模型输出的raw score不能直接作为最终QA结果需要后处理逻辑对open_circuit类置信度阈值压低到0.2宁可误报不能漏。对spurious_copper类置信度阈值调高到0.5外加面积过滤小于某个像素数的检测可以忽略。这类规则看似简单实际工业环境里作用极大。因为模型本身是在标注框上学习的而标注框存在主观差异这些先验知识只能通过后处理注入系统。5. 从训练到部署踩过的坑和能直接抄的作业5.1 训练时显存不足怎么办很多人拿到1500张图就开干结果batch size设64直接OOM。我通常先跑一次小batch测试确认数据加载正常再逐步加大batch。如果6G卡跑640分辨率batch 16是可以的如果一定要用大batch可以减少mosaic scale或者把图resize到512但代价是小目标检测率下降。还有一招开启--cache可以把图片缓存到内存减少IO压力。如果内存够速度提升明显。我自己跑这块数据时cache后每个epoch从3分钟降到1分钟。5.2 标签错乱与类别ID不匹配前面提到xml和txt名称冲突这里补充一个隐蔽情况YOLO的txt第一列是类别ID但它可能是从1开始而不是从0开始。有的工具生成txt时类别直接写成了“1 0.23 0.45 0.1 0.02”而classes.txt第一行是missing_hole按0开始才是正确。如果你训练前没确认这一点最终所有类别的检测结果会整体偏移一个类看confusion_matrix时你会惊讶地发现“漏孔”被识别成“鼠咬”但框的位置是对的。排查方法极简单训练第二轮在val集上输出ConfusionMatrix.png如果发现对角线旁边的类集中出现立即检查第一列ID是否偏移。5.3 训练到一半loss出现NaNdeepPCB图片大多是扫描件灰度值分布稳定一般不会产生NaN。但mosaic增强如果同时做了旋转和透视变换某些边缘可能产生全黑或全白图像块如果这部分图像里有空白标注loss除以零就会范NaN。遇到这类问题首先检查增强配置把rotate设为0perspective设为0。PCB是刚性板件旋转增强本来就不适合这类场景。另一个常见来源是torch版本和CUDA不一致导致conv反向传播偶发NaN。升级到torch 2.0以后基本不再遇到但老版本1.10确实会随机出现。记录一下省得下次重排环境。5.4 推理速度与产线节奏的平衡离线测试模型随便跑但部署在产线上就要考虑帧率。deepPCB的检测对象是一块完整的PCB板不是一块板分几十个局部图所以一版图只需要推理一次。640分辨率小模型如yolov5s在普通CPU上耗时约300-500ms在GPU上能压到30-50ms。如果产线节拍是1秒一块板CPU方案勉强可行节拍再快就得上GPU。为了进一步压缩时间可以把输入图降到416并把NMS的iou阈值从0.45提到0.6减少候选框数量。小缺陷的召回率会损失一点但可以通过后处理规则补回来。5.5 常见问题速查表症状可能原因排查顺序loss不降或乱跳标签路径不对、anchor不匹配、lr过高检查labels目录、autoanchor、lr0改0.001mAP很高但漏检小缺陷输入分辨率太低、anchor尺度太大提高img_size、重聚类anchor、切patch推理某类AP特别低类别不平衡、标注质量差统计类别实例数、检查标注框是否过小验证集出现负样本全误报背景差异大、缺负样本增强加入无缺陷类别或增大随机裁剪检测框偏移半格xml与txt坐标不一致、标记坐标旋转未同步用脚本交叉校验两类标注6. 最后一点个人经验PCB缺陷检测这类工业视觉任务数据集质量对模型上限的影响远大于算法创新。deepPCB这套数据虽然只有1500张但它的缺陷形态丰富、标注类型覆盖了6类关键制程风险是一个非常适合做“训练-评估-调优”循环的数据集。我习惯的做法是第一遍先不做任何高级技巧用默认配置跑通pipeline拿到baseline第二遍再做数据清理、autoanchor、类别加权第三遍再做切片推理和规则后处理。不要一上来就堆各种trick否则你很难判断提升到底来自哪个环节。如果你也是拿这套数据做练习或产线原型我建议重点盯住小目标类别的AP而不是整体mAP。工业缺陷检测里漏一个短路和漏一个残余铜的危害天差地别指标永远要跟业务风险绑定。希望这份实操记录能帮你少走几天弯路。