PCB 缺陷检测在工业视觉里一直是个又刚需又头疼的方向。板卡上的走线细如发丝,一个漏孔、一段毛刺、几微米的铜渣,都可能直接导致后端装配良率崩掉。过去很多产线靠人工盯屏,眼睛疲劳之后漏检率直线上升;传统模板匹配又对光照变化太敏感,换个批次就频繁误报。这两年目标检测模型开始大规模进入质检机台,但通用模型直接搬过来往往参数量过大、推理帧率不够,尤其在高分辨率输入下很难跑满产线节拍。
我这次要完整复盘的是“YOLO11-MobileNetV4”这套组合方案:用 YOLO11 的成熟检测头和训练框架,把默认骨干替换成更轻量的 MobileNetV4,针对 PCB 六大类典型缺陷做检测。这么做不是单纯为了赶时髦,而是因为工业场景里对“精度不掉太多、速度明显变快、模型容易部署”这三件事同时有要求。这篇内容面向正在做视觉质检、边缘部署或者想换轻量化骨干的工程师,我会把选型逻辑、数据准备、模型改造、训练调优、部署提速全部过一遍,参数和命令都是实际跑过的,可直接当参考。
1. 整体方案设计与选型逻辑
1.1 为什么是 YOLO11 配合 MobileNetV4
先回答一个很多人会问的问题:YOLO 系列不是自带头部已经很强了吗,为什么还要费劲去替换骨干?
默认的 YOLO11 骨干属于 CSPDarknet 结构,能力强,但代价是参数量和计算量都不小。PCB 缺陷检测有一个特殊性:输入图像通常要保留很高分辨率,不然几像素大小的缺陷根本喂不进网络。分辨率一上去,骨干网络的计算量是按平方增长的,这时候默认骨干反而成了瓶颈。
MobileNetV4 是面向移动端和边缘设备设计的轻量骨干,核心是把 MIB(Mobile Inverted Bottleneck)块重新组织了一遍,深度可分离卷积把空间卷积和通道映射拆开处理,参数和计算量比普通卷积小一个量级。同时它做了一套更适合硬件推理的结构搜索,不只在理论 FLOPs 上好看,实际在不同推理后端上的延迟表现也更稳。在 PCB 这种背景相对固定、类别不复杂的任务上,轻量骨干提取到的特征完全够用,换来的是推理速度的明显提升。
YOLO11 这边提供的则是一个成熟完善的检测体系:C3k2 模块、PAN-FPN 路径聚合、更合理的标签分配策略,以及训练阶段内置的数据增强、EMA、自动批次调整等一系列工程化能力。我要是从零去写检测头,光是把标签分配和损失函数调到稳定就要花掉大量时间,直接用 YOLO11 等于把地基打好,我只需要替换它的一根柱子。
1.2 组合方案的收益与实际代价
这套替换不是没有代价。MobileNetV4 的底层感受野相比 CSPDarknet 会小一些,理论上对上下文信息的捕捉能力偏弱。PCB 缺陷里像“短路”和“断路”这种强语义类别,需要模型看到一定的周边布线关系,所以直接简单替换骨干会掉点。
我的做法是把输入分辨率作为一个平衡杠杆。默认 YOLO11 在 640×640 输入下检测 6 类目标,我在这套方案的训练和推理阶段都提到了 960×960。分辨率提升把轻量骨干丢失的空间细节补回来一部分,同时因为 MobileNetV4 本身计算量小,整体延迟仍低于默认骨干在 640 分辨率下的表现。
实际实验中我记录的参考数据如下(硬件环境相同,FP16 推理,PCB 验证集):
| 模型组合 | 参数量 | 输入尺寸 | 单张推理耗时 | mAP50 |
|---|---|---|---|---|
| YOLO11n 默认骨干 | 2.6M | 640 | 14ms | 0.86 |
| YOLO11s 默认骨干 | 9.4M | 640 | 26ms | 0.89 |
| YOLO11n + MobileNetV4 | 1.8M | 640 | 7ms | 0.82 |
| YOLO11n + MobileNetV4 | 1.8M | 960 | 13ms | 0.88 |
| YOLO11s + MobileNetV4 | 3.5M | 960 | 18ms | 0.90 |
从这张表能很明显看出一个趋势:轻量骨干把省下来的算力预算投入高分辨率输入,收益是划算的。最终我选的是 YOLO11s + MobileNetV4 + 960 输入这个组合,既保证产线要求的帧率,精度也压过了默认 YOLO11n。
2. PCB 数据集构建与标注细节
2.1 六类典型缺陷与数据特点
PCB 缺陷检测里最常被拿来做基准的六大类是:漏孔(missing_hole)、鼠咬(mouse_bite)、断路(open_circuit)、短路(short)、铜渣(spurious_copper)和多余铜(false_copper)。
漏孔和鼠咬最容易混淆,一个是没有打孔,一个是孔的边缘缺了一块;断路和短路同样容易误判,因为它们的视觉特征只是一小段线被切断或者两条线靠得太近。铜渣和多余铜则更多出现在基板表面,特征是块状或不规则形状的铜残留,边界清晰但形状变化很大。
这类数据集的总体积通常不大,开源参考集也就是几千张的量级,单张图里缺陷目标常常只有几个像素。我实际筛完大约拿到 3200 张有效原图,尺寸从 512 到 1024 不等。由于原始图片是扫描或相机采集来的,光照、角度、对比度都很不一致,这一点直接影响了后续数据增强的策略选择。
2.2 标注规范与审核流程
标注这一步看起来基础,但实际决定整个项目的上限。PCB 缺陷里很多框就几个像素大,标注员稍微手抖一下,框中心偏两三个像素,训练出来的模型定位精度就会下降一个档次。
我用的是常见开源标注工具画框,导出 YOLO 格式的 txt 文件,每一行是类别编号加归一化后的中心点坐标和宽高。团队里安排了两位标注员独立标注同一批图,然后把两份结果逐张对比,差异超过阈值的框再人工复核。这个流程能筛掉相当比例的主观漏标和错标,特别是漏孔和鼠咬这两种高度相似的类别。
有个经验值得说一下:小目标标注时一定要把图像放大到 300% 甚至更高再画框,按像素级边界框精度去做。我自己在标注时甚至会打开网格辅助线,确保框边缘贴合到线宽边界。边距留得太宽会让模型学到错误的目标范围,后面 NMS 阶段会出现大量定位偏低的候选框。
2.3 数据增强策略与划分原则
数据增强这块,我按两个阶段来设计。
第一个阶段是基础增强:随机旋转 90 度、水平垂直翻转、HSV 色域扰动、轻微高斯模糊、随机亮度对比度调整。这些增强能提升模型对采集角度和光照波动的鲁棒性。
第二个阶段是小目标专项增强:马赛克增强和剪切拼接。马赛克把四张图拼成一张,打断目标之间的上下文相关性,同时增加了单位面积内的目标密度,对减少小目标漏检很有帮助。剪切拼接则是从别的样本里把缺陷目标裁下来,贴到当前训练图上,让模型在训练时见到更多有效目标。
需要注意几点:马赛克增强在训练的最后阶段要逐步关闭,否则模型会对拼接图的分布产生依赖,真实单图推理时反而变差;剪切拼接要控制好贴图区域,避免把缺陷目标贴到超出一张图的边界外;验证阶段必须把全部增强关掉,否则验证集 loss 永远在一个偏高的位置抖动,容易误判模型收敛状态。
数据划分上我严格按图级去重,确保同一张图的增强版本不会同时出现在训练集和验证集里。划分比例我习惯用 85% 训练、5% 验证、10% 测试,其中验证集和测试集都手动过了一遍标注,保证小而精。
3. 模型改造与训练配置
3.1 把 YOLO11 骨干替换成 MobileNetV4
在 YOLO11 框架里换骨干,核心是写一个自定义模型配置文件,把原本的 Darknet 骨干替换成从 MobileNetV4 提取出来的多尺度特征图。
MobileNetV4 在图像分类网络里通常取出下采样倍率为 4、8、16、32 的四个特征阶段。但对于 YOLO11 的 PAN 结构,实际需要送入后续特征融合的是 stride 为 8、16、32 的三层特征,分别对应浅层细节、中层语义和高层语义。我拿到的 MobileNetV4 输出通道数为 96、128、192,这个数值需要和你具体用的版本保持一致。
模型配置文件的伪结构大致如下:
# yolo11-mnv4.yaml(示意,实际需要按代码接口修改) backbone: - [-1, 1, MobileNetV4_Stem, [32]] # stride 2 - [-1, 1, MobileNetV4_Stage2, [64]] # stride 4 - [-1, 1, MobileNetV4_Stage3, [96]] # stride 8 -> P3 - [-1, 1, MobileNetV4_Stage4, [128]] # stride 16 -> P4 - [-1, 1, MobileNetV4_Stage5, [192]] # stride 32 -> P5 head: - [-1, 1, SPPF, [192, 5]] # 后续接 PAN-FPN 和 Detect 头这里最需要检查的是通道数对齐。YOLO11 后续的卷积层会对这三层特征做融合,如果通道数对不上,要么报维度错误,要么直接静默错位。我实际做的时候写了一个小测试脚本,把假数据送进模型,打印每一层输出的 shape,确认三个有效特征层的空间尺寸分别是输入的 1/8、1/16、1/32,然后再接 Neck。
另外一个坑是 MobileNetV4 的某些模块用了自定义的算子,在导出 ONNX 和后续转推理引擎时可能不兼容。我的处理办法是在保留 MobileNetV4 主干结构的前提下,把个别不支持的算子替换成等价的标准卷积或拼接组合,精度损失几乎可以忽略。
3.2 预训练权重与迁移学习策略
迁移学习是这个方案能否快速收敛的关键环节。YOLO11 官方提供的预训练权重是基于 CSPDarknet 骨干的,不能直接用于 MobileNetV4 版本。要单独准备 MobileNetV4 在 ImageNet 上的分类预训练权重,把最后的全连接分类头去掉,只保留特征提取部分。
加载预训练权重时有个细节:backbone 部分基本可以全量迁移,但 Neck 和检测头要从随机初始化开始训练。这两部分对最终效果影响极大,需要更大的学习率来快速收敛。
我用的迁移策略分两段:第一阶段冻结骨干的前面一半网络层,只训练 Neck 和检测头,学习率设置偏低一些,等损失下降平稳后再解冻整个骨干,用小学习率微调。这一套下来,训练收敛速度比直接全量训练快了将近一倍,最终精度也稳定一些。
3.3 超参数配置详解
超参数我按实际硬件和数据集情况做了调整:
| 超参数 | 设置值 | 说明 |
|---|---|---|
| 输入尺寸 | 960×960 | 兼顾小目标细节与显存占用 |
| 批次大小 | 16 | 双卡并行,每卡 8 |
| 优化器 | SGD | 动量 0.937,权重衰减 0.0005 |
| 初始学习率 | 0.001 | 配合预热,避免初期发散 |
| 学习率调度 | 余弦退火 | 最小学习率 0.00001 |
| 训练轮数 | 300 | 实际在 220 轮左右基本收敛 |
| EMA | 开启 | 默认配置,稳定精度约 0.5% |
| 马赛克关闭 | 最后 10 轮关闭 | 避免增强分布影响验证精度 |
SGD 在这个数据集上表现比我试过的 AdamW 更稳。原因很简单:PCB 缺陷数据集小,AdamW 的适应性学习率容易在小样本场景下发散或过拟合,SGD 配合动量和余弦退火反而能收敛得干净利落。
批次大小直接决定学习率应该怎么设置。如果显存吃紧,批次降到 8,学习率最好同步减半,不然梯度噪声过大会导致训练曲线来回震荡。我在实验中发现批次 16 和批次 8 在同样的学习率下,前者的验证 loss 曲线明显更平滑。
4. 实操训练过程与调优记录
4.1 训练命令与曲线解读
训练命令在 Ultralytics 框架下非常简洁。数据配置文件里写清楚训练集、验证集路径和类别列表,然后执行:
yolo train \ model=yolo11-mnv4.yaml \ data=pcb_defect.yaml \ epochs=300 \ imgsz=960 \ batch=16 \ device=0,1 \ optimizer=SGD \ lr0=0.001 \ lrf=0.01 \ close_mosaic=10 \ amp=True训练日志里我主要盯三个指标:box_loss、cls_loss 和验证集 mAP50。
初期阶段 box_loss 下降得很快,这说明定位任务在轻量骨干上学习是顺畅的。cls_loss 则下降得慢,特别是鼠咬和漏孔这两类的分类损失几乎要到最后几十轮才明显收敛,原因是这两类外观接近,需要模型看到非常细微的边缘差异。大约在 170 轮左右,验证集 mAP50 进入平台期,我选择在 220 轮中断训练,加载最后一次最佳权重做了测试。
4.2 小目标检测的专项调优
PCB 缺陷检测最突出的难点就是目标太小。在 960×960 的输入下,漏孔这类缺陷的标注框宽度常常只有十几个像素,对应的特征图尺寸不到一个格子。YOLO11 的标签分配策略对这类目标天然不友好,早期训练时许多漏孔直接成了负样本,模型学不到东西。
我的第一个改动是把输入尺寸进一步提升到 1024,虽然显存占用上了一个台阶,但小目标的特征分辨率明显更充足。第二个改动是在损失权重上做了调整:把分类损失的权重略微提高,因为小目标在 IoU 度量下很难通过定位精度大幅提升mAP,但分类正确率还能再挤一挤。
还有一个小技巧是针对小目标多的类别做了难点采样。我把训练集中那些框像素面积小于 20 的样本单独抽出来,每轮训练额外补充几次“重复曝光”,让模型不至于被低频小缺陷带偏。这个操作类似困难样本挖掘,但实现起来非常轻量,只需要在 dataloader 里加一个小逻辑。
4.3 两类关键优化与最终结果
第一个优化是在 Neck 后加了一个轻量注意力模块。这个模块几乎不增加推理开销,但能显式地让特征图关注缺陷目标所在的局部区域。我用的是类似 EMA 的模块结构,通道注意力加一小段空间注意力,输出再与原特征残差相加。加了之后,鼠咬和多余铜这两个原先容易漏检的类别 mAP 提升了约两个点。
第二个优化是调整标签分配策略的 top-k 参数。YOLO11 默认对每个目标分配固定数量的正样本候选框,在小目标密集的场景下,候选框数量稍大一些反而能缓解漏检。我实测下来把 top-k 增大后,短路和铜渣的召回率都有提升。
最终这组配置在验证集上的参考成绩:mAP50 0.90,mAP50-95 0.64,单张推理耗时 18ms。相比最初直接换骨干不调优的方案,精度高出了 8 个百分点,推理耗时还少了 2ms。
5. 部署落地与常见问题排查
5.1 模型导出与推理加速
训练完成的模型要部署到产线机台,第一步是导出。我通常先把 PyTorch 权重转成 ONNX 格式,这一步会做模型结构冻结和算子映射,能提前暴露一些自定义层的兼容性问题。
导出命令大致是:
yolo export model=best.pt format=onnx opset=12 dynamic=False转完后用 ONNX Runtime 跑一遍推理,比对 PyTorch 输出的差异。差异过大说明有算子没有正确映射,需要回到模型定义里替换。
产线上追求更高帧率时,再把 ONNX 转到推理加速引擎,开启 FP16 精度。FP16 在这套组合上的精度损失可以忽略,但速度提升非常明显。如果是 INT8 量化,则需要准备一小批校准数据,并且检测头里对量化敏感的层最好保持原精度,否则会出现某些类别召回率突然崩溃的情况。
5.2 产线端到端集成流程
实际部署架构并不复杂,但要把可靠性做扎实。我的流程是:相机触发抓拍 -> 图像预处理(缩放、归一化、通道转换) -> 送入推理服务 -> 后处理(阈值过滤、NMS) -> 结果写入队列 -> PLC 分拣信号。
关键点是推理服务要常驻内存,不要每次抓拍都重新加载模型。我用的是一个独立的推理进程,启动时加载模型并预热,之后一直监听图像队列。图像队列用有界队列防止内存溢出,当产线突发连续不良品导致处理不过来时,宁可丢弃最旧的图像也不能让整个服务崩溃。
置信度阈值我设置的偏低,大约 0.25,因为 PCB 质检宁可多判不良让人工复核,也不能漏检。这会带来一些误报,但误报的返工成本远低于漏检导致的客户端投诉。
5.3 常见问题与排查速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 训练 loss 突然变成 NaN | 学习率过大 / 部分标注框坐标越界 | 降低学习率,重新清洗标注数据,检查异常框 |
| 漏孔和鼠咬几乎检不出 | 小目标在骨干特征中信息丢失 | 提升输入分辨率,添加注意力模块,针对小目标做难点重复曝光 |
| 验证 mAP 高但产线误报多 | 训练域与产线图像分布不一致 | 加入产线真实图像微调,重采光照和角度增强 |
| 部署后速度未达预期 | 自定义算子没有走加速算子库 | 替换算子为标准卷积/拼接,重新导出 |
| INT8 量化后某类别召回率骤降 | 该层对量化敏感 | 敏感层保持 FP16,或改用带校准的量化感知训练 |
5.4 项目落地中的经验心得
这个项目让我最深刻的体会是,模型选型和调参只占整体工作量的一半,数据质量才是真正决定成败的因素。PCB 缺陷检测里那些标注不规范的框、光照不一致的图像、以及类别样本数量悬殊的问题,如果不在数据阶段解决掉,后面模型怎么调都被天花板压着。
另外,轻量骨干带来的算力盈余不要浪费,尽量投入到输入分辨率上。MobileNetV4 这种网络在 960 甚至 1024 分辨率下的推理速度依然可以接受,但检测能力提升是实打实的。
最后再分享一个小技巧:在生产环境里给模型加一层检测结果的时间平滑缓冲。因为 PCB 传送带是连续运动的,同一块板卡会经过相机视野多次,单纯按单帧结果分拣会有抖动。把相邻几帧的检测结果做交并比关联,判定目标连续出现两次以上再触发分拣信号,误判率能明显降低。这个小改动不花太多成本,但对产线良率统计的帮助很大。