基于Qt和PaddleOCR的工业视觉识别报警系统开发

目录

引言

技术栈

系统架构

[1. 硬件配置](#1. 硬件配置)

[2. 软件架构](#2. 软件架构)

核心功能实现

[1. 相机初始化与管理](#1. 相机初始化与管理)

[2. 多路视频显示](#2. 多路视频显示)

[3. OCR识别优化](#3. OCR识别优化)

字符筛选策略

易错字符替换

区域面积筛选

[4. 报警逻辑实现](#4. 报警逻辑实现)

[5. 多线程资源管理](#5. 多线程资源管理)

界面设计

布局特点

样式设置

性能优化

[1. OCR调用频率控制](#1. OCR调用频率控制)

[2. 日志管理](#2. 日志管理)

[3. GPU加速](#3. GPU加速)

遇到的问题及解决方案

[1. USB摄像头资源泄露](#1. USB摄像头资源泄露)

[2. OCR误识别](#2. OCR误识别)

[3. 多线程显示卡顿](#3. 多线程显示卡顿)

使用效果

总代码:

1.界面代码

2.执行代码

引言

在工业自动化领域,视觉识别与实时报警系统有着广泛的应用场景。本文将分享一个基于PyQt5、PaddleOCR和海康工业相机的多路视觉识别报警系统的开发经验。该系统能够同时监控三个摄像头,对识别到的特定产品型号进行实时报警。

技术栈

  • GUI框架:PyQt5

  • 视觉识别:PaddleOCR (GPU加速)

  • 工业相机:海康工业相机 (通过SDK控制)

  • USB相机:OpenCV

  • 报警控制:串口通信

  • 多线程:Python threading

系统架构

1. 硬件配置

  • 2路海康工业相机(千兆网口)

  • 1路USB摄像头

  • 2个声光报警器(通过串口控制)

2. 软件架构

复制代码
├── 主界面模块 (PyQt5)
├── 相机采集模块 (海康SDK + OpenCV)
├── OCR识别模块 (PaddleOCR)
├── 串口控制模块 (pySerial)
└── 多线程管理模块

核心功能实现

1. 相机初始化与管理

python 复制代码
# 海康工业相机初始化
self.camera1 = HKCamera(CameraIp='192.168.20.56')
self.camera1.set_Value(param_type="enum_value", node_name="PixelFormat",
                       node_value='BayerGB8')
self.camera1.set_Value(param_type="float_value", node_name="AcquisitionFrameRate",
                       node_value='15.0000')

关键点

  • 工业相机需要设置像素格式和帧率

  • 采用连续自动增益确保图像质量

  • USB相机需要定期释放资源避免内存泄漏

2. 多路视频显示

通过定时器实现三路视频的实时显示:

python 复制代码
self._timer.timeout.connect(self._queryFrame)
self._timer.setInterval(67)  # 约15fps

_queryFrame方法中同时获取三路图像并显示在对应的Label控件上。

3. OCR识别优化

字符筛选策略
python 复制代码
def process_string(input_string):
    # 正则表达式1: 包含数字和字母的组合
    pattern_alphanumeric = re.compile(r'^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d-]{2,10}$')
    # 正则表达式2: 4-7位纯数字
    pattern_at_least_two_digits = re.compile(r'^\d{4,7}$')
易错字符替换
python 复制代码
def set_bing(set_a):
    # 替换易混淆字符
    jj = j.replace('0', 'O').replace('o', 'O').replace('s', '5')
          .replace('S', '5').replace('I', '1').replace('L', '1')
区域面积筛选

通过计算识别区域的面积,过滤掉过小或过大的无效识别:

python 复制代码
def are(i):
    # 计算四边形面积
    width = (width_A + width_B) / 2
    height = (height_A + height_B) / 2
    area = width * height
    return area

4. 报警逻辑实现

python 复制代码
def sendCmdToDevice(cmd, ser):
    cmdd = bytes.fromhex(cmd)
    ser.write(cmdd)
​
# 不同报警模式
LIGHT_BUZZ1 = "0110001A000101CE18"  # 闪光+声音1
LIGHT = "0110001A0001028E19"        # 仅闪光
BUZZ1 = "0110001A0001034FD9"        # 仅声音1
BUZZ_CMD_CLOSE = "0110001A0001000FD8"  # 关闭

5. 多线程资源管理

针对USB摄像头设计定期释放机制:

python 复制代码
def release_capture3(cap):
    while True:
        time.sleep(1800)  # 30分钟释放一次
        cap.release()
        cap.open(opt.cap_numb3)

界面设计

布局特点

  • 三路视频并排显示

  • 三个独立的识别结果显示框

  • 三个声光报警指示灯

  • 复位按钮和报警信息查询

样式设置

python 复制代码
self.label_4.setGeometry(QtCore.QRect(430, 690, 61, 61))
self.label_4.setText("<html><head/><body><p align=\"center\"><span style=\" font-size:18pt;\">⚠</span></p></body></html>")

性能优化

1. OCR调用频率控制

python 复制代码
if self.frame_counter % 5 == 0:
    self._performOCR1()
if self.frame_counter % 3 == 0:
    self._performOCR2()
    self._performOCR3()

2. 日志管理

python 复制代码
logging.disable(logging.DEBUG)  # 关闭不必要的日志输出

3. GPU加速

python 复制代码
ocr = PaddleOCR(use_angle_cls=True, use_gpu=True, lang='en')

遇到的问题及解决方案

1. USB摄像头资源泄露

问题 :长时间运行后USB摄像头无法打开 解决:使用守护线程定期释放和重连

2. OCR误识别

问题 :字符识别错误率高 解决:多重筛选 + 易错字符替换 + 面积过滤

3. 多线程显示卡顿

问题 :三路视频同时显示导致界面卡顿 解决:降低部分摄像头的OCR频率,优化帧率设置

使用效果

系统在实际工业环境中运行稳定,能够准确识别产品型号并触发相应的声光报警。三个摄像头可同时工作,互不干扰,满足生产线的实时监控需求

总代码:

1.界面代码
python 复制代码
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file '金寨窗口0.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1920, 1080)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
        MainWindow.setSizePolicy(sizePolicy)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(10, 10, 631, 421))
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(1300, 10, 611, 421))
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(660, 10, 621, 421))
        self.label_3.setObjectName("label_3")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(230, 670, 141, 61))
        self.pushButton.setStyleSheet("font: 20pt \"Arial\";")
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(870, 670, 141, 61))
        self.pushButton_2.setStyleSheet("font: 20pt \"Arial\";")
        self.pushButton_2.setObjectName("pushButton_2")
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setGeometry(QtCore.QRect(200, 540, 211, 61))
        self.lineEdit.setStyleSheet("font: 63 24pt \"Segoe UI Semibold\";")
        self.lineEdit.setText("")
        self.lineEdit.setObjectName("lineEdit")
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(870, 850, 221, 111))
        self.pushButton_4.setStyleSheet("font: 20pt \"Arial\";")
        self.pushButton_4.setObjectName("pushButton_4")
        self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_2.setGeometry(QtCore.QRect(850, 540, 211, 61))
        self.lineEdit_2.setStyleSheet("font: 63 24pt \"Segoe UI Semibold\";")
        self.lineEdit_2.setText("")
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.lineEdit_3 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_3.setGeometry(QtCore.QRect(1530, 540, 211, 61))
        self.lineEdit_3.setStyleSheet("font: 63 24pt \"Segoe UI Semibold\";")
        self.lineEdit_3.setText("")
        self.lineEdit_3.setObjectName("lineEdit_3")
        self.line = QtWidgets.QFrame(self.centralwidget)
        self.line.setGeometry(QtCore.QRect(-10, 780, 1921, 20))
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.line.sizePolicy().hasHeightForWidth())
        self.line.setSizePolicy(sizePolicy)
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line.setObjectName("line")
        self.line_2 = QtWidgets.QFrame(self.centralwidget)
        self.line_2.setGeometry(QtCore.QRect(640, 0, 20, 791))
        self.line_2.setFrameShape(QtWidgets.QFrame.VLine)
        self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line_2.setObjectName("line_2")
        self.line_3 = QtWidgets.QFrame(self.centralwidget)
        self.line_3.setGeometry(QtCore.QRect(1280, 0, 20, 791))
        self.line_3.setFrameShape(QtWidgets.QFrame.VLine)
        self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line_3.setObjectName("line_3")
        self.line_4 = QtWidgets.QFrame(self.centralwidget)
        self.line_4.setGeometry(QtCore.QRect(-10, 430, 1941, 20))
        self.line_4.setFrameShape(QtWidgets.QFrame.HLine)
        self.line_4.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line_4.setObjectName("line_4")
        self.label_4 = QtWidgets.QLabel(self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(430, 690, 61, 61))
        self.label_4.setObjectName("label_4")
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(1080, 680, 61, 61))
        self.label_5.setObjectName("label_5")
        self.label_6 = QtWidgets.QLabel(self.centralwidget)
        self.label_6.setGeometry(QtCore.QRect(1610, 670, 71, 61))
        self.label_6.setObjectName("label_6")
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(1580, 880, 201, 91))
        self.pushButton_3.setStyleSheet("font: 20pt \"Arial\";")
        self.pushButton_3.setObjectName("pushButton_3")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1920, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        self.pushButton_4.clicked.connect(MainWindow.openvideo) # type: ignore
        self.pushButton.clicked.connect(self.lineEdit.clear) # type: ignore
        self.pushButton.clicked.connect(MainWindow.clearSet1) # type: ignore
        self.pushButton_2.clicked.connect(self.lineEdit_2.clear) # type: ignore
        self.pushButton_2.clicked.connect(MainWindow.clearSet2) # type: ignore
        self.pushButton_3.clicked.connect(MainWindow.open_folder) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt;\">视频1</span></p></body></html>"))
        self.label_2.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt;\">视频3</span></p></body></html>"))
        self.label_3.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt;\">视频2</span></p></body></html>"))
        self.pushButton.setText(_translate("MainWindow", "复位"))
        self.pushButton_2.setText(_translate("MainWindow", "复位"))
        self.pushButton_4.setText(_translate("MainWindow", "开启"))
        self.label_4.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt;\">⚠</span></p></body></html>"))
        self.label_5.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt;\">⚠</span></p></body></html>"))
        self.label_6.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:18pt;\">⚠</span></p></body></html>"))
        self.pushButton_3.setText(_translate("MainWindow", "报警信息"))
