目录
[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. 多线程显示卡顿)
引言
在工业自动化领域,视觉识别与实时报警系统有着广泛的应用场景。本文将分享一个基于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技术和串口通信结合起来,构建一个实用的工业视觉系统。希望对从事类似项目的开发者有所启发。