【超详细】基于YOLOv8的PCB缺陷检测

主要内容如下:

1、数据集介绍
2、下载PCB数据集
3、不同格式数据集预处理(Json/xml),制作YOLO格式训练集
4、模型训练及可视化

运行环境:Python=3.8(要求>=3.8),torch1.12.0+cu113(要求>=1.8)
百度AI stduio下载链接https://aistudio.baidu.com/datasetdetail/272346

往期内容:

【超详细】跑通YOLOv8之深度学习环境配置1-Anaconda安装
【超详细】跑通YOLOv8之深度学习环境配置2-CUDA安装
【超详细】跑通YOLOv8之深度学习环境配置3-YOLOv8安装

1 数据集介绍

1.1 简介

印刷电路板(PCB)瑕疵数据集:是一个公共的合成PCB数据集,由北京大学发布,其中包含1386张图像以及6种缺陷(缺失孔,鼠标咬伤,开路,短路,杂散,伪铜),用于检测,分类和配准任务。本文我们选取了其中适用与检测任务的693张图像,随机选择593张图像作为训练集,100张图像作为验证集。

1.2 示例

在这里插入图片描述

2 下载数据集

官方链接https://robotics.pkusz.edu.cn/resources/dataset/
注意:百度网盘下载,速度很慢,不推荐!推荐去百度AI stduio数据集下载,速度快!
在这里插入图片描述
百度AI stduio下载链接https://aistudio.baidu.com/datasetdetail/272346
在这里插入图片描述

3 制作YOLO格式训练集

# 1.YOLO训练标签示例如下(转换目标):
# 类别id 归一化后框中心点x坐标, 归一化后框中心点y坐标, 归一化后框的宽度, 归一化后框的高度
0 0.292792 0.729031 0.367417 0.246281  

# 2.训练YOLOv8路径格式如下(图像与标签路径必须对应,注意新版本不需要train.txt和val.txt文件):
--datasets
	--images
		--train2017   # jpg/png
		--val2017
	--labels
		--train2017  # txt
		--val2017

3.1 Json格式数据集转换

3.1.1 下载后数据集布局

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
数据标签框分析

import json
from collections import defaultdict
import matplotlib.pyplot as plt

# 修改1
with open("E:\\datasets\\PCB\\PCB_DATASET\\Annotations\\train.json") as f:
    data = json.load(f)

imgs = {}
for img in data['images']:
    imgs[img['id']] = {
        'h': img['height'],
        'w': img['width'],
        'area': img['height'] * img['width'],
    }

hw_ratios = []
area_ratios = []
label_count = defaultdict(int)
for anno in data['annotations']:
    hw_ratios.append(anno['bbox'][3]/anno['bbox'][2])
    area_ratios.append(anno['area']/imgs[anno['image_id']]['area'])
    label_count[anno['category_id']] += 1

print(label_count, len(data['annotations']) / len(data['images']))

plt.hist(hw_ratios, bins=100, range=[0, 2])
plt.show()

plt.hist(area_ratios, bins=100, range=[0, 0.005])
plt.show()
'''
结果如下:
(defaultdict(int, {3: 399, 5: 416, 2: 435, 6: 447, 4: 412, 1: 418}),
 4.261382799325464)
1、从标签来看,总共6个类别,如果加上背景类,总共7个类别;
2、各类别之间的框数量相对较平均,不需要调整默认的损失函数。(如果类别之间相差较大,建议调整损失函数,如BalancedL1Loss);
3、平均每张图的框数量在4张左右,属于比较稀疏的检测;
4、真实框的宽高比,可以看到大部分集中在1.0左右,但也有部分在0.5-1之间,少部分在1.25-2.0之间;
5、真实框在原图的大小比例,可以看到大部分框只占到了原图的0.1%,甚至更小,因此基本都是很小的目标。
'''
3.1.2 转换代码
import json
import os

# 注意运行2次,train/val
# 修改1 json存储地址
json_path = "E:\\datasets\\PCB\\PCB_DATASET\\Annotations\\val.json"
# # 修改2,保存位置yolo存储地址
yolo_paths= "E:\\datasets\\PCB\\PCB_DATASET\\labels\\val2017/"   


with open(json_path) as f:
    data = json.load(f)

imgs = {}
for img in data['images']:
    imgs[img['id']] = {
        'h': img['height'],
        'w': img['width'],
        'file_name': img['file_name'],
    }