2.执行代码
python 复制代码
import cv2
from collections import Counter
import os
from numpy import ndarray
import sys
sys.path.append(r"C:\Program Files (x86)\MVS\Development\Samples\Python\MvImport")
from HKCamera_class import HKCamera
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QFileDialog, QMainWindow
from 金寨窗口0 import Ui_MainWindow
import logging
import numpy as np
from paddleocr import PaddleOCR
import re
import serial
import time
import argparse
import threading

def release_capture3(cap):    # 定期释放usb摄像头资源
    while True:
        # 每隔一段时间释放一次资源,这里设置为 30min
        time.sleep(1800)
        cap.release()
        cap.open(opt.cap_numb3)
parser = argparse.ArgumentParser()
parser.add_argument("--SERIAL_PORT1", type=str, default='COM5', help='第一个报警器的串口号')
parser.add_argument("--SERIAL_PORT2", type=str, default='COM8', help='第二 三个报警器的串口号')
parser.add_argument("--confid_level", type=float, default=0.88, help='识别的置信度')
parser.add_argument("--cap_numb3", type=int, default=1, help='第三个摄像头编号')
parser.add_argument("--frame_delay", type=int, default=67, help='获取画面帧数的延时')
opt = parser.parse_args()

######## 指令声明

LIGHT_BUZZ1 = "0110001A000101CE18"  # 闪光+声音1
LIGHT_BUZZ2 = "0110001A0001040E1B"  # 闪光+声音1
LIGHT = "0110001A0001028E19"  # 闪光
BUZZ1 = "0110001A0001034FD9"  # 声音1
BUZZ2 = "0110001A000105CFDB"  # 声音2
BUZZ_CMD_CLOSE = "0110001A0001000FD8"  # 关闭声音和闪光

