简介这份资源是面向电子制造质检、工业视觉算法学习者的PCB板缺陷检测数据集已按PASCAL VOC标准完成标注可直接用于训练与评估短路、开路、焊点不良、元件缺失等常见缺陷的识别模型。压缩包共893个文件以445张jpg图像与445个xml标注文件一一对应另附2个txt说明和1个py脚本整体约583.89MB解压后即可用PIL读图、ElementTree解析标注接入YOLO、Faster R-CNN或Mask R-CNN做迁移学习。目前已有2679人学习下载适合作为课程设计、毕业项目或产线检测原型的数据基础。数据集覆盖多种缺陷类别与程度能帮助读者快速搭建训练与验证流程理解VOC格式的边界框与类别标注方式并在此基础上完成数据增强、模型微调与缺陷定位实验为后续部署到实际检测系统提供可复用的数据与思路。1. 拿到 PCB 板缺陷检测数据集-voc.rar先别急着解压很多做工业质检的朋友第一次拿到 PCB 板缺陷检测数据集-voc.rar第一反应是解压、看图片、直接丢进 YOLO 训练然后被 mAP 0.3 的结果打脸。问题不在模型在于 VOC 格式本身和 PCB 缺陷的成像特性没对齐。PCB 缺陷检测属于小目标、高相似、强反光的典型场景缺件、短路、虚焊、异物这几类缺陷在 640 分辨率下可能只占十几个像素VOC 的 XML 标注里一个 bndbox 宽高比极端到 1:8 是常事。这个数据集的价值在于它把工业现场最头疼的几类缺陷做了框级标注适合拿来验证小目标检测链路、做数据增强策略对比、以及跑通从 VOC 到 YOLO/COCO 的格式转换。适合谁做 AOI 设备算法、产线视觉质检、以及想入门工业缺陷检测但拿不到真实产线数据的工程师。先别解压先想清楚你的模型输入尺寸和这批标注的匹配度这决定了后面一半的坑。2. 拆开 VOC 结构PCB 缺陷标注里藏着什么2.1 VOC 目录树与 PCB 场景的对应关系标准 VOC 数据集长这样Annotations 放 XMLJPEGImages 放原图ImageSets/Main 放 train/val/test 的 txt 列表。PCB 板缺陷检测数据集-voc.rar 解压后大概率也是这个结构但有几个 PCB 特有的点要提前确认。第一图片命名是否带板号或批次号如果有划分训练集时不能随机打散否则同一块板的不同缺陷会同时出现在训练和验证集里验证指标虚高。第二XML 里的 size 字段是否和实际图片分辨率一致工业相机换型后经常出现标注沿用旧分辨率的情况。第三缺陷类别名是否统一比如 short 和 short_circuit 混用后面训练时类别数会翻倍。先跑一段脚本把结构摸清楚别靠肉眼翻文件夹import os import xml.etree.ElementTree as ET from collections import Counter # 指向解压后的 VOC 根目录 VOC_ROOT ./PCB_VOC ANN_DIR os.path.join(VOC_ROOT, Annotations) IMG_DIR os.path.join(VOC_ROOT, JPEGImages) cls_counter Counter() size_mismatch [] wh_ratios [] for xml_name in os.listdir(ANN_DIR): if not xml_name.endswith(.xml): continue tree ET.parse(os.path.join(ANN_DIR, xml_name)) root tree.getroot() # 读取标注里记录的分辨率 size root.find(size) w_ann int(size.find(width).text) h_ann int(size.find(height).text) # 读取实际图片分辨率做比对 img_path os.path.join(IMG_DIR, xml_name.replace(.xml, .jpg)) if os.path.exists(img_path): from PIL import Image with Image.open(img_path) as im: w_img, h_img im.size if (w_ann, h_ann) ! (w_img, h_img): size_mismatch.append((xml_name, (w_ann, h_ann), (w_img, h_img))) # 统计类别和宽高比 for obj in root.findall(object): cls_counter[obj.find(name).text] 1 bbox obj.find(bndbox) bw int(bbox.find(xmax).text) - int(bbox.find(xmin).text) bh int(bbox.find(ymax).text) - int(bbox.find(ymin).text) if bh 0: wh_ratios.append(bw / bh) print(类别分布:, cls_counter) print(分辨率不一致数量:, len(size_mismatch)) print(宽高比 min/max:, min(wh_ratios), max(wh_ratios))这段脚本干三件事统计每个缺陷类别的框数量找出标注分辨率和实际图片不一致的样本以及看宽高比的极端程度。参数上VOC_ROOT 换成你解压后的实际路径。如果 size_mismatch 超过总数的 5%这批数据在训练前必须统一 resize 并同步修正 XML否则增强后的框会整体偏移。宽高比如果出现超过 10 的极端值说明存在细长型缺陷后面 anchor 设计要单独考虑。2.2 类别不平衡与长尾分布的处理判断PCB 缺陷天然不平衡缺件和短路可能几百个框异物和划痕可能只有几十个。VOC 格式本身不带类别权重这个信息要自己从统计结果里读出来。常见做法是训练时用类别加权采样或者对稀有类做针对性增强。但增强之前先确认稀有类的框是否标注准确工业数据里稀有类往往也是标注质量最差的一类因为标注员见得少。我一般会先按类别把框裁出来存成小图人工快速过一遍。如果某个类别的框大量贴边或者宽高只有几个像素这类样本在训练时要么被忽略要么引入噪声不如先过滤掉。过滤阈值建议宽或高小于 8 像素的框直接丢弃贴边超过框面积 30% 的标记为可疑单独放一个列表里复查。3. 从 VOC 到 YOLO转换脚本与四个边界坑3.1 转换脚本的完整实现VOC 的坐标是绝对像素值 xmin/ymin/xmax/ymaxYOLO 要的是归一化后的中心点加宽高。转换本身不复杂但 PCB 数据有几个边界情况必须处理图片和 XML 不对应、类别名带空格、坐标越界、以及空标注文件。import os import xml.etree.ElementTree as ET from PIL import Image VOC_ROOT ./PCB_VOC OUT_LABEL_DIR ./labels CLASSES [missing, short, scratch, foreign, solder] # 按实际类别顺序改 os.makedirs(OUT_LABEL_DIR, exist_okTrue) cls2id {c: i for i, c in enumerate(CLASSES)} def convert_one(xml_path, img_path, out_txt): tree ET.parse(xml_path) root tree.getroot() with Image.open(img_path) as im: w, h im.size lines [] for obj in root.findall(object): name obj.find(name).text.strip() if name not in cls2id: continue # 未登记类别直接跳过避免类别错位 bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 坐标裁剪到图片范围内防止越界导致归一化后为负 xmin max(0, min(xmin, w - 1)) ymin max(0, min(ymin, h - 1)) xmax max(0, min(xmax, w - 1)) ymax max(0, min(ymax, h - 1)) bw xmax - xmin bh ymax - ymin if bw 2 or bh 2: continue # 过滤退化框 cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h nw bw / w nh bh / h lines.append(f{cls2id[name]} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}) with open(out_txt, w) as f: f.write(\n.join(lines)) # 批量执行 for xml_name in os.listdir(os.path.join(VOC_ROOT, Annotations)): if not xml_name.endswith(.xml): continue stem xml_name[:-4] xml_path os.path.join(VOC_ROOT, Annotations, xml_name) img_path os.path.join(VOC_ROOT, JPEGImages, stem .jpg) if not os.path.exists(img_path): print(缺图跳过:, stem) continue convert_one(xml_path, img_path, os.path.join(OUT_LABEL_DIR, stem .txt))逻辑上先读实际图片尺寸而不是 XML 里的 size这是防分辨率不一致最稳的做法。坐标裁剪到 [0, w-1] 是为了防止标注员手抖写出超出图片的框归一化后出现负数会让训练直接报错。宽高小于 2 像素的框过滤掉PCB 缺陷里这种框多半是标注噪声。类别不在 CLASSES 里的直接跳过宁可少几个框也不要类别错位。参数说明CLASSES 必须和你的训练配置里的 names 顺序完全一致差一个顺序整个模型就废了。输出格式保留 6 位小数YOLO 默认解析没问题。如果后面用 YOLOv8标签目录结构和图片目录要平行这个脚本只生成 labels图片目录自己建软链接或复制。3.2 转换后必须做的三项校验转换完不能直接开训先做三项校验。第一标签行数和 XML 里的 object 数量对得上差太多说明有类别被跳过。第二随机抽 20 张图把框画出来看重点看贴边和密集区域PCB 板边缘的缺陷最容易标偏。第三统计每类框的数量和平均面积和转换前的统计对比确认没有类别丢失。画框校验用下面这段import cv2 import os IMG_DIR ./PCB_VOC/JPEGImages LBL_DIR ./labels CLASSES [missing, short, scratch, foreign, solder] for name in os.listdir(LBL_DIR)[:20]: stem name[:-4] img cv2.imread(os.path.join(IMG_DIR, stem .jpg)) h, w img.shape[:2] with open(os.path.join(LBL_DIR, name)) as f: for line in f: cid, cx, cy, nw, nh line.split() cx, cy, nw, nh map(float, (cx, cy, nw, nh)) x1 int((cx - nw / 2) * w) y1 int((cy - nh / 2) * h) x2 int((cx nw / 2) * w) y2 int((cy nh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 1) cv2.putText(img, CLASSES[int(cid)], (x1, y1 - 3), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1) cv2.imwrite(f./check_{stem}.jpg, img)这段只做可视化参数不用调跑完去文件夹里翻生成的 check 图。如果发现框整体偏移回去查 XML 的 size 和实际图片是否一致如果发现框大小对但位置飘查坐标裁剪逻辑是不是把 xmax 裁到了 w-1 导致宽高变了。4. 训练配置小目标 PCB 缺陷的参数怎么设4.1 输入分辨率与 anchor 的匹配PCB 缺陷检测数据集-voc.rar 里的图片分辨率如果普遍在 1000 以上直接 resize 到 640 会让小缺陷缩到 10 像素以下模型根本学不到。常见做法是训练用 1024 或 1280推理时再根据产线节拍降。但分辨率上去显存吃不消这时候要么用梯度累积要么把 batch 降到 4 或 2。anchor 方面YOLOv5/v8 的默认 anchor 是针对 COCO 的PCB 缺陷的宽高比分布和 COCO 差很远。如果前面统计出宽高比集中在 0.2 到 0.5 之间默认 anchor 的召回会很低。我一般会跑一遍 k-means 重新聚类 anchor聚类数保持 9输入用转换后的归一化宽高。# YOLOv5 聚类 anchor指向你的 labels 目录 python utils/autanchor.py --data ./pcb.yaml --img-size 1024 --thr 4.0 --n 9参数上--img-size 必须和训练输入一致--thr 是聚类时的 IoU 阈值PCB 小目标建议调到 4.0 以上让聚类更集中。跑完把输出的 anchor 替换到模型 yaml 里。如果不想动 anchor至少把训练输入尺寸提到 1024这是性价比最高的一步。4.2 数据增强的取舍PCB 缺陷增强不能照搬通用策略。Mosaic 会拼接四张图对小目标有好处但 PCB 板有固定纹理和边缘拼接后会出现不存在的板边模型可能学到伪特征。我的做法是 Mosaic 概率降到 0.3 到 0.5同时关掉上下翻转因为 PCB 的元件方向有语义翻转后缺件和正常的视觉特征会混淆。左右翻转可以保留旋转控制在正负 10 度以内。颜色增强也要克制。HSV 的 V 通道抖动调小因为工业相机的光照相对稳定大幅调亮度会让模型对光照变化过拟合。如果数据里本身就有不同批次的色差那反而应该保留甚至加大这取决于你的产线实际情况。# pcb.yaml 训练配置片段 train: ./images/train val: ./images/val nc: 5 names: [missing, short, scratch, foreign, solder] # 增强参数 mosaic: 0.4 flipud: 0.0 fliplr: 0.5 degrees: 10.0 hsv_v: 0.2 hsv_s: 0.3mosaic 0.4 是折中值flipud 直接关掉degrees 限制在 10 度。hsv_v 和 hsv_s 调低是为了不让模型依赖颜色判断缺陷。这些值不是绝对的如果你的数据里缺陷类型主要靠形状区分颜色增强可以再压。5. 避坑与排查PCB 缺陷训练里最常见的五个翻车点5.1 验证集 mAP 很高但产线漏检严重现象是验证集 mAP 0.85 以上上线后漏检率却超过 20%。原因通常是训练集和验证集划分时没有按板号隔离同一块板的不同缺陷同时进了训练和验证模型记住了这块板的背景纹理。解决方法是按板号或批次号做 GroupShuffleSplit确保同一块板只出现在一个集合里。如果数据里没有板号信息至少按图片文件名前缀分组。5.2 训练 loss 正常但框全部偏移现象是 loss 稳定下降但推理时框整体往一个方向偏。原因多半是 VOC 转换时用了 XML 里的 size 而不是实际图片尺寸两者不一致导致归一化基准错了。解决方法是回到 3.1 的脚本确认读的是 PIL 打开的实际尺寸。如果已经训完才发现重新转换标签再训不要试图用后处理修正偏移不是线性的。5.3 稀有类别完全学不到现象是某个类别 AP 始终为 0其他类别正常。原因是该类别样本太少且没有做重采样或加权。解决方法是先确认这个类别的框数量如果少于 50 个考虑过采样或者用 copy-paste 增强把稀有类缺陷贴到正常板上。但贴的时候要注意边缘融合硬贴会引入明显的人工边界模型学到的是边界而不是缺陷本身。5.4 推理时小缺陷直接消失现象是训练指标还行但推理时小缺陷检测不到。原因是推理分辨率低于训练分辨率或者 NMS 的置信度阈值设太高把小目标的低分框滤掉了。解决方法是推理分辨率和训练保持一致NMS 置信度从 0.25 降到 0.1 试同时看小目标的召回变化。如果降阈值后误检暴涨说明模型本身没学好小目标回去查训练输入尺寸和 anchor。5.5 不同批次图片色差导致指标波动现象是换一批板子后指标掉得厉害。原因是训练集里批次多样性不够模型对色差过拟合。解决方法是在增强里加大 HSV 抖动或者用颜色归一化把不同批次的图片先对齐到同一色调。工业现场常见做法是固定光源和相机参数但如果你拿到的数据已经混了多个批次增强是唯一的路。6. 把 VOC 数据集用出复利版本管理与迭代习惯PCB 板缺陷检测数据集-voc.rar 这类数据最大的价值不是训一次模型而是作为基线反复迭代。我自己的习惯是每次转换、每次训练配置变更都打一个 git tag标签文件单独存一份不要覆盖。因为工业缺陷的定义会随产线工艺变化今天叫短路的缺陷明天可能被拆成两类标注体系一变旧标签就废了。保留历史版本能让你在类别体系调整时快速回溯。另一个技巧是把验证集固定下来永远不参与训练哪怕做增强也不要用。每次模型迭代都在同一个验证集上对比指标才有可比性。如果验证集太小导致波动大可以按板号分层抽样扩大验证集但一旦定下来就不要动。最后说一个我踩过的坑早期我为了提升指标把验证集里难样本手动删了一批结果上线后这些难样本对应的缺陷类型全部漏检。验证集的作用是暴露问题不是刷分。现在我的做法是验证集只增不减难样本单独标记但保留训练时不用评估时单独看这一类。这个习惯让我在后来的项目中少走了很多弯路。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?