tmp = ''
for anno in data['annotations']:
    print(imgs[anno['image_id']]['file_name'])
    
    if imgs[anno['image_id']] != tmp:
        txt_path = os.path.join(yolo_paths, imgs[anno['image_id']]['file_name'].split('.')[0] + '.txt')
        txt_file = open(txt_path, 'w')
        # xywh --> xywh(归一化)
        bbox = [anno['bbox'][0] / imgs[anno['image_id']]['w'],
                anno['bbox'][1] / imgs[anno['image_id']]['h'],
                anno['bbox'][2] / imgs[anno['image_id']]['w'],
                    anno['bbox'][3] / imgs[anno['image_id']]['h']]
        
        cls_id = anno['category_id']
        # 保存
        txt_file.write(str(cls_id) + ' ' +" ".join([str(a) for a in bbox])+"\n")  # 生成格式0 cx,cy,w,h
        tmp = imgs[anno['image_id']]
    
    else:
        # xywh --> xywh(归一化)
        bbox = [anno['bbox'][0] / imgs[anno['image_id']]['w'],
                anno['bbox'][1] / imgs[anno['image_id']]['h'],
                anno['bbox'][2] / imgs[anno['image_id']]['w'],
                    anno['bbox'][3] / imgs[anno['image_id']]['h']]
        
        cls_id = anno['category_id']
        # 保存
        txt_file.write(str(cls_id) + ' ' +" ".join([str(a) for a in bbox])+"\n")  # 生成格式0 cx,cy,w,h    
3.1.3 生成训练集和验证集

注意:由于已有train2017和val2017的labels文件夹,直接按名字移动图像到对应文件夹即可。

import os
import shutil

Images_path = 'E:\\datasets\\PCB\\PCB_DATASET\\images'  # 源图路径

train_labels = 'E:\\datasets\\PCB\\PCB_DATASET\\labels\\train2017'  # train标签路径
val_labels = 'E:\\datasets\\PCB\\PCB_DATASET\\labels\\val2017'  # val标签路径

train_images = 'E:\\datasets\\PCB\\PCB_DATASET\\images\\train2017'  # 保存train图像路径
val_images = 'E:\\datasets\\PCB\\PCB_DATASET\\images\\val2017'  # 保存val图像路径


# 判断文件夹是否存在,不存在即创建
if not os.path.exists(train_images):
    os.mkdir(train_images)
if not os.path.exists(val_images):
    os.mkdir(val_images)


# 按照标签名移动对应图像
for label_name in os.listdir(train_labels):
    img_name = label_name[:-3] + 'jpg'  # txt2jpg
    shutil.move(os.path.join(Images_path, img_name), os.path.join(train_images, img_name))


for label_name in os.listdir(val_labels):
    img_name = label_name[:-3] + 'jpg'  # txt2jpg
    shutil.move(os.path.join(Images_path, img_name), os.path.join(val_images, img_name))

到此,YOLOv8训练数据集已经制作好,包括images(train2017与val2017)和labels(train2017与val2017)文件夹!

3.2 VOC格式数据集转换

3.2.1 下载后数据集布局

在这里插入图片描述
在这里插入图片描述

xml文件有用信息:对应图像文件名、对应图像大小、每个框信息(包括:类别、xyxy具体框位置信息)
在这里插入图片描述

3.2.2 转换代码
# 实现xml格式转yolov5格式
import xml.etree.ElementTree as ET
import os

# box [xmin,ymin,xmax,ymax]
def convert(size, box):
    x_center = (box[2] + box[0]) / 2.0
    y_center = (box[3] + box[1]) / 2.0
    # 归一化
    x = x_center / size[0]
    y = y_center / size[1]
    # 求宽高并归一化
    w = (box[2] - box[0]) / size[0]
    h = (box[3] - box[1]) / size[1]
    return (x, y, w, h)

# xml2yolo
def convert_annotation(xml_paths, yolo_paths, classes):
    xml_files = os.listdir(xml_paths)
    # 生成无序文件列表
    for file in xml_files:
        
        xml_file_path = os.path.join(xml_paths, file)
        yolo_txt_path = os.path.join(yolo_paths, file.split(".")[0]
                                     + ".txt")
        tree = ET.parse(xml_file_path)
        root = tree.getroot()
        size = root.find("size")
        # 获取xml的width和height的值
        w = int(size.find("width").text)
        h = int(size.find("height").text)
        # object标签可能会存在多个,所以要迭代
        with open(yolo_txt_path, 'w') as f:
            for obj in root.iter("object"):
                difficult = obj.find("difficult").text
                # 类别
                cls = obj.find("name").text
                if cls not in classes or difficult == 1:
                    continue
                # 转换成训练模式读取的标签
                cls_id = classes.index(cls)
                xml_box = obj.find("bndbox")
                box = (float(xml_box.find("xmin").text), float(xml_box.find("ymin").text),
                       float(xml_box.find("xmax").text), float(xml_box.find("ymax").text))
                boxex = convert((w, h), box)
                # yolo标准格式类别 x_center,y_center,width,height
                f.write(str(cls_id) + " " + " ".join([str(s) for s in boxex]) + '\n')
        print(f'xml_file :{file} --> txt Saved!')


if __name__ == "__main__":
    # PCB数据的类别
    classes_train = ['missing_hole', 'mouse_bite', 'open_circuit', 'short', 'spur', 'spurious_copper']   # 修改1,类别
    
    # xml存储地址
    xml_dir = "E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\Annotations/"  # 修改2,读取位置
    
    # yolo存储地址
    yolo_txt_dir = "E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\labels/"   # 修改3,保存位置
    
    # voc转yolo
    convert_annotation(xml_paths=xml_dir, yolo_paths=yolo_txt_dir,
                       classes=classes_train)

生成结果如下
在这里插入图片描述

