1. 从一块报废板子说起:PCB缺陷检测到底在解决什么问题
前阵子帮一个做小批量PCBA的朋友处理产线问题,他们每个月大概产出两千多片板子,主要是一些工业控制类的四层板。问题出在回流焊之后的人工目检环节——两个质检员拿着放大镜一片一片看,一天下来眼睛都快瞎了,漏检率还是压不下去。最典型的一次,一批板子发到客户手里,对方上电测试发现有三片存在线路微短路,追回来一查,是蚀刻工序留下的铜渣残留,肉眼在特定角度下才能勉强看到。这一来一回,赔了钱不说,交期也耽误了。
这件事让我意识到,PCB缺陷检测这件事,靠人眼死磕终究不是办法。后来我花了两周时间,用YOLO系列模型搭了一套自动检测流程,从数据集采集、标注、增强,到模型训练、验证、导出部署,跑通了一个完整的闭环。实测下来,在自建的五百多张图像数据集上,mAP@0.5能稳定在0.92以上,单张推理时间在普通消费级显卡上不到20毫秒。这套东西谈不上多高深,但确实把漏检率从人工的百分之八左右压到了百分之二以内。
这篇文章就是把这套流程完整拆开讲一遍。不管你是刚接触机器视觉缺陷检测的在校学生,还是产线上想搞自动化改造的工程师,或者只是对YOLO训练自己的数据集感兴趣的开发者,都能从里面找到可以直接抄作业的东西。我会把数据集怎么准备、标注怎么做、模型怎么选、参数怎么调、坑在哪里,全部摊开来说。核心关键词就几个:PCB、缺陷检测、数据集、模型训练、YOLO,整篇内容都围绕这几个词展开。
需要提前说明的是,PCB缺陷检测这个领域,公开数据集其实不算丰富,很多情况下你得自己采图、自己标。这既是门槛,也是机会——因为一旦你跑通了一次完整流程,后面换产品、换缺陷类型,无非就是重新采一批图、重新标一遍的事,方法论是通用的。
2. 内容整体设计与思路拆解
2.1 为什么选YOLO而不是其他方案
做缺陷检测,摆在面前的路其实有好几条。传统机器视觉那条路,用OpenCV做模板匹配、边缘检测、形态学操作,在缺陷类型单一、光照稳定的场景下确实能用,而且不需要GPU,部署成本低。但PCB的缺陷类型太多了——缺件、偏移、立碑、虚焊、短路、开路、铜渣、划痕,每一种的形态都不一样,你不可能给每种缺陷写一套规则。规则一多,维护成本就上去了,而且换个产品型号,阈值全得重调。
另一条路是语义分割,比如U-Net那一套,能给出像素级的缺陷区域。精度确实高,但标注成本也高——你得标像素级的mask,一张图标下来少说十分钟。对于小批量多品种的产线,这个标注成本扛不住。
选YOLO,核心原因是三点。第一,检测速度够快,YOLOv8n在RTX 3060上跑640尺寸的图,推理时间大概在5到8毫秒,完全能跟上产线节拍。第二,标注成本可控,只需要画矩形框,一张图熟练了半分钟就能标完。第三,生态成熟,Ultralytics那套框架把训练、验证、导出、部署全串起来了,你不需要自己去写数据加载器、写损失函数、写推理后处理,省下来的时间可以花在数据质量上。
提示:如果你做的缺陷是那种边界极其模糊、需要精确到像素的,比如某些半导体晶圆缺陷,那YOLO的框可能不够用,得考虑分割方案。但PCB上的大部分缺陷,用框标出来足够了。
2.2 整体流程的骨架
整个流程我把它拆成五步,后面会一步步展开讲。
第一步是需求定义与缺陷分类。你得先搞清楚要检测哪几类缺陷,每类缺陷长什么样,在图像里大概占多大面积。这一步决定了后面采图的数量和标注的粒度。
第二步是数据集采集与标注。采图要考虑光照、角度、背景的一致性,标注要统一标准,避免同一种缺陷两个人标出两种框。
第三步是数据增强与划分。PCB图像有个特点,缺陷区域往往很小,直接训练容易漏检。需要通过增强手段扩充样本,同时按比例划分训练集、验证集、测试集。
第四步是模型训练与调参。选哪个版本的YOLO,用多大的输入尺寸,学习率怎么设,batch size怎么定,这些都有讲究。
第五步是验证、导出与部署。训练完了不能只看loss曲线,得看实际的混淆矩阵和PR曲线,导出成ONNX或者TensorRT,再集成到产线系统里。
这五步里,最耗时间的其实是第二步,大概占整个项目百分之六十以上的精力。很多人一上来就急着调模型,结果数据集一塌糊涂,怎么调都上不去。我的经验是,数据集的质量决定了模型效果的上限,模型和参数只是逼近这个上限。
2.3 方案选型背后的取舍逻辑
在具体工具选型上,我也踩过一些坑。标注工具一开始用的LabelImg,后来换成了Labelme再换回LabelImg,最后还是觉得LabelImg最顺手,因为它直接输出YOLO格式的txt文件,省去了格式转换的麻烦。如果你需要标分割mask,那Labelme更合适,但纯检测任务,LabelImg足够了。
模型版本上,我试过YOLOv5、YOLOv7、YOLOv8,最后落在YOLOv8上。v5的生态最成熟,教程最多,但v8的API更简洁,而且anchor-free的设计对小目标更友好。PCB上的缺陷很多都是小目标,比如一个0402封装的电阻偏移,在整板图像里可能就十几个像素,v8的表现确实比v5好一截。
训练框架用的是Ultralytics的官方库,没有用MMDetection或者Detectron2。原因很简单,Ultralytics那套东西开箱即用,配置文件就一个yaml,训练命令就一行,对新手极其友好。MMDetection虽然灵活,但配置文件的层级太深,改个参数要翻好几层,对于快速迭代不友好。
3. 核心细节解析与实操要点
3.1 缺陷分类与判定标准
在动手采图之前,必须先跟产线的人坐下来,把缺陷分类定清楚。我当时的分类是这样的:
| 缺陷类别 | 英文代号 | 典型特征 | 最小可检尺寸 |
|---|---|---|---|
| 缺件 | missing | 焊盘上无元件 | 0.4mm x 0.2mm |
| 偏移 | shift | 元件偏离焊盘中心 | 偏移量大于焊盘宽度30% |
| 立碑 | tombstone | 元件一端翘起 | 翘起角度大于30度 |
| 虚焊 | cold_joint | 焊点表面粗糙、发暗 | 焊点面积小于正常值60% |
| 短路 | short | 相邻焊点间有锡桥 | 锡桥宽度大于0.1mm |
| 铜渣残留 | copper_residue | 蚀刻后残留的铜点 | 直径大于0.15mm |
这个表不是拍脑袋定的,是跟质检主管一起,拿实际的不良品一片一片看出来的。定标准的时候有个原则:宁可把标准定严一点,也不要定松。因为模型训练出来之后,你可以通过调置信度阈值来放宽,但如果标注的时候标准就松,模型学到的就是模糊的边界,后面怎么调都调不回来。
注意:缺陷分类不要贪多。我一开始想把所有能想到的缺陷都放进去,分了十二类,结果每类样本都不够,模型学得一塌糊涂。后来砍到六类,每类至少保证一百五十个实例,效果才起来。经验值是每类缺陷至少一百个实例,低于这个数,模型很难学到稳定的特征。
3.2 图像采集的硬件与参数
采图这块,我用的是一个五百像素的工业相机配一个定焦镜头,光源用的是环形白光LED。为什么用白光不用其他颜色?因为PCB的底色是绿色或者蓝色,白光下缺陷的对比度最自然,不会引入奇怪的色偏。
相机架设的角度很关键。垂直向下拍是最简单的,但有些缺陷比如立碑,垂直拍只能看到一个点,侧面拍才能看到翘起的高度。我的做法是主相机垂直拍,再加一个低角度相机补拍,两个相机的图像分别训练,推理的时候取并集。这样做的代价是推理时间翻倍,但召回率能提升大概五个百分点。
光照的稳定性比光照的强度更重要。我试过用自然光加补光灯,结果上午和下午拍的图色温不一样,模型在下午的图上表现明显差一截。后来改成全封闭的暗箱加固定光源,色温锁定在五千五百K,问题就解决了。
采集的分辨率也有讲究。我一开始用四千乘三千的分辨率,想着细节更丰富,结果训练的时候显存直接爆了,降到一千二百八十乘九百六十才跑起来。后来发现,对于0402封装的元件,一千二百八十的分辨率已经足够看清偏移了,再高就是浪费。分辨率的选择原则是:缺陷区域在图像里至少占二十个像素宽,低于这个数,模型很难学到有效特征。
3.3 标注规范与质量控制
标注这件事,最怕的就是标准不统一。我当时的做法是,先让两个标注员各自标五十张图,然后交叉检查,把不一致的地方拿出来讨论,形成一份标注规范文档。这份文档里写清楚了每种缺陷的框怎么画——是贴着缺陷边缘画,还是留一点余量。
我的规范是框贴着缺陷边缘画,不留余量。因为YOLO的损失函数里,框的位置是直接参与回归的,留余量会让模型学到一个偏大的框,推理的时候框会偏大,影响后处理的判断。
标注的质量控制,我用了一个笨办法但很有效:每标完一百张图,就抽十张出来重新标一遍,对比两次标注的IoU。如果IoU低于0.85,说明标注标准不稳定,得停下来重新对齐。这个办法虽然费时间,但能及早发现问题,避免标了几千张之后才发现标准跑偏了。
还有一个细节是难例的处理。有些缺陷处于临界状态,比如一个偏移量刚好在判定标准边缘的元件,标还是不标?我的做法是标上,但在训练的时候给这类样本一个较低的权重。YOLO本身不支持样本加权,但你可以通过复制正常样本、减少难例样本的方式来间接实现。
3.4 数据增强的策略与边界
PCB图像的数据增强,不能随便用那些通用的增强手段。比如随机旋转,对于PCB来说,旋转九十度之后,元件的方向就变了,模型会学到错误的方向特征。我试过加旋转增强,结果模型在正常图像上的表现反而下降了。
我实际用的增强手段是这几种:
- 亮度与对比度扰动:模拟不同光照条件下的图像变化,幅度控制在正负百分之十五以内。
- 高斯噪声:模拟相机传感器的噪声,标准差控制在五以内。
- 随机裁剪:从大图中裁剪出小图,增加小目标的样本量,裁剪尺寸不低于原图的百分之六十。
- 马赛克增强:YOLOv8自带的mosaic增强,把四张图拼成一张,对小目标检测很有效,但要注意拼接后的图像不能有重叠的缺陷区域。
提示:增强的幅度不要太大。我见过有人把亮度扰动开到正负百分之五十,结果模型学到的全是极端光照下的特征,正常光照下反而检不出来。增强的目的是让模型见过更多的变化,而不是让它见过不可能出现的情况。
3.5 数据集划分的比例与随机性
训练集、验证集、测试集的划分,我用的比例是七比二比一。这个比例不是固定的,如果你的数据量很大,比如上万张,那验证集和测试集的比例可以降到百分之十和百分之五。但如果数据量只有几百张,那验证集至少要有百分之二十,否则验证结果波动太大,没法判断模型到底有没有变好。
划分的时候有个坑:不能随机划分。因为PCB图像往往是从同一批板子上采的,同一批板子的图像相似度很高。如果随机划分,很可能训练集和验证集里有来自同一块板子的图像,验证结果会虚高。正确的做法是按板子划分,同一块板子的所有图像要么全在训练集,要么全在验证集。这样验证结果才能反映模型在没见过的新板子上的表现。
4. 实操过程与核心环节实现
4.1 环境搭建与依赖安装
环境这块,我用的是Ubuntu 22.04加一张RTX 3060,显存十二个G。Python版本是3.10,CUDA版本是11.8。这些版本不是随便选的,Ultralytics的库对CUDA版本有要求,11.8是经过验证比较稳的。
安装步骤很简单,先建一个虚拟环境,然后装Ultralytics:
conda create -n pcb_det python=3.10 conda activate pcb_det pip install ultralytics装完之后验证一下:
yolo checks这个命令会输出你的环境信息,包括CUDA是否可用、GPU型号、显存大小。如果CUDA那一栏显示的是false,那说明你的PyTorch版本和CUDA版本不匹配,得重新装PyTorch。
注意:不要用pip install torch直接装,那样装的是CPU版本。要去PyTorch官网找对应的CUDA版本的安装命令。我踩过这个坑,装完之后训练速度慢得离谱,一查才发现用的是CPU。
4.2 数据集目录结构与配置文件
Ultralytics要求的数据集目录结构是这样的:
pcb_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── pcb.yamlimages下面放图像文件,labels下面放同名的txt标注文件。txt文件的格式是每一行一个目标,内容是类别编号 中心x 中心y 宽度 高度,坐标都是归一化到零到一之间的。
pcb.yaml的内容是这样的:
path: /home/user/pcb_dataset train: images/train val: images/val test: images/test names: 0: missing 1: shift 2: tombstone 3: cold_joint 4: short 5: copper_residue这个yaml文件是训练的核心配置文件,路径和类别名都在这里定义。类别名的顺序要和标注文件里的类别编号对应,否则模型学出来的类别是乱的。
4.3 模型训练的参数设置与调优
训练命令就一行:
yolo detect train data=pcb.yaml model=yolov8s.pt epochs=200 imgsz=640 batch=16 lr0=0.01这行命令里的每个参数都有讲究,我一个个说。
model=yolov8s.pt选的是small版本,不是nano也不是medium。nano版本太小,对小目标的检测能力不够;medium版本太大,训练慢而且容易过拟合。small版本在速度和精度之间取得了比较好的平衡。如果你显存够大,可以试试medium,但我的经验是对于PCB这种小目标密集的场景,small已经够用了。
epochs=200是训练轮数。这个数不是固定的,得看loss曲线。我的做法是先设两百轮,然后看验证集上的mAP什么时候不再上升,如果一百五十轮就收敛了,那后面的五十轮就是浪费。但如果两百轮还在上升,那就得加到三百轮。
imgsz=640是输入图像的尺寸。这个尺寸决定了模型能看到多少细节。PCB图像里的小目标很多,尺寸太小会漏检,尺寸太大显存扛不住。640是一个比较通用的值,如果你显存够大,可以试到八百甚至一千零二十四,但速度会明显下降。
batch=16是批大小。这个值受显存限制,十二个G的显存跑640尺寸的图,batch设到十六差不多是极限了。如果显存不够,可以降到八或者四,但batch太小会导致训练不稳定,梯度更新的方向波动大。
lr0=0.01是初始学习率。这个值我试过0.001、0.01、0.1,最后发现0.01最稳。0.001收敛太慢,0.1容易震荡。如果你用的是预训练模型,0.01是合适的;如果是从头训练,可以适当降低到0.005。
训练过程中要盯着几个指标:box_loss、cls_loss、dfl_loss,还有验证集上的mAP@0.5和mAP@0.5:0.95。box_loss是框回归的损失,cls_loss是分类的损失,dfl_loss是分布焦点损失。这三个loss都应该随着训练轮数下降,如果某个loss不降反升,说明模型过拟合了,得加正则化或者减模型复杂度。
4.4 训练过程的监控与早停策略
Ultralytics默认会在训练结束后画一堆曲线图,但训练过程中你只能看命令行输出的日志。我习惯用TensorBoard来实时监控:
tensorboard --logdir runs/detect/train然后在浏览器里打开对应的端口,就能看到loss曲线和mAP曲线实时更新。这样你可以在训练过程中就判断模型有没有跑偏,不用等训练结束。
早停策略我用的是patience=50,意思是如果验证集上的mAP连续五十轮没有提升,就自动停止训练。这个参数能省不少时间,避免在已经收敛的模型上继续浪费算力。
提示:早停的patience不要设太小。我试过设成二十,结果模型在第三十轮的时候mAP有一个小幅下降,然后第四十轮又升上去了,但早停已经在第三十轮触发了,白白错过了后面的提升。五十是一个比较稳妥的值。
4.5 模型验证与指标解读
训练完之后,用验证集跑一遍:
yolo detect val model=runs/detect/train/weights/best.pt data=pcb.yaml输出的指标里,重点看这几个:
- mAP@0.5:IoU阈值为0.5时的平均精度,这个指标反映的是模型能不能把缺陷检出来。
- mAP@0.5:0.95:IoU阈值从0.5到0.95的平均精度,这个指标反映的是框的位置准不准。
- Precision:查准率,模型说是缺陷的里面有多少是真的缺陷。
- Recall:查全率,真的缺陷里面有多少被模型检出来了。
对于PCB缺陷检测,Recall比Precision更重要。因为漏检一个缺陷的代价远大于误检一个。误检了你可以人工复核,漏检了就直接流到客户手里了。所以调参的时候,宁可牺牲一点Precision,也要把Recall拉上去。
具体做法是降低置信度阈值。默认的置信度阈值是0.25,你可以降到0.15甚至0.1,这样更多的候选框会被保留,Recall会上升,但Precision会下降。降到多少合适,得看你的产线能接受多少误检率。
4.6 模型导出与推理部署
训练好的模型要导出成推理格式。我用的是ONNX:
yolo export model=best.pt format=onnx imgsz=640导出的ONNX模型可以用ONNX Runtime来推理,也可以进一步转成TensorRT来加速。TensorRT的推理速度比ONNX Runtime快大概百分之三十到五十,但转换过程稍微麻烦一点,需要装TensorRT的库。
推理的代码大概长这样:
from ultralytics import YOLO model = YOLO("best.onnx") results = model("test_image.jpg", conf=0.15) for result in results: boxes = result.boxes for box in boxes: cls = int(box.cls[0]) conf = float(box.conf[0]) xyxy = box.xyxy[0].tolist() print(f"类别: {cls}, 置信度: {conf:.2f}, 位置: {xyxy}")这段代码里,conf=0.15就是前面说的降低置信度阈值来提升Recall。实际部署的时候,这个阈值要根据产线的误检容忍度来调。
5. 常见问题与排查技巧实录
5.1 训练loss不下降怎么办
这是最常见的问题。可能的原因有几个:
第一,学习率设得太小。如果lr0设的是0.0001,那loss下降会非常慢,看起来就像不下降。解决办法是把学习率调到0.01试试。
第二,数据集标注有问题。如果标注文件里的坐标格式不对,或者类别编号超出了names的范围,模型学到的就是噪声。解决办法是写个脚本检查一遍所有标注文件,确保格式正确。
第三,预训练模型和任务不匹配。如果你用的是COCO预训练的模型,而你的数据集和COCO差异很大,那模型需要更多的轮数来适应。解决办法是增加epochs,或者用更大的学习率先跑几轮预热。
5.2 验证集mAP很高但实际推理效果差
这个问题通常是数据集划分有问题。前面说过,如果训练集和验证集里有来自同一块板子的图像,验证集的mAP会虚高。解决办法是按板子划分数据集,确保验证集里的板子在训练集里没出现过。
另一个可能的原因是验证集和实际推理场景的分布不一致。比如验证集用的是暗箱里拍的图,但实际推理是在产线上自然光下拍的,光照条件不一样,模型的表现自然差。解决办法是让验证集的采集条件和实际推理条件保持一致。
5.3 小目标漏检严重怎么办
PCB上的小目标漏检,是这类项目的典型难点。我试过几种办法:
第一种是提高输入分辨率。把imgsz从640提到八百甚至一千零二十四,小目标在图像里占的像素多了,模型更容易学到特征。代价是推理速度下降,显存占用增加。
第二种是用mosaic增强。mosaic把四张图拼成一张,相当于变相提高了小目标的密度,模型在训练时见过更多的小目标样本。
第三种是调整anchor的尺寸。YOLOv8是anchor-free的,不需要手动设anchor,但你可以通过调整reg_max参数来改变框回归的范围。默认的reg_max是十六,对于特别小的目标,可以降到八。
第四种是在损失函数里给小目标更高的权重。YOLOv8本身不支持这个,但你可以通过复制小目标的样本来间接实现。
5.4 模型过拟合怎么判断和处理
过拟合的典型表现是:训练集上的loss持续下降,但验证集上的loss开始上升,mAP停止增长甚至下降。
处理过拟合的办法有几个:
- 增加数据量。这是最根本的办法,但采图和标注都需要时间。
- 加数据增强。前面说的亮度扰动、高斯噪声、随机裁剪,都能增加数据的多样性。
- 减小模型复杂度。把yolov8s换成yolov8n,参数量少了,过拟合的风险就低了。
- 加正则化。YOLOv8默认有weight_decay,可以适当调大。
- 早停。在验证集mAP不再上升的时候及时停止训练。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| loss不下降 | 学习率太小 | 看loss曲线的斜率 | 调大lr0 |
| loss不下降 | 标注格式错误 | 检查txt文件 | 重新标注或修正格式 |
| mAP虚高 | 数据集划分不当 | 检查训练集和验证集是否有同板图像 | 按板子划分 |
| 小目标漏检 | 输入分辨率太低 | 看小目标在图像中的像素数 | 提高imgsz |
| 过拟合 | 数据量不足 | 看训练和验证loss的差距 | 加数据增强或减模型 |
| 推理速度慢 | 模型太大 | 看模型参数量 | 换小模型或转TensorRT |
| 误检率高 | 置信度阈值太低 | 看Precision指标 | 调高conf阈值 |
5.6 几个踩过的坑和独家技巧
第一个坑是标注文件的编码问题。LabelImg在某些系统上保存的txt文件是GBK编码的,而Ultralytics读的是UTF-8,结果读出来是乱码。解决办法是统一用UTF-8编码保存,或者在读之前转码。
第二个坑是图像文件名里的特殊字符。如果文件名里有空格或者中文,Ultralytics在读的时候可能会报错。解决办法是把所有文件名改成英文加数字的组合。
第三个技巧是用伪标签做半监督学习。当你标了几百张图之后,可以用训练好的模型去预测未标注的图像,把置信度高的预测结果当作标签,加入训练集。这样可以用较少的标注成本获得更多的训练数据。我试过用这个方法,在标注了三百张图的基础上,用伪标签扩充到八百张,mAP提升了大概三个百分点。
第四个技巧是多模型集成。训练几个不同版本的YOLO,比如yolov8n、yolov8s、yolov8m,推理的时候把它们的预测结果做NMS融合。这样做的代价是推理时间翻倍,但Recall能提升两到三个百分点。对于漏检代价很高的场景,这个投入是值得的。
6. 从训练到产线:部署环节的几个实际问题
模型训练出来只是第一步,真正把它用到产线上,还有几个问题要解决。
第一个是推理速度的稳定性。训练的时候你用的是离线图像,推理速度波动不大。但产线上是连续图像流,如果某一帧的推理时间突然变长,后面的帧就会堆积。解决办法是用TensorRT加速,并且设置一个推理超时,超时的帧直接跳过,避免阻塞。
第二个是模型更新的平滑过渡。产线不能停,你不可能把模型换掉再重新启动。我的做法是双模型并行,新模型先跑影子模式,只记录不报警,等验证稳定了再切换成主模型。这样切换的时候没有停机时间。
第三个是误检的人工复核流程。模型再准也会有误检,关键是误检之后怎么办。我的做法是,模型检出的缺陷自动截图,推送到一个复核界面,由质检员快速确认。确认是误检的,标记出来,定期拿这些误检样本去重新训练模型。这样就形成了一个闭环,模型会越用越准。
第四个是数据回流机制。产线上每天产生的图像,不能检完就扔。要把那些模型置信度处于临界区间的图像保存下来,定期人工复核,把新的缺陷类型和难例加入训练集。这个机制能让模型持续进化,适应新的缺陷形态。
这套流程跑下来,我从零开始到产线部署,大概花了三周时间。其中采图和标注占了两周,训练和调参占了三天,部署和调试占了四天。如果你有现成的数据集,那整个周期可以压缩到一周以内。
最后分享一个小技巧:训练的时候把最好的模型和最后一个模型都保存下来。best.pt是验证集上mAP最高的模型,last.pt是最后一轮的模型。有时候last.pt在实际推理中的表现反而比best.pt好,因为best.pt可能是在验证集上过拟合的。两个都留着,实际部署的时候都试一下,选效果好的那个。