☰
PCB缺陷检测数据集:三格式标签与YOLO11训练避坑指南
2026/9/30 3:35:13 网站建设 项目流程

简介:面向PCB板表面缺陷检测与工业视觉应用场景,这份资源以PDF文档形式提供一套完整数据集使用指南,适合目标检测算法开发者、智能制造质检项目人员及YOLO入门学习者。文档共1个文件,压缩包大小约6.92MB,内部包含数据集基本情况、标注格式说明以及百度网盘获取方式。数据集为真实采集的高清PCB板表面图片,覆盖missing_hole、mouse_bite、open_circuit、short、spur、spurious_copper六类缺陷,配有VOC、COCO、YOLO三种格式标签,可直接用于算法训练。同时附赠YOLO11一键训练脚本,支持GPU、CPU、Mac(M芯片)多平台运行,并提供博主训练结果日志作为参考。资源内还展示了数据集缩略图与labelimg标注截图,便于预先评估数据质量。目前已有974人学习浏览,适合需要快速获取工业缺陷检测数据并开展YOLO训练的开发者。

1. PCB板表面缺陷检测数据集:1000张图加三种标签,到底能解决什么问题

很多人第一反应是「1000张图能训练个啥」,但PCB表面缺陷检测跟自然图像里的目标检测完全是两回事。缺陷类型有限,常见的开路、短路、缺孔、毛刺、划痕加起来不超过十类,形态相对固定,而且每一处缺陷在图像上都有清晰的对比度边界。1000张标注图配合预训练权重和合理的数据增强,完全能跑出一个可以拿去验收的检测模型。这个资源做的事情就是:把图像、VOC/COCO/YOLO三种格式的标签、以及一个能在GPU/CPU/Mac三平台上跑通的YOLO11训练脚本打包好,让你不用把时间花在格式转换和环境折腾上,直接进入训练和调参环节。

2. 数据集与标签格式拆解:VOC/COCO/YOLO三种标注怎么组织、怎么互相转换

2.1 一个数据集同时给三种标注:目录结构和类别约定

拿到这类数据集,先别急着跑脚本,花十分钟把目录结构看明白,后面能省下大量排查时间。常见的组织方式是images/和labels/两个大目录,下面再按train/val拆开,同时保留一套完整的VOC风格目录。大致长这样:

pcb_defect_dataset/ ├── images/ │ ├── train/ │ │ ├── pcb_0001.jpg │ │ └── ... │ └── val/ │ ├── pcb_1001.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── pcb_0001.txt │ │ └── ... │ └── val/ │ └── ... ├── VOC/ │ ├── JPEGImages/ │ ├── Annotations/ │ └── ImageSets/ │ └── Main/ │ ├── train.txt │ └── val.txt ├── COCO/ │ ├── instances_train.json │ └── instances_val.json └── pcb.yaml

这里有个关键点:VOC/Annotations里的XML才是整个数据集的锚点,COCO的JSON和YOLO的TXT都从它转换而来。如果三份标签出现不一致,以VOC的XML为准去重新生成另外两份,而不是反过来。pcb.yaml是给ultralytics用的数据配置文件,里面写了路径、类别数和类别名,训练脚本直接读它。

类别约定上,PCB缺陷检测的常见类别包括missing_hole(缺孔)、mouse_bite(鼠咬)、open_circuit(开路)、short(短路)、spur(毛刺)、copper(铜渣)。类别ID从0开始编号,这个编号顺序必须和YOLO的TXT文件里每行第一个数字一一对应,错一位就是灾难。

2.2 标签格式差异是坑源:从VOC XML到COCO JSON再到YOLO TXT的对应关系

三种格式的本质是一样的,都是「哪张图、哪个类别、什么位置」,但表达方式完全不同,转换时最容易翻车的地方就在坐标系和类别ID的偏移上。

VOC的XML里,每个object用name存类别名,用bndbox存左上角和右下角的绝对像素坐标:

<annotation> <filename>pcb_0001.jpg</filename> <size> <width>640</width> <height>640</height> </size> <object> <name>open_circuit</name> <bndbox> <xmin>128</xmin> <ymin>96</ymin> <xmax>192</xmax> <ymax>160</ymax> </bndbox> </object> </annotation>

COCO的JSON则是一个大字典,包含images、annotations、categories三个数组。annotations里的一条记录用image_id关联图片,bbox存的是[x, y, width, height],注意不是[xmin, ymin, xmax, ymax],坐标是左上角加宽高,而且category_id从1开始计数。

YOLO的TXT最简洁,每行一个目标:class_id center_x center_y width height,其中坐标全部归一化到0到1之间,用像素坐标除以图片宽高。这个归一化很多人会忘,一忘就是所有框全部偏到图像边缘。