logging.disable(logging.DEBUG)  # 关闭日志的输出

def sendCmdToDevice(cmd, ser):   # 控制警报器
    cmdd = bytes.fromhex(cmd)
    ser.write(cmdd)

def most_common_element(lst):   # 提取列表中出现次数最多的字符
    # 使用Counter统计每个元素出现的次数
    count = Counter(lst)
    # 找到出现次数最多的元素
    most_common_item = count.most_common(1)[0][0]
    return most_common_item

def process_string(input_string):   # 在ocr的结果中筛选出对应型号
    aa=[]
    # 用空格切分字符串
    parts = input_string.split()
    # 正则表达式1: 包含数字和字母或者纯数字
    pattern_alphanumeric = re.compile(r'^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d-]{2,10}$')
    # 正则表达式2: 至少三个以上数字的纯数字
    pattern_at_least_two_digits = re.compile(r'^\d{4,7}$')
    # 遍历每个切分后的部分
    for part in parts:
        # 检查是否包含数字和字母或者纯数字
        if pattern_alphanumeric.match(part) or pattern_at_least_two_digits.match(part):
            aa.append(part)
    return aa

def set_bing(set_a):    # 替换一些易错字符
    resu =set()
    for j in set_a:
        jj = j.replace('0', 'O').replace('o', 'O').replace('s', '5').replace('S', '5').replace('I', '1').replace('L', '1').replace('v','V').replace('B','8').replace('p','P')
        resu.add(jj)
    return resu

