恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python+Faster-RCNN实现PCB元器件缺陷检测:从数据标注到模型调参全流程
首页
资讯中心
/
Python+Faster-RCNN实现PCB元器件缺陷检测:从数据标注到模型调参全流程
Python+Faster-RCNN实现PCB元器件缺陷检测:从数据标注到模型调参全流程
发布时间:2026/10/1 17:33:46
简介这是一套面向高校学生与开发者的PCB元器件缺陷检测实战项目基于Python与Faster-RCNN实现配套完整源码、开发文档与项目解析适合用作毕业设计、课程设计或工程练手。项目围绕VOC0712数据集展开涵盖训练、预测与评估三条主线既可训练标准数据集也能迁移到自建数据集既支持预训练权重推理也支持自训练权重验证并给出mAP评估流程。压缩包共79个文件以35个py源码与39个pyc编译文件为主另含md说明、txt依赖清单及license等整体约214KB目录按数据预处理、网络结构、训练与预测脚本分层组织便于按模块阅读与二次开发。资源已通过严格测试读者可据此掌握RPN、锚框聚类、数据增强与模型评估等关键环节并在此基础上替换主干网络或扩展缺陷类别。目前已有381人学习下载。1. 从一张贴片板说起Python Faster-RCNN 做 PCB 元器件缺陷检测到底在解决什么手里拿到一块刚过炉的 PCB贴片电阻歪了、电容偏了、某个 QFN 少了一角甚至整颗料根本没贴上——这类问题在批量产线上靠人眼盯盯两小时就开始漏。基于 Python Faster-RCNN 的 PCB 元器件缺陷检测本质就是把「找元件 判缺陷」这两件事交给一个两阶段目标检测网络先用区域建议网络RPN在整板上框出可能是元器件的位置再对每个候选框做分类和边框回归输出「是什么元件、在哪、有没有缺陷」。它适合谁做毕业设计、课程设计、项目开发的学生和刚转机器视觉的工程师尤其是手头只有普通工业相机、没有深度学习产线经验、想用 Python 快速跑通一套可复现方案的人。这套方案不追求产线级节拍追求的是你能从零把数据、训练、推理、评估这条链路走通并且知道每一步的参数为什么这么设。源码和开发文档的价值也在这里——它把踩过的坑固化下来让你少走弯路。2. 数据与标注PCB 缺陷检测的第一道生死线2.1 为什么 PCB 数据集比想象中难搞PCB 元器件缺陷检测的难点不在网络在数据。工业现场拿到的原始图往往是大分辨率整板图一块 300mm×200mm 的板子用 500 万像素相机拍单张图里可能有几百个元件小目标密集、类别极不均衡——正常元件成千上万真正的缺陷样本可能只有几十个。如果直接把整板图丢给 Faster-RCNNRPN 生成的 anchor 会淹没在背景里正负样本比例能到 1:1000 以上训练直接崩。常见做法是先把整板图按固定步长裁成 512×512 或 800×800 的小图裁的时候保留重叠区域overlap 一般取 100~200 像素避免元件被切断。裁完之后再筛只保留包含至少一个标注框的图块纯背景块直接丢掉。这一步能把无效样本砍掉一大半。标注格式建议直接用 VOC 的 XML 或 COCO 的 JSON。VOC 更直观适合手写解析脚本COCO 生态工具多pycocotools 评估方便。类别命名要克制别一上来分十几类。我一般先分两大类component正常元件和defect缺陷元件等模型稳了再细分缺件、偏移、极性反、破损这些子类。类别太细、样本又少Faster-RCNN 的分类头根本学不动。提示标注框要贴着元件本体画不要框到焊盘外的丝印。丝印文字会干扰 RPN让网络把丝印当成元件。2.2 用 Python 把 VOC 标注转成训练可用的格式Faster-RCNN 在 PyTorch 下最省事的入口是 torchvision 自带的fasterrcnn_resnet50_fpn它默认吃 COCO 格式的 target。所以拿到 VOC XML 后第一步是转成 COCO 风格的字典。下面这段脚本把 VOC 的 XML 读成{image_id, boxes, labels}结构供自定义 Dataset 使用。import os import xml.etree.ElementTree as ET import torch from PIL import Image # 类别映射0 是背景Faster-RCNN 内部保留标注从 1 开始 CLASS_MAP {component: 1, defect: 2} def parse_voc_xml(xml_path): tree ET.parse(xml_path) root tree.getroot() boxes, labels [], [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in CLASS_MAP: continue bbox obj.find(bndbox) # VOC 坐标是 1-based转成 0-based 的 xyxy x1 float(bbox.find(xmin).text) - 1 y1 float(bbox.find(ymin).text) - 1 x2 float(bbox.find(xmax).text) - 1 y2 float(bbox.find(ymax).text) - 1 # 过滤掉宽高小于 4 像素的脏框 if x2 - x1 4 or y2 - y1 4: continue boxes.append([x1, y1, x2, y2]) labels.append(CLASS_MAP[name]) if not boxes: return None return { boxes: torch.tensor(boxes, dtypetorch.float32), labels: torch.tensor(labels, dtypetorch.int64), }逻辑说明CLASS_MAP把类别名映射成整数背景 0 由网络内部处理所以标注从 1 开始。VOC 的坐标是 1-based减 1 转成 0-based否则框会整体偏移一个像素小目标上这个偏差很致命。过滤宽高小于 4 像素的框是因为这种框在 RPN 里几乎不可能匹配到正样本留着只会增加噪声。参数说明CLASS_MAP按你的实际类别改宽高阈值 4 是经验值如果你的元件最小尺寸在 10 像素以上可以提到 6~8。返回None表示这张图没有有效标注Dataset 里要跳过。2.3 自定义 Dataset 与数据增强的取舍torchvision 的检测模型要求 Dataset 返回(image_tensor, target)image 是[C,H,W]的 float tensortarget 是上面那种字典。PCB 图像增强要小心水平翻转通常安全垂直翻转对极性元件可能改变语义比如二极管方向旋转超过 15 度会让矩形元件框失真。我一般只用水平翻转 轻微亮度抖动颜色抖动幅度控制在 0.1 以内因为 PCB 的焊点颜色是重要特征抖太狠反而掉点。import torchvision.transforms as T from torch.utils.data import Dataset class PCBDataset(Dataset): def __init__(self, img_dir, xml_dir, transformsNone): self.img_dir img_dir self.xml_dir xml_dir self.transforms transforms self.ids [f[:-4] for f in os.listdir(xml_dir) if f.endswith(.xml)] def __getitem__(self, idx): img_id self.ids[idx] img Image.open(os.path.join(self.img_dir, img_id .jpg)).convert(RGB) target parse_voc_xml(os.path.join(self.xml_dir, img_id .xml)) if target is None: # 没有有效标注就换下一张避免训练时报空 target return self.__getitem__((idx 1) % len(self.ids)) if self.transforms: img self.transforms(img) return img, target def __len__(self): return len(self.ids)逻辑说明__getitem__里遇到空标注递归取下一张这是处理脏数据的实用写法比在外面预筛更省事。transforms只做ToTensor和归一化翻转类增强建议在 Dataset 内部按概率手动做因为翻转后 boxes 也要同步变换用 torchvision 的 transform 不好处理。参数说明归一化用 ImageNet 的均值方差[0.485,0.456,0.406]和[0.229,0.224,0.225]因为主干是 ResNet50 预训练权重输入分布对齐能加快收敛。3. 模型搭建与训练Faster-RCNN 在 PCB 上的参数怎么调3.1 主干与 anchor 的选型理由torchvision 的fasterrcnn_resnet50_fpn默认主干是 ResNet50 FPN对 PCB 这种小目标密集场景够用。如果你显存紧张比如只有 6G可以换fasterrcnn_mobilenet_v3_large_fpn速度更快但小目标召回会掉几个点。PCB 元件普遍偏小FPN 的多尺度特征很关键所以别用没有 FPN 的老版本。anchor 是 Faster-RCNN 的命门。默认 anchor 尺寸是(32,64,128,256,512)对 PCB 上几十像素的元件来说偏大。我一般改成(16,32,64,128,256)长宽比保留(0.5,1.0,2.0)因为元件大多是矩形极端长宽比很少。改 anchor 要在构建模型时传anchor_generator参数或者直接改源码里的AnchorGenerator。import torchvision from torchvision.models.detection.faster_rcnn import FastRCNNPredictor from torchvision.models.detection.anchor_utils import AnchorGenerator def build_model(num_classes3): # 自定义 anchor更小的尺寸适配 PCB 小元件 anchor_gen AnchorGenerator( sizes((16,), (32,), (64,), (128,), (256,)), aspect_ratios((0.5, 1.0, 2.0),) * 5 ) model torchvision.models.detection.fasterrcnn_resnet50_fpn( weightsDEFAULT, anchor_generatoranchor_gen, min_size800, max_size1333 ) # 替换分类头num_classes 包含背景 in_features model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor FastRCNNPredictor(in_features, num_classes) return model逻辑说明sizes每个尺度只给一个值配合 5 个 FPN 层级正好一一对应。aspect_ratios用元组重复 5 次表示每个层级都用同样的长宽比。min_size800是输入短边缩放目标PCB 小图块如果本身是 512会被放大到 800小目标特征更明显但显存占用上升。参数说明num_classes一定要包含背景两类缺陷就是 3。weightsDEFAULT加载 COCO 预训练权重这是收敛快的关键别从随机初始化开始训。3.2 训练循环与学习率策略Faster-RCNN 的训练和普通分类不一样模型在 train 模式下直接返回 loss 字典不需要你手动算 loss。优化器用 SGDmomentum 0.9weight_decay 0.0005初始学习率 0.005batch size 2 时。如果显存够、batch 能到 4学习率可以提到 0.01。学习率调度用StepLR每 3 个 epoch 乘 0.1或者用MultiStepLR在 8、11 epoch 降。import torch from torch.utils.data import DataLoader import torchvision.transforms as T def collate_fn(batch): return tuple(zip(*batch)) device torch.device(cuda if torch.cuda.is_available() else cpu) model build_model(num_classes3).to(device) transform T.Compose([T.ToTensor(), T.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]) dataset PCBDataset(images, annotations, transformstransform) loader DataLoader(dataset, batch_size2, shuffleTrue, collate_fncollate_fn, num_workers2) params [p for p in model.parameters() if p.requires_grad] optimizer torch.optim.SGD(params, lr0.005, momentum0.9, weight_decay0.0005) lr_scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size3, gamma0.1) for epoch in range(12): model.train() for images, targets in loader: 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() lr_scheduler.step() print(fepoch {epoch}, loss {losses.item():.4f})逻辑说明collate_fn必须自定义因为每张图的 target 框数量不同默认的 collate 会报错。loss_dict里通常有loss_classifier、loss_box_reg、loss_objectness、loss_rpn_box_reg四项直接求和反传。训练时不要做验证Faster-RCNN 在 train 模式下不返回预测结果验证要切model.eval()。参数说明batch_size2是 8G 显存的稳妥值显存大可以往上加。num_workers在 Windows 上设 0 更稳Linux 上设 2~4。12 个 epoch 是中小数据集的常见量级数据少于 500 张可以减到 8。3.3 推理与后处理把输出变成能看的框推理时切model.eval()输入同样要归一化。模型返回的是 list每个元素对应一张图包含boxes、labels、scores。默认 score 阈值 0.05实际用的时候要卡到 0.5 以上再用 NMS模型内部已经做了但可以再补一次去掉重叠框。model.eval() with torch.no_grad(): img Image.open(test.jpg).convert(RGB) tensor transform(img).unsqueeze(0).to(device) outputs model(tensor)[0] # 按分数过滤 keep outputs[scores] 0.5 boxes outputs[boxes][keep].cpu().numpy() labels outputs[labels][keep].cpu().numpy() scores outputs[scores][keep].cpu().numpy() for box, label, score in zip(boxes, labels, scores): print(flabel{label}, score{score:.3f}, box{box.astype(int)})逻辑说明unsqueeze(0)把单张图变成 batch1。outputs是字典直接按 score 过滤。label 1 是正常元件2 是缺陷实际部署时只关心 label 2 的框。参数说明score 阈值 0.5 是起点漏检多就降到 0.3误检多就提到 0.7。PCB 缺陷检测通常宁可误检不可漏检所以阈值偏低更安全。4. 避坑与排查PCB 缺陷检测训练中最容易翻车的 5 个点4.1 现象loss 一直不降卡在 2.0 以上原因最常见的是标注坐标没转成 0-based或者类别映射漏了背景导致 label 从 0 开始。Faster-RCNN 内部把 0 当背景如果你的标注也用 0分类头永远学不对。另一个原因是 anchor 尺寸和实际目标差太远RPN 匹配不到正样本。解决先打印几个 target 的 boxes 和 labels确认坐标范围和类别从 1 开始。再把 anchor 尺寸改成和实际框分布匹配的值用kmeans聚一下训练集框的宽高取 5 个聚类中心当 anchor size。4.2 现象验证时 mAP 很高实际推理全是误检原因验证集和训练集来自同一批图分布太像模型过拟合。PCB 缺陷检测里不同批次板子的光照、丝印颜色、焊点反光差异很大训练集覆盖不到就会翻车。解决按板子批次划分训练/验证别随机切。验证集至少留 2~3 个不同批次的板子。如果条件允许加一些不同光照下拍的图做增强。4.3 现象小元件0402 封装几乎全漏检原因输入图缩放后小元件只剩几个像素FPN 最底层特征也抓不住。默认min_size800对整板图来说缩放比例太小。解决把整板图裁成更小的图块比如 400×400让元件在图块里占比更大。或者把min_size提到 1200但显存要够。另一个办法是在 FPN 里加一层更高分辨率的特征这个改动大新手先别碰。4.4 现象训练到一半 loss 突然变 NaN原因学习率太大或者某张图的 boxes 坐标超出图像边界导致 ROI Align 采样越界。PCB 标注时手抖把框画到图外很常见。解决在 Dataset 里加一步 clamp把 boxes 限制在[0, W, 0, H]范围内。学习率从 0.005 降到 0.001 试试。如果还 NaN检查有没有宽高为 0 的框混进去。4.5 现象推理速度太慢单张图要好几秒原因min_size800加上 ResNet50 主干在没 GPU 的机器上就是慢。另外如果没设torch.no_grad()会白白算梯度。解决推理一定包torch.no_grad()。速度要求高就换 MobileNet 主干或者把min_size降到 600。批量推理时把多张图拼成一个 batch比一张张跑快很多。5. 从能跑到好用评估指标与一个提点的小技巧模型训完只是开始怎么判断它到底行不行得看指标。目标检测通用的是 mAPmean Average PrecisionIoU 阈值 0.5 下的 mAP0.5 是入门线PCB 缺陷检测我一般还会单独看缺陷类label 2的召回率因为正常元件检测得再好缺陷漏了等于白做。用 pycocotools 算指标最省事把预测结果转成 COCO 格式的 json 再调COCOeval。指标含义PCB 场景参考值mAP0.5IoU0.5 时各类 AP 均值正常元件 0.9缺陷 0.6Recall0.5缺陷类召回率优先保证 0.85FPS单卡推理帧率产线参考 10离线 2单图耗时端到端推理时间含预处理 500ms 算合格一个提点的小技巧在 RPN 阶段把正样本 IoU 阈值从默认 0.7 降到 0.5。PCB 元件密集默认阈值太严很多真实元件框匹配不上正样本RPN 召回上不去。改这个参数在RPN的fg_iou_thresh里降到 0.5 后缺陷召回通常能涨 3~5 个点代价是误检略增配合 score 阈值调一下就能压回来。# 修改 RPN 正样本 IoU 阈值需在 build_model 后手动改 model.rpn.fg_iou_thresh 0.5 # 默认 0.7 model.rpn.bg_iou_thresh 0.3 # 默认 0.3保持逻辑说明fg_iou_thresh是判定正样本的 IoU 下限降低它意味着更多候选框被当成正样本RPN 学到的目标更全。bg_iou_thresh是负样本上限一般不动动了容易让正负样本失衡。参数说明0.5 是我在几个 PCB 数据集上试出来的平衡点再低比如 0.4误检会明显增多。改完要重新训不能只改推理。这套方案我从数据清洗一路踩到 anchor 调参最深的教训是PCB 缺陷检测的瓶颈从来不在网络结构而在你有没有把标注质量当回事。我见过太多人拿着脏标注训半个月最后怪 Faster-RCNN 不行。先把每一张图的框画准、类别分对再谈调参顺序反了就是白费电。希望帮到你。本文还有配套的精品资源点击获取