基于YOLOv5的PCB板缺陷检测系统的设计与实现

news2025/2/7 22:01:36

image-20240622204651718

简介

随着电子设备的广泛应用,PCB(印刷电路板)作为其核心部件,其质量和可靠性至关重要。然而,PCB生产过程中常常会出现各种缺陷,如鼠咬伤、开路、短路、杂散、伪铜等。这些缺陷可能导致设备故障,甚至引发严重的安全问题。为了提高PCB检测的效率和准确性,我们基于YOLOv5目标检测模型,开发了一套PCB电路板故障检测系统。本报告将详细介绍该系统的实际与实现,包括系统架构、功能实现、使用说明、检测示例、数据集获取与介绍、YOLOv5模型介绍及其训练过程。

系统架构

系统组成

  1. 用户界面(GUI):基于PyQt5实现,提供图像、视频及实时摄像头检测功能。
  2. 检测模型:基于YOLOv5的目标检测模型,用于识别PCB上的各种缺陷。
  3. 视频处理模块:处理视频流,实现逐帧检测。
  4. 数据管理模块:负责数据的加载、保存及标注。

工作流程

  1. 用户加载图像/视频或启动摄像头
  2. 系统调用YOLOv5模型进行缺陷检测
  3. 检测结果显示在GUI上,包括缺陷位置、种类及数量
  4. 用户可以保存检测结果

功能实现

import sys
import cv2
import torch
import numpy as np
import time
import os
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QFileDialog, QLabel, QVBoxLayout, QWidget, \
    QHBoxLayout, QSlider, QMessageBox
from PyQt5.QtGui import QImage, QPixmap, QFont
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, Qt, QMutex, QWaitCondition


class YoloV5Detector:
    def __init__(self):
        self.model = torch.hub.load('ultralytics/yolov5', 'custom', path="./yolov5s_pcb.pt")

    def detect_image(self, image_path):
        image = cv2.imread(image_path)
        results = self.model(image)
        return results

    def detect_frame(self, frame):
        results = self.model(frame)
        return results