def hide_label_and_send_cmd(label,ser=None):   # 隐藏警告符号,并停止报警
    label.setVisible(False)  # 隐藏警告符号
    sendCmdToDevice(BUZZ_CMD_CLOSE, ser)

def In_which(text, hun):
    a = 0
    for i in hun:
        if text in i:
            a += 1
    return a

def are(i):    # 计算字符面积
    zs = i[0][0]
    ys = i[0][1]
    yx = i[0][2]
    zx = i[0][3]

    # 计算宽度为左上角到右上角的距离
    width_A = np.sqrt(((zs[0] - ys[0]) ** 2) + ((zs[1] - ys[1]) ** 2))
    # 计算宽度为左下角到右下角的距离
    width_B = np.sqrt(((zx[0] - yx[0]) ** 2) + ((zx[1] - yx[1]) ** 2))
    # 计算高度为左上角到左下角的距离
    height_A = np.sqrt(((zs[0] - zx[0]) ** 2) + ((zs[1] - zx[1]) ** 2))
    # 计算高度为右上角到右下角的距离
    height_B = np.sqrt(((ys[0] - yx[0]) ** 2) + ((ys[1] - yx[1]) ** 2))
    # 取平均值作为宽度和高度
    width = (width_A + width_B) / 2
    height = (height_A + height_B) / 2
    # 计算面积
    area = width * height
    return area

ocr = PaddleOCR(use_angle_cls=True,use_gpu=True, lang='en')
ocr2 = PaddleOCR(use_angle_cls=False, use_gpu=True, lang='en')