2.3 只拿到一种标签时怎么办:VOC转COCO/YOLO的可靠转换脚本

很多实际场景里,数据集只给了VOC格式,需要自己转出COCO和YOLO。下面这个脚本是我常用的做法,逻辑清晰且不容易出错。先建好目标目录结构,再读XML生成对应文件。转换的原则是:先从VOC的XML里读出类别名列表,按固定顺序排序后生成类别ID映射,再一次性生成COCO和YOLO两份标签。

import os import json import xml.etree.ElementTree as ET from glob import glob # 基础配置:VOC目录和输出目录 voc_root = "VOC" yolo_img_dir = "images/train" yolo_lbl_dir = "labels/train" coco_out = "COCO/instances_train.json" image_dir = "VOC/JPEGImages" # 先扫描所有XML,按字母序固定类别列表,避免ID漂移 xml_files = sorted(glob(os.path.join(voc_root, "Annotations", "*.xml"))) class_names = [] for xml_file in xml_files: root = ET.parse(xml_file).getroot() for obj in root.findall("object"): name = obj.find("name").text if name not in class_names: class_names.append(name) class_names.sort() class2id = {name: i for i, name in enumerate(class_names)} # YOLO标签 + COCO annotations 共用同一个目标遍历 coco_images, coco_annotations = [], [] ann_id = 1 for img_id, xml_file in enumerate(xml_files): root = ET.parse(xml_file).getroot() filename = root.find("filename").text width = int(root.find("size/width").text) height = int(root.find("size/height").text) coco_images.append({ "id": img_id, "file_name": filename, "width": width, "height": height, }) yolo_lines = [] for obj in root.findall("object"): name = obj.find("name").text xmin = float(obj.find("bndbox/xmin").text) ymin = float(obj.find("bndbox/ymin").text) xmax = float(obj.find("bndbox/xmax").text) ymax = float(obj.find("bndbox/ymax").text) # 防御无效标注:宽或高为0时直接跳过 if xmax <= xmin or ymax <= ymin: continue # YOLO 格式:归一化中心坐标和宽高 cx = (xmin + xmax) / 2 / width cy = (ymin + ymax) / 2 / height w = (xmax - xmin) / width h = (ymax - ymin) / height yolo_lines.append(f"{class2id[name]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}") # COCO 格式:左上角加宽高,category_id 从 1 开始 coco_annotations.append({ "id": ann_id, "image_id": img_id, "bbox": [xmin, ymin, xmax - xmin, ymax - ymin], "area": (xmax - xmin) * (ymax - ymin), "category_id": class2id[name] + 1, "iscrowd": 0, }) ann_id += 1 # 写出 YOLO TXT,文件名与图片名一一对应 yolo_path = os.path.join(yolo_lbl_dir, filename.replace(".jpg", ".txt")) with open(yolo_path, "w") as f: f.write("\n".join(yolo_lines)) # 写出 COCO JSON coco_data = { "images": coco_images, "annotations": coco_annotations, "categories": [{"id": i + 1, "name": n} for i, n in enumerate(class_names)], } with open(coco_out, "w") as f: json.dump(coco_data, f) print("categories:", class_names) print("converted", len(coalesced_images) if False else len(coco_images), "images")

这段代码的核心逻辑是先把类别名排序生成固定映射,这样即便XML里的类别顺序是乱的,YOLO的TXT和COCO的JSON也能保持相同的ID语义。代码里有一个容易忽略的防御点:宽高为0的标注直接跳过,这类脏数据在PCB数据集中经常出现,尤其是切片边缘的残缺目标。运行后在categories里核对一遍类别顺序,如果和你预期的不一致,说明数据里混入了未知类别,这时候要停下来排查而不是硬跑训练。

2.4 类别不平衡:1000张图里每类缺陷的真实分布

1000张图听起来不多,但PCB缺陷数据集的特殊性在于:每一张图里可能有0到好几个缺陷,而不同缺陷类别的出现频率差别非常大。比如短路和开路这类常见缺陷可能占了60%的标注框,而鼠咬和缺孔可能只有几十个样本。训练前我建议先数一下每类的框数量,只需一行命令:

# 统计YOLO格式标签里每个类别的框数 awk '{count[$1]++} END {for (c in count) print c, count[c]}' labels/train/*.txt | sort -n

看到分布之后再做决定:如果最少的类别不足30个框,不要指望模型自己能学好这一类,优先考虑对该类做针对性过采样,或者在第5章里提到的二次微调阶段单独补充难样本。这也是为什么要保留三种格式标签——后续如果用mmdetection做对比实验,COCO格式可以直接喂;用ultralytics跑训练,YOLO格式就是标准输入,不用再动。格式齐全不是噱头,是省掉你从零写转换器的后悔药。

