【YOLOv8实时产品缺陷检测】

news/2024/10/7 17:40:17 标签: python, 目标检测, 视觉检测, YOLO, 制造

YOLOv8应用于产品缺陷检测实例

  • 项目概况
    • 项目实现
    • YOLOv8安装及模型训练
    • 关键代码展示
    • 动态效果展示

项目概况

本项目是应用YOLOv8框架实现训练自定义模型实现单一零件的缺陷检测,软件界面由PyQt5实现。
功能已正式使用,识别效果达到预期。

项目实现

项目使用了以下几个要素:

  1. 全新的界面设计 ,PyQt5结合QtDesigner自定义界面设计,快速构建想要的UI;
  2. 相机选型和光源 ,项目使用了迈德威视工业相机GYD-GE130M-T,对照官方给出的文档很容易就可以实现相机的使用,光源方面使用12V常规灯带打光,去除其他光源的干扰;
  3. IO触发 功能,实现串口控制IO,以快速输出检测识别结果,便于对接工业传送装置;

YOLOv8_14">YOLOv8安装及模型训练

可参考该大佬文章: 传送门

项目展示图: 商业用途请勿随意使用

关键代码展示

迈德威视相机使用线程代码片.

python">import time

import cv2
from ultralytics import YOLO
import numpy as np
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSignal, QThread
from MindVisionSdk import mvsdk
import warnings

warnings.filterwarnings('ignore')