class PyQtMainEntry(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)


        self.ser1 = serial.Serial(opt.SERIAL_PORT1, 9600, timeout=2.5)
        self.ser2 = serial.Serial(opt.SERIAL_PORT2, 9600, timeout=2.5)
        # self.ser3 = serial.Serial(opt.SERIAL_PORT3, 9600, timeout=2.5)


        self.label_4.setVisible(False)
        self.label_5.setVisible(False)
        self.label_6.setVisible(False)
        self.list_zong1 = []
        self.list_zong2 = []
        self.list_zong3 = []
        self.set_zong1 = set()
        self.set_zong2 = set()
        self.set_zong3 = set()
        self.set_12hun = set()
        self.guo = []

        self.list1 = []
        self.list2 = []
        self.list3 = []

        self.daan1 = ''
        self.daan2 = ''

        # self.showMaximized()
        # 第一个摄像头
        self.camera1 = HKCamera(CameraIp='192.168.20.56')
        self.camera1.set_Value(param_type="enum_value", node_name="PixelFormat",
                               node_value='BayerGB8')
        self.camera1.set_Value(param_type="enum_value", node_name="GainAuto",
                               node_value='Continuous')
        self.camera1.set_Value(param_type="float_value", node_name="AcquisitionFrameRate",
                               node_value='15.0000')
        # self.camera1.set_Value(param_type="enum_value", node_name="ExposureAuto",
        #                        node_value='Continuous')  # 自动曝光
        self.camera1.start_camera()

        # 第二个摄像头
        self.camera2 = HKCamera(CameraIp='192.168.20.20')
        self.camera2.set_Value(param_type="enum_value", node_name="PixelFormat",
                               node_value='BayerGB8')
        self.camera2.set_Value(param_type="enum_value", node_name="GainAuto",
                               node_value='Continuous')
        self.camera2.set_Value(param_type="float_value", node_name="AcquisitionFrameRate",
                               node_value='15.0000')
        # self.camera2.set_Value(param_type="enum_value", node_name="ExposureAuto",
        #                        node_value='Continuous')   #自动曝光
        # self.camera2.set_Value(param_type="enum_value", node_name="DecimationHorizontal",
        #                        node_value='2')
        # self.camera2.set_Value(param_type="enum_value", node_name="DecimationVertical",
        #                        node_value='2')
        self.camera2.start_camera()

        self.camera3 = cv2.VideoCapture(opt.cap_numb3)

        # 启动后台线程来定期释放第三个摄像头的资源
        release_thread2 = threading.Thread(target=release_capture3, args=(self.camera3,))
        release_thread2.daemon = True  # 设置为守护线程,随主线程结束而结束
        release_thread2.start()

        self.is_camera_opened = False
        self._timer = QtCore.QTimer(self)
        self._timer.timeout.connect(self._queryFrame)
        self._timer.setInterval(opt.frame_delay)

        self.frame_counter = 0    # 统计画面的帧数

        # self.pushButton_4.click()

    def openvideo(self):
        self.is_camera_opened = not self.is_camera_opened
        if self.is_camera_opened:
            self.pushButton_4.setText("关闭")
            self._timer.start()
        else:
            self.pushButton_4.setText("打开")
            self._timer.stop()
            sendCmdToDevice(BUZZ_CMD_CLOSE, self.ser1)
            sendCmdToDevice(BUZZ_CMD_CLOSE, self.ser2)
            # sendCmdToDevice(BUZZ_CMD_CLOSE, self.ser3)
            self.label_4.setVisible(False)
            self.label_5.setVisible(False)
            self.label_6.setVisible(False)

    def open_folder(self):
        folder_path = r'D:\MVS\MVS\Development\Samples\Python\shiyan\baojing'
        QDesktopServices.openUrl(QUrl.fromLocalFile(folder_path))

    def clearSet1(self):     # 复位按钮一
        self.set_zong1.clear()
        # self.list_zong1.clear()
        self.list1.clear()
        sendCmdToDevice(BUZZ_CMD_CLOSE, self.ser1)
        self.label_4.setVisible(False)

    def clearSet2(self):      # 复位按钮二
        self.set_zong2.clear()
        # self.list_zong2.clear()
        self.list2.clear()
        sendCmdToDevice(BUZZ_CMD_CLOSE, self.ser2)
        self.label_5.setVisible(False)

    def execute_after_n_calls(n, w2set):
        def decorator(func):
            def wrapper(self, *args, **kwargs):
                wrapper.count += 1
                result = func(self, *args, **kwargs)
                if wrapper.count % n == 0:
                    w2set(self)
                return result
            wrapper.count = 0
            return wrapper
        return decorator

    @QtCore.pyqtSlot()
    def _queryFrame(self):
        try:
            if not self.camera3.grab():
                print("No frame grabbed.")
                self.camera3.release()
                self.close()
            else:
                self.frame1: ndarray = self.camera1.get_image()
                self.frame2: ndarray = self.camera2.get_image()
                ret3, self.frame3 = self.camera3.read()

                if not ret3:
                    print("No frame retrieved.")
                    # self.camera1.release()
                    self.camera3.release()
                    self.close()
                else:

                    self.frame11 = cv2.resize(self.frame1, (640, 480))
                    self.frame22 = cv2.resize(self.frame2, (640, 480))
                    self.frame33 = cv2.resize(self.frame3, (640, 480))

                    if ret3:
                        qimage = cv2.cvtColor(self.frame11.copy(), cv2.COLOR_BGR2RGB)
                        qimage = QtGui.QImage(qimage.data, qimage.shape[1], qimage.shape[0], QtGui.QImage.Format_RGB888)
                        pixmap = QtGui.QPixmap.fromImage(qimage)

                        qimage2 = cv2.cvtColor(self.frame22.copy(), cv2.COLOR_BGR2RGB)
                        qimage2 = QtGui.QImage(qimage2.data, qimage2.shape[1], qimage2.shape[0], QtGui.QImage.Format_RGB888)
                        pixmap2 = QtGui.QPixmap.fromImage(qimage2)

                        qimage3 = cv2.cvtColor(self.frame33.copy(), cv2.COLOR_BGR2RGB)
                        qimage3 = QtGui.QImage(qimage3.data, qimage3.shape[1], qimage3.shape[0], QtGui.QImage.Format_RGB888)
                        pixmap3 = QtGui.QPixmap.fromImage(qimage3)

                        self.label.setPixmap(pixmap)
                        self.label_3.setPixmap(pixmap2)
                        self.label_2.setPixmap(pixmap3)

                        # 每隔一定帧数执行一次OCR
                        self.frame_counter += 1
                        if self.frame_counter % 5 == 0:
                            self._performOCR1()
                        if self.frame_counter % 3 == 0:
                            self._performOCR2()

                        # self._performOCR1()
                        # self._performOCR2()
                        # self._performOCR3()
                        if self.frame_counter % 3 == 0:
                            self._performOCR3()
        except:
            pass