3. YOLO11一键训练脚本:GPU/CPU/Mac三平台跑通的最小链路

3.1 为什么选YOLO11:ultralytics统一API与预训练权重的价值

训练脚本选了YOLO11而不是YOLOv5或YOLOv8,原因是ultralytics把三件事做得很顺:预训练权重自动下载、命令行和Python API统一、硬件后端自动探测。YOLO11在相同FLOPs下比v8有稳定的小幅精度提升,而训练和推理的接口几乎没有变化,这意味着一张显卡上跑通的命令,换CPU和Mac也只需要改device参数。

模型的档次选择上,yolo11n是最小的,适合CPU训练和快速验证;yolo11s和yolo11m适合有独显的机器。PCB缺陷属于小目标检测,模型容量不是首要瓶颈,数据质量和标注质量才是。所以第一次跑通流程,直接用yolo11n就够了,后续再根据验证集指标往上加容量。

3.2 一键训练脚本的分平台逻辑:环境检查、设备选择、batch自适应

一键脚本的核心不是把yolo train包装一下,而是让它在不同平台上做出不同的决定。我习惯写一个train_pcb.py,启动时先探测平台和计算设备,再决定batch大小和设备号。以下是可以在本地直接跑的最小版本:

# train_pcb.py import platform import torch from ultralytics import YOLO def detect_device_and_batch(image_size=640): system = platform.system() if torch.cuda.is_available(): # NVIDIA GPU 平台:用0号卡,batch按2G显存约8来粗估 device = 0 gpu_mem = torch.cuda.get_device_properties(0).total_memory / 1024**3 batch = max(4, int(gpu_mem / 2)) print(f"[平台] NVIDIA GPU 可用,显存 {gpu_mem:.1f}GB,batch={batch}") elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): # Apple Silicon Mac:MPS后端,batch保守,避免算子回退导致OOM device = "mps" batch = 8 print("[平台] Apple Silicon Mac 可用,使用 MPS 后端,batch=8") else: # CPU 兜底:最小模型 + 小batch,能跑通流程就行 device = "cpu" batch = 4 print("[平台] 未检测到GPU/MPS,使用CPU,batch=4,建议换yolo11n") return device, batch def main(): # 数据配置:改成你自己的pcb.yaml路径 data_yaml = "pcb.yaml" device, batch = detect_device_and_batch() # 第一次跑建议用yolo11n.pt,CPU上也能在合理时间内出结果 model = YOLO("yolo11n.pt") results = model.train( data=data_yaml, epochs=100, imgsz=640, batch=batch, device=device, workers=0 if device == "cpu" else 4, cache=False, # CPU/MPS上开cache容易爆内存,先关掉 patience=20, # 验证集20轮不涨就早停 project="runs/pcb", name="yolo11n", pretrained=True, ) print(f"训练完成,最优权重保存在 {results.save_dir}") if __name__ == "__main__": main()

这段脚本的关键逻辑是detect_device_and_batch:GPU上按显存大小估算batch,2GB显存大约塞得下8张640分辨率的图做训练;Mac上直接用MPS后端但batch只给8,因为MPS的显存管理没有CUDA成熟,大batch容易在某个不支持的算子处崩掉;CPU上batch给4且关闭多进程数据加载,这是为了不让数据加载把CPU打满导致训练进度卡死。workers=0在Windows和Mac上尤其重要,多进程数据加载在这两个平台上经常触发奇怪的中断。

如果你不想写Python脚本,也可以直接用命令行的方式,ultralytics的训练入口是等价的:

# Linux GPU yolo train model=yolo11n.pt data=pcb.yaml epochs=100 imgsz=640 batch=16 device=0 # macOS Apple Silicon yolo train model=yolo11n.pt data=pcb.yaml epochs=100 imgsz=640 batch=8 device=mps # 纯CPU yolo train model=yolo11n.pt data=pcb.yaml epochs=100 imgsz=480 batch=4 device=cpu

3.3 三个平台的真实差异:CUDA/MPS/CPU的行为与调参

这三个平台的差异不只是速度,行为模式也完全不同,调参策略跟着变。

CUDA平台上,amp=True(混合精度)默认开,显存利用率高,batch可以根据显存往上加。但要注意,batch加到24以上时,BN层的统计量会被稀释,部分缺陷类别的召回率反而下降。我一般GPU上就用16或24,不再往上追。

