简介:面向毕业设计、课程设计与项目开发场景的PCB元器件缺陷检测完整项目,基于Python与Faster-RCNN实现,适合具备一定深度学习基础、需要完成目标检测实操的开发者。压缩包内共79个文件,以Python源码(35个py)和pyc编译文件为主,另含Markdown开发文档、依赖清单及配置文件,整体约214KB,内容紧凑、便于快速下载与查阅。项目围绕VOC07数据格式展开,覆盖数据预处理与扩充、anchors计算、模型训练、预测和评估等环节,支持在公开数据集或自建数据集上运行。训练部分提供resnet50、vgg16等骨干网络实现;预测与评估部分可加载预训练权重或自行训练权重,并调用脚本计算mAP等指标。当前已有381人浏览学习,源码经过严格测试并附有训练、预测、评估的操作说明,结构清晰、模块划分合理,可直接作为毕业设计或课题研究的基础工程,在此基础上扩展算法或更换数据集都较为方便。
1. 用 Python 和 Faster R-CNN 做 PCB 元器件缺陷检测,到底解决什么问题
PCB 板出厂前要过外观检测,传统做法是老师傅拿放大镜一块一块看,漏检率全看当天状态,速度也上不去。而“基于 python+faster-RCNN 的 PCB 元器件缺陷检测”这件事,本质上是把漏检率稳定压到人工之下,同时让检测动作可重复、可量化。它的核心价值不是“用深度学习换个方式看板子”,而是用 Faster R-CNN 的两阶段检测结构,在缺件、偏移、桥连、虚焊这些细微缺陷上拿到比单阶段模型更稳的召回。
这套方案适合三类人:正在做毕业设计或课程设计、需要从零跑通一个目标检测全流程的学生;工厂或实验室里想验证 PCB AOI 替代人检可行性的工程师;以及刚接触 Faster R-CNN、想知道这个模型在实际工业图像上到底怎么落地的人。整个过程没有玄学,数据、标注、训练、调参、部署,每一步都能复现,但每一步也都有坑。下文按我自己的项目习惯,从原理、数据、训练到部署逐步拆开讲。
2. Faster R-CNN 为什么适合 PCB 缺陷检测:先看懂模型再动手
2.1 两阶段结构在工业检测里的天然优势
Faster R-CNN 属于两阶段检测器,第一阶段用 Region Proposal Network(RPN)在整张图上找可能存在目标的候选框,第二阶段对每个候选框做分类和回归。和 YOLO、SSD 这类单阶段模型相比,它的运行速度慢一些,但候选框质量高,对密集小目标的定位精度更好。
PCB 缺陷场景恰好是个精度优先的任务。一块板子上几十个元器件,缺件、偏移、桥连这类缺陷占图像面积通常不到百分之一,而且同类缺陷在不同板卡上的形态差异很大。用单阶段模型直接回归,容易在特征图上把小目标的信息淹没。Faster R-CNN 的 RPN 阶段会先扫一遍全图锚点,把可能是缺陷的区域裁出来,再进分类器精细判断,这个“先粗筛再精判”的流程,在工业视觉里更接近老师傅先扫一眼再拿放大镜确认的决策方式。
选型理由如果不落到数据上是空的。我见过不少人拿公开 COCO 预训练权重直接跑 PCB 数据,检出率很差,回来骂模型不行。其实问题出在 PCB 图像和自然图像的域差异太大,必须微调。Faster R-CNN 的微调成本可控,ResNet-50 + FPN 主干在单张 1080Ti 上就能训练,这也是它在毕设和中小项目里比更重的模型更常用的原因。
2.2 RPN 和 Anchor:Faster R-CNN 的核心机制
RPN 做的事是在特征图的每个位置上铺一组预设尺寸的 Anchor(锚框),然后判断每个 Anchor 里有没有目标,并粗略回归一次框的位置。最后选出得分最高的若干框,作为第二阶段 ROI 池化的输入。
Anchor 的尺寸和比例直接决定了模型对小目标的敏感度。torchvision 里 Faster R-CNN 默认的 anchor_sizes 是 ((32,), (64,), (128,), (256,), (512,)),这是按 COCO 数据集的物体尺度设计的,搬到 PCB 上一定会不匹配。PCB 元器件缺陷的尺寸通常集中在 25×25 到 200×200 像素之间,具体要看你的拍摄分辨率。
修改 AnchorGenerator 是调优的第一步:
from torchvision.models.detection.rpn_utils import AnchorGenerator # 针对 PCB 小元器件设计的 5 组 Anchor 尺寸 anchor_sizes = ((16,), (32,), (64,), (128,), (256,)) aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes) anchor_generator = AnchorGenerator(anchor_sizes, aspect_ratios)代码逻辑是把原来适配自然图像的锚框尺寸收敛到 PCB 板卡的元器件尺度范围内,比例保持 0.5、1.0、2.0 三种,覆盖长条形电阻和方形芯片的横竖比例。参数说明:16 和 32 对应小型贴片电阻电容,64 和 128 对应中型芯片,256 兜底大型连接器。如果板卡图像里最小缺陷只有 10 像素,需要再往下加一组 (8,);如果拍摄距离近,图像里元器件普遍很大,就把 16 这一组去掉,给 256 以上留空间。
2.3 特征金字塔在 PCB 多尺度缺陷上的作用
FPN(Feature Pyramid Network)是 Faster R-CNN 里另一个不能忽视的组件。它让不同层级的特征图携带不同尺度的语义信息,小缺陷回到底层高分辨率特征上找,大缺陷在高层语义特征上判断。torchvision 的 fasterrcnn_resnet50_fpn 默认带 FPN,但默认配置的 anchor 分配和 FPN 层级匹配是按 COCO 调的。
训练时如果发现小元器件频繁漏检,除了调 anchor,还要看 FPN 的输出层次。Debug 时可以打印每个 proposal 是由哪一层网络输出的:检查 model.rpn 的输出,或者直接在训练脚本里记录 gt 框的尺寸分布,按面积分布去对应 FPN 的 P2、P3、P4 层。一个简单经验——图像尺寸为 800×600 左右时,12×12 到 20×20 像素的缺陷目标主要从 P2 层召回。如果数据集里这种小目标很多,可以考虑在数据预处理时不压缩图像,或者使用更大的输入尺寸。
3. 数据与标注:PCB 缺陷检测项目最容易被卡住的环节
3.1 数据从哪来:公开集打底、自采补足
PCB 缺陷检测没有统一的公开大基准数据集,现实做法分两步。先用已有的 PCB 开源数据集(如部分高校公开的模板缺陷数据)跑通全流程,验证模型结构和训练参数没问题;再拿自己的板卡图像做标注、微调。纯靠自采往往数量不够,纯靠公开数据又和实际产线差异大。
我一般会把数据集按缺陷类型分成缺件、偏移、桥连、虚焊、极性反、破损六类,每类最少 200 张,总计 1200 张起步。这个量对微调 Faster R-CNN 来说够用,但分布必须均衡,不能缺件 500 张、破损 50 张,不然 mAP 值会被多数类拉高,而被少数类拖垮。
3.2 标注格式统一:用 LabelImg 产出干净可用的 VOC 数据集
标注工具用 LabelImg 最省事,输出格式选择 VOC(Pascal VOC XML)。它的产出是每张图片对应一个同名 XML 文件,里面记录每个目标的类别名和 bbox 坐标。
标注完成后,第一步不是训练,是写一个脚本检查所有 XML 的合法性。常见问题有三类:bbox 坐标越界(xmax 超过图片宽度)、bbox 高宽为 0 或负值、类别名里混进了空格。这三类错误在训练时不会直接报错,而是以“loss 异常波动”或“检测框奇怪偏移”的形式出现,特别难排查。
import xml.etree.ElementTree as ET import os xml_dir = "Annotations" img_width, img_height = 1280, 1024 # 按你的实际图像尺寸改 for xml_file in os.listdir(xml_dir): tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() for obj in root.iter("object"): name = obj.find("name").text bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) w, h = xmax - xmin, ymax - ymin if w <= 0 or h <= 0: print(f"[ERROR] {xml_file}: {name} bbox 高宽异常 {w}x{h}") if xmin < 0 or ymin < 0 or xmax > img_width or ymax > img_height: print(f"[WARN] {xml_file}: {name} bbox 越界")这段脚本循环读取每个 XML,逐个检查目标框的高宽和坐标范围,发现问题立即定位到文件和类别名。参数说明:xml_dir 指向标注目录,img_width 和 img_height 填写标注时使用的原始图像尺寸。越界的 bbox 是因为标注时拖框拖出了图像边界,负尺寸是标注误操作;我处理时一般是直接用脚本把越界坐标 clamp 到边界,负尺寸则删掉该目标样例,重新标。
3.3 数据集划分与增强:小样本也能把数据用足
划分比例我用 7:2:1,训练 70%、验证 20%、测试 10%。划分脚本要保证各类缺陷在每个集合里的比例一致,不能简单随机切分,否则测试集里可能完全没有某一类缺陷,mAP 计算出来虚高。
import random from collections import defaultdict random.seed(42) xml_files = [f for f in os.listdir(xml_dir) if f.endswith(".xml")] category_map = defaultdict(list) for xml_file in xml_files: tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() categories = [] for obj in root.iter("object"): category = obj.find("name").text categories.append(category) category_map[tuple(sorted(set(categories)))].append(xml_file) train_files, val_files, test_files = [], [], [] for key, items in category_map.items(): random.shuffle(items) n = len(items) train_files += items[: int(n * 0.7)] val_files += items[int(n * 0.7) : int(n * 0.9)] test_files += items[int(n * 0.9) :]这段脚本先按每个 XML 里包含的类别组合分组,再在组内按 7:2:1 比例切分,保证每个类别组合在三个集合中都能出现。代码逻辑是:categories 排序后做 key,同类组合数据一起划分,不会出现某一类全落在训练集、测试集里没有的情况。random.seed(42) 固定随机种子,保证每次跑划分结果一致,这对后续对比实验很重要——同一个项目里,我的习惯是只用这一版划分,不做重复采样。
图像增强方面,PCB 缺陷检测的关键约束是:缺陷方向有实际含义。偏移缺陷的框方向不能翻转,翻转后的图像会让“偏移”这类缺陷的物理方向失效。我一般只做两种增强:小幅亮度扰动和对比度扰动,配合 HSV 空间的微调,模拟不同光照条件下拍摄的图像。几何增强只保留 90 度旋转,且旋转时必须同步旋转 bbox 标注,这里用 torchvision 的 transforms 配合自定义函数实现,注意别用随机的水平翻转——它会直接把缺陷的物理方向翻反,训练出来的模型在真实板卡上检测方向性缺陷时会翻车。
4. 训练 Faster R-CNN 检测 PCB 缺陷:从环境搭建到参数调优
4.1 环境与工程结构:torchvision 是最稳定的起点
环境方面我推荐直接用 torchvision 内置的 Faster R-CNN 实现,它封装了 RPN、ROI 池化、损失计算全流程,比从零实现稳定太多。毕设和课程设计里需要展示源码深度,解读 torchvision 自带的源码就够了,完全没必要自己重写检测头。
工程结构上,项目源码一般按 dataset.py(数据加载与增强)、model.py(模型构建与 Anchor 调整)、train.py(训练主流程)、predict.py(推理脚本)四块组织,再加 config.yaml 放参数。开发文档围绕这四块写:数据集格式说明、模型结构说明、训练参数说明书、复现步骤。评阅人和面试官关心的不是代码量,而是你能不能讲清每块代码对应论文里的哪一部分。
conda create -n pcb_det python=3.8 conda activate pcb_det pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 -f https://download.pytorch.org/whl/torch_stable.html安装时注意 torch 和 torchvision 的版本必须匹配,两者之间用 0.14.1 配 1.13.1 是一组常见稳定组合。如果 GPU 显存只有 8G,可以把图像 resize 到 800×800,batch_size 设为 4。CPU 也能训练但极慢,我只建议拿 CPU 做推理验证,训练还是找个 GPU 环境,云服务器按小时租也可以。
4.2 构建模型与数据加载:修改分类头、绑定 Anchor 配置
模型构建代码是项目里最核心的展示段落:
import torchvision from torchvision.models.detection.faster_rcnn import FastRCNNPredictor # 使用 COCO 预训练权重做迁移学习基础 model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights="DEFAULT") num_classes = 7 # 背景 + 6 类缺陷 in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes) # 替换 Anchor 生成器,适配 PCB 元器件尺寸 from torchvision.models.detection.rpn_utils import AnchorGenerator anchor_sizes = ((16,), (32,), (64,), (128,), (256,)) aspect_ratios = ((0.5, 1.0, 2.0),) * len(anchor_sizes) model.rpn.anchor_generator = AnchorGenerator(anchor_sizes, aspect_ratios)代码逻辑分三步:加载 COCO 预训练模型、替换分类头输出为 6 类加背景、替换 Anchor 配置。参数说明:num_classes 必须等于缺陷类别数加 1,算错了会在训练时报 shape 不匹配。这里要提醒一个常见误用——很多人只改分类头不动 Anchor 配置,导致模型在 PCB 数据上收敛慢、小目标漏检。预训练权重的作用是让主干网络已经学到了通用的低级视觉特征,PCB 图像虽然和自然图像差异大,但边缘、纹理、颜色梯度这些底层特征是通用的,迁移学习的收益依然明显。
数据加载部分,torchvision 的 Faster R-CNN 需要自定义 Dataset 返回图像和 target 字典,target 里必须包含 boxes、labels、image_id 三个键,训练时还需要 area、iscrowd。我习惯写一个 PCBDefectDataset 类,内部用 xml.etree 解析 VOC 文件,并按需做归一化。
4.3 训练主循环:一个能跑的最小脚本
训练脚本核心段如下:
import torch from torch.utils.data import DataLoader model.train() optimizer = torch.optim.SGD(model.parameters(), lr=0.005, momentum=0.9, weight_decay=0.0005) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1) for epoch in range(12): total_loss = 0.0 for images, targets in dataloader: images = [img.to(device) for img in images] targets = [{k: v.to(device) for k, v in t.items()} for t in targets] loss_dict = model(images, targets) losses = sum(loss for loss in loss_dict.values()) optimizer.zero_grad() losses.backward() optimizer.step() total_loss += losses.item() scheduler.step() print(f"Epoch {epoch + 1}, Loss: {total_loss / len(dataloader):.4f}") if (epoch + 1) % 3 == 0: torch.save(model.state_dict(), f"checkpoints/epoch_{epoch + 1}.pth")代码逻辑是标准的 PyTorch 训练循环:模型接收原始图像和标注字典,torchvision 内部计算分类损失、回归损失、RPN 损失,sum 之后反向传播。参数说明:学习率 0.005 是批量大小为 4、图像尺寸 800×800 下的常用起点;如果 batch_size 是 8,学习率可以提高到 0.01,保持线性比例。StepLR 每 3 个 epoch 衰减为原来的十分之一,12 个 epoch 内完成三次衰减,是工业检测项目里比较稳的配置。如果 loss 在前 10 个 epoch 就不降了,先看是不是学习率太大,再检查标注数据是不是有脏数据。
训练过程一定要看 loss 曲线。我在训练时习惯用 tensorboard 记录 loss_dict 里每一项的值:
pip install tensorboard tensorboard --logdir runs在浏览器里打开 6006 端口看 loss 曲线。total loss 在训练初期在 1.0 到 2.0 之间波动是正常的,很多人看到 loss 不降到 0.1 就慌,其实 Faster R-CNN 在工业数据集上总 loss 能稳定到 0.2 以下就算不错。画图时发现横坐标太密集、曲线看不清,可以在 tensorboard 里调大 smooth 参数,或者用日志文件按 epoch 采样输出,不用像处理普通 matplotlib 图表那样折腾横轴刻度。
4.4 评估模型:mAP 计算和检测效果验证
训练完不能只看 loss,必须做量化评估。torchvision 官方提供了评估工具,我用它计算 mAP 和各类别的 AP 值:
python -m torchvision.ops.detection.coco_eval --model fasterrcnn_resnet50_fpn --weights checkpoints/epoch_12.pth --data VOCdevkit/2007评估完成后重点看三样东西:各类别 AP 是否均衡(缺件类特别高、破损类特别低就是数据分布问题);小目标的 AP 是否明显低于大目标(说明 anchor 还没调到位);验证集和测试集 mAP 差距是否过大(过拟合信号,PCB 小数据集上过拟合很常见)。
除此之外我会在测试集上把检测框叠加到原图上保存下来,逐张翻看。mAP 数字是宏观指标,单独看检测框,能发现很多 mAP 看不出的问题——比如漏检是不是总出现在板卡边缘区域,误检是不是集中在某个特定元器件型号上。这类现象驱动的分析,比调参更值得花时间。
5. 避坑指南:PCB 缺陷检测训练中四个高频翻车点
5.1 陷阱一:训练 loss 不降反升,最终发散
现象是 loss 在前几个 epoch 不降,甚至逐渐增大到 NaN。
原因有两个:一是学习率在当前 batch_size 和图像尺寸组合下过大;二是标注数据里存在异常 bbox,比如坐标超出图像范围、负尺寸,导致回归目标计算异常。
解决方法是先把学习率降到 0.001 试跑 10 个 epoch,如果 loss 稳步下降说明是学习率问题,调回 0.005 并配合 warmup 就好;如果依然发散,回到第 3.2 节那个 XML 检查脚本,把异常标注全部清理掉再训练。我在第一次跑 PCB 数据时就踩过这个坑,最后查出来是标注阶段有一批图片用了 960×1280 的原图,另一批用了 640×480 的缩略图,两批 XML 里的坐标混在一起,回归目标完全错乱。
5.2 陷阱二:检测框整体偏移半个身位
现象是缺陷类型判断正确,但框的位置明显偏向一侧。
原因是数据增强里加了水平翻转或 90 度旋转,但 bbox 坐标没有同步变换。特别是方向性缺陷(偏移、极性反),翻转后框对不上,模型学到了错误的映射关系。
解决方法是做增强时只保留不影响缺陷物理含义的操作:亮度扰动、对比度扰动、模糊、噪声。几何变换要么不做,要么做成完全同步 bbox 的自定义函数。验证方法很直接,把增强后的图像和 bbox 画出来看一张,框如果和缺陷对齐了就说明没问题。
5.3 陷阱三:GPU 显存溢出,训练中断
现象是训练跑到一半报 CUDA out of memory。
原因是 batch_size 和图像尺寸的组合超过了显存容量。8G 显存跑 800×800 图像,batch_size 只能设 4;跑到 1200×1200 就得降到 2 或 1。
解决方法是梯度累积技术,用小 batch_size 模拟大 batch 效果:
accumulation_steps = 4 # 模拟 batch_size = 实际 batch_size × 4 optimizer.zero_grad() for i, (images, targets) in enumerate(dataloader): loss_dict = model(images, targets) losses = sum(loss for loss in loss_dict.values()) losses.backward() if (i + 1) % accumulation_steps == 0: optimizer.step() optimizer.zero_grad()代码逻辑是前 4 个 batch 只做前向和梯度累积,不更新参数,第 4 个 batch 结束后统一更新一次,等效于 batch_size 乘以 4。参数说明:accumulation_steps 设 4 表示等效 batch_size 乘以 4;梯度累积会等比放大学习率,如果 batch_size 从 4 用累积做到 16,学习率从 0.005 调到 0.01 效果更好。另一个常用做法是训练时禁用图像缩放缓存,或把图像从 800×800 降到 640×640,代价是小目标检出率下降,能不动尽量不动。
5.4 陷阱四:小元器件漏检严重,大缺陷全检出来了
现象是电阻、电容这类小贴片元件缺陷经常漏检,而大芯片缺陷几乎全中。
原因是 Anchor 尺度覆盖不全,默认 anchor 最小的 32 像素,对 16 像素的小目标无能为力;或者输入图像尺寸太小,小目标在特征图上只剩几个像素。
解决方法是按 2.2 节的代码调整 AnchorGenerator,把最小尺寸降到 16 或 8。还可以将输入图像尺寸提高到 1216×1216 或原图比例,让小目标在特征图上占据更多像素。改完之后重新训练 6-8 个 epoch,观察小目标的 AP 是否提升。要注意这是“用训练时间换精度”的做法,训练速度和显存占用都会上升,适合毕设这种不在乎单次训练时长的场景。如果是产线落地,我会优先考虑按板卡分区裁图,把图像切成 4 块分别检测,等效提高小目标分辨率,比无限加大输入尺寸更可控。
6. 把训练好的模型用起来:推理脚本、性能验证与产线视角
训练结束,项目交付的核心是 predict.py 能稳定跑出检测结果。一个最小可用的推理脚本如下:
import torch import torchvision from PIL import Image from torchvision import transforms as T model = torchvision.models.detection.fasterrcnn_resnet50_fpn(weights=None) num_classes = 7 in_features = model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor = torchvision.models.detection.faster_rcnn.FastRCNNPredictor(in_features, num_classes) model.load_state_dict(torch.load("checkpoints/epoch_12.pth", map_location="cpu")) model.eval() img = Image.open("test_pcb.jpg").convert("RGB") x = T.ToTensor()(img).unsqueeze(0) with torch.no_grad(): pred = model(x)[0] score_threshold = 0.5 keep = pred["scores"] > score_threshold boxes = pred["boxes"][keep] labels = pred["labels"][keep] scores = pred["scores"][keep]推理脚本的关键逻辑是模型加载后必须 eval() 切到推理模式,否则 BatchNorm 和 Dropout 在验证时行为不对,检测结果会异常。参数说明:score_threshold 是置信度阈值,PCB 检测我一般设 0.5,这值设高了漏检多,设低了误检多。如果产线上对误检容忍度更低,就把阈值调到 0.7,同时接受一部分缺陷漏检,这个权衡要按实际产线要求来定。
推理跑通后,要量化单人检测速度,统计每张图的平均推理耗时。GPU 上 ResNet-50 的 Faster R-CNN 处理 800×800 图像大约在 100-200ms 之间,CPU 上可能要 1-3 秒。产线检测节拍如果要求每块板子 500ms 以内,这种情况我一般会走模型导出路线,把 PyTorch 模型转成 TorchScript 再跑推理,在 CUDA 上能带来 30%-50% 的加速。
scripted_model = torch.jit.script(model) scripted_model.save("pcb_det_scripted.pt")如果板卡检测是部署在嵌入式盒子里做实时检测,就需要进一步考虑量化。INT8 量化能把模型体积压到四分之一,推理速度翻倍,但精度会掉 1-2 个点,PCB 缺陷检测这类对漏检敏感的任务要谨慎用量化。我一般建议先用 TorchScript 做一次精度和速度的基准测试,如果达不到产线节拍,再做量化分支验证。
在整个项目里,开发文档最值得写的是调参记录和踩坑记录。你改了 anchor、调了学习率、换了增强方式,每次改动对 mAP 的影响都要记录下来。这不仅是毕设答辩的加分项,更是以后自己接手新板卡类型的底稿——换一块新 PCB,把数据和 anchor 按记录改一遍,半天就能复现一条好结果。这个习惯帮我避过很多次重头调参。
最后提一句我在这个项目上最大的教训:不要迷信公开预训练权重的默认参数能把 PCB 数据直接跑好,也不要一上来就追求复杂的模型结构。先把 Faster R-CNN 默认配置跑通,拿到一个 baseline,再围绕 anchor 和数据做一轮一轮的调优,每一步只改一个变量,效果可验证、可回溯。希望这份笔记帮你在 PCB 缺陷检测这条路上少走点弯路。
本文还有配套的精品资源,点击获取