搞 PCB 外观缺陷检测这个需求,我前前后后折腾了快两年,最近终于把一套基于 YOLO26 的 6 类 PCB 缺陷智能检测系统从数据标注一直撸到了中英文双版的可执行程序。整套源码、训练好的权重和效果演示资源我都整理好了,如果你所在团队也在做 AOI、SMT 后段或者来料 PCBA 质检,这篇内容应该能帮你省掉不少弯路。
先说清楚这篇要讲什么:我不打算只贴一堆命令让你自己猜,而是从“为什么用 YOLO26”“数据怎么准备”“模型怎么训练”“界面怎么开发”“踩过哪些坑”这几个维度把整个项目拆开。无论你是刚接触目标检测的新手,还是已经跑过 YOLO 系列的老手,只要对照这篇的步骤走,基本都能把这套 6 类 PCB 缺陷检测系统复现出来。
1. 为什么要用 YOLO26 做 PCB 缺陷检测
1.1 PCB 质检场景的痛点和需求
PCB 板检测不是“有没有问题”那么简单。实际的产线场景里,板面有铜箔纹理、丝印、阻焊层颜色变化,再加上光照角度、反光、镜头畸变,图像噪声非常重。而缺陷本身又分很多种:短路可能是一条很细的铜丝搭在两个焊盘之间,断路可能是走线上一个不起眼的缺口,缺孔可能只是过孔位置少了一个洞,毛刺往往只有几个像素宽。
传统的视觉方案大多是基于模板比对或手动设计特征,再用阈值分割、blob 分析去抓缺陷。问题在于:换一种板材、换一批油墨颜色、换一条产线的光照,原本调好的阈值就失效了。维护这些规则的成本非常高,而且细小缺陷很容易漏掉。这也是我当时决定转向深度学习目标检测的原因——直接把“缺陷定位”当成“在图像里找目标框”的问题,模型自动学习特征,鲁棒性高很多。
1.2 YOLO26 相比传统视觉方案的优势
YOLO26 是 YOLO 系列里比较新的一个版本,在主干的特征提取上做了更多跨层融合和轻量化注意力,检测头也把分类和回归分开处理,训练阶段还支持动态标签分配。这些特性放到 PCB 质检这种“小目标多、背景纹理复杂”的场景里,最直接的收益就是:同样的缺陷,用旧版模型可能漏检的细丝短路,YOLO26 能框得更准。
但我要说句实在话,选 YOLO26 不只是因为它精度高。工程上更重要的原因是:它的训练和导出链路非常成熟,一套代码能从 PyTorch 训练到 ONNX、TensorRT 导出,部署时不用自己造轮子。而且社区里能查到的资料多,团队新成员上手快。对于中小团队做产线验证,这是很现实的优势。
1.3 为什么锁定 6 类缺陷
很多人一上来就想把“所有缺陷”都塞给模型,我的建议恰恰相反。缺陷类别不是越多越好,一定要结合客户实际的不良品管控要求。我当时做的这个项目,是通过历史维修记录和人工复判数据,把产线上出现频率最高的缺陷聚类成 6 类:短路、断路、缺孔、毛刺、焊桥、划痕。这 6 类占了全部不良品的八成以上,先把它们做稳,后面再逐步扩展。
类别锁定的另一个原因是数据成本。每一类缺陷如果要训练到可用的精度,至少需要几百张典型样本。如果一开始就搞二十几类,样本根本攒不够,最终结果必然是漏检误检一大堆。所以我建议所有做工业检测的朋友:分类宁少勿多,先把高频、高风险缺陷做透。
2. 整体系统设计与方案选型
2.1 系统架构:采集、标注、训练、推理、展示
整个项目从功能上拆成五个层次:图像采集层、数据标注层、模型训练层、推理服务层、中英文界面层。图像采集层面对的是工业相机或者离线图片文件夹,数据标注层负责把原始图片转换成 YOLO 格式,训练层跑 YOLO26,推理服务层负责把模型封装成可供界面调用的检测器,最后界面层提供鼠标点选、批量检测、结果导出这些交互功能。
层与层之间用清晰的接口隔开。比如界面层不直接操作模型权重,只调用一个Detector类的predict(image) -> List[Defect]接口。这样以后想换模型、换相机、换界面框架,都不会牵一发动全身。整个架构看起来很简单,但实际写代码时,把接口定义清楚能让后期省很多事。
2.2 工具链选型:框架、训练环境、推理部署
训练框架我用了 PyTorch,搭配 Python 3.10。显卡要求不高,8GB 显存就能训 640 分辨率的 YOLO26 小模型,当然显存更大可以开更大 batch 或更高分辨率。标注工具用的是开源标注软件,标注结果导出成 JSON,再写脚本转成 YOLO 的 txt 格式。界面开发选了 PySide6(Qt 的 Python 绑定),因为桌面端部署方便,不依赖浏览器。
推理部署分两条线:产线 GPU 机器用 TensorRT 引擎,开发机和普通电脑用 ONNX Runtime。这样做的原因是 TensorRT 在 NVIDIA 显卡上能跑到很低的延迟,而 ONNX Runtime 便于在没有 GPU 的环境里做演示和调试。语言文件我用 JSON 做中英文 key-value 映射,启动时读配置决定显示中文还是英文,切换时刷新界面文本。
2.3 中英文双版本的实现思路
中英文双版不是简单地把按钮文字翻译一下就完事,还要考虑模型输出的类别名、日志信息、导出报告表头、甚至是异常提示。我这边统一维护了一个lang/zh.json和lang/en.json,里面是扁平化的 key-value 结构,例如:
{ "main_window_title": { "zh": "PCB 缺陷智能检测系统", "en": "PCB Defect Intelligent Detection System" }, "label_short": { "zh": "短路", "en": "Short" } }界面启动时先从配置文件读取language字段,把这个 key-value 结构加载成字典。凡是要显示文字的地方,都通过一个tr(key)函数去查字典。切换语言时,遍历所有控件重新setText即可。这里有一个很容易踩的坑:JSON 文件必须用 UTF-8 编码,并且解析时要设置ensure_ascii=False,否则中文会变成\uXXXX,虽然也能显示,但维护起来非常痛苦。
2.4 目录结构和源码组织
源码我按功能划分目录,避免所有脚本堆在一个文件里。大致结构如下:
PCB-YOLO26/ ├── checkpoints/ # 训练权重、导出模型 ├── configs/ # 数据集配置、推理配置 ├── data/ # 数据集、标注文件 ├── docs/ # 使用文档、效果演示说明 ├── lang/ # 中英文语言文件 ├── scripts/ # 数据转换、训练、导出脚本 ├── src/ │ ├── data_utils/ # 数据加载与增强 │ ├── models/ # 模型封装 │ ├── detector.py # 推理核心 │ ├── train.py # 训练入口 │ ├── export.py # 模型导出 │ └── ui/ │ ├── main_window.py │ └── widgets.py ├── requirements.txt └── README.md这样的组织方式好处很直接:训练脚本、导出脚本、界面脚本各自独立,即使某个模块出了问题,也不会影响其他模块。后续要交给别人维护,看目录结构就能明白系统是怎么串起来的。
3. 数据准备与标注实操
3.1 6类缺陷定义与示例
先明确各类缺陷的英文标签和判定标准,避免团队内部标注口径不一致。我在项目里用的定义如下:
| 类别 | 中文标签 | 英文标签 | 典型表现 | 检测难点 |
|---|---|---|---|---|
| 短路 | 短路 | Short | 两焊盘或导线之间有额外铜连接 | 细丝可能只有几个像素 |
| 断路 | 断路 | Open | 走线断裂或铜箔缺口 | 缺口与噪声边缘相似 |
| 缺孔 | 缺孔 | Missing Hole | 过孔位置缺失钻孔 | 对比度低 |
| 毛刺 | 毛刺 | Burr | 导线边缘凸起多余铜 | 形状变化大 |
| 焊桥 | 焊桥 | Solder Bridge | 焊盘间焊料连接 | 光照反光强烈 |
| 划痕 | 划痕 | Scratch | 表面机械划伤 | 细长、亮度多变 |
每类缺陷的标注规则最好单独写一页说明,尤其要写清楚“什么情况算、什么情况不算”。比如毛刺长度小于多少像素不算,划痕多长以上才记不良。没有统一标准,标注员之间会出现严重偏差,模型训练出来自然不稳。
3.2 数据采集与增强策略
原始图片采集时,尽量覆盖不同光照角度、不同批次的板材、不同相机曝光参数。不要只挑看起来清晰的图片,实际产线中曝光不足、反光厉害的样本也要收集一些,因为它们才是部署时的“拦路虎”。我当时是每周去产线抽一批不良板,在标准光源下拍一组,在模拟工况下再拍一组,原始图攒了大概 1200 多张。
数据增强方面,YOLO26 训练框架自带 mosaic、hsv 扰动、随机翻转、缩放等策略。对于 PCB 这种纹理丰富的图像,我建议 mosaic 强度可以保持在 1.0,但 hsv 的饱和度和亮度扰动不要太大,否则会过度改变铜箔、阻焊油墨的颜色,让模型学到不真实的特征。另外,可以考虑针对小缺陷做随机裁剪放大增强,也就是把原图某一区域裁出来再 resize 到训练尺寸,强制模型去学习小目标。
3.3 标注格式转换与数据集划分
开源标注软件导出的一般是 JSON 或 XML,格式里包含类别名称和多边形或矩形坐标。YOLO 训练需要的是每张图片对应一个 txt 文件,每行格式是:
class_id x_center y_center width height其中坐标都归一化到 0 到 1。转换脚本核心逻辑大概是这样:
def convert_label(json_path, target_size): with open(json_path, "r", encoding="utf-8") as f: label = json.load(f) lines = [] for obj in label["objects"]: class_id = class_name_to_id[obj["category"]] x1, y1, x2, y2 = obj["bbox"] img_w, img_h = label["image_width"], label["image_height"] cx, cy = (x1 + x2) / 2 / img_w, (y1 + y2) / 2 / img_h w, h = (x2 - x1) / img_w, (y2 - y1) / img_h lines.append(f"{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") return lines转换之后,数据集按照 8:1:1 的比例分成训练集、验证集、测试集。划分时注意:同一个 PCB 板上出现的多个缺陷图片不要同时出现在训练集和验证集里,否则会高估模型泛化能力。最简单的方法是先把图片按板卡批次分组,再按组划分。
3.4 标注质量检查
模型精度上限很大程度由标注质量决定。训练前一定要做一次可视化检查:把标注框画回原图,逐张过一遍。我一般会找出三类问题:一是漏标,图像里明明有缺陷但没框;二是框太松,把一大片背景都包含进来;三是类别错标,比如把划痕标成断路。
另外要统计每个类别的目标数量。如果某一个类别只有几十个框,要么补充数据,要么考虑暂时合并到相近类别。我在项目中就发现“缺孔”样本特别少,后来专门从历史返修记录里翻出大量小图,再用程序拼接成大图补充,才把这一类数量拉上来。
4. 模型训练与调优
4.1 训练配置参数与超参数选择
训练入口我封装在train.py里,一条命令就能启动:
python train.py --model yolov26m.pt --data configs/pcb.yaml --epochs 300 --batch-size 16 --img 640 --device 0pcb.yaml里需要配置类别数和类别名,比如:
names: 0: Short 1: Open 2: Missing_Hole 3: Burr 4: Solder_Bridge 5: Scratch这里有一个很容易犯的错:类别顺序必须和标注转换脚本里的class_name_to_id完全一致,否则训练时类别全乱。输入分辨率我默认用 640,因为 PCB 缺陷虽然细小,但很多缺陷放在整板图中只有十几个像素,如果用 640 训练,小目标信息会丢。如果显存够,可以尝试 960 甚至 1280,代价是训练变慢、推理变慢。对于强调漏检率的场景,我会优先选择 960。
4.2 训练过程与日志解读
训练日志里最关键的不是 loss 降到多低,而是验证集上的 mAP50 和 mAP50-95。YOLO26 的 loss 由 box_loss、cls_loss、dfl_loss 三部分组成。训练初期 loss 会快速下降,但到中后期,loss 曲线可能还有波动,此时不要盲目继续训,先看 mAP 是不是还在涨。
我习惯设置每 10 个 epoch 保存一次验证集最优权重,最后看results.csv选择最优模型。如果发现训练集 loss 降得很低但验证集 mAP 不涨,大概率是过拟合了,可以加大增强、增加数据或者降低模型复杂度。如果 loss 直接变成 NaN,优先检查学习率是不是太大、数据里有没有损坏的全黑或全白图片。
4.3 精度提升技巧:难例挖掘、损失权重、TTA
训练一轮之后,把验证集里所有漏检和误检的图片整理出来,人工看一遍。漏检通常说明样本多样性不够;误检则说明模型把背景纹理当成了目标。我会把这两类图以及它们的局部裁剪图重新加入训练集,再补训一轮。这个操作比盲目调参有效得多。
类别不平衡时,可以在配置里给低频类别更高的 cls 损失权重。比如“缺孔”样本少,权重设为 1.5,其他类 1.0。还有一个技巧是测试时开启 TTA,对图像做多尺度缩放和翻转,再综合结果。TTA 能提升离线检测的召回率,但推理时间会成倍增加,不适合产线实时场景,我会把它放在“精细复判”模式里,作为误漏检抽检工具。
4.4 模型导出与量化
模型训练好后,导出部署格式:
python export.py --weights checkpoints/best.pt --include onnx engine --device 0导出 ONNX 后建议先检查输出节点是否正确,再用 TensorRT 的trtexec工具转成 engine。如果显存有限,可以改用 FP16 精度,精度损失通常很小。INT8 量化能进一步提速,但需要准备一份校准图片集,且量化后 mAP 可能下降 1 到 3 个点,必须放在测试集上重新验证后再决定是否上线。
5. 推理应用与界面开发
5.1 推理 pipeline 和后处理
推理流程不复杂,但细节决定成败。每一步的顺序是:读取图像 -> 去除 EXIF 旋转信息 -> Letterbox 缩放 -> BGR 转 RGB -> 归一化 -> 模型推理 -> NMS 去重 -> 坐标映射回原图。我没有直接用原图尺寸送进模型,而是先做 letterbox,保留原始宽高比,避免目标被拉伸变形。
NMS 参数我通常设为conf=0.25, iou=0.45。在小缺陷多的场景下,如果想要更高召回率,可以把conf降到 0.15,但误检也会增多。一个更稳的玩法是保留低置信度框,再用规则过滤:比如面积小于多少像素的框直接丢弃,或者缺陷长度太短忽略。这些后处理规则可以写进配置,不用改代码。
检测结果可视化时,每个类别用不同颜色画框,左上角显示类别和置信度。显示文本要经过中英文映射,例如检测到Short时,中文模式显示“短路 0.92”,英文模式显示“Short 0.92”。这样一套界面切语言,结果页也跟着切。
5.2 中英文双版界面细节
界面用的是 PySide6,整体分三栏:左边是图片文件列表,中间是图像显示区,右边是缺陷结果表格。顶部工具栏提供“打开图片”“开始检测”“语言切换”“导出报告”等按钮。
语言切换主要有三种做法。第一种是重启应用时根据配置文件加载语言;第二种是运行中切换并立即刷新所有界面文本;第三种是用 Qt 自带的翻译机制配合.ts文件。我的项目里用的是第二种,因为用户反馈“不想重启程序”。实现方式也简单:切换语言按钮触发一个信号,主窗口拿到新的语言字典后,调用一个统一的refresh_texts()方法,把所有按钮、标签、表格表头重新设置一遍。
还要注意字体问题。英文和中文混排时,如果系统没装中文字体,界面会出现方块。部署到产线电脑前,我一般会确认安装了“微软雅黑”或“Noto Sans CJK SC”字体。跨平台部署时,这个坑特别常见。
5.3 实时检测与批量检测模式
系统支持两种运行模式:单图/批量模式,和实时预览模式。单图模式适合抽检,点击文件列表中的图片,右侧立刻显示检测结果。批量模式遍历整个文件夹的图片,检测结果统一存成 CSV,每一行包括图片名、缺陷类别、置信度、x1/y1/x2/y2 坐标。
实时预览模式面向产线相机接入,我从Detector里单开一个线程,相机帧经过队列送入推理线程,推理完成后把结果叠加到画面再显示。为了避免 UI 卡顿,所有模型推理都在后台线程执行,界面只负责显示最新帧。相机不一定是工业相机,项目里先用普通 USB 摄像头模拟产线实时流,只要图像分辨率足够,效果演示完全够用。
5.4 集成到产线 MES 系统的接口预留
很多工厂希望把检测结果直接推送进 MES 或品质管理系统。我在系统里加了一个可选的 HTTP 服务,接口只做两件事:接收图片,返回检测结果。返回的 JSON 结构大致是:
{ "status": "ok", "image_id": "PCB_000123", "result": "NG", "defects": [ { "class": "Short", "confidence": 0.91, "bbox": [120, 45, 156, 70] } ] }这类接口不涉及登录逻辑,只用简单 token 校验。实际部署时只在内网开放端口,安全性和功能都够用。接口代码和界面代码完全分离,以后接机械臂、接报警灯都方便。
6. 常见问题与排查实录
6.1 漏检和误检
漏检最直接的表现是“明明肉眼能看到,模型却没框出来”。排查时先看这张图是不是和训练集差异很大。我遇到过一次,某批 PCB 的油墨颜色比训练数据深很多,导致整个图像亮度分布偏移,模型在验证集上不错,到现场却大量漏检。解决办法是采集现场图片加入训练集重新调优。
误检则恰恰相反,模型把丝印字符、划痕纹理、甚至板材边缘当成目标。我会把误检的图片导出来,看目标周围的上下文。如果误检区域集中在反光区域,通常是因为训练数据里反光样本太少。与其调 NMS 阈值,不如补充负样本——也就是不放任何标注框的合格板图片,让模型学会“这些背景不是缺陷”。
6.2 训练不收敛或 loss 异常
这是一个高频问题。loss 一直不降,第一步检查类别顺序是否一致;第二步检查学习率,我通常初始学习率不超过 0.01,batch size 大了要相应调小;第三步检查标签是否越界,归一化后坐标不应该有负数或大于 1 的值。写一个脚本扫描所有 txt 标签,把非法行打印出来,比肉眼检查高效得多。
loss 突然变成 NaN,大多数情况是数据里有异常图片。比如全黑图片、全白图片、损坏的 TIF 文件。我后来在数据加载前加了一个预处理函数,先统计图片的方差,如果方差接近 0 就跳过,问题立刻消失。
6.3 推理速度慢
推理慢的原因依次排查:模型是不是用了最大尺寸、输入图像是不是太大、NMS 之后候选框是不是太多、部署推理引擎是不是没有用 TensorRT。我这里有一个实测参考:960 输入分辨率在 GPU 上大约 15ms,640 大约 9ms;如果切到 CPU 的 ONNX Runtime,640 大约 35ms。
如果产线要求每张处理时间小于 50ms,建议直接上 GPU + TensorRT,并把输入分辨率控制在 640。还有一个容易被忽略的点:批量推理时,小 batch 的耗时并不等于单张时间乘以 batch,某些框架有固定开销。如果你单帧能到 20ms,但批量 4 帧反而更慢,那就保持单帧流水线,不要盲目 batch。
6.4 中英文切换乱码或资源缺失
中英文切换后,如果新开的界面正常但表格、下拉框里还是旧的文字,多半是控件没有全部刷新。我踩过这个坑,排查了很久才发现有个QLabel控件在初始化时直接写了中文字符串,没有走tr()函数。把硬编码字符串全部替换成语言 key 之后,问题才解决。
另一种情况是打开英文版一切正常,切回中文某些控件变成乱码。检查 JSON 文件是否保存成 UTF-8 with BOM,部分解析库对 BOM 处理不佳。我在代码里统一用utf-8-sig读取,兼容有 BOM 和无 BOM 的文件。
6.5 模型在不同光照下波动
PCB 检测对光照敏感,同一块板子在不同角度下,模型可能一会儿判为 NG,一会儿判为 OK。解决思路是让训练数据充分覆盖光照变化。我建议采集时把光源亮度分成三档,每档各拍一组,同时保留一些自然光干扰的图像。模型学习到的是缺陷本质特征,而不是特定光线下的小阴影。
还有一招是在图像预处理阶段增加光照归一化。但要注意,任何预处理都会增加运行开销,而且可能把某些微弱缺陷的对比度给抹掉。所以我的建议是:不要在部署时过度依赖预处理,优先在数据采集阶段标准化光源,训练时再做模拟变化。
7. 效果演示与实测数据
7.1 测试集表现
我在自建测试集上跑了最终模型,6 类缺陷的平均 mAP50 大约是 0.94,mAP50-95 约 0.80。这里强调一点,这些指标只说明在这个具体数据集上的表现,不同产线、不同板材会差很多,不能当作通用承诺。单张 640 分辨率图片在 GPU 上的推理时间约 9ms,CPU 上约 35ms,完全能满足常规抽检和低速产线实时检测需求。
每个类别的表现并不一样,“短路”和“焊桥”这类高对比度缺陷精度最高,“划痕”和“缺孔”相对低一些,因为细长形缺陷和低对比度缺陷天然难度更大。我在项目里不对所有类别设同一个置信度阈值,而是对“划痕”单独降低阈值,再配合面积过滤去抑制误检,效果比全局阈值好不少。
7.2 现场效果演示细节
做效果演示时,我准备了 12 张测试图片,6 类缺陷各 2 张,外加 2 张无缺陷合格板。先打开系统,切到英文模式,加载第一张短路图,模型会快速画出红框并显示“Short 0.92”,再切换回中文模式,结果同步变成“短路 0.92”。这个切换操作虽然简单,但能在现场直观体现“中英文双版”的价值。
演示时一定要提前检查字体和路径。我遇到过太久没开机导致模型文件路径失效的情况,还有字体缺失导致中文显示方块的问题。演示脚本最好固定一个相对路径,程序启动时自动检查权重文件是否存在,并给出清晰错误提示。
7.3 源码使用说明和二次开发建议
拿到压缩包后,先按照 README 安装依赖,再跑一遍数据集转换脚本,接着训练、导出、启动界面。我建议第一次运行不要把全部数据喂进去,先用 30 张图片快速验证整个流程能通,再逐步扩大数据集。如果环境里有 GPU,优先用 GPU 跑训练;没有 GPU 也能用小模型在 CPU 上训练,就是慢不少。
二次开发如果要新增一个“铜面污染”类别,需要改的地方是:类别列表、语言包、标签转换脚本和配置pcb.yaml。不用改动架构。如果需要接入新的相机,只需要实现一个采集线程,把帧送进Detector。整个系统设计得比较模块化,新功能基本是加文件,而不是改骨架。
8. 留给后来者的一点建议
如果只看一个东西,我想说:这个项目的核心不在 YOLO26 本身,而在数据闭环。模型再强,没有稳定的数据采集、标注口径、难例回流机制,到产线上照样会翻车。我中间有几次精度卡住,不是模型没调好,而是数据分布和现场不一致。把训练数据和现场数据的差异管理好,比反复调参有用得多。
最后再分享两个小技巧:一是每个版本的模型都保存一份训练配置和测试集指标,这样后面回归对比时不会糊涂;二是给所有界面文本留好语言 key,哪怕现在只做中文,将来加英文也是几分钟的事。这套系统后续还可以继续扩展 7 类、8 类缺陷,也可以接入更快的相机做高速在线检测,我准备下一步往这个方向再改造一版。