class VideoProcessor(QThread):
    frame_processed = pyqtSignal(np.ndarray)
    fps_signal = pyqtSignal(float)
    progress_signal = pyqtSignal(str)
    object_count_signal = pyqtSignal(int)
    category_count_signal = pyqtSignal(dict)

    def __init__(self, video_path=None, use_camera=False, output_dir='output'):
        super().__init__()
        self.video_path = video_path
        self.use_camera = use_camera
        self.running = False
        self.paused = False
        self.mutex = QMutex()
        self.condition = QWaitCondition()
        self.detector = YoloV5Detector()
        self.out = None
        self.output_dir = output_dir

    def run(self):
        self.running = True
        if self.use_camera:
            cap = cv2.VideoCapture(0)
        else:
            cap = cv2.VideoCapture(self.video_path)

        if not cap.isOpened():
            return

        # Create unique filename based on current time
        current_time = time.strftime("%Y%m%d_%H%M%S")
        os.makedirs(self.output_dir, exist_ok=True)
        output_file = os.path.join(self.output_dir, f'output_{current_time}.mp4')

        fourcc = cv2.VideoWriter_fourcc(*'mp4v')
        fps = cap.get(cv2.CAP_PROP_FPS)
        if fps == 0:  # If fps cannot be obtained, set a default value
            fps = 30
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        self.out = cv2.VideoWriter(output_file, fourcc, fps, (width, height))

        total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        duration = total_frames / fps

        last_time = time.time()
        frame_count = 0

        while self.running:
            self.mutex.lock()
            if self.paused:
                self.condition.wait(self.mutex)
            self.mutex.unlock()

            ret, frame = cap.read()
            if not ret:
                break
            #frame = cv2.resize(frame, (640, 640))
            results = self.detector.detect_frame(frame)
            annotated_frame = results.render()[0]
            self.frame_processed.emit(annotated_frame)

            object_count = len(results.xyxy[0])  # Number of detected objects
            categories = results.names  # Object category names
            category_counts = {category: (results.pred[0][:, -1] == i).sum().item() for i, category in categories.items()}
            non_zero_category_counts = {cat: count for cat, count in category_counts.items() if count > 0}
            self.object_count_signal.emit(object_count)
            self.category_count_signal.emit(non_zero_category_counts)

            if self.out:
                self.out.write(annotated_frame)

            frame_count += 1
            if frame_count % 10 == 0:
                current_time = frame_count / fps
                progress_text = f'{self.format_time(current_time)} / {self.format_time(duration)}'
                self.progress_signal.emit(progress_text)

            current_time = time.time()
            show_fps = 1.0 / (current_time - last_time)
            last_time = current_time
            self.fps_signal.emit(show_fps)

        cap.release()
        if self.out:
            self.out.release()

    def format_time(self, seconds):
        mins, secs = divmod(seconds, 60)
        return f'{int(mins):02}:{int(secs):02}'

    def pause(self):
        self.paused = True

    def resume(self):
        self.paused = False
        self.condition.wakeAll()

    def stop(self):
        self.running = False
        self.resume()
        self.wait()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('YOLOv5 PCB Defect Detection')
        self.setGeometry(100, 100, 1000, 700)

        self.layout = QVBoxLayout()

        self.label = QLabel(self)
        self.layout.addWidget(self.label)

        self.fps_label = QLabel(self)
        self.fps_label.setFont(QFont('Arial', 14))
        self.layout.addWidget(self.fps_label)

        self.object_count_label = QLabel(self)
        self.object_count_label.setFont(QFont('Arial', 14))
        self.layout.addWidget(self.object_count_label)

        self.category_count_label = QLabel(self)
        self.category_count_label.setFont(QFont('Arial', 14))
        self.layout.addWidget(self.category_count_label)

        self.progress_label = QLabel(self)
        self.progress_label.setFont(QFont('Arial', 14))
        self.layout.addWidget(self.progress_label)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(0, 100)
        self.slider.setValue(0)
        self.slider.sliderPressed.connect(self.pause_video)
        self.slider.sliderReleased.connect(self.resume_video)
        self.layout.addWidget(self.slider)

        button_layout = QHBoxLayout()

        self.btn_image = QPushButton('Open Image', self)
        self.btn_image.setFont(QFont('Arial', 14))
        self.btn_image.clicked.connect(self.open_image)
        button_layout.addWidget(self.btn_image)

        self.btn_video = QPushButton('Open Video', self)
        self.btn_video.setFont(QFont('Arial', 14))
        self.btn_video.clicked.connect(self.open_video)
        button_layout.addWidget(self.btn_video)

        self.btn_camera = QPushButton('Open Camera', self)
        self.btn_camera.setFont(QFont('Arial', 14))
        self.btn_camera.clicked.connect(self.open_camera)
        button_layout.addWidget(self.btn_camera)

        self.btn_save = QPushButton('Save Result', self)
        self.btn_save.setFont(QFont('Arial', 14))
        self.btn_save.clicked.connect(self.save_result)
        button_layout.addWidget(self.btn_save)

        self.layout.addLayout(button_layout)

        self.container = QWidget()
        self.container.setLayout(self.layout)
        self.setCentralWidget(self.container)

        self.cap = None
        self.current_frame = None
        self.video_processor = None

    def open_image(self):
        self.stop_video_processing()
        file_name, _ = QFileDialog.getOpenFileName(self, 'Open Image', '', 'Images (*.png *.xpm *.jpg *.jpeg *.bmp)')
        if file_name:
            self.show_loading_message(True)
            results = YoloV5Detector().detect_image(file_name)
            self.current_frame = results.render()[0]
            self.display_image(self.current_frame)
            self.fps_label.setText('')
            self.progress_label.setText('')
            self.object_count_label.setText('')
            self.category_count_label.setText('')
            self.show_loading_message(False)

    def open_video(self):
        self.stop_video_processing()
        file_name, _ = QFileDialog.getOpenFileName(self, 'Open Video', '', 'Videos (*.mp4 *.avi *.mov *.mkv)')
        if file_name:
            self.show_loading_message(True)
            self.start_video_processing(file_name)

    def open_camera(self):
        self.stop_video_processing()
        self.show_loading_message(True)
        self.start_video_processing(use_camera=True)

    def show_loading_message(self, show):
        self.btn_image.setEnabled(not show)
        self.btn_video.setEnabled(not show)
        self.btn_camera.setEnabled(not show)
        self.btn_save.setEnabled(not show)
        if show:
            QApplication.setOverrideCursor(Qt.WaitCursor)
        else:
            QApplication.restoreOverrideCursor()

    def start_video_processing(self, video_path=None, use_camera=False):
        self.video_processor = VideoProcessor(video_path=video_path, use_camera=use_camera)
        self.video_processor.frame_processed.connect(self.display_image)
        self.video_processor.fps_signal.connect(self.update_fps)
        self.video_processor.progress_signal.connect(self.update_progress)
        self.video_processor.object_count_signal.connect(self.update_object_count)
        self.video_processor.category_count_signal.connect(self.update_category_count)
        self.video_processor.start()
        self.show_loading_message(False)

    def stop_video_processing(self):
        if self.video_processor is not None:
            self.video_processor.stop()
            self.video_processor = None

    def pause_video(self):
        if self.video_processor is not None:
            self.video_processor.pause()

    def resume_video(self):
        if self.video_processor is not None:
            self.video_processor.resume()

    def update_fps(self, fps):
        self.fps_label.setText(f'FPS: {fps:.2f}')

    def update_progress(self, progress):
        self.progress_label.setText(progress)

    def update_object_count(self, count):
        self.object_count_label.setText(f'Object Count: {count}')

    def update_category_count(self, category_counts):
        category_count_text = 'Category Counts: ' + ', '.join([f'{cat}: {count}' for cat, count in category_counts.items()])
        self.category_count_label.setText(category_count_text)

    def display_image(self, frame):
        self.current_frame = frame
        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        height, width, channel = frame.shape
        bytes_per_line = 3 * width
        convert_to_Qt_format = QImage(rgb_frame.data, width, height, bytes_per_line, QImage.Format_RGB888)
        p = convert_to_Qt_format.scaled(1000, 600, aspectRatioMode=1)
        self.label.setPixmap(QPixmap.fromImage(p))

    def save_result(self):
        if self.current_frame is not None:
            file_name, _ = QFileDialog.getSaveFileName(self, 'Save Image', '', 'Images (*.png *.jpg *.jpeg *.bmp)')
            if file_name:
                cv2.imwrite(file_name, self.current_frame)
        elif self.video_processor:
            self.video_processor.stop()
            self.video_processor.wait()
            self.video_processor = None


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

