简介:一份面向目标检测与缺陷识别场景的PCD表面元器件缺陷检测数据集,适用于YOLO系列网络训练,也适合PCB质检、电子制造自动化等方向的研究者与工程师。数据集包含608张标注图像及对应609个txt标签文件,另附class类别文件和可视化脚本(共1219个文件,压缩包57.41MB),已按训练集、验证集划分并经数据增广处理,可直接用于模型训练与验证。缺陷类别共7种,涵盖Miss、mask defect、defect等类型,覆盖电路板表面常见元器件异常。配套的show脚本可将标注框绘制到图像上,便于直观检查标注质量与模型预测效果。目前已有383人学习,适合需要快速获得带标签数据集、开展YOLO系列缺陷检测实验的开发者。
1. 从“能用”到“好用”:PCD表面元器件缺陷检测数据集为什么值得拆开看
PCB板上的表面元器件(PCD)缺陷检测,和常见的PCBA整板缺陷检测完全是两回事。整板检测关注的是焊点少锡、桥连、元件偏移,而PCD级检测要把视角拉近到单个电容、电阻、排阻的表面:miss(漏贴)、mask defect(掩膜缺陷)、本体缺损、划痕等。这类缺陷在整板标注里往往只占几个像素,模型很难学到区分性特征。这个数据集专门拿超过600张近景图像做标注,7个缺陷类别,全部转成YOLO格式,并已经划分好训练集、验证集,还做了数据增广。对真正要落地表面元器件质检的团队来说,600张图听起来少,但配合已有的YOLOv8预训练权重,足以在几天内训练出一个能跑在产线工控机上的检测模型。下面把目录结构、标签格式、可视化脚本和训练参数一条条拆开,方便你直接复现。
2. 目录结构、class文件与YOLO标签格式:一套可直接送入训练管线的最小闭环
先讲清楚这个数据集交付了什么。除了超过600张JPG/PNG图像,每张图都对应一个同名txt标签文件,标签采用YOLO标准格式:类别ID加归一化的中心点坐标和宽高。数据集中附带class文件,列出全部7个类别,顺序决定标签文件里的ID编号,训练时classes列表必须和这个文件保持一致。另外数据集还做了data增广,意味着train目录里的图片可能是原始图、旋转、亮度变化、马赛克增强等副本,但标签文件会同步变换过,可以直接训练,不需要再做对齐。
2.1 典型的YOLO数据集目录结构
拿到资源后,解压完一般是下面这种组织方式:
pcd_defect_dataset/ ├── train/ │ ├── images/ │ │ ├── 6_PNG_jpg.rf.e9eafa26558f3ce2defce65e40498238.jpg │ │ └── ... │ └── labels/ │ ├── 6_PNG_jpg.rf.e9eafa26558f3ce2defce65e40498238.txt │ └── ... ├── valid/ │ ├── images/ │ └── labels/ ├── classes.txt └── show_annotations.py(部分打包版本会把可视化脚本放在根目录)需要说明的是,不同发布者会把数据yaml配置放在不同位置,常见做法是在根目录放一个data.yaml,内容指向train和valid的图片目录。如果收到的手动包里没有data.yaml,自己新建一个即可,路径用绝对路径或者相对你执行命令的目录来写。
2.2 标签文件与class文件的对应关系
每个txt文件里,每行代表一个目标框,格式为:
0 0.5123 0.4788 0.1234 0.0867从左到右依次是:类别ID、x中心(归一化到0-1)、y中心、宽度、高度。注意这里中心点和宽高都是相对图片宽高的比例值,不是像素坐标。类别ID是从0开始的,不是1。所以classes.txt里的第一个类对应ID 0,第二个对应ID 1,以此类推。
拿这个数据集的类别来说,7个缺陷类型可能包括Miss、mask defect、defect等,实际名称以classes.txt为准。如果names列表写错顺序,训练时loss照常下降,但推理时标签显示会错位,所以先打开class文件检查一下。下面是一个读取yaml配置的示例代码,训练前把names顺序和txt里的ID对齐:
from pathlib import Path def load_class_names(class_txt_path): names = [] with open(class_txt_path, 'r') as f: for line in f: line = line.strip() if line: names.append(line) return names classes = load_class_names('classes.txt') print(f'类别总数: {len(classes)}') for idx, name in enumerate(classes): print(f'ID {idx}: {name}')这段代码会把classes.txt里的每一行读取为一个类别名,并输出对应的ID。训练脚本需要同时引用类别数和类别名列表,确保和标签文件使用相同顺序。如果你的数据里有新的缺陷类型需要加入,必须修改class文件并在所有对应标签上新增或修改ID,否则模型输出的置信度会错配。
2.3 检查标签边界是否越界
从Roboflow导出的数据通常不会越界,但如果是手工整理的YOLO标签,常常出现宽度或高度大于1、中心点负数这类问题。下面这个脚本可以快速统计越界框:
import glob label_files = glob.glob('train/labels/*.txt') bad_count = 0 for label_file in label_files: with open(label_file, 'r') as f: for line_num, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: print(f'{label_file}:{line_num} 非5段格式: {line}') bad_count += 1 continue _, x, y, w, h = map(float, parts) if x < 0 or y < 0 or w > 1 or h > 1 or x > 1 or y > 1: print(f'{label_file}:{line_num} 越界: {line}') bad_count += 1 print(f'异常标签数量: {bad_count}')参数说明:这里假设标签文件都在train/labels目录下,glob匹配所有txt。x、y、w、h都是0到1的归一化值,如果w或h大于1说明标注框比整张图还大,需要回看原图。对于越界的框,推荐在训练前用脚本裁剪到[0,1]区间,而不是直接删除,因为删除会让那张图少一个真实目标,削弱对缺陷的召回能力。
| 异常类型 | 判断条件 | 处理方式 |
|---|---|---|
| 中心点越界 | x或y小于0或大于1 | 裁剪到0-1区间 |
| 宽高越界 | w或h大于1 | 检查标注框是否跨图 |
| 类别ID越界 | 类别ID大于等于nc | 检查classes顺序 |
2.4 数据增广后的训练注意事项
这个数据集已经做了data增广,很多旋转、平移后的图片特征与原图有较大差异。YOLO训练时如果继续使用默认的在线Mosaic增强,相当于在增广过的图片上再做一次组合,有几率把缺陷纹理变成完全不真实的伪影。常见做法是关闭或降低Mosaic强度,例如设置mosaic=0.0,只保留hsv扰动和水平翻转。同时,增广后的标签文件里坐标可能已经出界,使用上一节的越界检查脚本跑一遍,确保所有框都在有效范围内。
3. show脚本可视化边界框:先确认标签可靠,再谈模型训练
模型训练前的数据验证环节,很多人会直接跳过,直接开训。但如果标签有错,后面所有调试都会回头查数据。这个数据集自带show脚本,作用是把box绘制回图像上,便于确认每个目标的类别和位置是否符合直觉。下面给一个类似的脚本实现,基于OpenCV,可以批量化生成带标注框的预览图。
3.1 单张图片的标注绘制函数
import cv2 from pathlib import Path def draw_yolo_boxes(image_path, label_path, class_names): img = cv2.imread(str(image_path)) if img is None: print(f'读取失败: {image_path}') return None height, width = img.shape[:2] with open(label_path, 'r') as f: lines = f.readlines() for line in lines: parts = line.strip().split() if len(parts) != 5: continue cls_id = int(parts[0]) x_center = float(parts[1]) * width y_center = float(parts[2]) * height box_w = float(parts[3]) * width box_h = float(parts[4]) * height x1 = int(x_center - box_w / 2) y1 = int(y_center - box_h / 2) x2 = int(x_center + box_w / 2) y2 = int(y_center + box_h / 2) x1 = max(0, x1) y1 = max(0, y1) x2 = min(width - 1, x2) y2 = min(height - 1, y2) color = (0, 255, 0) if cls_id == 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) label = f'{cls_id}:{class_names[cls_id]}' if cls_id < len(class_names) else str(cls_id) cv2.putText(img, label, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) return img这里的坐标转换逻辑是:YOLO标签里存储的是相对比例,训练时网络也按比例回归,但绘制到图像上时必须乘以图像的宽和高,才能得到像素坐标。画框前做了越界裁剪,避免矩形画出边界时OpenCV报错或显示异常。color变量在示例里简单区分了第一类与其他类,实际使用时建议根据类别ID生成固定颜色,确保同一类在所有图像里颜色一致。
3.2 批量生成预览图
from pathlib import Path def batch_visualize(data_root, output_root): data_root = Path(data_root) output_root = Path(output_root) output_root.mkdir(parents=True, exist_ok=True) class_names = load_class_names(data_root / 'classes.txt') for label_file in (data_root / 'train/labels').glob('*.txt'): image_file = data_root / 'train/images' / (label_file.stem + '.jpg') if not image_file.exists(): image_file = data_root / 'train/images' / (label_file.stem + '.png') if not image_file.exists(): print(f'图片不存在: {label_file.stem}') continue img = draw_yolo_boxes(image_file, label_file, class_names) if img is not None: output_path = output_root / (label_file.stem + '.jpg') cv2.imwrite(str(output_path), img) print(f'可视化结果保存在 {output_root}')注意,这里图片后缀同时判断了jpg和png,因为原始数据里两种格式都存在,文件名有_jpg和_png后缀,但实际文件可能是jpg扩展名,需要现场确认。另外,show脚本往往还会把检测框的面积和类别名打印在终端,方便快速统计类别分布。
提示:如果可视化出来的标注框整体偏向某一边,或者框没有贴合器件轮廓,大概率是标签归一化时用的width/height顺序反了。建议从图中心到像素坐标的换算逻辑里重新检查一下。
| 可视化现象 | 数据问题 | 处理建议 |
|---|---|---|
| 框整体偏移 | 归一化坐标以错误尺寸计算 | 重新读取图片宽高,修正坐标 |
| 多个框重叠在同一区域 | 标注重复 | 保留置信度高的标注,删除重复框 |
| 框内明显没有缺陷 | 类别误标 | 回看原图,修正类别ID |
| 框太小且集中在一角 | 目标被截断 | 考虑裁剪或调整原图预处理 |
3.3 可视化结果如何指导训练策略
批量生成预览图后,建议按类别归类:单独创建一个目录,把每个类别出现的图像副本放进去,用图片浏览器快速浏览。这一步能帮助确定两件事:第一,类别是否平衡,某些缺陷如果只有几十个框,训练时要考虑类别权重;第二,目标尺度分布,如果大部分框的宽高小于图片尺寸的10%,说明这是典型的小目标检测场景,训练时imgsz需要适当调大,或者使用多尺度训练。
4. YOLOv8训练PCD缺陷检测:数据划分、超参与收敛排查
数据集已经准备好,接下来进入训练环节。以YOLOv8为例,因为官方对自定义数据集支持最省事,只需要一个yaml和一个train命令。
4.1 创建数据集配置文件
# pcd_yolov8.yaml path: ./pcd_defect_dataset train: train/images val: valid/images nc: 7 names: 0: Miss 1: mask_defect 2: defect 3: other_class_3 4: other_class_4 5: other_class_5 6: other_class_6这里的names列表里的顺序必须和class文件一致。如果你不知道具体类名,可以先运行前面的load_class_names脚本,把输出复制到这里。path字段可以是绝对路径,也可以是相对当前终端的路径,建议用绝对路径,避免在不同目录下执行命令时路径解析错误。
4.2 训练命令与参数选择
yolo detect train data=pcd_yolov8.yaml model=yolov8s.pt \ epochs=120 imgsz=640 batch=16 device=0 \ patience=15 lr0=0.01 augment=True参数说明:
- data指向yaml配置文件的路径。
- model使用yolov8s.pt预训练权重,相比nano版本对缺陷的细节特征捕捉更好,又不会像large那样明显拖慢训练速度。
- imgsz设为640,输入分辨率。若PCD缺陷框面积占比很小,可以提高到768或1024,但训练和推理耗时也会上涨。
- batch根据显卡显存调整,16G显存可以用16,8G建议降到8。
- patience=15表示验证集指标连续15轮不提升就早停,避免无效空转。
- augment=True启用内置的Mosaic、MixUp等增强。这个数据集已经做过离线增强,所以在线增强强度可以适当降低,比如设置hsv_h=0.01,防止过拟合到增广后的伪纹理。
训练启动后,重点关注两个输出:一个是loss曲线,另一个是验证阶段的mAP50和mAP50-95。如果mAP50在20轮后仍然低于0.6,通常是数据侧问题,回到第2章检查标签格式和类别分布。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| imgsz | 640或768 | 小目标缺陷建议768 |
| epochs | 120 | 配合patience早停 |
| batch | 8-16 | 显存小时降到8 |
| lr0 | 0.01 | 发散时降到0.001 |
| augment | True | 过拟合时降低增强强度 |
4.3 常见坑:训练发散与类别误检
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| loss变成nan | 学习率过大或标签中有非法字符 | 调低lr0到0.001,检查txt编码 |
| mAP50-95很低 | 类别ID与names错位 | 打印验证集预测结果,核对类名 |
| 验证集AP/AR为0 | 标签文件为空 | 确认valid/labels下是否有对应txt |
| 训练速度很慢 | 在线增强计算量大 | 降低augment相关参数或关闭Mosaic |
表格里的四个现象基本覆盖了初次训练PCD数据集时最容易遇到的问题。特别提醒,这个数据集里的图片文件名带_jpg、_png后缀,Roboflow格式下图片和标签都保留了这个后缀,但两个文件的basename是一致的。如果遇到某些图片没有对应标签,不要直接删图,而是检查是不是标签文件后缀写错了,例如.txt被命名成.png.txt。
4.4 验证与导出模型
训练结束后,用best.pt做一次验证:
yolo detect val data=pcd_yolov8.yaml model=runs/detect/train/weights/best.pt验证过程会生成每类别的precision、recall和mAP。对表面元器件缺陷检测,recall往往比precision更重要,因为漏检的缺陷板如果流到下一道工序,返工成本远高于误检产生的复检。如果recall低,优先检查miss类和mask defect类的样本量,并考虑使用第5章的技巧。
4.5 训练结果如何筛选
当best.pt验证完毕后,打开验证生成的混淆矩阵和预测样本图,观察哪些类别容易被相互混淆。PCD缺陷中,mask defect与defect经常看起来相似,如果mAP差异较大,可以把这两个类别合并,或为低AP类别增加单独的训练图像。一般我会先跑一个baseline,记录mAP50,再修改类别权重重新训练,对比两者的混淆矩阵差异。不要只看总mAP,要逐类看AP50,否则少数类的召回问题会被多数类掩盖。
5. 类别不平衡与小目标漏检:用类别权重和滑窗推理把漏检压下来
PCD表面元器件缺陷数据集的7个类别里,miss(漏贴)和mask defect这类异常的出现频率通常远低于普通defect。直接用默认损失函数训练,模型会倾向把少数类预测为多数类,表现为recall偏低。这时可以采用两种不改变网络结构的做法。
5.1 类别权重调整
在YOLOv8中,可以通过设置分类损失权重来缓解不平衡。常见做法是在训练命令中加入与类别名对应的损失权重列表,把少数类的权重调到1.5~2.0,多数类保持1.0。注意权重不要超过2.5,否则训练初期loss震荡严重,模型反而学不到稳定特征。每次调整权重后,重新训练前先重置学习率,并观察前20轮的mAP曲线,如果轻微抖动但整体上升,属于正常现象。
5.2 滑窗推理(sliding window)用于小目标
对于在640分辨率下缺陷框小于二十像素的情况,另一种有效手段是把原图分成重叠的滑窗,对每个窗口独立推理,再将结果映射回原图坐标。常见做法是窗口大小320×320,步长160,重叠率50%。推理时把每个窗口的检测框坐标加上窗口左上角的偏移量,最后用NMS合并重叠框。
import cv2 from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') def sliding_window_detect(image_path, win_size=320, stride=160, conf=0.25): img = cv2.imread(image_path) h, w = img.shape[:2] all_boxes = [] for y in range(0, h - win_size + 1, stride): for x in range(0, w - win_size + 1, stride): crop = img[y:y + win_size, x:x + win_size] results = model.predict(crop, imgsz=win_size, conf=conf, verbose=False) for box in results[0].boxes: x1, y1, x2, y2 = map(int, box.xyxy[0]) all_boxes.append((x + x1, y + y1, x + x2, y + y2, float(box.conf), int(box.cls))) return all_boxes这段代码的sliding_window_detect函数会遍历整张图,对每个窗口独立跑模型。注意窗口尺寸需要和训练时的imgsz保持一致,否则特征尺度会漂移。stride越小重叠越多,检测率越高但耗时翻倍。实际项目中,先在全图上跑一次,统计漏检区域的位置,再针对漏检区域做局部滑窗,这样能把推理耗时控制在可接受范围。把滑窗步长设为0.5倍窗口尺寸,通常能在召回率不下降的前提下把误检率控制住。
本文还有配套的精品资源,点击获取