3.2.3 生成训练集和验证集

注意:这边下载数据集没有分train2017和val2017文件夹,所以需自己按比例或按数据量进行划分!

import os
import shutil
import random

Images_path = 'E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\JPEGImages'  # 源图路径
Labels_path = 'E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\labels'  # 源标签路径

train_labels = 'E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\labels\\train2017'  # train标签路径
val_labels = 'E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\labels\\val2017'  # val标签路径

train_images = 'E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\images\\train2017'  # 保存train图像路径
val_images = 'E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\images\\val2017'  # 保存val图像路径

radio = 0.2  # 按照比例划分的验证集比例
nums = 100  # 按照数量划分的验证集数量
is_radio = False  # 如果为True,则按照比例进行划分,否则按照数量划分

# 判断文件夹是否存在,不存在即创建
if not os.path.exists(train_images):
    os.mkdir(train_images)
if not os.path.exists(val_images):
    os.mkdir(val_images)

if not os.path.exists(train_labels):
    os.mkdir(train_labels)
if not os.path.exists(val_labels):
    os.mkdir(val_labels)


Imgs = os.listdir(Images_path)
if is_radio:
    val_nums = int(len(Imgs) * radio)
else:
    val_nums = nums
val_Imgs = random.sample(Imgs, val_nums)

for val_name in val_Imgs:
    shutil.move(os.path.join(Images_path, val_name), os.path.join(val_images, val_name))

    val_name = val_name[:-3] + 'txt'  # jpg2txt
    shutil.move(os.path.join(Labels_path, val_name), os.path.join(val_labels, val_name))

if (len(Imgs) - len(val_Imgs)) > 0:
    for i in val_Imgs:
        if i in Imgs:
            Imgs.remove(i)
    train_Imgs = Imgs
    for train_name in train_Imgs:
        shutil.move(os.path.join(Images_path, train_name), os.path.join(train_images, train_name))

        train_name = train_name[:-3] + 'txt'  # jpg2txt
        shutil.move(os.path.join(Labels_path, train_name), os.path.join(train_labels, train_name))

运行即可,注意创建多级文件夹报错时,手动创建!

4 模型训练及可视化

4.1 创建数据集yaml文件

注意:路径一定填对,类别与id一定要对应(下面用的是上面xml格式生成的数据集训练)!!!
创建ultralytics\cfg\datasets\PCB.yaml文件,内容如下:

path: E:\\datasets\\PCB\\VOCdevkit\\VOC2007 # dataset root dir
train: images/train2017 # train images (relative to 'path') 4 images
val: images/val2017 # val images (relative to 'path') 4 images

# Classes for DOTA 1.0
names:
  0: missing_hole
  1: mouse_bite
  2: open_circuit
  3: short
  4: spur
  5: spurious_copper

4.2 创建一个训练脚本

在主目录下创建一个train.py,内容如下:

from ultralytics import YOLO

if __name__ == '__main__':
    # Load a model
    # model = YOLO("yolov8n.yaml")  # build a new model from scratch
    model = YOLO("yolov8n.pt")  # load a pretrained model (recommended for training)

    # Use the model
    model.train(data="PCB.yaml", imgsz=640, batch=16, workers=8, cache=True, epochs=100)  # train the model
    metrics = model.val()  # evaluate model performance on the validation set
    # results = model("ultralytics\\assets\\bus.jpg")  # predict on an image
    path = model.export(format="onnx", opset=13)  # export the model to ONNX format

问题1:若爆显存,降低batch和workers大小!

训练结果如下
在这里插入图片描述
在这里插入图片描述

4.2 创建一个预测脚本

在主目录下创建一个detect.py,内容如下:

from ultralytics import YOLO

if __name__ == '__main__':
    # Load a model
    model = YOLO("runs\\detect\\train\\weights\\best.pt")  # load model
    model.predict(source="E:\\datasets\\PCB\\VOCdevkit\\VOC2007\\images\\val2017", save=True, save_conf=True, save_txt=True, name='output')

预测结果
在这里插入图片描述

  • 23
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
基于yolov5的PCB缺陷检测是一种利用深度学习技术进行自动化视觉检测的方法。该方法引入了CVPR 2023 BiFormer:Vision Transformer with Bi-Level Routing Attention,以提升检测精度。PCB数据集中包含了六种常见的缺陷类型,分别是"missing_hole"、"mouse_bite"、"open_circuit"、"short"、"spur"和"spurious_copper"。这些缺陷属于小目标缺陷检测范畴。PCB缺陷检测在电子产业中非常重要,因为产品的外观缺陷直接关系到企业的发展。利用深度学习技术,可以对PCB图像进行分析,提高自动化视觉检测的准确度和图像判读能力,并对缺陷进行分类。智能系统可以根据不同产品的不同缺陷标准进行灵活应对。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [基于yolov5的PCB缺陷检测](https://blog.csdn.net/u012505617/article/details/130844232)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [基于yolov5的PCB缺陷检测,引入CVPR 2023 BiFormer:Vision Transformer with Bi-Level Routing Attention...](https://blog.csdn.net/m0_63774211/article/details/129715988)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值