使用说明

  1. 加载图像/视频
    • 点击“Open Image”按钮加载待检测的PCB图像。
    • 点击“Open Video”按钮加载待检测的视频文件。
    • 点击“Open Camera”按钮启动摄像头进行实时检测。
  2. 显示结果
    • 系统会在界面上显示检测到的缺陷位置和种类。
    • 检测过程中,界面会显示实时帧率(FPS)、进度、缺陷数量及类别统计信息。
  3. 保存结果
    • 点击“Save Result”按钮可以保存当前检测结果。

检测示例

系统在测试过程中,能够准确检测并标注出PCB上的各种缺陷,如鼠咬伤、开路、短路、杂散和伪铜等,检测效果如下图所示:

image-20240622204848995

image-20240622204907168

2

image-20240622204955958

数据集获取与介绍

数据集介绍

为了训练和评估模型,我们使用了一个包含不同PCB缺陷的公开数据集。数据集包含以下内容:

  1. 图像数量:数据集中包含1386张标注好的PCB缺陷图像。
  2. 缺陷类型:鼠咬伤、开路、短路、杂散、伪铜等6种缺陷。
  3. 标注信息:每张图像都包含详细的标注信息,包括缺陷的位置和类别。

数据集获取

数据集可以通过以下链接获取:

  • 公开数据集下载链接: https://robotics.pkusz.edu.cn/resources/dataset/

下载后,将数据集解压到指定目录,并根据YOLOv5格式进行组织。

YOLOv5介绍

YOLOv5简介

YOLOv5(You Only Look Once Version 5)是一个先进的目标检测模型,由Ultralytics团队开发。与其前身相比,YOLOv5在速度和精度上有了显著提升,能够在实时应用中表现出色。

官方模型

YOLOv5提供了多种预训练模型,分别适用于不同的检测任务和需求。我们选择了轻量级的YOLOv5s模型,并在此基础上进行了微调,以适应PCB缺陷检测的特定需求。