MPS平台看起来是「能用」,实际上要接受两个现实:第一,一部分PyTorch算子(比如某些归一化和索引操作)在MPS后端没有实现,会默默回退到CPU,导致训练速度远低于GPU预期;第二,cache=True在MPS上容易把统一内存吃满,因为Mac的统一内存既要给系统又要给显卡。所以脚本里MPS的batch只有8,cache强制为False。训练时如果看到终端跳MPS does not support xxx的警告,只要能继续跑就不用管,一旦报错中断,优先降batch和关cache。

CPU平台是最容易被低估的。很多人把YOLOv8时代的经验照搬过来,直接跑yolo11s加100轮,然后发现一晚上只跑了二十轮。CPU训练的核心约束是算力,不是内存。正确的做法是:用yolo11n、imgsz=480(CPU推理和训练的分辨率对速度影响极大)、epochs先设置50轮验证流程,确认能收敛后再跑全量。还有一个很少人提的点:CPU训练时把workers设为0,反而比设成4更快,因为多进程数据加载在CPU训练场景下会和训练线程抢核心。

4. 训练与推理避坑:五个真实翻车点与修复记录

4.1 现象:Loss正常下降,mAP却不到0.3,原因在类别ID没对齐

训练日志里loss从1.8慢慢降到0.3,看起来一切正常,但验证集mAP只有0.2。用训练好的权重跑几张图,发现检测框的位置是对的,但类别全部错位:短路被标成开路,开路被标成毛刺。这说明模型其实是学到了目标位置,但类别ID和真实类别名的映射是乱的。

原因几乎总是同一个:数据集的TXT标签和pcb.yaml里的names顺序不一致。比如TXT里的0代表open_circuit,但pcb.yaml里第0个名字写成了short。这种错位在别人打包好的数据集里经常出现,因为转换脚本可能经历了多轮手工修改。

解决办法是训练前先做一次机检:

# 抽取labels里出现过的所有类别ID cut -d' ' -f1 labels/train/*.txt | sort -n | uniq # 再对比pcb.yaml里的names顺序 grep names pcb.yaml -A 10

两边都确认后,如果确实不是从0开始连续排列的,就要重新生成标签而不是硬改yaml。另外留意一个细节:验证集mAP低但训练loss正常,除了ID错位,还有一种可能是验证集本身就存在脏标注,用第5章的热力图方法可以进一步区分。

4.2 现象:batch=16直接OOM,降低batch后BN统计量崩溃

显存8GB的卡,batch=16跑640分辨率,几轮之后直接CUDA out of memory。然后把batch降到4,能跑起来了,但loss开始震荡,前50轮完全不下行,甚至出现loss: nan。

这里其实是两个问题叠加。OOM是显存不够,好解决;但降到batch=4后BN层算出来的均值和方差极不稳定,YOLO11骨干网络里的C2f模块对BN统计量非常敏感,小batch下训练信号被噪声淹没。

解决思路不是在小batch上硬扛,而是降低单样本的内存占用:imgsz从640降到512,或者换yolo11n。如果模型和分辨率都不能动,可以试试把batch固定为8再开amp=True,比降到4好很多。这条经验同样适用于MPS平台,Mac上OOM的第一反应不是关cache,而是降imgsz。

4.3 现象:Mac上MPS训练报错或奇慢,问题出在torch版本与数据加载

MPS平台训练时,终端频繁出现RuntimeError: MPS backend out of memory,或者某个自定义的op在MPS上不支持直接中断。还有一种情况是训练能跑但速度只有GPU的十分之一,看CPU占用发现终端反复在torch和系统之间切换,像是卡住了。

原因分两类:一是torch版本太老,MPS后端在PyTorch 2.0之前只是实验性实现,很多算子缺失;二是数据加载用了多进程,Mac的统一内存模型下DataLoader子进程和训练进程争抢内存。

解决方案是先把环境固定在torch 2.1以上版本:

pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu

Mac上这句命令会安装包含MPS后端的CPU版torch。装完试着跑一步torch.backends.mps.is_available(),返回True再训练。训练脚本里保持workers=0、cache=False,这两个参数是Mac上的稳定前提。如果还想再快一点,把imgsz降到480,MPS的算子回退压力会小很多。

4.4 现象:CPU训练一晚上只跑几十轮,根因是模型尺寸和cache策略

CPU训练的慢有两种:一种是真慢,速度条每轮要十分钟以上;另一种是假慢,前几轮正常然后越跑越慢,最后几乎停滞。后者通常是cache=True把内存吃满,系统开始疯狂swap。

