尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

PCB元件检测数据集实战:VOC/COCO/YOLO标签转换与YOLOv8训练指南

发布时间:2026/9/27 1:06:03

资讯中心
01
ARTICLE

PCB元件检测数据集实战:VOC/COCO/YOLO标签转换与YOLOv8训练指南

PCB元件检测数据集实战:VOC/COCO/YOLO标签转换与YOLOv8训练指南
简介面向目标检测入门与进阶用户的PCB电路板元件数据集采用真实场景高质量图片构建场景覆盖多种板面布局与元件形态使用LabelImg标注且框体质量较高可服务于PCB缺陷检测、元件识别与高校课程实训。压缩包共2000个文件约125.03MB其中1000个xml文件对应VOC格式990个txt文件适配YOLO系列训练同时提供coco格式标注文件及配套yaml配置文件三种标签分目录存放无需额外转换即可对接YOLOv5、YOLOv8等常用检测框架。附赠Linux与Windows双平台的YOLO环境搭建与训练教程html文档以及3个Python脚本可实现训练集、验证集、测试集划分或生成ImageSets下的txt索引便于按项目需求灵活组织与迭代数据。目前已有1400余人学习下载适合课程作业、毕业设计或PCB元件检测项目起步参考可显著省去标注整理与格式转换的重复劳动。1. 一千张PCB元件图为什么很多人卡在第一步而不是训练一份YOLO目标检测PCB电路板元件数据集含1000张图片还把VOC、COCO、YOLO三种格式的标签都准备好了听起来是把数据丢进训练脚本就能跑。但真正做过PCB元件检测的人都知道第一步往往卡在标签格式统一上VOC的XML、COCO的JSON、YOLO的TXT三套坐标体系和类别索引规则完全不一样直接混用会导致模型连损失函数都收敛不了。这篇文章按这套数据集的常见结构把标签转换、数据集划分、YOLOv8训练和反复出现的翻车点拆开讲清楚。适合手里有一批PCB图片、想按VOC/COCO/YOLO标准整理数据或者准备跑YOLOv8训练自己的电路板元件检测模型的人。2. 从VOC到COCO再到YOLO三套标签体系怎么对齐不踩雷拿到这类数据集第一件事不是急着训练而是把三套标签格式的差异摸清。很多新手犯的错是以为VOC和COCO只是文件后缀不同实际上它们的坐标基准、类别索引、文件组织方式完全不同。这一章先讲清差异再给一份可复用的转换脚本最后说三个我自己踩过的坑。2.1 三种格式到底差在哪XML、JSON、TXT各自为政VOC格式沿用了Pascal VOC比赛的标准一个图片对应一个XML文件放在Annotations目录下。XML里用objectname记录类别名用bndbox记录目标框坐标是xmin, ymin, xmax, ymax这种左上右下绝对像素值。它的好处是能看懂坏处是训练时没人直接用——大多数框架都要求把类别名映射成数字ID。COCO格式把所有标注塞进一个JSON文件内部按images、annotations、categories三个数组组织。关键点是category_id从1开始计数目标框是[x, y, width, height]同样使用绝对像素值。很多人在这一步翻车就是因为把COCO的类别ID直接当YOLO的类别序号用。YOLO格式是一张图对应一个TXT文件每一行表示一个目标格式是class_id cx cy w h其中cx, cy是中心点坐标w, h是宽高全部除以图片宽高做了归一化取值在0到1之间。这里class_id从0开始计数。也就是说同一个“电阻”类别在COCO里可能是category_id1在YOLO里就是class_id0。维度VOC XMLCOCO JSONYOLO TXT坐标基准绝对像素绝对像素归一化到0~1框的表示xmin, ymin, xmax, ymaxx, y, width, heightcx, cy, width, height类别表示字符串名称从1开始的数字ID从0开始的数字序号文件组织每图一个XML全部标注在一个JSON每图一个TXT是否直接训练不直接部分框架可用YOLO系列直接读这三套格式本质上是同一批标注的不同投影转换的核心工作就是把“坐标表示”和“类别索引”换算清楚。换算错了训练出的模型mAP会一直趴在0附近看起来像是模型问题其实是数据问题。2.2 转换脚本从XML一步拿到COCO和YOLO标签常见做法是写一个Python脚本先解析VOC的XML再分别输出COCO的JSON文件和YOLO的TXT文件。以下脚本可以直接放到数据集根目录运行只需要按实际情况改CLASS_NAMES列表。import xml.etree.ElementTree as ET import json import os from glob import glob # 类别清单顺序一旦确定就不要更改 CLASS_NAMES [resistor, capacitor, diode, transistor, inductor, connector] def parse_voc(xml_path): 解析单个VOC XML文件返回文件名、图片尺寸和目标框列表 tree ET.parse(xml_path) root tree.getroot() filename root.find(filename).text size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) objects [] for obj in root.findall(object): name obj.find(name).text bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) objects.append({name: name, bbox: [xmin, ymin, xmax, ymax]}) return filename, img_w, img_h, objects def voc_to_coco(xml_dir, json_path, img_dir): 把整个VOC标注目录转成COCO格式的JSON文件 images, annotations, categories [], [], [] cat_id_map {name: i 1 for i, name in enumerate(CLASS_NAMES)} categories [{id: i 1, name: name} for i, name in enumerate(CLASS_NAMES)] ann_id 1 for img_id, xml_path in enumerate(sorted(glob(os.path.join(xml_dir, *.xml)))): filename, w, h, objs parse_voc(xml_path) images.append({ id: img_id 1, file_name: filename, width: w, height: h }) for obj in objs: xmin, ymin, xmax, ymax obj[bbox] bw xmax - xmin bh ymax - ymin annotations.append({ id: ann_id, image_id: img_id 1, category_id: cat_id_map[obj[name]], bbox: [xmin, ymin, bw, bh], area: bw * bh, iscrowd: 0 }) ann_id 1 with open(json_path, w) as f: json.dump({images: images, annotations: annotations, categories: categories}, f) def voc_to_yolo(xml_dir, yolo_dir, class_names): 把每个VOC XML转成对应的YOLO TXT标签 os.makedirs(yolo_dir, exist_okTrue) for xml_path in glob(os.path.join(xml_dir, *.xml)): filename, w, h, objs parse_voc(xml_path) txt_path os.path.join(yolo_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) lines [] for obj in objs: cls_id class_names.index(obj[name]) xmin, ymin, xmax, ymax obj[bbox] cx (xmin xmax) / 2.0 / w cy (ymin ymax) / 2.0 / h bw (xmax - xmin) / w bh (ymax - ymin) / h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines)) # 按需调用 voc_to_coco(Annotations, annotations/instances_train.json, JPEGImages) voc_to_yolo(Annotations, labels, CLASS_NAMES)这段代码里parse_voc负责从XML里把文件名、宽高、目标框全部读出来这样后续转两种格式时不用重复解析。voc_to_coco里的cat_id_map把类别名映射成从1开始的ID这是COCO官方评估工具pycocotools的硬性要求ID从0开始会导致评估时报错或者mAP算出来是0。voc_to_yolo里有一个容易忽略的细节TXT文件名用的是XML文件的basename而不是XML里filename标签的内容。这样写成TXT才能和图片文件名严格对应因为后面划分数据集、训练读图时都是按文件名配对找标签的。2.3 转换脚本里最容易抄错的三个参数第一个坑是类别列表的顺序。CLASS_NAMES这个列表的顺序就是YOLO里class_id的隐式定义一旦训练中途发现某个类别序号对不上全部TXT都要重新生成。更稳妥的做法是单独维护一份classes.txt训练时也用这份文件生成names配置保证训练和验证用的是同一套顺序。第二个坑是宽高方向和整除。VOC的xmin, xmax对应水平方向ymin, ymax对应垂直方向转成YOLO时cx用xmin xmax除以2cy用ymin ymax除以2width用xmax - xmin除以图片宽height用ymax - ymin除以图片高。方向写反了坐标不会报错但mAP会低得离谱。第三个坑是COCO的area字段。虽然YOLO训练时用不到area但COCO的评估脚本会依赖它计算小目标、中目标、大目标的AP。area要用width * height算出来不能随手填0。如果这个数据集以后要和其他COCO格式的数据合并或者要用pycocotools做标准评估area缺失会直接导致评估失败。我一般会在转换完随机抽5张图做一次可视化把框画回原图确认位置没错再进入下一步划分。3. 划分脚本怎么分才不出幺蛾子分层抽样与划分后校验数据集划分看起来是最没技术含量的一步很多人直接random.shuffle一把梭。对1000张图这种规模随机划分很可能把某个类别的所有样本都分进训练集验证集里这个类别一个都看不到训练结果自然没法看。这一章讲为什么要分层、脚本怎么写、划分完怎么校验。3.1 为什么随手shuffle不值得信小数据集的类别分布陷阱PCB元件数据集的类别分布通常不是均匀的。以常见板卡为例电阻和电容可能各占几百个目标而变压器、晶振这种大元件可能只有几十个目标。如果按图片随机划分那些样本量少的类别被分到验证集的概率无法保证甚至可能整类消失。更隐蔽的问题是“单图多类别”带来的相关性。一张PCB图上往往同时有电容、电阻、连接器如果直接用图片ID做随机划分训练集和验证集可能包含来自同一块板子的高度相似图片验证指标会虚高。分层抽样做不到完全消除这种相关性但至少能保证每个类别在三个集合里都有样本。做法是把每张图的“类别存在向量”作为分层标签比如某张图同时含电阻和电容它的向量就是[1, 1, 0, 0, 0, 0]。StratifiedShuffleSplit按这个向量做分层每层内部再随机抽这样既保证了类别覆盖又保留了随机性。3.2 分层划分脚本一次拆出train、val、test三个集合下面这段脚本按照“类别存在向量”做两层划分第一层把全部数据拆成80%的训练集和20%的临时集第二层把临时集对半拆成验证集和测试集最终得到800张训练、100张验证、100张测试。比例可以根据自己的数据量调整。import os import numpy as np from glob import glob from sklearn.model_selection import StratifiedShuffleSplit IMG_DIR images LABEL_DIR labels NUM_CLASSES 6 X np.array(sorted(glob(os.path.join(IMG_DIR, *.jpg)))) def label_vector(txt_path): 读YOLO格式TXT统计这张图包含哪些类别 vec np.zeros(NUM_CLASSES) if not os.path.exists(txt_path): return vec with open(txt_path) as f: for line in f: parts line.strip().split() if parts: vec[int(parts[0])] 1 return vec y np.array([label_vector(p.replace(IMG_DIR, LABEL_DIR).replace(.jpg, .txt)) for p in X]) # 第一层80%训练 / 20%临时 sss1 StratifiedShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, temp_idx next(sss1.split(X, y)) # 第二层临时集对半拆成验证和测试 temp_X, temp_y X[temp_idx], y[temp_idx] sss2 StratifiedShuffleSplit(n_splits1, test_size0.5, random_state42) val_idx, test_idx next(sss2.split(temp_X, temp_y)) val_imgs temp_X[val_idx] test_imgs temp_X[test_idx] train_imgs X[train_idx] def write_list(imgs, list_path): 把图片路径写入txt文件训练时可直接读取 with open(list_path, w) as f: for p in imgs: f.write(p \n) os.makedirs(splits, exist_okTrue) write_list(train_imgs, splits/train.txt) write_list(val_imgs, splits/val.txt) write_list(test_imgs, splits/test.txt)这段脚本有两个关键参数。test_size0.2是第一层划分的临时集比例random_state42决定了随机种子固定成同一个值才能保证每次运行得到完全相同的划分结果。很多人训练复现不了就是因为这次用42下次用7。label_vector函数读取YOLO格式的TXT每行第一个数字是类别ID这个函数只关心“有没有”不关心“有几个”因为分层抽样按图片粒度做。如果某个类别的目标总数特别少比如整个数据集只有30个变压器分层按“类别存在向量”依然可能遗漏这时候可以把稀有类别单独抽出来先保证测试集里包含它们再把剩余数据按比例划分。3.3 划分完先过一遍校验图片、标签、路径一个都不能少划分完不要急着训练先跑一遍完整性校验。常见的情况有三种训练集里某张图没有对应的TXT标签TXT里写了越界的坐标值图片路径里的目录名和实际目录对不上。这些错误在训练阶段会以“image not found”或者loss异常的形式暴露出来但定位起来费时不如在划分后直接拦截。def check_split(img_list, label_list): missing_img [p for p in img_list if not os.path.exists(p)] missing_lbl [p for p in label_list if not os.path.exists(p)] bad_lines [] for lbl in label_list: for line in open(lbl): parts line.strip().split() if len(parts) ! 5: bad_lines.append((lbl, 字段数不对: line.strip())) continue for v in parts[1:]: if not (0.0 float(v) 1.0): bad_lines.append((lbl, 坐标越界: line.strip())) break return missing_img, missing_lbl, bad_lines train_imgs [line.strip() for line in open(splits/train.txt)] train_lbls [p.replace(IMG_DIR, LABEL_DIR).replace(.jpg, .txt) for p in train_imgs] missing_img, missing_lbl, bad_lines check_split(train_imgs, train_lbls)校验项通过标准失败时的常见原因图片文件存在每个路径都能找到jpgimages目录路径写错、文件名大小写不一致标签文件存在每个jpg都有同名txt转换脚本漏跑、目录名不一致坐标范围cx, cy, w, h都在0到1之间VOC转YOLO时未除以图片宽高字段数量每行恰好5个字段空行、多余空格、类别ID超范围这段校验脚本跑完如果有问题先回到转换环节修不要带着脏数据进训练。我在实际项目里吃过这个亏验证集只有100张图里面有2张缺标签训练时loss曲线看起来完全正常但验证mAP忽高忽低最后花了半天才发现是划分阶段丢了一个标签文件。4. 用YOLOv8把PCB元件检测跑通数据YAML、训练命令与产物解读标签转好了数据也划分完了接下来就是把YOLOv8训练跑起来。这一章不讲算法原理只讲怎么把数据集喂给YOLOv8、超参怎么定、训练完看哪些文件。训练这事有一定玄学成分但大部分问题都能从产物里找到线索。4.1 目录结构怎么摆images和labels必须严格同名YOLO系列对数据集目录结构要求很明确图片和对应的TXT标签必须同名只是后缀不同。常见做法是建立images/train、images/val、images/test和labels/train、labels/val、labels/test六个目录。训练时YOLO会根据图片路径自动去labels目录找同名TXT。用一段bash脚本把划分好的图片和标签移动到对应目录注意图片和标签要同步移动mkdir -p images/train images/val images/test mkdir -p labels/train labels/val labels/test while read p; do mv $p images/train/ mv ${p%.jpg}.txt labels/train/ done splits/train.txt while read p; do mv $p images/val/ mv ${p%.jpg}.txt labels/val/ done splits/val.txt while read p; do mv $p images/test/ mv ${p%.jpg}.txt labels/test/ done splits/test.txt移动完成后YOLO要求的数据集配置文件pcb.yaml如下path: ./pcb_dataset train: images/train val: images/val test: images/test nc: 6 names: 0: resistor 1: capacitor 2: diode 3: transistor 4: inductor 5: connectorpath是数据集根目录train、val、test是相对path的目录路径。nc是类别数量names里的类别顺序必须和第2章转换脚本里的CLASS_NAMES做成同一份。我见过有人训练时用一份names验证时又换了顺序结果mAP一直在0.5左右上不去就是这个原因。4.2 训练命令与关键超参首次跑通用这些值训练命令用ultralytics的YOLOv8yolo detect train \ datapcb.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ patience30 \ workers4 \ seed42第一次把这个数据集跑通我建议直接用yolov8n.pt预训练模型这个权重文件在第一次运行时自动下载不需要自己单独找。nano版本模型参数量小1000张图的规模下不容易过拟合显存占用也低。如果显存有余量再试yolov8s.pt做对比。几个超参的选择依据epochs120是PCB元件检测这类任务比较稳的轮数数据集不复杂时一般到60到80轮就收敛了120轮是给mosaic增强和低学习率阶段留余量。imgsz640是速度和精度的平衡点如果板卡图里的贴片电阻特别小可以改成960但显存占用会明显上涨。batch16在8GB显存上比较稳妥16GB显存可以上到32。patience30的意思是验证集指标连续30轮不提升就提前停止这个值不要调太小否则模型还没收敛就被叫停。4.3 训练完先看哪几个文件不要在TensorBoard里泡太久训练结束后产物都在runs/detect/train目录下。先看weights/best.pt和weights/last.pt这两个权重文件best.pt是按验证集指标选出的最优权重后续预测和部署都用它last.pt是最后一轮的权重一般不直接使用。再看results.csv它记录了每一轮的loss、精度和mAP曲线用Excel或pandas打开都能看。重点观察验证集loss如果训练loss一直在降、验证loss在某个点开始反弹就是过拟合信号此时应该减少epochs或加强数据增强。confusion_matrix.png反映每个类别的误检情况对角线越亮越好某个非对角线格子特别亮说明两个类别持续混淆。最后看val_batch_pred.jpg这类带预测框的样例图直观感受预测框是否贴合元件边界。我习惯把训练命令里project和name参数固定下来比如projectruns/pcb、nameexp1这样多次实验的结果不会混在一起。5. 训练PCB检测模型的避坑清单五个反复出现的翻车现场这一章全部来自实际跑PCB数据集时反复出现的坑每条都按“现象 → 原因 → 解决”写建议收藏起来对照排查。5.1 训练启动就报“image not found”中断了现象训练命令跑几秒后报错提示某张图片找不到训练直接中断。原因最常见的有两种。一种是VOC XML里filename字段和实际图片文件名不一致比如XML里写的是IMG_0001.JPG磁盘上实际是img_0001.jpg另一种是划分脚本生成图片路径时用了绝对路径移动文件后路径失效。解决在转换脚本里增加一步文件名清洗统一改成小写jpg后缀并以清洗后的文件名为准重新生成TXT标签。划分后先执行第3章的校验脚本确认所有图片路径都能打开再进训练。5.2 训练能跑loss也下降mAP一直趴在0附近现象训练过程没有任何报错loss曲线正常下降但每个类别的mAP都是0或者徘徊在0.01附近。原因绝大多数情况是类别索引错位。COCO格式要求category_id从1开始YOLO要求class_id从0开始。转换脚本里如果直接沿用了COCO的IDYOLO的TXT里类别ID就会整体偏移模型学到的根本不是真实的类别标签。解决检查YOLO的TXT文件看第一列数字是否在0到nc-1范围内。随机挑一个类别多的文件把每个类别的第一列数字打印出来人工核对一遍。更根治的办法是转换脚本统一维护一份类别名到ID的映射表COCO和YOLO都从这份表生成。5.3 贴片电阻、小电容这类小目标几乎检测不到现象训练完成后大元件如连接器、电解电容检测效果不错贴片电阻、贴片电容这种小目标漏检严重mAP一塌糊涂。原因PCB元件检测里很多目标在640分辨率下只有十几个像素YOLO默认的特征图下采样倍数对小目标不够友好目标信息在多次下采样后丢失了。这是小目标检测的经典问题不是模型越改越差。解决第一步把imgsz从640提高到960小目标在输入里占的像素比例变大效果通常立竿见影。第二步检查数据增强确保mosaic是开启的它能把不同图的小目标拼到同一张图里增加小目标样本数量。第三步如果还不够就把测试图先切成四块分别推理再拼接这种tiling方案对小目标密集场景是通用做法。5.4 类别严重不平衡损失函数被某几个类主导现象训练时loss下降得很快但查看每个类别的PR曲线发现样本少的类别AP普遍偏低样本多的类别AP很高整体mAP不够理想。原因PCB数据集里电阻、电容样本可能各占几千个目标变压器类可能只有几十个目标。YOLOv8默认的损失函数对每个目标平等对待多数类在梯度更新中占主导少样本类很难学好。解决少样本类做针对性增强包括随机旋转、亮度扰动、复制粘贴到其他板上。复制粘贴要注意别把元件贴到焊盘以外的位置。另一个办法是收集更多该类别样本1000张图规模下代价最小的还是从数据集切分阶段就把稀有类别的验证集样本单独保证至少让评价指标能真实反映该类别的水平。5.5 验证集loss在降mAP却不涨甚至波动现象训练loss和验证loss都正常下降但每隔几轮mAP出现大幅波动整体没有稳步上升。原因常见的原因是训练和验证使用了不一致的类别顺序。比如训练时names用了一套顺序验证时加载的数据集配置里names是另一套顺序模型输出的类别ID和验证集标注的类别ID对不上。另一个原因是验证集太小100张验证图在随机增强影响下指标波动大。解决训练和评估永远只用同一个pcb.yaml文件不要手动改names。验证集如果只有100张图波动大是正常的可以把imgsz固定关闭验证阶段的增强减少随机因素。更彻底的办法是扩大验证集到200张代价是训练集减少100张这个取舍要看总体数据量。6. 用val模式做一次毕业验收混淆矩阵、漏检回看与置信度阈值最后一次训练结束后不要急着部署先用验证集做一次完整的“毕业验收”。训练流程里验证集已经在发挥作用但一次性指标不够要把预测结果拉出来人工看一遍。yolo detect val \ datapcb.yaml \ modelruns/detect/train/weights/best.pt \ imgsz640 \ conf0.25 \ save_jsonTrue跑完后重点看两个东西。一是confusion_matrix_normalized.png看哪些类别互相混淆。PCB场景里贴片电阻和贴片电容外观接近混淆常见如果真的混得厉害要考虑是否标注边界本身就不清晰。二是验证集里每张图的预测框确认漏检和误检的具体形态。预测图回看用predict模式yolo predict \ modelruns/detect/train/weights/best.pt \ sourceimages/test \ conf0.30 \ save_txtTrue \ save_confTrue \ projectruns/pcb_verify把每个测试图的预测结果画到原图上重点看三类未检出的元件是否被周围密集元件遮挡、小目标是否被过滤、以及是否存在把一个元件框成两个的重复检测。阈值是最后一道调优手段PCB元件检测我一般会把conf调到0.25到0.45之间跑三遍对比。工业场景我倾向于保守宁可多几个低置信度候选框给人工复核也别漏掉元件的真实位置。我的习惯是每次训练完固定存一份best.pt加一份当时的pcb.yaml权重和配置放同一个目录这样几个月后再回看还能精确还原当时的实验条件。这个习惯帮我解决过不少“明明当时指标很好现在复现不出来”的问题。数据格式对齐和划分校验是这类数据集项目里最不值得省时间的环节前期花半天把这两步做扎实后面训练省下的时间远不止半天。希望这篇笔记能帮你少踩几个坑。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。