这个项目展示了如何将PyQt、工业相机、OCR技术和串口通信结合起来,构建一个实用的工业视觉系统。希望对从事类似项目的开发者有所启发。

相关推荐
05大叔1 小时前
AI智能伴侣项目
人工智能·microsoft
中达瑞和-高光谱·多光谱2 小时前
光谱相机选购指南:核心参数全解析
数码相机
wq8973872 小时前
[AI问答]Ubuntu 24.04 上 PyTorch的环境搭建
人工智能·pytorch·ubuntu
安逸sgr2 小时前
MCP 协议深度解析(八):Prompts 提示模板与 Sampling 采样机制!
人工智能·分布式·学习·语言模型·协议·mcp
东离与糖宝2 小时前
小米MiMo-V2-Pro开放调用,Java后端快速接入全流程实战
java·人工智能
balmtv2 小时前
GPT-5.4镜像实测:gpt技术拆解——当AI学会操控电脑
人工智能·gpt·电脑
大傻^2 小时前
Spring AI 2.0 生产部署指南:从 1.x 迁移、性能调优与云原生实践
人工智能·spring·云原生·springai
不懒不懒2 小时前
【机器学习模型评估:8种算法对比实战(本篇文章先介绍6种)】
人工智能·机器学习
ejjdhdjdjdjdjjsl2 小时前
halcon算子
人工智能·算法·计算机视觉