资讯动态

基于Python和Faster R-CNN的PCB元器件缺陷检测实战

发布时间:2026/10/5 8:06:21 来源:尧图企业网站定制
简介一份基于Python与Faster-RCNN的PCB元器件缺陷检测完整项目面向毕业设计、课程设计与项目开发场景适合具备一定深度学习基础、希望快速搭建目标检测模型并落地的学习者。项目支持VOC0712数据集训练、自定义数据集训练与评估覆盖数据预处理、模型构建、训练预测及评估全流程可帮助理解Faster-RCNN在工业质检中的应用。资源共79个文件以Python源码含py与pyc、Markdown开发文档、txt说明等为主压缩包仅214KB轻量但结构完整。内容包含数据增强脚本、锚点计算、多种骨干网络ResNet、VGG等实现、训练回调与预测工具并附有开发文档与项目解析源码经过严格测试便于二次开发与论文撰写参考。已有381人学习下载适合作为课设、毕设的起点项目或企业内训的参考范例。1. 基于PythonFaster R-CNN的PCB元器件缺陷检测这套方案能解决什么PCB元器件缺陷检测放在非标自动化领域一直被视作“看着简单、落地扎手”的典型。生产线上的错件、漏件、反件、偏位加上焊点桥连和少锡缺陷种类多且尺寸小传统模板匹配遇上反光和板型差异就翻车。用基于Python的Faster R-CNN做检测本质上是把“找缺陷”转成“先找候选框、再逐框分类”的两阶段目标检测任务用训练好的模型替代人工目检。这套方案适合三类人做毕业设计需要完整可演示项目的在校生课程设计里想从数据标注一路做到评估报告的学生以及在小批量产线上验证视觉检测可行性的工程师。它给你的不是一份冷冰冰的代码而是一条能跑通、能交代、能继续调的落地路径。2. 准备训练数据从PCB裸板照片到Pascal VOC标注集2.1 为什么选Pascal VOC格式而不是直接写COCO JSONFaster R-CNN的训练生态里Pascal VOC格式一张图配一个同名的xml文件是兼容面最广的中间格式。不管是torchvision的detection接口还是mmdetection的VOC数据集配置都默认接受这种“图片xml”的文件组织方式。COCO的JSON格式表达能力更强但对初学者不友好标注工具导出的字段经常对不上踩坑成本高。VOC格式的xml内容直白每个实例一个object节点里面写类别名和axis-aligned的bounding box坐标。对PCB元器件缺陷这类“小目标多、背景单一”的数据VOC格式足够用没必要绕远路。xml里关键就三个信息图片尺寸、类别名、目标框左上角和右下角坐标。框的标注质量直接决定模型上限这一点在后面踩坑章节还会反复提。常见做法是类别名起得简短比如missing_hole、short、open_circuit不要用中文也不要带空格否则后面训练配置解析会多出很多无关的转义问题。目录结构按VOC约定放即可JPEGImages放原图Annotations放xmlImageSets/Main放train.txt、val.txt、test.txt这三个划分文件。2.2 用labelImg把元器件缺陷框出来类别定义与框选规范标注工具用labelImg最省事。Pascal VOC模式打开左边选Create RectBox沿着缺陷边缘往外留1到2个像素的余量画框。这里有一条后来帮我大忙的规范每个框只框一个缺陷实例两个缺陷挨得再近也不要合框。合框会让模型学到“一个框里有多个标签”的冲突模式训练时分类损失震荡推理时置信度被拉低。对元器件缺陷而言漏件和反件这种“整颗元器件”级别的缺陷框要覆盖元件本体偏位和桥连这种局部缺陷框要紧贴缺陷区域不要把整片焊盘都框进去。标注完成的xml长这样annotation folderJPEGImages/folder filenamepcb_001.jpg/filename size width1280/width height1024/height depth3/depth /size object namemissing_hole/name bndbox xmin342/xmin ymin518/ymin xmax361/xmax ymax540/ymax /bndbox /object /annotation这个结构是VOC系列的基准格式。filename必须和图片文件名完全一致size节点里的宽高是原始分辨率不是标注窗口的分辨率改小了会让模型拿坐标去缩放原始图时出现系统性偏移。xmin、ymin是左上角xmax、ymax是右下角都取整数像素即可。2.3 数据增强与训练集划分按板划分而不是按图划分PCB缺陷检测里一个最容易忽视的问题同一块板子的不同照片高度相似如果直接把所有图随机切分成train和val相当于让模型记住了这块板的纹理val分数虚高换一块新板就现原形。我一般会先把所有图片按“板号”分组同一个板号的图全部进同一个集合再按约7:2:1的比例分成train、val、test。数据集不大时用python脚本做划分比手工拖文件可靠得多。以下脚本把图片按文件名前缀的板号分组并写入ImageSets/Main下的txt文件import os import random from collections import defaultdict img_dir JPEGImages txt_dir ImageSets/Main os.makedirs(txt_dir, exist_okTrue) # 按板号分组假设文件名形如 board03_sample12.jpg board_map defaultdict(list) for f in os.listdir(img_dir): if not f.lower().endswith(.jpg): continue board_id f.split(_)[0] # 取出板号 board_map[board_id].append(os.path.splitext(f)[0]) # 去掉扩展名存 all_boards list(board_map.keys()) random.seed(42) random.shuffle(all_boards) n len(all_boards) train_boards all_boards[:int(n*0.7)] val_boards all_boards[int(n*0.7):int(n*0.9)] test_boards all_boards[int(n*0.9):] def write_list(path, boards): samples [] for b in boards: samples.extend(board_map[b]) with open(path, w) as fp: fp.write(\n.join(samples)) write_list(os.path.join(txt_dir, train.txt), train_boards) write_list(os.path.join(txt_dir, val.txt), val_boards) write_list(os.path.join(txt_dir, test.txt), test_boards) print(ftrain板的样本数: {sum(len(board_map[b]) for b in train_boards)})逻辑上先按板号聚合再对板号列表做shuffle最后按板号把对应样本写入txt。参数说明n是板总数0.7/0.9这两个阈值决定划分比例randseed固定为42保证可复现。注意train.txt里只写图片文件名不带.jpg后缀也不带目录路径因为多数训练框架内部会按约定拼接路径。2.4 公开PCB缺陷数据集与小样本条件下的起步策略如果手头没有产线实拍图公开的PCB_DATASET一类的学术数据集可以作为起步素材它的典型设定包含6类常见工艺缺陷图样多为单板局部放大视野。起步阶段用这类数据把训练链路跑通再迁移到自己场景的真实图片上比一开始就死磕自采数据更稳。小样本条件下还有一个技巧对每张训练图做随机亮度抖动和轻微高斯模糊模拟产线不同光源下的变化。torchvision的transforms里ColorJitter、RandomAffine都能直接接在数据管线里代价是训练轮次适当加长否则增强出来的分布模型还没吸收完就收敛了。3. 搭Faster R-CNN训练流程backbone选择、anchor参数与训练命令3.1 Faster R-CNN在PCB缺陷检测里的角色分工Faster R-CNN是典型的两阶段检测器第一阶段RPNRegion Proposal Network负责在feature map上滑动anchor粗筛出“像缺陷”的候选框第二阶段ROI Head对候选框做RoI Pooling逐框分类和回归精修坐标。对PCB这类“目标小、数量多、形态差异集中在局部纹理”的场景两阶段结构比单阶段的YOLO更稳RPN先干掉绝大部分背景框第二阶段的分类器只需要在有限候选里做决策。代价是推理速度慢但缺陷检测场景的实时性要求通常不如辅助驾驶苛刻一块中端GPU每张图跑到200ms以内就能接受。anchor参数是RPN的核心。默认配置通常是3个scale乘3个ratio共9个anchor但PCB元器件缺陷的宽高比跨度很大——漏件接近正方形桥连是长条开路可能是细线。如果全部用默认的0.5/1.0/2.0比例长条形缺陷的候选框从一开始就匹配不齐后面全凭回归硬拉训练效率很低。常见做法是把ratio改成0.25/0.5/1.0/2.0/4.0这样更宽的范围scale根据你的图片尺寸和缺陷像素大小调整。3.2 backbone选型ResNet50-FPN是默认盘VGG16留给老环境torchvision自带fasterrcnn_resnet50_fpn这是最省事的起点PyTorch官方实现已经把FPN、RPN、ROI Head串好了预训练权重在ImageNet上训过拿来在PCB数据上微调比从零训练收敛快得多。这里给一组我实际跑过多次的参数建议输入图长边缩放到1333短边不超过800torchvision内部默认batch_size取2到4初始学习率0.005用SGDmomentum0.9weight_decay设1e-4。训练轮次看数据量几百张图的场景通常在20到30个epoch内val mAP就不再明显上升。如果机器是老环境装不了新版本torchvision退路是用mmdetection的Faster R-CNN配置backbone换ResNet50不带FPN速度更快但对小目标召回率会有可见下降。VGG16作为backbone在今天已经偏慢且特征语义弱不推荐新项目再用除非你被锁定在旧依赖里。训练入口脚本的骨架长这样import torch import torchvision from torch.utils.data import DataLoader from voc_dataset import VOCLikeDataset # 自封装的数据集类 model torchvision.models.detection.fasterrcnn_resnet50_fpn( weightstorchvision.models.detection.FasterRCNN_ResNet50_FPN_Weights.COCO_SUPERSEDED, num_classes7 # 6类缺陷 1个背景类 ) device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) params [p for p in model.parameters() if p.requires_grad] optimizer torch.optim.SGD(params, lr0.005, momentum0.9, weight_decay1e-4) lr_scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.1) train_loader DataLoader( VOCLikeDataset(train.txt, augmentTrue), batch_size2, collate_fnlambda batch: tuple(zip(*batch)) ) for epoch in range(25): model.train() for images, targets in train_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) loss sum(loss_dict.values()) optimizer.zero_grad() loss.backward() optimizer.step() lr_scheduler.step() print(fepoch {epoch1}, loss: {loss.item():.4f}) torch.save(model.state_dict(), ffasterrcnn_pcb_epoch{epoch1}.pth)这里num_classes7的细节要说明白torchvision的Faster R-CNN内部把背景也算一类所以类别数必须是“缺陷类别数1”也就是6个缺陷类型就填7。填成6会导致最后一层分类头输出维度比ROI Head期望的少1训练到第一个backward就报维度不匹配。loss_dict里包含rpn_box_reg_loss、rpn_objectness_loss等分量打印总量即可但调试时分开看分量更有用——rpn_objectness_loss高说明anchor匹配有问题后面单独讲。3.3 核心训练参数怎么调learning rate、anchor匹配与NMS阈值先讲learning rate。SGD配合0.005的初始学习率是torchvision官方微调范式的常用值但如果你的数据集只有一两百张图0.005会让loss在前几个epoch冲高因为预训练权重偏离PCB域太多。我遇到这种情况会把初始学习率压到0.001等loss曲线走平后再靠StepLR按0.1倍衰减。batch_size小于2时学习率还要相应调低否则BN统计量不稳定表现为val mAP在epoch间剧烈抖动。anchor匹配和NMS阈值这两个参数训练时先别动用默认值跑通一轮。原因在于RPN的正负样本定义和NMS的抑制逻辑耦合紧密启动阶段同时改两个参数出问题都分不清是谁的锅。推荐做法是第一次训练全默认保存模型后跑一次val再回头调anchor ratio。如果漏检集中在长条缺陷就把ratio列表往极端方向扩展如果误检板边纹理优先提高NMS的IoU阈值从默认的0.7改到0.5或0.6。参数改动一次只动一个每动一次就重训一轮这个习惯能省掉大量“到底是谁导致翻车”的排查时间。3.4 从预训练权重开始微调显存估算与检查点策略显存是新手最常见的硬门槛。长边1333、batch_size2的ResNet50-FPN在12G显存的卡上勉强能跑batch_size4建议24G。如果显存只有8G把长边缩到1000、batch_size设1配合gradient_accumulation做梯度累积也能训但速度慢不少。检查点策略上每轮都存pth会让磁盘吃紧我一般只保留val mAP最高的两个权重文件用epoch数和mAP作为文件名后缀方便回滚。4. 评估与阈值调优mAP怎么看、置信度怎么设4.1 从val.txt里的PR曲线读模型短板训练完的第一件事不是拿测试图乱试而是跑一段标准评估脚本输出每个类别在IoU0.5和IoU0.75下的AP值再汇总mAP。torchvision没有内置评估器常见做法是接torchmetrics的检测模块或者手写VOC风格的AP计算。手写也不复杂对模型输出的所有预测框按置信度降序排列逐个和真值框算IoU大于阈值记为TP否则FP累计后画PR曲线、算面积。PR曲线是定位模型短板的利器。一个类别AP明显低于其他类先看这个类别的样本数是不是太少样本数没问题再看PR曲线是“早期掉点”还是“尾部拉平”。早期掉点意味着高置信度的预测框大量误检问题出在分类器分不清这个缺陷和背景尾部拉平意味着召回上不去漏检多问题出在RPN没把这类目标框出来。这两种毛病的处理路径完全不同前者调分类器的难例挖掘策略后者扩anchor范围或补数据。4.2 confidence阈值不是越高越好部署时很多人习惯把置信度阈值设到0.8以上认为这样误检少。PCB缺陷检测恰恰相反缺陷漏到产线后端比多一次人工复检代价大得多。实际项目中我通常把阈值设在0.3到0.5之间宁可让模型多框几个“疑似缺陷”给人眼复核也不要漏掉短路和反件。这个tradeoff可以根据产线代价调整缺陷流出导致整批报废的阈值往下压复核人力成本高的阈值往上抬。以下代码展示如何加载训练好的模型对一张测试图做推理并按阈值过滤输出import torch import torchvision from PIL import Image, ImageDraw model torchvision.models.detection.fasterrcnn_resnet50_fpn( weightsNone, num_classes7 ) model.load_state_dict(torch.load(fasterrcnn_pcb_best.pth, map_locationcpu)) model.eval() img Image.open(test_boards/board07_sample03.jpg).convert(RGB) transform torchvision.transforms.ToTensor() tensor transform(img).unsqueeze(0) with torch.no_grad(): pred model(tensor)[0] conf_thresh 0.4 boxes pred[boxes][pred[scores] conf_thresh] scores pred[scores][pred[scores] conf_thresh] labels pred[labels][pred[scores] conf_thresh] draw ImageDraw.Draw(img) for box, score, label in zip(boxes, scores, labels): x1, y1, x2, y2 box.tolist() draw.rectangle([x1, y1, x2, y2], outlinered, width3) draw.text((x1, y1-12), f{label} {score:.2f}, fillred) img.save(result_board07_sample03.jpg)参数说明pred[scores]对应每个预测框的置信度pred[labels]是类别编号pred[boxes]是xyxy格式的坐标。threshold从0.4起步微调推荐按0.05的步长观察结果图上的误检和漏检数量变化。把过滤后的box、score、label各自再索引一次是因为三者必须按同一套mask过滤遗漏任何一行都会出现框和标签错位的诡异结果。4.3 按类别统计的召回率比mAP更容易说清项目价值mAP适合在报告里体现整体水平但答辩或项目评审时按类别列的召回率表格更有说服力。比如短路召回率98%漏件召回率92%开路只有85%这个数字直接告诉评审“问题集中在哪类缺陷”。统计方式很简单对测试集所有图片逐张推理算出每个类别的TP除以TPFN。这个表格也会反向指导数据采集——召回率最低的类别就是最缺训练样本或最难标注的类别下一轮迭代优先补它。5. 常见问题避坑漏检、误检、显存溢出与标注错误排查5.1 小目标缺陷频繁漏检RPN正样本不足现象训练损失正常下降但推理时小于20×20像素的桥连和开路几乎全部漏检。原因在于ResNet50的FPN虽然有多层特征但PCB缺陷尺寸太小在最深的P5层上已经退化到几个像素RPN在这些层上采样到的anchor与真值的IoU普遍低于0.7形不成正样本。解决把输入图放大长边从1333提到1600或2000同时把RPN的anchor scale缩小增加小尺寸anchor的数量。如果显存不够放大图另一个手段是对小目标缺陷做过采样训练时把包含小缺陷的图重复采样提高它们在一个epoch里的占比。这套组合拳我实际试下来小目标召回率能从70%附近拉到85%以上。5.2 训练集和验证集分布不一致val mAP虚高现象val mAP高达0.92换到新拍的板子测试mAP直接掉到0.7。原因数据集划分时没有按板号隔离同一块板的不同照片同时出现在train和val里模型记住的是这块板的背景纹理和光照不是缺陷本身。解决重做数据划分强制按板号分组如果连板号信息都没有至少对val图片做亮度直方图检查确保分布和train差异明显。很多公开的PCB数据包按板子编号组织目录正好可以用。这是整个项目里最隐蔽的坑也是最常见的“论文指标好看、现场不能用”的根源。5.3 显存溢出CUDA out of memory在epoch跑到一半出现现象前几个epoch正常跑到第15个epoch显存突然爆掉。原因显存占用不是恒定的模型在BN统计量变化时中间激活值的波动可能临时抬高显存峰值加上DataLoader的预处理worker如果没设好num_workers数据管线堆积也会挤压显存。解决先把batch_size降到1确认能跑完一个完整epoch再把图像长边缩短到1100观察显存占用曲线。我一般还会加一条监控规则保存每个epoch结束时的reserved显存画出来看有没有随着训练上涨的异常趋势。超了就给DataLoader的num_workers设成0或2避免多进程缓存抢占显存。5.4 标注文件里有脏数据训练到一半报缓冲区异常现象训练进程在某个epoch突然报TypeError或者index out of range但重启后再跑又能跑几个epoch。原因xml标注里存在空object节点、坐标超出图片边界、类别名和配置里的class_names不一致等脏数据。这类问题初期不会触发只有当这批脏样本被sampler采到才炸。解决在训练前跑一遍全量校验脚本逐个解析xml检查xminxmax、yminymax、坐标在图片宽高内、object数量不为0。这个脚本的优先级比训练本身还高脏数据不除训练过程就跟抽奖一样。import os import xml.etree.ElementTree as ET from PIL import Image ann_dir Annotations img_dir JPEGImages for xml_name in os.listdir(ann_dir): xml_path os.path.join(ann_dir, xml_name) tree ET.parse(xml_path) root tree.getroot() img_name root.find(filename).text img_path os.path.join(img_dir, img_name) if not os.path.exists(img_path): print(f图片缺失: {img_name}) continue w, h Image.open(img_path).size for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) x1 int(box.find(xmin).text) y1 int(box.find(ymin).text) x2 int(box.find(xmax).text) y2 int(box.find(ymax).text) if not (0 x1 x2 w and 0 y1 y2 h): print(f坐标越界: {xml_name}, {name}, ({x1},{y1})-({x2},{y2}))这个检查脚本的关键在两层第一层查图片文件是否真的存在避免xml注记里filename写错第二层对每个object的框做边界约束越界的框在RPN采样时会生成负样本区域拖累分类器。实际跑一遍会发现人工标注的框偶尔会超出图片右边界或下边界几个像素这是手滑点标的常见副产品。5.5 预训练权重加载报错key不匹配现象load_state_dict时报unexpected key or missing key尤其是num_classes改过之后。原因Faster R-CNN的分类头输出维度是“类别数背景数”修改num_classes后box_predictor层的权重形状和预训练权重不一样PyTorch加载时不会自动跳过。解决加载时加strictFalse让框架只加载匹配的层不匹配的分类头从头训练。这个操作是正常微调流程的一部分不需要惊慌但要确认除了分类头和回归头之外的其他层都加载成功可以用load后的missing_keys列表核对缺失的层应该只包含和类别数相关的模块。6. 部署与验证把模型跑成可调用的缺陷检测服务训练结束只是开始真正让方案落地的是把模型包成一个可复用的推理接口。我常用的是FastAPI包一层HTTP服务内部加载模型权重接收图片字节流返回JSON格式的检测结果。这样产线侧不管是C#上位机还是Python采集脚本都只需要发一个HTTP POST就能拿到缺陷框列表。接口里要固定输入尺寸的缩放逻辑和训练时保持一致推理时把图像转成RGB再进模型避免灰度图或RGBA四通道图导致张量形状错误。上线前还有一个必做的验证动作采集一批完全没参与过训练和验证的新板照片人工标注好真值跑一次完整的召回率统计。这不是为了刷指标而是确认模型没有过拟合到训练数据的特定光照和板卡批次。做这个验证时我吃过一次亏测试集用了同一批板子的不同照片指标好看到客户现场换了新批次板卡漏检率飙升。后来养成的习惯是每次换板卡批次都保留10张新图加入回归测试集模型更新前先用新图过一遍。这个习惯帮我挡掉了好几次现场翻车。推理速度的验证同样重要。GPU端一次推理80到150ms对多数产线够用但如果目标是把检测塞进每秒一帧的流水线就要考虑用TensorRT把模型转成engine格式或者退而求其次在生产工位用单张GPU卡做串行推理。CPU端跑Faster R-CNN通常要1秒以上不适合在线检测只能做离线抽检。把模型阈值和输出格式固化到配置文件里不要散落在代码各处。改一次阈值就要重新部署一次是笨办法写成config.json让现场工程师直接改数值省去来回折腾。这套方案做到这里技术上的完整性已经足够支撑毕业设计答辩或一次项目评审但真正验证它价值的是你拿自己产线的板子跑一遍——能框出缺陷、能给出置信度、能把结果回溯到产线工位项目才算闭环。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