【YOLOv8实时产品缺陷检测】

项目概况

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

项目实现

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

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

YOLOv8安装及模型训练

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

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

关键代码展示

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

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)

主界面逻辑代码片

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视觉框架动态检测产品缺陷

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值