训练过程

数据准备

  1. 数据标注:使用LabelImg等工具对数据集进行标注,生成YOLOv5格式的标注文件。
  2. 数据划分:将数据集划分为训练集、验证集和测试集,比例为7:2:1。

模型训练

  1. 环境配置:在训练前,配置好所需的深度学习环境,包括安装PyTorch、YOLOv5等依赖项。

  2. 训练配置:设置训练参数,如学习率、批量大小、训练轮数等。

  3. 模型训练

    :使用以下命令启动训练:

    python train.py --img 640 --batch 16 --epochs 50 --data data.yaml --weights yolov5s.pt --cache
    
  4. 模型评估:在验证集上评估模型性能,记录精度、召回率、mAP等指标。

模型优化

  1. 超参数调整:根据评估结果调整学习率、批量大小、数据增强策略等超参数。
  2. 迁移学习:使用预训练模型进行迁移学习,提高模型在特定任务上的表现。

模型部署

  1. 模型保存:将训练好的模型保存为权重文件(如yolov5s_pcb.pt)。
  2. 集成到系统:将模型集成到PCB缺陷检测系统中,实现实时检测功能。

结论

本报告详细介绍了基于YOLOv5的PCB电路板故障检测系统,包括系统架构、功能实现、使用说明、检测示例、数据集获取与介绍、YOLOv5模型介绍以及训练过程。该系统通过PyQt5提供了友好的用户界面,支持图像、视频和实时摄像头的缺陷检测,具有较高的检测精度和速度。未来可以进一步优化模型和系统,以提升PCB缺陷检测的准确性和实时性。

完整资料地址

  • yolov5预训练模型地址:https://github.com/ultralytics/yolov5
  • 本文项目完整链接:链接:https://pan.baidu.com/s/1MpiPisKl4KeW_qu5rDa4LA?pwd=t3gu
    • 包含: PyQT用户交互页面、训练好的PCB板瑕疵检测模型(yolov5s)、几张测试图片
    • 提取码:t3gu
  • 数据集:链接:https://robotics.pkusz.edu.cn/resources/dataset/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1850915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASP.NET MVC-简单例子

环境: win10,.NET Framework 4.6.1 参考: ASP.NET MVC 简介 | 菜鸟教程 https://www.runoob.com/aspnet/mvc-intro.html 准备 查看 net framework 版本: cmd-> C:\Windows\Microsoft.NET\Framework\v4.0.30319>MSBuild /version Mic…

C语言笔记第16篇:编译和链接

1、翻译环境和运行环境 在ANSI C的任何一种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执行机器指令(二进制指令) 第2种是执行环境,它用于实际执行代码 2、翻译环境 那翻译环境是怎…

动态ARP

定义 动态ARP表项由ARP协议通过ARP报文自动生成和维护,可以被老化,可以被新的ARP报文更新,可以被静态ARP表项覆盖。 动态ARP适用于拓扑结构复杂、通信实时性要求高的网络。 ARP地址解析过程 动态ARP通过广播ARP请求和单播ARP应答这两个过…

软件测试笔记

一、介绍 软件测试是为了尽可能多地发现软件系统中的错误而不是证明软件的正确性。 1、软件缺陷是什么? 软件在使用过程中存在的任何问题都叫软件的缺陷,简称bug。 缺陷的判定标准 软件未实现需求说明书中明确要求的功能——少功能 软件出现了需求说…

经典游戏案例:愤怒的小鸟

