ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PCB缺陷检测实战:用YOLOv5小样本训练并评估99.8%准确率

PCB缺陷检测实战:用YOLOv5小样本训练并评估99.8%准确率 简介这是一套面向PCB电路板缺陷检测需求的数据集资源适用于目标检测算法开发者、智能制造质检人员以及深度学习初学者可用于训练和评估电路板缺陷识别模型。压缩包内共含2000个文件其中以JPG图片和TXT标注文件为主体另有1个YAML配置文件整体体积约120.94MB。具体来看1297张PCB板缺陷图片均采用YOLOv5格式标注对应的TXT文件可直接供模型训练使用JPG原图保留了清晰的纹理与光照信息YAML文件则配置好类别与路径。已有470人学习/下载。数据集覆盖了短路、断路、污损、划痕等多种典型缺陷类型标注规范、目录结构清晰可直接投入YOLOv5等主流目标检测框架进行迁移学习与精度调优。读者既能借此省去繁琐的数据采集和标注时间又能快速验证检测算法在实际PCB场景中的效果还可以作为科研实验的基准数据为整套PCB缺陷识别项目提供可靠样本支撑。1. 1297张图撑起99.8%准确率这个PCB缺陷数据集到底能不能用标题一出来就带着一股“小样本干大事”的味道。1297张图片、YOLOv5格式标注、99.8%识别准确率这三个数字放在一起做过缺陷检测的人都会先皱眉。实际落地时PCB缺陷检测常是从AOI图像里找缺角、短路、划痕、漏铜这几类典型问题每个缺陷类别可能只有几十到几百张样本能凑齐1297张已经算干净数据。这套数据集适合用来跑通YOLOv5训练自己的数据集的完整流程也适合新入行者练习数据标注和目标检测。但99.8%这个准确率若没说明测试集构成和置信度阈值只能当作参考。这里不玄学我们把它拆开看数据怎么组织、怎么训练、怎么评估以及哪里容易翻车。2. 拆开数据集YOLOv5标注格式的构成与质检清单拿到一个标题声称“YOLOv5格式标注”的PCB缺陷数据集第一件事不是急着开训而是先确认数据组织方式对不对。YOLOv5的格式非常具体和VOC的XML、COCO的JSON完全不同。如果只拿到一堆图片加XML或者只有一个CSB文件严格来说都不能直接喂给YOLOv5。下面我们按目录结构、标签检查、数据划分的顺序过一遍。2.1 YOLOv5格式到底长什么样txt标签与文件夹结构YOLOv5标准布局一般是 images/ 和 labels/ 并排images里再分train和val子目录labels对应同样的结构。每个图片有一个同名txt文件后缀不同例如 images/train/pcb_001.jpg 对应 labels/train/pcb_001.txt。txt每一行代表一个目标框格式是五段数值类别id 中心点x 中心点y 框宽 框高四个坐标都是0到1的归一化值相对图片宽度和高度计算。比如一行“2 0.512 0.345 0.056 0.023”代表类别2比如划痕中心在图片横向51.2%、纵向34.5%的位置框宽占5.6%、高占2.3%。这种格式的好处是不同分辨率图片下框的绝对大小被归一化训练时任意缩放都不需要改标注。反过来如果某个工具导出的是像素坐标就必须除以图片宽高做一次转换常见的数据标注工具如LabelImg、CVAT导出的VOC格式都要这么转。拿到数据集后先清点文件数量和图片能否对应find images -name *.jpg | wc -l find labels -name *.txt | wc -l图片数和标签数不一致意味着有图片没标注或者标注没有对应图片。这类问题很多我一般立一个规矩图片名和标签名必须完全一致包括前缀绝不允许缺张或多张。还要检查labels下是否有空文件空txt代表这张图没有缺陷框。如果把它留在训练集里YOLOv5会把它当成背景图参与负样本学习本身没有错但一旦数量过多会压制正样本梯度所以最好单独放在“无缺陷”目录里或者统计数量后再决定。2.2 用脚本检查标注质量类别、框大小、坐标越界数据标注工具导出的YOLOv5标签有时混入非法值坐标小于0或大于1、宽高为负、类别索引超出类别数、框太小导致目标基本不可辨。这些问题会让训练loss异常或让模型学到噪声。我习惯用一段简单的Python脚本扫描所有标签。import os from collections import Counter labels_dir labels class_counter Counter() bad_files [] box_widths [] box_heights [] for root, _, files in os.walk(labels_dir): for f in files: if not f.endswith(.txt): continue path os.path.join(root, f) with open(path, r) as fh: lines fh.readlines() if len(lines) 0: print(fempty label: {path}) continue for line in lines: parts line.strip().split() if len(parts) ! 5: bad_files.append((path, wrong field count)) continue cls, x, y, w, h parts try: x, y, w, h map(float, [x, y, w, h]) cls int(cls) except ValueError: bad_files.append((path, non-number)) continue if cls 0: bad_files.append((path, negative class)) if not (0 x 1 and 0 y 1): bad_files.append((path, center out of range)) if w 0 or h 0: bad_files.append((path, non-positive size)) class_counter[cls] 1 box_widths.append(w) box_heights.append(h) print(class distribution:, class_counter) print(bad files:, bad_files[:10], total, len(bad_files)) print(box width mean/median:, sum(box_widths)/len(box_widths), sorted(box_widths)[len(box_widths)//2])这段脚本逐行解析每个标签统计类别分布和框尺寸把坐标越界、宽高非正、字段数不对的文件单独列出来。注意脚本把class也转成int是为了防呆如果类别编号不连续这里会暴露。框尺寸的均值和中位数能帮你判断目标大小占图片比例如果中位数小于0.01说明目标极小YOLOv5的默认锚框需要重新聚类。跑完脚本后把bad_files里的文件全部删掉或重新标注绝不留着训练。这些数据就是噪声模型会专门学习错误框导致验证指标好看但实际光线下泛化能力极差。2.3 数据划分与类别不平衡初判1297张图片按常见8:1:1划分就是训练1038张、验证130张、测试129张。但如果某类缺陷只有几十个框简单随机划分可能让这一类在验证集中一个框都见不到指标直接失真。先按图片维度统计“包含某类缺陷的图片数”而不是按缺陷框数统计因为目标检测训练里一张图同时出现多个同类型缺陷很常见模型学的是“图里有这种缺陷”而非“图上固定出现多少次”。import os from collections import defaultdict, Counter image_label_map defaultdict(list) for root, _, files in os.walk(labels): for f in files: if f.endswith(.txt): img_id f[:-4] image_label_map[img_id].append(os.path.join(root, f)) img_class_counter Counter() for img_id, paths in image_label_map.items(): cls_on_img set() for p in paths: with open(p) as fh: for line in fh: cls_on_img.add(int(line.split()[0])) for c in cls_on_img: img_class_counter[c] 1 print(img_class_counter)统计完如果发现少数类图片数不到验证集的20%要么对该类做过采样要么在划分时用分层抽样保证每个类别在train/val/test中都出现。YOLOv5训练时可以用超参数里的类别权重缓解但最直接的办法是先按缺陷类别对图片文件列表分层切分。另外特别提醒按板子编号划分不要随机打乱图片再切。如果同一个板子不同区域被裁成多张图或者同一批次生产照片有强相关性随机划分会造成验证集泄漏——模型在训练时已经见过相似背景验证分数虚高。生产环境里这是最大的坑后面专门讲。3. 用YOLOv5在PCB缺陷数据上跑通训练从配置到命令行数据检查完进入训练环节。目标检测训练看起来就是一条命令行但背后有很多参数和模型选型需要根据数据集规模定制。1297张图属于典型小样本不能用大模型无脑跑。3.1 环境准备与模型选型YOLOv5s还是YOLOv5n环境要求不算高Python 3.8以上PyTorch 1.8以上有GPU更好。CPU也能训但1297张图、640输入、200轮可能要跑几十小时建议至少一块GTX 1660级别显卡。git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt这里注意不要直接 pip install yolov5那是一个非官方的包名网上很多帖子会引导你装错。装完依赖后先确认GPU是否被PyTorch识别python -c import torch; print(torch.cuda.is_available())输出True再继续否则后面训练会默默用CPU速度差几十倍。模型选型上1297张图我一般从YOLOv5s开始而不是YOLOv5m或l。YOLOv5s参数量约700万对小样本足够拟合YOLOv5m参数翻倍很容易把训练集背下来然后过拟合。如果显存特别紧张或者要做边缘端部署可以选YOLOv5n但它对微小缺陷的定位能力会弱一截。PCB上的划痕、短路往往只有几十个像素模型不能太轻。3.2 写data.yaml和模型配置路径与类别名要严格对应YOLOv5用yaml文件描述数据集路径建议用绝对路径或相对yolov5目录的路径。下面是一份完整的pcb.yamltrain: ../pcb_dataset/images/train val: ../pcb_dataset/images/val test: ../pcb_dataset/images/test nc: 4 names: [open_circuit, short, scratch, solder_bridge]nc是类别数量names的顺序就是标签txt里类别id的映射顺序。我见过最隐蔽的坑是有人用VOC转YOLO脚本时类别顺序对不上比如names写成[scratch,short,open_circuit,solder_bridge]而标签里的0其实是open_circuit于是模型把所有缺陷都学错。写好后测试一下能否正常加载python -c import yaml; dyaml.safe_load(open(pcb.yaml)); print(d)模型配置文件用默认的YOLOv5s.yaml即可暂时不要改深度和宽度倍数。但锚框需要关注默认锚框是针对COCO里几十到几百像素的物体设计的PCB缺陷框往往宽度小于图片宽度的10%也就是小于64像素640输入时。如果发现你的标签框宽高中位数低于0.05就要重算锚框。YOLOv5自带kmeans脚本在utils/autoanchor.py里训练时加上--noautoanchor会在首轮自动计算但建议事先跑一次看结果。3.3 训练命令与超参数小样本收敛的关键设置训练命令本身不复杂关键在超参数。我在这类小样本PCB数据上常用的命令是python train.py --data pcb.yaml --weights yolov5s.pt --img 640 \ --batch 16 --epochs 200 --patience 30 \ --mosaic 0 --device 0 --seed 42 --project runs/train --name pcb_exp1逐项解释--weights yolov5s.pt 是COCO预训练权重小样本必须用否则从头训很难收敛。--img 640是输入尺寸如果你的缺陷框原始分辨率高比如裁切后的金相图可以到1280但显存占用会翻四倍。--batch 16在8G显存下可以如果报OOM就降到8。--epochs 200看似很多但配合--patience 30如果连续30轮验证集没有提升训练会自动停止实际往往不到100轮就停了。--mosaic 0 很多人不理解。默认的mosaic增强把四张图拼一张对常规目标检测能提升鲁棒性但小样本PCB缺陷场景里四张板子的拼接线会被模型误当成特征尤其当缺陷很小、背景是深绿色板面时模型会去学习“拼接线附近才是缺陷”这种假规律。我在这类数据上关掉mosaic后验证mAP反而涨了2到3个点。如果你想要更强的增强可以保留mosaic但把训练轮数翻倍不过代价是过拟合风险变大。YOLOv5的超参数还可以通过--hyp自定义比如提高类别损失的权重cls: 0.7 fl_gamma: 1.5fl_gamma是focal loss的gamma大于1会让模型更关注难分类的少数类缺陷。这个调整对类别不平衡极有效但不建议刚开始就改先把基线跑通再看哪类缺陷拉低了指标。3.4 训练过程监控loss曲线和验证指标训练启动后终端每轮会打印box_loss、obj_loss、cls_loss和验证集的mAP0.5。光看这些数字不够我习惯同时开TensorBoardtensorboard --logdir runs/train浏览器打开 http://localhost:6006 就能看到曲线。小样本训练最典型的特征是train loss一路下降val loss先降后升这时模型开始背训练集了。遇到这种情况不要继续加epoch优先把--mosaic保持0再考虑加dropout或权重衰减。另一条经验是如果mAP0.5已经超过0.95但mAP0.5:0.95只有0.4说明模型框的位置不太准或者置信度分布不理想需要用更严格的IoU阈值重新评估这个问题下一章专门细说。训练结束后best.pt保存在runs/train/pcb_exp1/weights/下。后续所有评估都用best.pt不要用last.pt。4. 复现99.8%准确率的正确评估方式别让指标骗了你很多人拿到一个模型问的第一句话是“准确率多少”。目标检测领域一句“准确率99.8%”如果不带定义基本没有可比性。PCB缺陷检测里79.8%可能是负类正确率也可能是在某个高置信度阈值下的precision甚至可能是训练集上的分类正确率。要把这个数字落到实处先搞清楚评估口径再用统一流程跑一套完整指标。4.1 准确率在缺陷检测里的含义从二分类到mAP如果缺陷检测被简化成“图里有没有缺陷”的二分类那1297张图里假设大多数是正常板模型全预测成“无缺陷”准确率也能到90%以上。这没有意义。目标检测的评估要从三方面看类别是否正确、框是否定位准确、置信度是否可区分。常用指标是precision预测出来的框里有多少是对的、recall真实缺陷里有多少被框出来了、以及mAP0.5和mAP0.5:0.95。mAP0.5表示IoU阈值0.5下的平均精确率mAP0.5:0.95则要求IoU从0.5到0.95逐步提高后取平均后者更严格。99.8%如果指的是mAP0.5只能说明框定位大概可用如果指的是mAP0.5:0.95那在1297张小样本上需要非常高标注质量很可能存在数据泄漏或者测试集太小。另外一个关键点是混淆矩阵。YOLOv5验证时会生成confusion_matrix.png里面能清楚看到哪些类别互相混淆比如“短路”老是被标成“划痕”这比单一准确率有用得多。4.2 用官方val.py计算mAP与PR曲线官方验证命令简单直接但参数要固定方便对比不同实验。python val.py --data pcb.yaml --weights runs/train/pcb_exp1/weights/best.pt \ --img 640 --batch 16 \ --conf-thres 0.25 --iou-thres 0.45输出结果包括每一类的precision、recall、mAP0.5、mAP0.5:0.95以及一张F1曲线图和一个混淆矩阵。--conf-thres 和 --iou-thres 是评估的阈值设置。这两个值一旦改变mAP也会变。官方默认conf-thres 0.25、iou-thres 0.45但如果你想复现标题里的99.8%就必须知道对方用的什么阈值。我把所有实验的这两个阈值固定下来只允许调整训练侧的超参数这样不同轮次之间的对比才有意义。注意官方val.py默认会用测试集吗它会读取data.yaml里的val字段。如果你只有val集就把它当作测试集如果单独留了test集最好用test字段指定。很多人在评估时不经意用了训练集指标自然好看到吓人。尤其是1297张图训练集里随便挑几十张验证mAP轻松上0.95但那样做没有参考价值。4.3 自己写一个严格评估脚本独立测试集与置信度校准官方val.py能出整体mAP但它不会告诉你“在某个置信度阈值下recall到底能到多少”。实际部署时我们需要根据漏检率和误检率的容忍度去选阈值。这时候我会单独写一个评估脚本对测试集逐张推理计算不同conf下的precision和recall。import torch import numpy as np from pathlib import Path model torch.hub.load(ultralytics/yolov5, custom, pathbest.pt, force_reloadTrue) model.conf 0.1 # 初始阈值放低后面再扫 results [] for img_path in test_images: det model(img_path) # 推理 preds det.xyxy[0].cpu().numpy() # Nx6: [x1,y1,x2,y2,conf,cls] results.append(preds) # 这里省略 GT 读取和 IoU 匹配逻辑 # 核心对每个 confidence 阈值计算 precision/recall画出 PR 曲线 # 选择 precision 和 recall 的平衡点或者根据业务需求固定 recall 下限自己实现IoU匹配并不复杂但很容易写错框匹配逻辑。我建议先读官方val.py里的process_batch函数理解它是怎么进行一对多匹配的如果你不想深挖至少要在自己的脚本里保证同一个真实框只匹配一次预测框按置信度从高到低排序后优先匹配这样得到的TP/FP才准确。评估还有一个隐藏坑如果测试集里的图片是从训练集视频帧中连续截取的那很多测试图与训练图只有几帧差别模型相当于“见过”了。严格做法是按板子编号划分后测试板子绝不出现在训练里。别小看这一条很多声称99.8%准确率的数据集测试集中包含训练集同批次图片水分极大。5. PCB缺陷检测小样本训练的五个常见翻车点与排查方法小样本加上PCB特有的高反光背景训练过程有不少固定套路能踩。下面这几个坑是我见过最多的每个都按现象、原因、解决来写。5.1 翻车点一标签框里混入背景模型学到“边框”而不是“缺陷”现象训练loss很低但测试时模型在板子边缘或布线附近输出大量假阳性框。 原因标注时框范围画得太大把周围正常的焊盘、走线包了进去。模型学到的不是一个缺陷区域而是“这个框范围内的平均颜色和纹理”。PCB图像背景高度结构化一旦框包含背景模型很容易把类似背景误判为缺陷。 解决重新检查所有标签尤其是边框超过目标轮廓1.5倍的样本手动修正。没有捷径1297张图逐张看虽然耗时但这一步不做后面所有指标都不可信。可以先把框与图片目标对比可视化写一个脚本在原图上画出gt框抽几百张人工核对。import cv2 import numpy as np with open(labels/train/pcb_001.txt) as f: lines f.readlines() img cv2.imread(images/train/pcb_001.jpg) h, w img.shape[:2] for line in lines: cls, x, y, bw, bh map(float, line.split()) x1 int((x - bw/2) * w) y1 int((y - bh/2) * h) x2 int((x bw/2) * w) y2 int((y bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0,0,255), 2) cv2.imwrite(check.png, img)这个脚本把每个标注框还原成绝对像素并画在原图上。如果发现框里包含大量非缺陷区域就去修标签。5.2 翻车点二类别不平衡模型把稀有缺陷全部漏检现象验证集里多数类mAP很高但某个稀有缺陷的recall接近0模型好像没学过这个类别。 原因1297张图中比如有1000张是“划痕”50张是“漏铜”模型在训练时大量更新划痕的梯度漏铜的特征被淹没。focal loss虽然能缓解但如果训练样本绝对数量太少梯度还是不够。 解决先做少数类过采样把漏铜样本在数据列表中重复几份或者在线增强时提高它的采样概率。我自己会在划分后用脚本统计各类图片数然后对图片列表做重复。重复不是复制像素而是让YOLOv5在一个epoch里看到漏铜样本更多次。同时配合调整超参数里的cls权重cls: 0.7 fl_gamma: 2.0fl_gamma从默认1.5加到2.0能进一步放大难样本的loss贡献。注意别加太大否则正常样本也会被干扰模型开始过度聚焦噪声。5.3 翻车点三随机划分导致同一块板子进入训练和验证现象验证mAP高达0.98但部署到现场后掉到0.7以下。 原因数据里同一块PCB的不同区域被裁成多张图或者同一个生产批次的图片背景几乎相同随机划分时它们同时出现在训练集和验证集。验证指标看起来漂亮实际模型并没有学会泛化到新板子而是记住了当前批次的纹理和位置。 解决按板子编号或批次ID分组划分数据。比如文件名是panel123_region1.jpg、panel123_region2.jpg就要把panel123所有区域归入同一个集合严禁跨集合。如果采集时间不同按天分桶也可以。最保险是只留出几个从未见过的PCB板子作为测试集训练过程中绝不碰。5.4 翻车点四高准确率来自阈值过严实际召回率惨不忍睹现象模型confidence普遍很低你非要调到0.7以上才输出结果这时precision确实很高99.8%就是这么来的但recall掉到60%以下现场大量真缺陷没被框出来。 原因评估时只看了一对conf/thres值下的precision没有看PR曲线全貌。缺陷检测的生产要求通常是“错检可以多一点漏检必须压到极低”所以recall往往比precision更重要。 解决把conf阈值调低到0.05重新计算recall然后用验证集生成PR曲线找到precision和recall的交点或者根据业务设定的recall下限反推阈值。不要用默认0.25当圣旨。我一般会在部署时设一个较低的conf如0.15再配合二次筛选模块而不是让模型直接做最终判断。5.5 翻车点五训练集和测试集来自不同光照或相机型号模型迁移失效现象在自己采的数据上mAP有0.95换到产线另一台相机拍的图上直接掉一半。 原因PCB缺陷检测最大变量就是光照。同一块板子亮一点、暗一点、反光角度变一点缺陷的边缘和颜色都会改变。深度学习模型很容易过拟合到训练采集时的光照系统。 解决训练阶段加光照增强YOLOv5的超内容里可以把hsv_h、hsv_s、hsv_v调大特别是hsv_v的随机亮度变化。另外如果生产环境有多个相机位置采集数据最好覆盖不同亮度、不同角度和不同板面批次。模型部署后建议每周收集难例加进训练集否则几个月后板面改版或光源老化模型性能会慢慢衰减。6. 从1297张图起步用数据增强和迁移学习把模型推向生产小样本训练最可靠的提升路径就是“迁移学习预训练 数据增强 难例回流”。这三个动作比调任何超参数都重要。6.1 用YOLOv5自带增强把小数据集炸出花样在训练命令里通过hyp参数调整增强强度不用写额外代码hsv_h: 0.015 hsv_s: 0.7 hsv_v: 0.5 degrees: 10.0 translate: 0.1 scale: 0.5 fliplr: 0.5PCB缺陷检测不要加degrees太大因为板子的焊盘方向是固定的旋转15度会让模型学到错误的方向性特征。scale 0.5表示随机缩放到原尺寸的0.5到1.5倍小样本下有效。fliplr水平翻转一般是安全的但如果缺陷方向与走线方向强相关比如划痕总是沿某个布线方向翻转会引入假样本。是否翻转需要通过验证集观察是否提升泛化再决定保留还是关闭。6.2 迁移学习先用COCO预训练再收集线上难例1297张图直接从头训练YOLOv5实验效果很差。使用yolov5s.pt作为预训练权重即便COCO的类别和PCB无关模型也已经学会了通用边缘、纹理和物体结构特征。训练时用--weights yolov5s.pt并在前几轮冻结backbone可以进一步稳定python train.py --data pcb.yaml --weights yolov5s.pt --freeze 10 --epochs 200--freeze 10表示冻结前10层backbone让前面的低层特征保持预训练参数只更新后面的检测头。这在小数据量下能明显减少过拟合。训练完部署到产线后把每天检测出的漏检框和误检框收集起来打标后增量训练。没有这步回流1297张图模型的寿命可能只有几星期。6.3 一个验证技巧用目标检测粗定位再用分类模型细分缺陷类型当缺陷类别之间视觉差异极小单靠YOLOv5检测头容易混淆时我常用的做法是拆成两步。YOLOv5负责把所有疑似缺陷区域框出来然后按框裁图喂给一个小型分类网络比如ResNet18判断具体缺陷类型。这样检测模型只需要学会“这里有不正常”不需要同时学会“这是哪四种不正常”分类压力被解耦。实际项目中这个技巧能把混淆严重的类别准确率提升3到5个百分点。先用混淆矩阵找到哪些类别最易混再决定是否值得做。我自己在这些小样本项目里养成的习惯是所有实验都固定评估脚本、固定阈值、固定划分方式绝不靠单一数字下结论。1297张图能做的事很有限但模型能不能用验证方法比训练更花功夫。希望这些记录能帮你在PCB缺陷检测上少走弯路也让你拿到别人报的99.8%时能自信地问一句你的mAP是在哪个阈值、哪个测试集上算的。本文还有配套的精品资源点击获取
返回列表