做PCB缺陷检测这行,我前后换过三套方案。最早用传统图像处理,后来用两阶段检测器,直到把YOLO26跑通,6类缺陷的检出率才真正稳下来。这篇就围绕这套系统展开,讲一讲选型理由、数据准备、训练调参、部署交付这些环节里我踩过的坑和留下的可用代码,适合正在搞工业视觉检测、想用YOLO系列模型做小目标缺陷识别的朋友参考。
先交代项目背景:我负责的是一条PCB测试产线的质检环节优化,需要检测短路、断路、鼠咬、毛刺、针孔、多余铜这6类常见缺陷。课题目标不只是“跑通一个模型”,而是要做成一套能交付、能演示、能给别人复现的系统。所以除了模型训练本身,数据规范、部署方式、中英文文档以及源码组织,每一块都不能省。下面按实际推进的顺序来写,把关键决策的原因和现场踩坑一并说清楚。
1. 为什么选YOLO26而不是传统视觉方案
1.1 PCB缺陷检测的真正难点
PCB缺陷检测和通用目标检测有个明显区别:缺陷的绝对尺寸非常小。一块板卡在500万像素工业相机下成像后,一个鼠咬缺陷可能只有20×30像素,针孔甚至只有8×8。在整图缩放为640×640之后,这类缺陷几乎退化成了几个像素点,召回率极难保证。
除了小目标问题,背景干扰也很麻烦。PCB板面本身有走线、焊盘、字符丝印,不同批次还有绿油颜色深浅差异和玻璃纤维纹理。传统视觉方案里,我最早用过基于阈值分割和连通域分析的方法,在某种颜色阻焊层下效果还行,可一旦换批次、换光源角度,误检率立刻飙升,后面的处理逻辑完全没有泛化能力。
后来换成两阶段检测器,精度确实上来了,但速度撑不住产线节拍。工业场景不是只看准确率,一块板卡检测必须控制在几百毫秒内,否则就得加多台工位机,成本翻倍。这就是我转向YOLO26最直接的原因:它在保持高精度的同时,推理速度比两阶段方案快一个数量级,而且部署路径更顺。
1.2 6类缺陷的业务定义与识别要点
6类缺陷的英文名、成因和视觉特征如下表。这些名称不是随便定的,在给海外客户交付或写论文时,中英文对照能省掉大量沟通成本,尤其是“鼠咬”这种直译容易让人困惑的术语,我在项目文档里统一保留了英文名并加了注释。
| 类别 | 中文名称 | 英文名称 | 典型成因 | 视觉特征 |
|---|---|---|---|---|
| 0 | 短路 | Short | 蚀刻不足、锡膏桥接 | 相邻线路之间出现异常连接 |
| 1 | 断路 | Open Circuit | 蚀刻过度、外力损伤 | 导线中途断开,出现缺口 |
| 2 | 鼠咬 | Mousebite | 蚀刻药水局部过浓 | 线路边缘出现半月形凹陷 |
| 3 | 毛刺 | Burr | 钻孔或冲切残留 | 线路边缘突出的多余材料 |
| 4 | 针孔 | Pinhole | 基材气泡、电镀不均 | 焊盘或线路上极小圆孔 |
| 5 | 多余铜 | Spurious Copper | 曝光残留、清洗不净 | 非导通区域的零散铜屑或铜块 |
最容易混淆的是鼠咬和断路:前者只是线路边缘凹陷,但线路本身还没断开;后者是线路彻底断开了。标注时我曾因为这两类边界不清导致验证集评分虚高,后来把标注规范重新梳理了一遍,明确“线路宽度被切断超过50%才算断路”,才把混淆问题压下来。
1.3 YOLO26在工业检测里的优势与取舍
YOLO26并不是遥不可及的黑科技,它的核心思路和现有YOLO系列一脉相承:把目标检测当成回归问题,一次前向直接输出类别和边界框。相比两阶段检测器省去了区域提议网络,速度自然快。26版本在骨干和颈部结构上又做了一轮调整,给小目标分配了更强的语义分支,这对PCB缺陷场景是实打实的加分项。
另一个让我下决心用它的原因是生态成熟。官方仓库支持训练、导出、部署的完整链路,换到其他自研框架时,光是复现数据增强和损失函数就要花大量时间。我这套系统最后交付给现场工程师,对方只需要会用命令行启动训练和推理,不需要理解内部细节。
当然,YOLO26也有取舍。它的官方模型权重是在通用数据集上预训练的,直接拿来检测PCB缺陷效果很差,必须用工业数据做针对性微调。另外它对超大分辨率输入并不友好,这个后面在推理优化章节再展开。
2. 数据准备与标注规范:决定项目成色的隐藏环节
2.1 数据采集与增强策略
很多项目死在模型效果不好,但根因其实是数据没做好。PCB缺陷本身是小概率事件,良品板占绝大多数,如果直接从产线原图里随机抽,抽到缺陷样本的概率非常低。我当时的策略是:先用已有AOI设备标出的历史缺陷坐标,从原始图像里把缺陷区域连同上下文一起截出来;再用这些截出的样本按不同背景、不同旋转角度贴回正常板卡图像,生成合成缺陷样本。
合成样本不能替代真实样本,但能帮助模型先学会“什么是缺陷”,后续再用真实缺陷图像微调。对于PCB这种背景高度结构化的场景,合成数据效果相当不错,可以理解为给模型预先灌输“缺陷的视觉模式”,真正部署时再让它适应真实的光照和噪点。
数据增强方面,我用的是马赛克增强、随机翻转、HSV色域扰动和随机擦除。这里有个反直觉的细节:马赛克增强在小目标检测里是一把双刃剑。它能把四张图缩拼成一张,丰富背景多样性,但拼接缩小时小缺陷会被压缩到几乎消失,导致模型学不到有效特征。我最后把马赛克概率调到0.5,并且在最后50个epoch关闭马赛克,让模型在小目标尺度上做平稳收尾,这个改动直接让针孔类的召回率涨了约4个百分点。
2.2 标注规范与边界案例处理
标注工具我用的是开源的图像标注工具,导出格式直接选YOLO格式。每张图片的标签文件是纯文本,一行代表一个目标框,格式是:
类别id 中心点x 中心点y 宽度 高度所有坐标都归一化到0~1之间。例如一张640×640的图中,缺陷框左上角在(100, 120),宽80、高60,对应的中心点就是(140, 150),归一化后是(0.21875, 0.234375),宽高归一化后是(0.125, 0.09375),写入文件就是:
2 0.21875 0.234375 0.125 0.09375标注时有几个容易踩的坑,我直接列出来:
- 边界框需要完整框住缺陷区域,但不要留太大空白,否则会引入背景噪声。我曾见过有人把框画成包裹整段走线,模型训练时学会的是“看到走线就报警”,后果可想而知。
- 对于焊盘边缘的毛刺,通常把焊盘本身包含进框内,让模型有足够的上下文来判断“这是焊盘之外凸出的异常材料”。
- 一个框里同时存在两种缺陷时,按主导缺陷标注,不要一个框打两个类别标签。我曾尝试过复标签,结果增加了不少损失波动,收益却不明显。
- 模糊图像必须单列一个“难例文件夹”,单独评审,不要混进训练集里污染整体分布。
2.3 数据划分最容易被忽视的“同板泄露”
划分训练集和验证集时,最容易犯的错误是直接按图片文件随机切分。同一块PCB的多张不同角度图像会被同时分到训练集和验证集,模型虽然没见过验证集那张图,但见过同一块板上几乎一样的纹理,验证指标会虚高不少。现场看起来漂亮,换到新板子立刻现原形。
我最终的划分方式是:按板卡编号分组,同一块板的所有图片全部落在同一个集合里。训练集、验证集、测试集比例大致是7:2:1。这样做出来的指标才是真实泛化能力的反映,项目验收时也能给客户一个靠谱的预期。
3. 训练环境与调参:从模型收敛到稳定检出
3.1 环境搭建与训练入口
训练环境我用的是常规的深度学习流程,先建独立虚拟环境,再装显卡驱动对应的CUDA版本和PyTorch,最后拉取YOLO26官方推理和训练代码。依赖装错是最常见的坑,字段版本不匹配导致报错的情况我遇到过好几次,所以直接把整个环境导出成requirements文件交付给现场,保证复现环境一致。
训练命令大概是这样的:
python train.py \ --data ./data/PCB.yaml \ --weights yolov26.pt \ --imgsz 640 \ --batch 16 \ --epochs 300 \ --device 0其中PCB.yaml描述了训练集和验证集路径、类别数量和类别名称。这里特别提醒一句,类别顺序一定要和数据集的标签顺序一致,因为模型输出的类别id是纯数字,如果训练时0号是短路、部署时0号变成了断路,整个系统都会静默出错。
3.2 关键超参数的选择逻辑
关于输入分辨率,我选择了640×640还是1280×1280?答案是分阶段用。480×480的缩略图能粗略发现缺陷,但细节不够;直接用1280×1280训练,小目标召回率明显提升,但对显存的要求也高。我的做法是:先用640×640做快速实验验证pipeline通不通,再把分辨率提高到1280做正式训练。下表是我整理过的关键超参,可以直接抄作业:
| 参数 | 初始值 | 最终值 | 说明 |
|---|---|---|---|
| imgsz | 640 | 1280 | 小目标检测,分辨率越高越好,但需兼顾显存 |
| batch | 16 | 8 | 1280分辨率下显存不足时降batch |
| epochs | 300 | 300 | 配合早停,以验证集指标为准 |
| warmup | 3 | 3 | 前3个epoch用较低学习率热身 |
| 学习率 | 0.01 | 0.01 | 配合余弦退火衰减 |
| mosaic | 1.0 | 0.5 | 后半程关闭或降低,保护小目标 |
| 冻结骨干 | 10 | 0 | 预训练模型微调时冻结前10层,之后全量微调 |
还有一个关键点:不要盲目追求大batch。小目标检测对batch size的敏感度很高,太大容易让样本内的罕见缺陷被淹没在多数类别中。我最后用batch=8配合梯度累积,等效到16的batch效果,既稳定了训练,又控制了显存占用。
3.3 loss曲线判断模型状态与类别不平衡处理
训练时我主要盯三条曲线:box_loss、cls_loss和dfl_loss。box_loss衡量预测框和真实框的坐标误差,cls_loss衡量类别判断错误,dfl_loss负责分布聚焦,用来优化边界框的质量。一个比较理想的状态是这三条曲线在训练中段稳步下降,验证集曲线没有明显反弹;如果cls_loss快速收敛但box_loss还在高位震荡,大概率是标注框本身不精准。
PCB缺陷数据天然存在类别不均衡:短路、断路比较多,针孔比较少。我的处理办法是给每个类别单独计算样本数量,对针孔和多余铜这类稀缺类别做在线过采样,让每个batch里都有足够多的稀缺样本。另外我用了focal loss思路对难例加权重,不需要改代码太多,直接在损失函数配置里调整类别权重系数就行。
最后我拿到的验证结果也值得参考:短路类的AP最高,因为特征最鲜明;针孔类AP最低,因为目标太小,容易和板面纹理混淆。整体mAP大约在0.84左右,F1分数约0.79,单张640分辨率推理耗时约35毫秒,满足产线200毫秒内的节拍要求。
4. 推理部署优化与工程化落地
4.1 模型导出与加速
训练得到best.pt后,我做的第一件事不是直接部署,而是先转成ONNX再根据部署平台选择推理后端。PyTorch模型在生产环境里太重,而且对依赖版本敏感,转到ONNX之后可以方便地接通用推理引擎做加速。导出命令大致是:
python export.py --weights best.pt --include onnx --opset 12如果现场机器用的是特定加速卡,再做一步量化模型转换。FP16量化在这里够用,能把推理耗时再压掉大约20%,配合超大图的切片推理,整体检测时间可以控制在预期节奏内。
导出之后必须做精度对齐测试:把同一张图分别用PyTorch模型和ONNX模型跑一遍,对比输出框的坐标和分数。因为归一化方式或输入节点命名问题,导出模型经常会出现莫名其妙的性能下降,这一步不检查,上线之后再排查非常痛苦。
4.2 推理参数与NMS调优
部署推理时,conf_thres和iou_thres这两个参数我调试了很久。conf_thres默认值0.25在这个项目里偏低,会漏出大量低置信度误检框。把conf_thres调到0.35左右,iou_thres调到0.45,误检率最平衡。
PCB缺陷还有一个特点:小目标密集分布。如果板卡上有20个几乎挨在一起的毛刺,默认的非极大值抑制可能会把它们当成同一个目标过滤掉。我后来把针对小目标的NMS策略调整为按面积区分处理,小框用更低的重叠阈值,保留密度更高的邻域框,才把这类密集场景的检出数拉满。
4.3 部署中的常见坑与现场问题
部署阶段遇到的问题比训练阶段更多,这里列一个典型问题排查表:
| 现象 | 原因 | 解法 |
|---|---|---|
| 模型导出后精度暴跌 | 输入归一化方式不一致 | 导出前固定归一化参数,推理时匹配训练阶段处理 |
| 蓝绿阻焊层误检严重 | 背景颜色干扰 | 增加对应颜色背景的训练样本或做颜色域变换增强 |
| 大图推理时显存溢出 | 整图直接送入模型 | 切片推理,先切块推断再拼接结果 |
| 检测框抖动 | conf阈值偏低或NMS重叠阈值不合理 | 提高conf_thres,调低iou_thres |
| 新批次板卡漏检 | 不同批次纹理差异大 | 小样本微调,准备少量新批次标注并增量训练 |
5. 中英文双版:让项目能交付、能展示、能复用
5.1 双版结构如何组织
这套系统之所以要做中英文双版,是因为实际交付对象不完全是国内团队。评审材料、海外同行交流、开源仓库推广都需要英文说明,而现场工程师又必须看中文文档快速上手。我给项目设计了双目录结构:docs目录下同时维护README.zh-CN.md和README.en.md,代码内注释也分中英两版,同一份内容同步更新。
真正实践下来,维护双版最大的成本不是翻译,而是避免英文文档和中文文档内容漂移。我后来的做法是:所有截图、路径、命令都从一份公共配置里生成,文档只保留叙述性描述。这样一来,无论中文版改了什么命令,英文版只需要同步替换参数即可。
5.2 README、源码组织与效果演示的呈现
README是项目的门面,我花了大量时间打磨。开头放一段项目简介和2~4张效果对比图,让读者半分钟内明白这套系统解决什么问题;接着是环境要求、安装步骤、快速开始、训练指南、推理指南、数据集格式说明和模型权重说明。效果演示部分不只是丢一张图,而是同时提供静态样例输出、视频流检测截图和一组统计指标表格,让读者能直观看到实际效果。
目录结构按“训练、推理、可视化、工具、文档”五块拆分,核心入口都封装在一个顶层脚本里。现场工程师不需要看懂整个模型原理,只需根据需求选择训练还是推理入口。
5.3 效果演示的细节
演示环节最容易被忽视的是可视化质量。我专门写了一个可视化模块,把检测结果叠加到原图上,并且用不同颜色区分缺陷类别,左上角显示FPS和当前帧的缺陷统计。输出既可以保存为带标注的图像或视频,也可以打印成JSON格式的检测结果,方便后续对接产线系统。
这样做的好处是,项目验收和对外展示时不需要反复解释模型指标,对方看到可视化的实时检测效果,配合每类的精度召回数据表,几分钟就能建立信任。
6. 从源码到复现:完整跑通这套系统的顺序
6.1 目录结构与核心模块
我最后交付的源码目录大致是这样:
pcb-defect-yolo26/ ├── data/ # 数据集配置与标签说明 ├── docs/ # 中英文文档 ├── scripts/ # 训练、推理、部署脚本 ├── utils/ # 工具函数、可视化、指标计算 ├── train.py # 训练入口 ├── detect.py # 推理入口 ├── export.py # 模型导出入口 ├── requirements.txt # 依赖列表 └── README.md # 项目说明train.py只负责训练逻辑,detect.py只负责推理,两个入口完全分离,这样项目的逻辑非常清晰,不会出现改一个功能影响另一个功能的情况。
6.2 复现步骤与常见报错
按下面的顺序,可以比较顺畅地复现这套系统:
- 准备数据集,确认PCB.yaml中的类别顺序与标注一致;
- 安装依赖,导出环境;
- 运行train.py启动训练,观察loss曲线;
- 运行export.py导出ONNX模型;
- 运行detect.py测试单张图片;
- 输入视频流地址,验证实时检测效果。
如果跑的过程报错,多和数据格式、依赖版本相关。我遇到过的报错里,有一类特别有代表性:数据集图片是灰度图,但模型输入要求三通道,导致推理阶段维度不匹配。解决方法是统一在数据预处理里把灰度图复制成三通道,不要靠模型自适应,以免不同设备行为不一致。
6.3 后续扩展方向
整套系统跑通之后,我在思考几个后续扩展方向:一是利用未标注的产线图像做半监督学习,可以进一步降低标注成本;二是轻量化模型,让这套检测跑在边缘小设备上,满足手持检测仪的实时需求;三是把缺陷坐标自动映射到PCB的CAD坐标体系,方便维修工位快速定位缺陷位置。
7. 个人实操中的一些体会
这套系统从立项到交付,给我最深的感受是:检测模型本身只是整条链路中的一环,数据规范、工程部署和文档质量对项目成败的影响往往更大。
我在实际操作中的一个建议是:先花一两天跑通最小案例,从训练到推理整条链路全通一遍,再回头补数据和调参数。最小案例帮我提前发现了一堆和模型无关的环境问题,如果一开始就埋头造数据集,很容易在最后部署阶段被各种环境错误消耗大量时间。
最后再分享一个小技巧:PCB缺陷检测的标注边界要反复和现场质检人员对齐,我因为前期没有让一线人员参与标注评审,后期花在清洗数据上的时间比想象中多得多。现在我会建议团队在项目启动时就拉上质检员做一轮标注共识会,这个动作节省的时间价值,远超开会本身。