处理方式很直接:CPU上只跑yolo11n,imgsz用480或416,cache=False,同时把workers设为0。一个有用的技巧是调低验证频率:val=False或者每10轮验证一次,因为CPU上验证同样要跑一遍推理,验证太频繁会吃掉大量训练时间。我一般CPU上做验证跑epochs=50, val=False,等训练结束后单独用model.val()做一次性评估。

另外确认一下自己机器的物理核心数,然后用torch.set_num_threads(8)限制线程数,开太多线程反而会因为上下文切换把性能拖垮。CPU训练不是用来刷精度的,它的价值是让你在没显卡的环境里把数据流水线和代码逻辑验证一遍,真正出指标还是得落在GPU上。

4.5 现象:小缺陷被数据增强洗掉,检测结果虚高或虚低

PCB缺陷很多是40x40像素以下的小目标,YOLO默认的mosaic=1.0会把四张图拼在一起缩放,一张640的图里某个缺陷可能被缩到十几个像素,甚至直接切出画面。训练时模型看到了大量「变小的缺陷」和「消失的缺陷」,验证时对真实尺寸的小缺陷反而检测不到。这种现象在验证集上表现为precision高但recall低:模型只要检测到就很准,但大量小缺陷根本没被召回。

解决方法是针对小目标场景裁剪数据增强的强度。在model.train()参数里显式关掉mosaic,并调低翻转和缩放的范围:

model.train( data=data_yaml, epochs=100, imgsz=640, batch=16, device=device, mosaic=0.0, # 小目标数据不要拼图 scale=0.2, # 缩放幅度限制在20%以内 translate=0.1, # 平移不超过10% flipud=0.0, # PCB是刚性物体,上下翻转会让语义失真 fliplr=0.5, hsv_h=0.0, # 色相变化对PCB铜箔颜色无意义,关掉 hsv_s=0.2, hsv_v=0.2, )

这套参数是我在PCB和类似的工业缺陷检测上验证过的保守配置。尤其是mosaic=0.0,对小目标场景收益非常明显,代价是训练轮数要增加一点,因为可用的有效样本变少了。

5. 验证与进阶:用混淆矩阵、热力图和一次二次微调把模型拉到可用线

5.1 混淆矩阵解读:哪两类缺陷在互相打架

训练结束后,ultralytics会在runs/pcb/yolo11n/下生成confusion_matrix.png和confusion_matrix_normalized.png。两张图都要看:非归一化版本能看到绝对数量,归一化版本能看到比例。看的时候注意力放在对角线以外的深色块,特别是两个类别之间的互相混淆。比如短路和开路经常互相误判,因为两者在图像上都是「铜箔线条的异常断裂」,形态非常接近。如果混淆集中在某一个类别上,说明该类样本量太少,这就是二次微调要优先补的方向。

5.2 热力图验证:看模型到底在看哪块铜箔

混淆矩阵告诉你模型分不清哪两类,热力图告诉你模型把注意力放在哪里。用训练好的权重对验证集做一次带save_dir的预测,然后挑几张典型图片叠加注意力热力图:

from ultralytics import YOLO model = YOLO("runs/pcb/yolo11n/weights/best.pt") model.predict( source="images/val", save=True, save_txt=True, # 保存检测框坐标,方便对比标注 conf=0.25, imgsz=640, device=0, # 按平台换成 mps 或 cpu )

打开输出目录里带框的图片,重点看两类情况:一是模型对真实缺陷没有产生任何框(漏检),二是模型在正常的铜箔走线上产生大量低置信度框(误检)。如果漏检区域的铜箔背景和已检出的缺陷区域差异很小,说明模型是学到了「位置特征」而不是「纹理特征」,后续可以在二次微调里给这类难样本增加标注。

5.3 进阶策略:1000张图的增强上限与二次微调

第一次训练跑完后,不要急着换模型结构。先做一次「难样本收集」:把验证集里漏检和误检的图片挑出来,通常二三十张就够。如果确认这些图片的标注本身有问题(框偏了、类别标错了),修正后补进训练集,这是最直接有效的一轮迭代。然后加载best.pt继续训练,而不是重新从预训练权重开始:

model = YOLO("runs/pcb/yolo11n/weights/best.pt") model.train(data=data_yaml, epochs=30, imgsz=640, batch=16, device=0)

从best权重继续训练30轮,学习率会自动重置但不影响已经收敛的特征提取层。这一轮的目的不是继续压loss,而是让模型重新适应修正后的标注分布,特别是上一轮混淆严重的类别。我自己的习惯是,每次微调后都重新看混淆矩阵,直到对角线上的数值稳定在0.8以上,才考虑换yolo11s加大模型容量。这个验证习惯帮我避开过好几次「换大模型反而变差」的坑,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询