ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PCB缺陷检测实战:Faster-RCNN开源工程从数据到部署全解析

PCB缺陷检测实战:Faster-RCNN开源工程从数据到部署全解析 简介一套基于Python与Faster-RCNN的PCB元器件缺陷检测完整工程面向毕业设计、课程设计与项目开发场景适合具备一定深度学习基础、希望掌握目标检测落地流程的读者。资源中共计包含79个文件包括35个Python脚本、39个pyc编译缓存、2个Markdown文档、1个TXT说明等Python脚本覆盖从VOC0712数据集预处理、Kmeans锚框聚类、数据增强、各类骨干网络VGG16、ResNet50/101、FPN配置到模型训练、预测、soft-NMS后处理及mAP评估的完整流程Markdown文档为开发文档与项目解析帮助理解Faster-RCNN结构及PCB缺陷检测调试要点。压缩包整体大小为214KB目录划分清晰便于按功能模块快速查阅。目前已有379人学习浏览项目源码经过严格测试可参照训练自己的数据集或预训练权重直接预测并可通过评估脚本检验模型效果适合在毕业设计和实际项目中延展使用。1. 为什么PCB缺陷检测选择faster-RCNN这套开源工程PCB板上的元器件缺陷焊点偏移、极性反装、少件漏贴靠人工目检在产线上基本是盯着放大镜看漏检率随疲劳直线上升。视觉检测里faster-RCNN虽然不是最新的但论落地稳定性两阶段框架对小区域细缺陷的召回能力仍然比一阶段模型可靠这正是PCB检测最看重的一点。这个Python工程把VOC格式的数据流水线、ResNet50FPN的backbone、RPN候选框生成、RoI分类回归整条链路都串好了训练、预测、评估三种入口都有现成脚本适合毕设和课设直接二次开发。下面按数据准备、训练、评估的顺序拆解这套代码重点说清楚参数在哪里改、为什么这么改。2. faster-RCNN在PCB场景下的网络结构与Anchor机制2.1 backbone选型ResNet50-FPN为什么是默认答案项目nets目录下放了vgg16、resnet50、resnet50_FPN、resnet50_ECA_FPN四套骨干网络。VGG16是faster-RCNN祖师爷级别的搭配但它的感受野集中在局部PCB元件的缺陷往往只有几十个像素VGG16浅层特征语义信息不够深层特征又丢了空间位置。ResNet50的残差结构解决了梯度消失50层深度能把语义特征提得更充分FPN又通过自顶向下的路径把高层语义回传到低层让小目标检测同时拿到细节和语义。项目中resnet50_FPN.py的几个核心结构点# nets/resnet50_FPN.py 中特征金字塔的自上而下融合逻辑 def _make_layer(self, block, planes, blocks, stride1): downsample None if stride ! 1 or self.inplanes ! planes * block.expansion: downsample nn.Sequential( nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size1, stridestride, biasFalse), nn.BatchNorm2d(planes * block.expansion), ) layers.append(block(self.inplanes, planes, stride, downsample))四层下采样后FPN把C2到C5的输出分别做1x1卷积统一通道数再逐层上采样相加。PCB缺陷检测里焊盘旁边的微小桥连、电容本体裂纹这类目标FPN的P2层原始图像1/4分辨率能保留足够的位置精度这是ResNet50-FPN比单纯用VGG16或裸ResNet50实用的根本原因。resnet50_ECA_FPN.py则是在FPN基础上嵌入了通道注意力模块对反光、色差干扰有轻微抑制作用但推理速度会慢5%左右如果训练资源吃紧默认的resnet50_FPN就够。2.2 RPN与Anchor在PCB小目标上的配置思路2.2.1 anchors.py里的三种配置RPN是faster-RCNN的提案网络核心思想是在特征图每个像素位置预置一组不同尺寸和比例的Anchor框判断框内是否有目标并修正边界。PCB元件和自然图像的目标尺度差异很大一张1024x1024的PCB图像里一个0402封装电阻只有30x15像素默认的Anchor配置直接套用VOC自然图像参数会大量漏检。utils/anchors.py里对三种backbone分别生成锚框# utils/anchors.py def get_anchors(input_shape, backbone_name, anchor_ratios[0.5, 1, 2], anchor_scales[8, 16, 32]): if backbone_name resnet50_FPN or backbone_name resnet50_ECA_FPN: # FPN结构在每层特征图上设置不同尺寸的anchor anchors np.zeros((feat_shape[0], feat_shape[1], 9, 4)) for i, r in enumerate(anchor_ratios): for j, s in enumerate(anchor_scales): anchor_h base_size * s * np.sqrt(r) anchor_w base_size * s * np.sqrt(1 / r)anchor_scales[8, 16, 32]表示以基础边长乘以8、16、32生成三档尺寸anchor_ratios[0.5, 1, 2]控制宽高比。PCB元件绝大多数是水平或垂直的长条形比如电阻、电容本体宽高比经常接近2:1我一般会在自定义数据集上把ratios改成[0.5, 0.8, 1, 1.5, 2]扩到5个比例虽然每张图RPN候选框数量增加但对细长元件召回明显变好。2.2.2 kmeans_anchors的用法kmeans_anchors.py是YOLO系常用的Anchor聚类脚本faster-RCNN里也能用。它的原理是把训练集XML里的真实框宽高聚类出最匹配当前数据分布的几组尺寸替代手工设定的scales和ratiospython kmeans_anchors.py --annotation_path voc_annotation.txt --cluster_num 9运行后输出9组聚类结果类似w,h的坐标对。需要注意的是聚类出的数值单位是像素要和get_anchors里的anchor_scales换算对应。常见做法是把聚类结果直接覆盖进utils_bbox.py里生成先验框的位置让RPN的初始提案更贴合PCB器件尺度。如果PCB板上只有一种主要器件类型聚类数设为6就够板子器件种类杂、尺寸跨度大9个更稳。在utils_bbox.py里RPN输出的偏移量通过下面这段解码成真实候选框# utils/utils_bbox.py 中 proposal 解码 def decode_bbox(self, anchors, regressions): cx anchors[..., 0] regressions[..., 0] * anchors[..., 2] cy anchors[..., 1] regressions[..., 1] * anchors[..., 3] w anchors[..., 2] * torch.exp(regressions[..., 2]) h anchors[..., 3] * torch.exp(regressions[..., 3])这里的regressions[..., 2]和[..., 3]是宽高的对数偏移指数还原所以训练时回归目标要提前做log编码训练的稳定性很大程度上取决于这个编码和解码是否对称。改动Anchor尺寸后一定要同步检查regressions的初始值范围避免训练初期loss爆炸。3. 数据从裸图到VOC格式voc_annotation与数据增强3.1 VOC目录与XML标注的组织方式这个工程要求数据组织成VOC07风格目录。很多第一次做目标检测的同学上来就把图片扔进JPEGImages跑train.py报找不到文件本质是xml文件名和图片名对不上。标准的VOC结构是三层目录缺一不可VOCdevkit/ VOC2007/ JPEGImages/ # 原始PCB图像 Annotations/ # 每个图像对应的xml标注 ImageSets/ Main/ # train.txt, val.txt 划分文件XML里重点关注size节点的width和heightfaster-RCNN的get_random_data做数据增强时会按这个尺寸做裁剪和缩放如果标注时图像被resize过但XML尺寸没同步改增强后所有真实框全部错位。工程里的order_name.py做的就是这类数据规整工作它把目录下的图片和XML统一重命名成纯数字序号如000001.jpg并剔除没有对应XML的孤立图片。PCB缺陷检测数据集的图片命名经常带批次号、面板号这类字段中文名或空格会让很多图像处理库直接报错重命名这一步看似低级实际能挡掉大半训练中断问题。对自建数据集我一般会额外写一个检查脚本遍历XML里每个object把标注框超图像边界的情况自动裁剪掉这类脏数据在PCB板边缘器件上非常常见。3.2 voc_annotation.py的类目映射逻辑voc_annotation.py是数据侧的核心入口作用是把VOC目录解析成一个txt文件每一行记录图片路径、归一化后的真实框坐标和类别索引# voc_annotation.py 核心解析逻辑 def convert_annotation(voc_path, image_id): in_file open(os.path.join(voc_path, Annotations, %s.xml % image_id), encodingutf-8) tree ET.parse(in_file) root tree.getroot() for obj in root.iter(object): difficult obj.find(difficult).text cls obj.find(name).text if cls not in classes or int(difficult) 1: continue xmlbox obj.find(bndbox) b (float(xmlbox.find(xmin).text), float(xmlbox.find(ymin).text), float(xmlbox.find(xmax).text), float(xmlbox.find(ymax).text)) list_file.write( ,.join([str(int(a)) for a in b]) , str(cls_id))代码里classes必须在脚本顶部按自己的类目列表改比如[missing_hole, mouse_bite, open_circuit, short, spurious_copper]类和索引的顺序一旦定下训练期间不能改动。difficult字段为1的样本会被过滤掉这个机制在PCB数据里用来剔除模糊不清、争议较大的缺陷样本很合适。注意每张图如果有多个缺陷目标会在一行里追加多组坐标txt的读取顺序要和dataloader.py里的解析逻辑完全一致。运行方式python voc_annotation.py --voc_path VOCdevkit --output train.txt生成train.txt后检查前几行内容确认坐标系是否正确可以用summary.py做一次全量统计检查类别数量分布。PCB缺陷数据的正负样本比例往往极不均衡比如同一批板子上短路缺陷数量远少于缺孔后面训练时需要针对性调采样权重。3.3 data_expansion.py的针对性增强数据增强脚本data_expansion.py不是通用的随机HSV调整而是针对PCB图像特点做了几类变换组合# data_expansion.py 中针对PCB板的增强逻辑 def image_augment(image, boxes, labels): image, boxes random_flip(image, boxes, flip_ratio0.5) image, boxes random_rotate(image, boxes, angle_limit10) # 针对PCB焊盘反光亮度与对比度扰动 image cv2.convertScaleAbs(image, alpharandom.uniform(0.8, 1.2), betarandom.randint(-10, 10)) image, boxes random_translation(image, boxes, offset_ratio0.05)PCB板面有铜箔反光、丝印遮挡亮度扰动能模拟不同拍摄照明条件降低模型对光线的过拟合。随机旋转的angle_limit这里设置为10度PCB缺陷检测不要用大角度旋转因为器件本体的方向性有意义转90度会让模型学到错误的方向特征。增强过程中必须把坐标变换同步作用于XML或数组里的boxdata_expansion.py返回的是变换后的boxes和labels而不是只改图像这点很多自己写增强的人容易漏掉图像变了坐标没变val集合指标惨不忍睹。需要注意data_expansion.py生成的增强数据是离线写入数据集的和训练时dataloader.py里的在线增强可以叠加使用。如果单片PCB板上的元器件数量非常多超过50个目标离线增强会成倍放大训练集的标注复杂度这时候优先加大在线增强的强度而不是死磕离线生成。4. 训练流程与核心参数从VOC07到自定义数据集4.1 train.py的启动方式与参数表训练入口train.py的参数表决定整个训练过程的行为最需要关注的几个配置参数名建议初值调参方向说明annotation_pathtrain.txt指向voc_annotation生成的txtclasses_pathmodel_data/voc_classes.txt每行一个类名行号即类别索引model_pathmodel_data/resnet50_voc_weights.pth预训练权重路径没有就填空字符串input_shape(600, 600)训练输入分辨率PCB大图先缩到该尺寸Init_Epoch0冻结阶段的起始epochFreeze_Epoch50冻结backbone只训练RPN和分类头的轮数Epoch100解冻后完整训练轮数Freeze_batch_size8冻结阶段batch可以适当调大batch_size4解冻阶段显存占用变大及时调小learning_rate1e-4解冻后建议降为1e-5启动训练前必须确认两件事classes_path指向的文件内容类别顺序是否和voc_annotation.py里写的一致以及model_path是否指向与backbone匹配的预训练权重。用ResNet50-FPN结构却加载了VGG16的预训练权重绝大多数情况是第一个epoch就shape报错或者loss保持高位不下降。train.py里还有个细节是Freeze_backbone的逻辑前50个epoch冻结骨干网只更新RPN和分类头这一步显著降低显存占用和训练时间后期解冻让整个网络联合微调在PCB数据量有限的情况下能有效防止backbone在前期被扰动过大。4.2 dataloader与utils_fit的联动dataloader.py和utils_fit.py是训练循环的引擎。dataloader.py把每一行txt解析成图像、真实框、类别三个张量其中utils.py里的get_random_data负责在线数据增强随机裁剪缩放、HSV扰动、翻转、mixup拼图。这一部分值得重点看的逻辑是utils_fit.py里的学习率调整# utils/utils_fit.py def fit_one_epoch(net, optimizer, epoch, train_dataloader, val_dataloader, loss_history): for iteration, (images, boxes, labels) in enumerate(train_dataloader): outputs net(images) loss_value loss_function(outputs, boxes, labels) loss_value.backward() optimizer.step() if iteration % 10 0: print(Epoch %d, Iter %d, Loss %.4f % (epoch, iteration, loss_value.item()))训练时看loss曲线不要看单个iteration的波动每10个iteration输出一次的日志先累积看整个epoch的平均loss。PCB缺陷检测的loss收敛有个特征前半段RPN的loss占主导后半段分类和回归loss占比上升。如果RPN loss一直不降检查Anchor尺寸和真实框匹配情况如果分类loss降了回归loss不动多半是PCB图像里大尺寸背景区域被当成正样本可以调低RPN的正样本IoU阈值。显存不够时优先把input_shape从600降到544再考虑调小batch_size因为分辨率下降带来的性能损失在PCB这种强纹理场景下比减batch更可控。4.3 断点续训与多阶段训练策略train.py支持从断点继续训练关键点是每次epoch结束会保存logs/下的权重文件best_epoch_weights.pth记录验证集mAP最高的权重last_epoch_weights.pth记录最近状态。续训时把model_path指向last_epoch_weights.pth同时把Init_Epoch改成断点epoch数例如上次训练到第50个epoch崩溃续训时Init_Epoch设为50。多阶段训练策略上PCB数据集很小时不要直接100个epoch全程解冻第一阶段冻结backbone训练到RPN的loss趋于平缓观察Freeze_Epoch到50时RPN proposal的质量第二阶段解冻后学习率从1e-5开始训练20到30个epoch后再评估一次验证集。整个流程跑下来一套中等复杂度PCB板数据比如1000张训练图像在单张RTX 3060上约2到4小时完成。5. 模型评估与预测阶段的实用调优5.1 get_map.py与指标验证模型训完不能只看lossmAP和PR曲线是更可信的指标。工程里的get_map.py可以单独评估验证集的检测效果python get_map.py --model_path logs/best_epoch_weights.pth --classes_path model_data/voc_classes.txt输出会在map_out/下生成每个类别的PR曲线和mAP数值。PCB缺陷检测对误报容忍度低实际关注点不是整体mAP而是每个缺陷类别的AP比如短路类的AP至少要达到0.85才算合格如果某类AP明显低于其他类大概率是这类缺陷样本太少要么补数据要么对该类在loss_function里增权。评估时的IoU阈值默认设置成0.5PCB小目标场景建议同时跑一个0.75阈值的版本IoU 0.75下的mAP能反映框定位的精细程度。定位不准的缺陷框即便类别预测对了产线也没法用。5.2 使用自训练权重做缺陷预测与阈值调优frcnn_predict.py是预测脚本加载自训练的权重时把model_path改成自己的权重文件路径classes_path改成自己的类别文件。运行时关注两个预调参数# frcnn_predict.py 中的检出阈值 conf_thres 0.5 # 置信度阈值低于该值的检测结果直接丢弃 nms_iou 0.3 # NMS时判断两个框是否重叠的IoU阈值PCB板元器件密集相邻器件之间的候选框很容易重叠nms_iou从默认的0.5降到0.3能明显减少重复框。如果某类缺陷频繁漏检把conf_thres降到0.3到0.4看预测图片上的置信度分布找到漏检框的实际置信度再针对性调阈值。当conf_thres低于0.5时误检框也会增加需要结合get_map.py的PR曲线找到精确率和召回率的平衡点。滚动生产环境一般把conf_thres设为0.6以上用更严的阈值换取稳定的低误检率。utils_bbox.py里的soft_nms实现值得单独提一句它在nms_iou较高时不再直接删除重叠框而是按重叠程度线性降低置信度PCB板上两个相邻的缺陷目标距离极近时soft_nms能保住置信度略低的那个真实框。实际使用时对比一下普通NMS和soft_nms在验证集上的召回差异通常能带来3%到5%的recall提升对于短路和桥连这种成对出现的缺陷效果格外明显。本文还有配套的精品资源点击获取
返回列表