学习目标:愤怒的小鸟核心玩法 游戏画面 项目结构目录 部分核心代码 using System.Collections; using System.Collections.Generic; using birds; using utils; using UnityEngine;public class GameManager : MonoBehaviour {public static GameManager sInstanc…

【免费】中国电子学会2024年03月份青少年软件编程Python等级考试试卷一级真题(含答案)

2024-03 Python一级真题 分数:100 题数:37 测试时长:60min 一、单选题(共25题,共50分) 1. 下列哪个命令,可以将2024转换成2024 呢?( A)(2分) A.str(2024) B.int(2024) C.fl…

Flutter Android 调试桥 (adb)

客户端:用于发送命令。客户端在开发计算机上运行。您可以通过发出 adb 命令从命令行终端调用客户端。 守护程序adbd:用于在设备上运行命令。守护程序在每个设备上作为后台进程运行。 服务器:用于管理客户端与守护程序之间的通信。服务器在开…

fastapi+vue3+primeflex前后端分离开发项目环境搭建

创建后端项目 创建文件夹: mkdir backend创建python虚拟环境: python -m venv venv使用Pycharm打开文件夹,然后配置python解释器为venv虚拟环境。 安装fastapi: pip install "fastapi[all]"编写第一个程序&#xf…

牛顿迭代法(求解整数的近似平方根)

情景再现 面试官:给你一个整数怎样最快求解他的近似平方根? 小白:可以用while循环呀! 面试官:有没有更好的方法? 小白:可以从这个数的左右两边开始迭代。 面试官:除了这个呢&#xf…

学会python——统计文件中文字出现次数(python实例九)

目录 1、认识Python 2、环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3、统计文本文件中单词频率 3.1 代码构思 3.2 代码示例 3.3 运行结果 4、总结 1、认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计…

使用 Python 进行测试(7)...until you make it

总结 我很懒,我想用最少的行动实现目标,例如生成测试数据。我们可以: 使用随机种子保证数据的一致性。 >>> random.seed(769876987698698) >>> [random.randint(0, 10) for _ in range(10)] [10, 9, 1, 9, 10, 6, 5, 10…

pytest测试框架pytest-sugar插件生成进度条

Pytest提供了丰富的插件来扩展其功能,介绍下插件pytest-sugar,可以帮助我们在控制台中显示彩色的测试结果和进度条,提供失败的堆栈回溯信息。 为了使用 pytest-sugar,需要满足以下条件: Python 3.8 或更高版本pytest…

Nominatim免费的地址解析,逆地址解析,OpenStreetMap开源地图数据【全网最全】

视频学习地址 国内的一些地址解析供应商的API都开始付费了,就想找个免费的地址解析和逆地址解析的应用,最终选择了Nominatim OpenStreetMap 文章目录 一、选型1-1、数据源1-2、地理编码引擎2-1、初尝Nominatim2-1-1、地址解析2-1-2、逆地址解析 2-2、OS…

【MMSegmentation 环境配置】

MMSegmentation 环境配置 1. 创建python 环境2. 安装pytorch3. 安装MMCV4. 安装 MMSegmentation.5. 测试是否安装成功 1. 创建python 环境 conda create --name openmmlab python3.8 -y conda activate openmmlab2. 安装pytorch On GPU platforms: conda install pytorch tor…

平凉特色小吃,味蕾的诱惑之旅

平凉,这座历史悠久的城市,不仅拥有深厚的文化底蕴,更有着让人垂涎欲滴的特色小吃。每一种小吃都承载着当地人的情感与记忆,成为了平凉独特的饮食符号。平凉特色小吃酿皮更是别具风味。爽滑透明的凉皮,配上香辣可口的调…

动态规划——买卖股票的最佳时机含冷冻期

1、题目链接 leetcode 309. 买卖股票的最佳时机含冷冻期 2、题目分析 该题有我们可以定义三种状态,买入状态,可交易状态 ,冷冻期状态 我们可以建立一个n*3的二维数组来表示这三种状态: 根据这个图可以看出, 可以从…

JAVA同城服务场馆门店预约系统支持H5小程序APP源码

📱一键预约,畅享无忧体验🏢 🚀一、开启预约新纪元 在繁忙的都市生活中,我们常常因为时间紧张而错过心仪的门店或场馆服务。然而,有了“门店场馆预约小程序”,这些问题都将迎刃而解。这款小程序…

前端编程语言——JS语言结构、函数、数组、字符串、日期、对象、定时器(2)

0、前言: 这篇文章记录的是我自己的学习笔记。在python中通过input来获取输入,在JS中用prompt(),来获取输入。写JS代码要记得每个代码结束要加上分号。 1、JS编程语言结构: 顺序结构:从上往下依次执行分支结构&#…

2021-03-29:加密与解密

前段时间导师分配的任务主要是看《加密与解密》这本书,“书本写的很详细,认真看会看懂的!” 是的啊,书本写的很详细,可是作为一个没基础的小白看起来还是挺吃力的,概念一个接一个的出现,虽然看…