class MyThread(QThread):
    camera_signal = pyqtSignal(object)  # 输出相机数据流
    info_signal = pyqtSignal(str)  # 输出信息状态

    def __init__(self, parent=None):
        super().__init__(parent)
        self.drawLine = False
        self.monoCamera = None
        self.FrameBufferSize = None
        self.isPause = False
        self.mCamera = None
        self.pFrameBuffer = None
        self.model = YOLO(model="./YoloModel/best.pt")

    def __del__(self):
        mvsdk.CameraUnInit(self.mCamera)
        mvsdk.CameraAlignFree(self.pFrameBuffer)

    def run(self):
        # 枚举相机设备列表
        DevList = mvsdk.CameraEnumerateDevice()
        if len(DevList) >= 1:
            DevInfo = DevList[0]  # 选取设备列表第一个相机
            try:
                self.mCamera = mvsdk.CameraInit(DevInfo, -1, -1)
                self.info_signal.emit("【{}】 初始化相机成功".format(time.strftime("%Y-%m-%d %H:%M:%S")))
            except mvsdk.CameraException as e:
                self.info_signal.emit("【{}】 初始化相机异常:{}".format(time.strftime("%Y-%m-%d %H:%M:%S"), e))

        cap = mvsdk.CameraGetCapability(self.mCamera)
        self.monoCamera = (cap.sIspCapacity.bMonoSensor != 0)
        if self.monoCamera:
            mvsdk.CameraSetIspOutFormat(self.mCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
        else:
            mvsdk.CameraSetIspOutFormat(self.mCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
        mvsdk.CameraSetTriggerMode(self.mCamera, 0)
        mvsdk.CameraSetAeState(self.mCamera,True)  # True为自动曝光 False为手动曝光
        mvsdk.CameraSetRotate(self.mCamera, 0)
        mvsdk.CameraSetMirror(self.mCamera, 0, True)
        mvsdk.CameraPlay(self.mCamera)
        # 计算RGB buffer所需的大小,这里直接按照相机的最大分辨率来分配
        self.FrameBufferSize = cap.sResolutionRange.iWidthMax * cap.sResolutionRange.iHeightMax * (
            1 if self.monoCamera else 3)

        # 分配RGB buffer,用来存放ISP输出的图像
        # 备注:从相机传输到PC端的是RAW数据,在PC端通过软件ISP转为RGB数据(如果是黑白相机就不需要转换格式,但是ISP还有其它处理,所以也需要分配这个buffer)
        self.pFrameBuffer = mvsdk.CameraAlignMalloc(self.FrameBufferSize, 16)
        while True:
            try:
                if not self.isPause:

                    pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.mCamera, 10)
                    mvsdk.CameraImageProcess(self.mCamera, pRawData, self.pFrameBuffer, FrameHead)
                    if self.drawLine:
                        mvsdk.CameraImageOverlay(self.mCamera, self.pFrameBuffer, FrameHead)
                    mvsdk.CameraReleaseImageBuffer(self.mCamera, pRawData)
                    # 此时图片已经存储在pFrameBuffer中,对于彩色相机pFrameBuffer=RGB数据,黑白相机pFrameBuffer=8位灰度数据

                    # 把pFrameBuffer转换成opencv的图像格式以进行后续算法处理
                    frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(self.pFrameBuffer)
                    frame = np.frombuffer(frame_data, dtype=np.uint8)
                    frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth,
                                           1 if FrameHead.uiMediaType == mvsdk.CAMERA_MEDIA_TYPE_MONO8 else 3))
                    frame = cv2.resize(frame,(960,540), interpolation=cv2.INTER_LINEAR)
                    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    results = self.model.predict(source=frame,verbose=False)
                    # 提取检测结果
                    # for result in results:
                    #     boxes = result.boxes.xyxy  # 边界框坐标
                    #     scores = result.boxes.conf  # 置信度分数
                    #     classes = result.boxes.cls  # 类别索引
                    #     # 如果有类别名称,可以通过类别索引获取
                    #     class_names = [self.model.names[int(cls)] for cls in classes]
                    #     # 打印检测结果
                    #     for box, score, class_name in zip(boxes, scores, class_names):
                    #         print(f"Class: {class_name}, Score: {score:.2f}, Box: {box}")
                    annotated_frame = results[0].plot()
                    showImage = QtGui.QImage(annotated_frame.data, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
                    self.camera_signal.emit(showImage)
                    self.info_signal.emit("【{}】 实时显示中......".format(time.strftime("%Y-%m-%d %H:%M:%S")))
            except mvsdk.CameraException as e:
                if e.error_code != mvsdk.CAMERA_STATUS_TIME_OUT:
                    self.info_signal.emit("【{}】 实时显示线程报错:{}".format(time.strftime("%Y-%m-%d %H:%M:%S"), e))
            finally:
                self.msleep(20)

主界面逻辑代码片

python">import os.path
import sys
import time

import cv2
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5.QtWidgets import QMessageBox
from WindowUI import Ui_MainWindow
from CameraThread import MyThread
from MindVisionSdk import mvsdk
from Yaml_Tool import myYamlTool


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.camera_thread = MyThread()
        self.camera_thread.camera_signal.connect(self.showVideo)
        self.camera_thread.info_signal.connect(self.showStatus)
        self.pushButton_open_close.clicked.connect(self.camera_start_stop)
        self.pushButton_play.clicked.connect(self.camera_play_pause)
        self.pushButton_play.setEnabled(False)
        self.pushButton_catch.clicked.connect(self.catchPicture)
        self.pushButton_addline.clicked.connect(self.drawLine)
        self.radioButton_run.setStyleSheet("QRadioButton::indicator:!checked {border: 2px solid #8f8f91;}"
                                           "QRadioButton::indicator:checked {background-color: #55ff7f;}"
                                           "QRadioButton::indicator:checked:hover {border: 2px solid #8f8f91;}")
        self.radioButton_mark.setStyleSheet("QRadioButton::indicator:!checked {border: 2px solid #8f8f91;}"
                                            "QRadioButton::indicator:checked {background-color: #55ff7f;}"
                                            "QRadioButton::indicator:checked:hover {border: 2px solid #8f8f91;}")
        self.yamlTool = myYamlTool()
        self.yamlData = self.yamlTool.read_yaml('config.yaml')
        if self.yamlData['runMode'] == 'mark':
            self.radioButton_mark.setChecked(True)
        else:
            self.radioButton_run.setChecked(True)

        self.radioButton_mark.clicked.connect(lambda: self.selectMode(0))
        self.radioButton_run.clicked.connect(lambda: self.selectMode(1))
        self.doubleSpinBox_threshold.setValue(self.yamlData['threshold'])
        self.doubleSpinBox_threshold.valueChanged.connect(self.thresholdChange)

    def closeEvent(self, event):
        """
        重写关闭按钮事件
        :param event:
        :return:
        """
        reply = QMessageBox.question(self, '警告', '<font color=red><b>确定退出工具?</b></font>',
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            if self.camera_thread.isRunning():
                self.camera_thread.__del__()
                self.camera_thread.terminate()
            event.accept()
        else:
            event.ignore()

    def camera_start_stop(self):
        """
        打开关闭相机按钮事件
        :return:
        """
        if self.pushButton_open_close.text() == "打开相机":
            self.camera_thread.start()
            self.camera_thread.isPause = False
            self.pushButton_open_close.setText("关闭相机")
            self.statusbar.showMessage("【{}】 已打开相机".format(time.strftime("%Y-%m-%d %H:%M:%S")))
            self.pushButton_play.setEnabled(True)
            self.pushButton_play.setText("暂停")
            self.pushButton_catch.setEnabled(True)
        else:
            self.camera_thread.__del__()
            self.camera_thread.terminate()
            self.camera_thread.isPause = True
            self.pushButton_open_close.setText("打开相机")
            self.statusbar.showMessage("【{}】 已关闭相机".format(time.strftime("%Y-%m-%d %H:%M:%S")))
            self.label_display.setText("相机已关闭")
            self.pushButton_play.setEnabled(False)
            self.pushButton_play.setText("播放")
            self.pushButton_catch.setEnabled(False)

    def camera_play_pause(self):
        """
        相机暂停和继续
        :return:
        """
        if self.camera_thread.isRunning():
            if self.pushButton_play.text() == "暂停":
                mvsdk.CameraPause(self.camera_thread.mCamera)
                self.camera_thread.isPause = True
                self.statusbar.showMessage("【{}】 实时显示已暂停".format(time.strftime("%Y-%m-%d %H:%M:%S")))
                self.pushButton_play.setText("播放")
            else:
                mvsdk.CameraPlay(self.camera_thread.mCamera)
                self.camera_thread.isPause = False
                self.statusbar.showMessage("【{}】 实时显示已恢复".format(time.strftime("%Y-%m-%d %H:%M:%S")))
                self.pushButton_play.setText("暂停")

    def catchPicture(self):
        """
        抓取图片
        :return:
        """
        if self.camera_thread.isRunning():
            try:
                mvsdk.CameraSetMirror(self.camera_thread.mCamera, 1, True)
                pFrameBuffer = mvsdk.CameraAlignMalloc(self.camera_thread.FrameBufferSize, 16)
                pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.camera_thread.mCamera, 1000)
                mvsdk.CameraImageProcess(self.camera_thread.mCamera, pRawData, pFrameBuffer, FrameHead)
                mvsdk.CameraReleaseImageBuffer(self.camera_thread.mCamera, pRawData)
                mvsdk.CameraSetMirror(self.camera_thread.mCamera, 1, False)
                # 此时图片已经存储在pFrameBuffer中,对于彩色相机pFrameBuffer=RGB数据,黑白相机pFrameBuffer=8位灰度数据
                # 该示例中我们只是把图片保存到硬盘文件中
                if self.yamlData['runMode'] == 'mark':
                    if not os.path.exists("CatchImage"):
                        os.mkdir("CatchImage")
                    dirName = 'CatchImage'
                else:
                    if not os.path.exists("RunImage"):
                        os.mkdir("RunImage")
                    dirName = 'RunImage'
                catchTime = time.strftime("%Y%m%d%H%M%S")
                status = mvsdk.CameraSaveImage(self.camera_thread.mCamera,
                                               "./{}/grab_{}.jpg".format(dirName, catchTime),
                                               pFrameBuffer,
                                               FrameHead, mvsdk.FILE_JPG, 100)
                if os.path.exists("./{}/grab_{}.jpg".format(dirName, catchTime)):
                    # 进行目标检测
                    results = self.camera_thread.model.predict('./{}/grab_{}.jpg'.format(dirName, catchTime))
                    annotated_frame = results[0].plot()
                    # 保存处理后带标签的图片
                    cv2.imwrite("./{}/process_{}.jpg".format(dirName, catchTime), annotated_frame)
                    # 将图像数据转换为QImage格式
                    height, width, channel = annotated_frame.shape
                    bytes_per_line = 3 * width
                    qimage = QtGui.QImage(annotated_frame.data, width, height, bytes_per_line,
                                          QtGui.QImage.Format_RGB888)
                    # 将QImage转换为QPixmap
                    pixmap = QtGui.QPixmap.fromImage(qimage)

                    self.label_show.setPixmap(pixmap)
                    self.label_show.setScaledContents(True)
                    # 提取检测结果
                    self.textBrowser.append(
                        "*********************************{}*************************************".format(
                            time.strftime("%Y-%m-%d %H:%M:%S")))
                    find_labels = []
                    for result in results:
                        boxes = result.boxes.xyxy  # 边界框坐标
                        scores = result.boxes.conf  # 置信度分数
                        classes = result.boxes.cls  # 类别索引
                        # 如果有类别名称,可以通过类别索引获取
                        class_names = [self.camera_thread.model.names[int(cls)] for cls in classes]
                        # 打印检测结果
                        for box, score, class_name in zip(boxes, scores, class_names):
                            self.textBrowser.append(f"检测标签: {class_name}, 置信度: {score:.2f}")
                            if score >= self.yamlData['threshold']:
                                find_labels.append(class_name)
                    if len(find_labels) == 2 and 'pin-left-pass' in find_labels and 'pin-right-pass' in find_labels:
                        self.label_result.setText("PASS")
                        self.label_result.setStyleSheet("background-color: #55ff7f")
                        # TODO 给出PASS信号
                    else:
                        self.label_result.setText("FAIL")
                        self.label_result.setStyleSheet("background-color: #d80000")
                        # TODO 给出FAIL信号
                    self.textBrowser.append(
                        "*****************************************************************************************")
            except mvsdk.CameraException as e:
                QMessageBox.critical(self, "错误", "抓图失败({}): {}".format(e.error_code, e.message), QMessageBox.Yes,
                                     QMessageBox.Yes)

    def drawLine(self):
        """
        添加网格线
        :return:
        """
        if self.pushButton_addline.text() == "添加网格线":
            self.camera_thread.drawLine = True
            self.pushButton_addline.setText("去除网格线")
        else:
            self.camera_thread.drawLine = False
            self.pushButton_addline.setText("添加网格线")

    def showVideo(self, showImage):
        """
        显示视频
        :param showImage:
        :return:
        """
        self.label_display.setPixmap(QtGui.QPixmap.fromImage(showImage))

    def showStatus(self, info):
        """
        显示状态
        :param info:
        :return:
        """
        self.statusbar.showMessage(info)

    def selectMode(self, mode):
        """
        选择抓图模式
        :param mode:
        :return:
        """
        if mode == 0:
            self.yamlTool.update_yaml('config.yaml', 'runMode', 'mark')
        else:
            self.yamlTool.update_yaml('config.yaml', 'runMode', 'run')
        self.yamlData = self.yamlTool.read_yaml('config.yaml')

    def thresholdChange(self, value):
        """
        置信阈值改变事件
        :param value:
        :return:
        """
        self.yamlTool.update_yaml('config.yaml', 'threshold', value)
        self.yamlData = self.yamlTool.read_yaml('config.yaml')


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

动态效果展示

yolov8视觉框架动态检测产品缺陷


http://www.niftyadmin.cn/n/5693123.html

相关文章

【M365运维】在SPO文档库里删除文档时,遇到文档被签出无法删除。

【问题】SPO的存储空间剩的不多了&#xff0c;在清理文档库时&#xff0c;遇到有些文档被签出但用户已经离职&#xff0c;删除文件时报错。 【解决】翻SPO的设置时&#xff0c;看到有“管理没有已签入版本的文件”&#xff0c;在里面获取文件的所有权之后就可以删除了。 具体…

Linux下网络转发功能

1、背景介绍 项目中使用上位机电脑&#xff0c;需要通过网络访问一个Linux主机&#xff0c;但是该Linux主机没有网络直接与上位机相连&#xff0c;只能通过插箱内另外一个Linux主机转发才能访问&#xff0c;示意图如下&#xff1a; 2、网络转发配置 Linux网络中转主机需要进行…

Why RTSP?RTSP播放器优势探究

RTSP优势探究 好多开发者搞不清楚&#xff0c;低延迟的传输&#xff0c;到底是走RTMP、WebRTC还是RTSP&#xff1f;如果走RTSP&#xff0c;RTSP播放器的优势有哪些&#xff1f;能否达到期望的延迟&#xff1f;答案是肯定的&#xff0c;废话不多说&#xff0c;上效果图&#xf…

Spring Boot项目使用MyBatis Plus的详细步骤

在Spring Boot项目中使用MyBatis Plus&#xff0c;可以极大地简化数据库操作&#xff0c;提高开发效率。以下是在Spring Boot项目中集成和使用MyBatis Plus的详细步骤&#xff1a; 一、环境准备 确保已安装Java和Spring Boot&#xff1a;MyBatis Plus是基于Java和Spring Boot…

[OS] 编译 Linux 内核

编译 Linux 内核&#xff1a;详细教程与 Kthreads 入门结合 我们将学习如何编译 Linux 内核&#xff0c;同时结合 Kthreads 的知识来理解各个步骤的目的。对于虚拟环境下的开发环境配置&#xff0c;本文将为你提供逐步指导。 1. 下载内核源代码 首先&#xff0c;我们需要从官…

大模型项目如何判断用RAG还是微调

大模型项目如何判断用RAG还是微调 在大模型项目中&#xff0c;选择使用检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;还是微调&#xff08;Fine-Tuning&#xff09;取决于多个因素&#xff0c;包括项目的具体需求、数据的可用性、性能要求、成本和…

STM32PWM应用

目录 一、输出比较(OC) 二、PWM&#xff1a; 1、简介 2、基本结构 3、参数计算 三、PWM驱动LED呼吸灯 1、代码 四、PWM驱动Sg90舵机 1、工作原理 2、完整代码 五、PWM驱动直流电机 1、TB6612芯片模块 2、完整代码&#xff1a; 一、输出比较(OC) OC&#xff08;Outp…

SparkSubmit进程无法强制kill掉以及Flink相关error

SparkSubmit进程无法强制kill掉 文章目录 SparkSubmit进程无法强制kill掉0. 写在前面1. 正文2. Flink配合Kafka使用问题的记录 0. 写在前面 操作系统&#xff1a;Linux&#xff08;CentOS7.5&#xff09;Spark版本&#xff1a;Spark3.0.0Scala版本&#xff1a;Scala2.12.1Flin…