简介这是一份面向目标检测学习者的电路板元器件图像数据集适用于YOLO系列模型的训练与验证尤其适合小目标、密集目标检测场景的实战练习。数据按YOLOv5目录结构组织可直接投入训练无需额外转换。压缩包共约2000个文件包含1660个txt标签、339张jpg图像和1个py可视化脚本整体约63.56MB。数据集划分清晰训练集约1500张、验证集约80张、测试集约40张覆盖保险丝、散热片、IC、电感器、晶体管、电容器等34类元器件类别定义见classes文件。配套可视化脚本可随机读取一张图片并绘制边界框保存至当前目录无需修改即可运行便于快速检查标注质量。目前已有835人学习下载适合希望上手工业质检、PCB缺陷检测或小目标检测的研究者与开发者参考使用。1. 电路板元器件检测为什么你的 YOLO 模型总在 0402 电阻上翻车板子一拍模型一跑电容电阻全糊成一团——这是很多做电路板元器件目标检测的工程师遇到的第一个下马威。PCB 上的元器件有三个天然难点尺寸极小0402 封装在 1080p 图像里可能只有十几个像素、排列密集一排排电阻电容间距极小、类别外观高度相似电容和电阻在低分辨率下几乎一样。用 COCO 预训练的 YOLO 直接推理mAP 能低到让你怀疑人生。这个方向要落地核心不在模型结构多花哨而在于数据集的质量和划分方式。标题里提到的“划分好的数据集、类别 class 文件、数据可视化脚本”恰好是三个最容易被忽视但最决定成败的环节。这篇文章面向想用 YOLO 做电路板元器件检测的从业者从数据集结构讲到训练参数再到小目标和密集场景的调优技巧每一步都给可复现的操作。2. 电路板元器件数据集从目录结构到 class 文件的完整搭建2.1 为什么 PCB 数据集不能照搬 COCO 的组织方式COCO 数据集一张图里可能只有三五个目标标注框大、类别差异明显。PCB 元器件图像完全不同一张板子照片里可能有 200 到 500 个目标每个目标框面积占比不到千分之一。如果按 COCO 的目录习惯把所有图片堆在一个文件夹里训练时 dataloader 的 IO 会成为瓶颈而且你没法快速定位某个类别的样本分布。我一般会按下面的结构组织pcb_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ ├── classes.txt └── visualize.pyimages 和 labels 严格一一对应文件名相同、扩展名不同。train/val/test 的划分比例建议 7:2:1但如果你的板子型号少于 5 种建议按板子型号划分而不是随机划分——同一块板子的不同角度照片如果同时出现在训练集和验证集里验证指标会虚高这是血泪经验。classes.txt 每行一个类别名顺序就是 YOLO 训练时 class_id 的映射依据。PCB 场景常见的类别定义resistor capacitor ic diode led connector transistor inductor注意类别粒度如果你把 0402 和 0603 的电阻分成两个类模型很难学到差异反而拉低整体 mAP。常见做法是按功能分大类尺寸信息交给回归框去学。2.2 标注格式转换从 LabelImg 的 XML 到 YOLO 的 TXT大多数标注工具输出的是 PASCAL VOC 格式的 XMLYOLO 需要的是归一化的中心点坐标加宽高。转换脚本如下import xml.etree.ElementTree as ET import os def voc_to_yolo(xml_path, classes, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in classes: continue cls_id classes.index(cls_name) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) # 归一化中心点坐标和宽高 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) return lines逻辑说明YOLO 格式要求所有坐标归一化到 0 到 1 之间cx、cy 是框中心点相对于图像宽高的比例w、h 是框宽高相对于图像宽高的比例。参数上img_w 和 img_h 必须和标注时看到的图像尺寸一致如果标注后图片被缩放或裁剪过这里就会错位。转换完成后建议随机抽 20 张用可视化脚本叠加检查确认框和类别都对得上。2.3 数据可视化脚本训练前必须做的 sanity check可视化脚本的作用不只是“看看图”而是验证三件事标注框是否对齐、类别 ID 是否映射正确、小目标是否真的小到需要特殊处理。import cv2 import os import random def visualize_yolo(img_dir, label_dir, classes, num_samples10): img_files os.listdir(img_dir) samples random.sample(img_files, min(num_samples, len(img_files))) for img_file in samples: img_path os.path.join(img_dir, img_file) label_path os.path.join(label_dir, os.path.splitext(img_file)[0] .txt) img cv2.imread(img_path) h, w img.shape[:2] if not os.path.exists(label_path): continue with open(label_path) as f: for line in f: parts line.strip().split() cls_id int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) # 反归一化回像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 1) cv2.putText(img, classes[cls_id], (x1, y1 - 3), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 255, 0), 1) cv2.imwrite(fvis_{img_file}, img)逻辑说明脚本读取 YOLO 格式的 txt把归一化坐标反算回像素坐标在原图上画框并标注类别名。参数上num_samples 控制抽样数量建议至少看 10 张覆盖不同板子型号。如果发现框整体偏移大概率是标注时图像尺寸和实际训练图像尺寸不一致如果类别名张冠李戴检查 classes.txt 的顺序是否和标注时的顺序一致。3. 小目标与密集检测YOLO 训练参数怎么调才不白跑3.1 输入分辨率640 不够用的时候该往上推到多少YOLO 默认输入 640×640。对于 PCB 上的 0402 封装元器件在 1080p 原图上可能只有 12×8 像素缩放到 640 后只剩 7×5 像素特征图经过 32 倍下采样后基本消失。这就是为什么很多人用默认参数训练 PCB 数据集mAP 卡在 0.3 上不去。常见做法是把输入分辨率提到 1280 甚至 1536。代价是显存占用和推理时间成倍增加。以 YOLOv8 为例640 到 1280 的显存占用大约翻 3 倍推理时间翻 2.5 倍。如果你的显卡是 8GB 显存1280 分辨率下 batch size 只能开到 4 到 8。一个折中方案是切图推理把 1080p 原图切成 4 块 640×640 的子图分别推理再合并结果。这样小目标的相对像素面积大了 4 倍但要注意切图边界处的目标会被截断需要在切图时保留 10% 到 15% 的重叠区域。3.2 Anchor 和损失函数小目标检测的玄学与科学YOLOv5 和 YOLOv8 都支持自动 anchor 计算但自动计算的 anchor 是基于你的训练集分布来的。如果训练集里小目标占比超过 70%自动 anchor 会偏向小尺度这是好事。但如果你的数据集里混了大目标比如连接器、大电容自动 anchor 会被拉偏。我一般会手动检查 anchor 分布python utils/autoanchor.py --check-anchors --data pcb.yaml如果发现最小的 anchor 仍然大于 16×16 像素说明小目标匹配不到合适的 anchor。这时候有两个选择一是增加更小的 anchor 尺寸二是改用 anchor-free 的检测头。YOLOv8 默认就是 anchor-free 的所以如果你用的是 v8这个问题自动绕过了。损失函数方面小目标检测对 CIoU 的敏感度很高。CIoU 里的宽高比惩罚项对小框的梯度贡献不稳定容易导致小目标回归震荡。可以尝试换成 SIoU 或 EIoU在 PCB 数据集上通常有 1 到 3 个点的 mAP 提升。改法是在损失函数配置里替换# 在 loss.py 中替换 bbox_loss from utils.metrics import bbox_iou # 原 CIoU 计算替换为 SIoU iou bbox_iou(pred_bboxes, target_bboxes, SIoUTrue)参数说明SIoU 引入了角度惩罚让预测框更快对齐到目标框的方向对小目标的收敛更友好。但 SIoU 在训练初期不稳定建议前 10 个 epoch 用 CIoU 预热之后再切换。3.3 密集场景的 NMS 阈值调不好就是一片框叠框PCB 上元器件排列密集两个相邻电阻的中心点距离可能只有 20 像素。默认 NMS 的 IoU 阈值是 0.45意味着两个框重叠超过 45% 就会被抑制掉一个。在密集场景下这会导致大量漏检。我一般会把 NMS 的 IoU 阈值提到 0.6 到 0.7同时开启 class-agnostic NMS 还是 class-specific NMS 要看情况。如果不同类别的元器件挨得很近比如电阻和电容并排用 class-specific NMS 更安全如果同类元器件密集排列class-agnostic NMS 配合高 IoU 阈值效果更好。在 YOLOv8 的推理配置里from ultralytics import YOLO model YOLO(best.pt) results model.predict( sourcepcb_test.jpg, conf0.25, # 置信度阈值小目标建议降到 0.2 iou0.65, # NMS IoU 阈值密集场景建议 0.6-0.7 imgsz1280, # 输入分辨率 agnostic_nmsFalse # 按类别分别做 NMS )conf 阈值降到 0.2 会引入更多误检但小目标的召回率会明显提升。实际部署时可以在后处理阶段加一个基于面积的过滤如果框的面积小于某个像素阈值且置信度低于 0.4直接丢弃。4. 避坑指南PCB 元器件检测中五个让你白跑一天的坑4.1 验证集 mAP 很高但实际推理全是漏检现象训练日志里 mAP0.5 到了 0.85但拿新板子的照片推理漏检率超过 50%。原因验证集和训练集来自同一批板子、同一拍摄角度、同一光照条件。模型学到了板子的背景纹理而不是元器件本身。这是典型的过拟合到采集条件。解决划分数据集时按板子型号和拍摄条件分层抽样。如果只有一种板子至少按光照条件明场、暗场、侧光和角度正面、倾斜 15 度、倾斜 30 度划分。验证集里必须包含训练集没见过的拍摄条件。4.2 小目标标注框只有几个像素训练时 loss 直接变 NaN现象训练开始几个 step 后 loss 变成 NaN或者框的回归 loss 剧烈震荡。原因小目标的宽高归一化后可能只有 0.005 到 0.01计算 IoU 时分母极小梯度爆炸。另外如果标注框的宽高有 0 值标注时手抖画了一条线归一化后直接除零。解决在数据加载阶段加一个过滤把宽或高小于 2 像素的框直接丢弃。同时在 loss 计算前对宽高加一个极小值 eps1e-7 防止除零。如果 loss 仍然震荡把学习率降到 0.001 以下warmup epoch 加到 5。4.3 类别不平衡导致小类别几乎检不出来现象电阻和电容的 AP 都在 0.8 以上但 LED 和晶体管的 AP 不到 0.3。原因训练集里电阻电容的样本量是 LED 的 20 倍以上。YOLO 的损失函数对类别不平衡没有内置的加权机制。解决两个方向。一是数据层面对稀有类别做过采样或数据增强旋转、亮度扰动、局部裁剪。二是损失层面在分类损失里加类别权重权重和样本量成反比。YOLOv8 的分类损失是 BCE可以在计算时传入 pos_weight 参数。4.4 数据增强把元器件转没了现象训练时 mAP 正常上升但验证时发现模型对旋转后的板子检测效果极差。原因默认的 mosaic 和 mixup 增强对小目标不友好。mosaic 把四张图拼成一张每张图里的元器件被进一步缩小0402 电阻直接变成 3 到 4 个像素模型根本学不到。解决PCB 数据集建议关闭 mosaic 和 mixup只保留 HSV 扰动、轻微旋转±10 度和水平翻转。如果一定要用 mosaic把 mosaic 的概率从 1.0 降到 0.3 以下并且确保拼接后的子图分辨率不低于 640。4.5 推理时图像预处理和训练时不一致现象训练时 mAP 0.8部署到产线相机上推理效果断崖式下跌。原因训练时用的是 letterbox 填充到 640×640部署时直接 resize 到 640×640宽高比变了元器件的形状失真。或者训练时用的是 BGR 通道部署时用了 RGB。解决把训练时的预处理代码原封不动搬到推理端。letterbox 的填充值通常是 114也要一致。如果部署在 TensorRT 上注意 TensorRT 的预处理是在 GPU 上做的归一化系数1/255和通道顺序要和训练时对齐。5. 从训练到部署用验证集反推模型到底能不能上产线训练完一个 PCB 元器件检测模型mAP 只是参考真正决定能不能上产线的是三个指标最小可检元器件尺寸、密集区域的召回率、推理延迟。最小可检尺寸的验证方法从验证集里挑出所有标注框面积小于 20×20 像素的样本单独算一个 AP。如果这个子集的 AP 低于 0.5说明模型对小目标的检测能力不够需要提高输入分辨率或切图推理。密集区域召回率的验证方法把验证集里目标数量超过 100 个的图片单独拿出来算 recall 而不是 mAP。因为密集场景下 mAP 会被 NMS 的抑制效果掩盖recall 更能反映真实漏检情况。推理延迟的验证方法在目标硬件上跑 100 次推理取 P99 延迟而不是平均延迟。产线上偶尔一次 200ms 的延迟可能导致整条线卡顿。如果 P99 延迟超标优先考虑降低输入分辨率或换更小的模型变体比如从 YOLOv8m 换到 YOLOv8s。一个我常用的验证脚本框架import time import numpy as np from ultralytics import YOLO model YOLO(best.pt) latencies [] for img_path in val_images[:100]: t0 time.perf_counter() results model.predict(img_path, imgsz1280, conf0.25, iou0.65) latencies.append(time.perf_counter() - t0) latencies np.array(latencies) print(fP50: {np.percentile(latencies, 50)*1000:.1f}ms) print(fP99: {np.percentile(latencies, 99)*1000:.1f}ms)参数说明imgsz 和 conf、iou 必须和实际部署时一致否则延迟数据没有参考意义。P99 延迟如果超过产线节拍时间就需要考虑模型量化或 TensorRT 加速。最后说一个我自己的习惯每次训练完新模型我会从验证集里随机抽 50 张图打印出每张图的漏检数和误检数而不是只看总 mAP。因为 mAP 是全局平均一张密集板子的 20 个漏检可能被其他简单图片的高 AP 掩盖掉。只有逐图看漏检分布才能判断模型是真的学到了元器件特征还是只是拟合了背景。希望帮到你。本文还有配套的精品资源点击获取