《PaddleOCR》—— 多相机协同工业字符检测与异常报警系统

文章目录

PaddleOCR

PaddleOCR 是百度基于飞桨(PaddlePaddle)框架开源的全场景文字识别工具,支持多语言、多场景、高精度的 OCR 能力,覆盖文本检测、识别、方向分类等全流程,广泛应用于文档扫描、车牌识别、票据处理、工业质检等场景。

项目介绍

图形化界面设计:

主要用于产品包装的编号进行识别确认,当编号对比不正确时,发出警报和闪光,并保存错误图片结果。

项目代码:
注意:要改成自己的端口号,和摄像头的固定ip地址才可以进行使用。

python 复制代码
# 导入OpenCV库,用于计算机视觉任务,如图像和视频处理
import cv2
# 从collections模块导入Counter类,用于统计可迭代对象中元素的出现次数
from collections import Counter
# 导入os模块,用于与操作系统进行交互,如文件和目录操作
import os
# 从numpy模块导入ndarray类型,用于处理多维数组
from numpy import ndarray
# 导入sys模块,用于访问与Python解释器紧密相关的变量和函数
import sys
# 将指定路径添加到Python模块搜索路径中,以便可以导入该路径下的模块
sys.path.append(r"D:\MVS\Development\Samples\Python\MvImport")
# 从HKCamera_class模块导入HKCamera类,可能用于操作海康相机
from HKCamera_class import HKCamera
# 从PyQt5库导入QtCore、QtGui和QtWidgets模块,用于创建图形用户界面(GUI)
from PyQt5 import QtCore, QtGui, QtWidgets
# 从PyQt5.QtCore模块导入所有内容,方便使用其中的类和函数
from PyQt5.QtCore import *
# 从PyQt5.QtGui模块导入所有内容,用于处理图形、图像和字体等
from PyQt5.QtGui import *
# 从PyQt5.QtWidgets模块导入QFileDialog和QMainWindow类,QFileDialog用于文件选择对话框,QMainWindow是主窗口的基类
from PyQt5.QtWidgets import QFileDialog, QMainWindow
# 从金寨窗口0模块导入Ui_MainWindow类,可能是使用Qt Designer设计的主窗口界面类
from 金寨窗口0 import Ui_MainWindow
# 导入logging模块,用于记录程序运行时的信息
import logging
# 导入numpy库,用于科学计算和数组操作
import numpy as np
# 从paddleocr模块导入PaddleOCR类,用于进行光学字符识别(OCR)
from paddleocr import PaddleOCR
# 导入re模块,用于正则表达式操作,可用于字符串匹配和替换
import re
# 导入serial模块,用于串口通信
import serial
# 导入time模块,用于处理时间相关的操作,如延时
import time
# 导入argparse模块,用于解析命令行参数
import argparse
# 导入threading模块,用于创建和管理线程
import threading

# 定义一个函数,用于定时释放并重新打开摄像头资源
def release_capture2(cap):
    while True:
        # 每隔30分钟(1800秒)执行一次释放和重新打开操作
        time.sleep(1800)
        # 释放当前摄像头资源
        cap.release()
        # 重新打开指定编号的摄像头
        cap.open(opt.cap_numb3)

# 创建一个命令行参数解析器对象
parser = argparse.ArgumentParser()
# 添加一个命令行参数,用于指定第二个串口的端口号,默认值为COM4
parser.add_argument("--SERIAL_PORT1", type=str, default='COM5', help='第二个串口的端口号')
# 添加一个命令行参数,用于指定第一个串口的端口号,默认值为COM5
parser.add_argument("--SERIAL_PORT2", type=str, default='COM6', help='第一个串口的端口号')
# 添加一个命令行参数,用于指定识别置信度水平,默认值为0.88
parser.add_argument("--confid_level", type=float, default=0.88, help='识别置信度水平')
# 添加一个命令行参数,用于指定第三个摄像头的编号,默认值为1
parser.add_argument("--cap_numb3", type=int, default=1, help='第三个摄像头编号')
# 添加一个命令行参数,用于指定获取帧的延迟时间,默认值为67
parser.add_argument("--frame_delay", type=int, default=67, help='获取帧的延迟时间')
# 解析命令行参数,并将结果存储在opt对象中
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):
    aa = []
    # 使用空格分割输入字符串
    parts = input_string.split()
    # 定义正则表达式模式1:包含字母和数字,长度在2到10之间
    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}$')
    # 遍历分割后的每个部分
    for part in parts:
        # 检查是否符合模式1或模式2
        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

# 创建两个PaddleOCR对象,分别用于不同的OCR任务
ocr = PaddleOCR(use_angle_cls=True, use_gpu=True, lang='en', use_mp=True, total_process_num=8)
ocr2 = PaddleOCR(use_angle_cls=False, use_gpu=True, lang='en', use_mp=True, total_process_num=8)

# 定义一个继承自QMainWindow和Ui_MainWindow的类,用于创建主窗口
class PyQtMainEntry(QMainWindow, Ui_MainWindow):
    def __init__(self):
        # 调用父类的构造函数
        super().__init__()
        # 设置UI界面
        self.setupUi(self)

        # 打开第一个串口,波特率为9600,超时时间为2.5秒
        self.ser1 = serial.Serial(opt.SERIAL_PORT1, 9600, timeout=2.5)
        # 打开第二个串口,波特率为9600,超时时间为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.showMaximized()
        # 初始化第一个海康相机,指定相机IP地址
        self.camera1 = HKCamera(CameraIp='169.254.194.100')
        # 设置相机的像素格式为BayerGB8
        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')
        # 设置相机的采集帧率为15.0000
        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()

        # 初始化第二个海康相机,指定相机IP地址
        self.camera2 = HKCamera(CameraIp='169.254.194.200')
        # 设置相机的像素格式为BayerGB8
        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')
        # 设置相机的采集帧率为15.0000
        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_capture2, args=(self.camera3,))
        # 将线程设置为守护线程,主线程退出时该线程也会退出
        release_thread2.daemon = True
        # 启动线程
        release_thread2.start()

        # 初始化摄像头打开状态为False
        self.is_camera_opened = False
        # 创建一个Qt定时器对象
        self._timer = QtCore.QTimer(self)
        # 当定时器超时时,调用_queryFrame方法
        self._timer.timeout.connect(self._queryFrame)
        # 设置定时器的间隔时间,单位为毫秒
        self._timer.setInterval(opt.frame_delay)

        # 初始化帧计数器为0
        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\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)

    # 定义一个装饰器函数,用于在函数调用n次后执行另一个函数
    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:
                    # 当调用次数达到n的倍数时,执行指定的函数
                    w2set(self)
                return result
            # 初始化函数调用次数为0
            wrapper.count = 0
       # 返回包装函数
            return wrapper
        # 返回装饰器函数
        return decorator

    # 声明这是一个 PyQt 的槽函数,用于处理定时器超时事件,定期查询并处理摄像头帧
    @QtCore.pyqtSlot()
    def _queryFrame(self):
        try:
            # 尝试从第三个摄像头抓取一帧图像
            if not self.camera3.grab():
                # 如果抓取失败,打印提示信息
                print("No frame grabbed.")
                # 释放第三个摄像头资源
                self.camera3.release()
                # 关闭当前窗口
                self.close()
            else:
                # 如果抓取成功,从第一个海康相机获取图像,并指定返回的图像类型为 ndarray
                self.frame1: ndarray = self.camera1.get_image()
                # 从第二个海康相机获取图像,并指定返回的图像类型为 ndarray
                self.frame2: ndarray = self.camera2.get_image()
                # 从第三个摄像头读取一帧图像,ret3 表示是否成功读取,self.frame3 为读取的图像
                ret3, self.frame3 = self.camera3.read()

                # 检查是否成功从第三个摄像头读取到图像
                if not ret3:
                    # 如果读取失败,打印提示信息
                    print("No frame retrieved.")
                    # 注释掉的代码,原可能用于释放第一个相机资源
                    # self.camera1.release()
                    # 释放第三个摄像头资源
                    self.camera3.release()
                    # 关闭当前窗口
                    self.close()
                else:
                    # 如果读取成功,将第一个相机的图像调整为 640x480 大小
                    self.frame11 = cv2.resize(self.frame1, (640, 480))
                    # 将第二个相机的图像调整为 640x480 大小
                    self.frame22 = cv2.resize(self.frame2, (640, 480))
                    # 将第三个摄像头的图像调整为 640x480 大小
                    self.frame33 = cv2.resize(self.frame3, (640, 480))

                    # 再次检查是否成功从第三个摄像头读取到图像
                    if ret3:
                        # 将第一个相机调整大小后的图像从 BGR 颜色空间转换为 RGB 颜色空间
                        qimage = cv2.cvtColor(self.frame11.copy(), cv2.COLOR_BGR2RGB)
                        # 将转换后的图像数据封装为 QtGui.QImage 对象
                        qimage = QtGui.QImage(qimage.data, qimage.shape[1], qimage.shape[0], QtGui.QImage.Format_RGB888)
                        # 将 QImage 对象转换为 QPixmap 对象
                        pixmap = QtGui.QPixmap.fromImage(qimage)

                        # 将第二个相机调整大小后的图像从 BGR 颜色空间转换为 RGB 颜色空间
                        qimage2 = cv2.cvtColor(self.frame22.copy(), cv2.COLOR_BGR2RGB)
                        # 将转换后的图像数据封装为 QtGui.QImage 对象
                        qimage2 = QtGui.QImage(qimage2.data, qimage2.shape[1], qimage2.shape[0], QtGui.QImage.Format_RGB888)
                        # 将 QImage 对象转换为 QPixmap 对象
                        pixmap2 = QtGui.QPixmap.fromImage(qimage2)

                        # 将第三个摄像头调整大小后的图像从 BGR 颜色空间转换为 RGB 颜色空间
                        qimage3 = cv2.cvtColor(self.frame33.copy(), cv2.COLOR_BGR2RGB)
                        # 将转换后的图像数据封装为 QtGui.QImage 对象
                        qimage3 = QtGui.QImage(qimage3.data, qimage3.shape[1], qimage3.shape[0], QtGui.QImage.Format_RGB888)
                        # 将 QImage 对象转换为 QPixmap 对象
                        pixmap3 = QtGui.QPixmap.fromImage(qimage3)

                        # 将第一个相机图像对应的 QPixmap 对象设置到名为 label 的标签上进行显示
                        self.label.setPixmap(pixmap)
                        # 将第二个相机图像对应的 QPixmap 对象设置到名为 label_3 的标签上进行显示
                        self.label_3.setPixmap(pixmap2)
                        # 将第三个摄像头图像对应的 QPixmap 对象设置到名为 label_2 的标签上进行显示
                        self.label_2.setPixmap(pixmap3)

                        # 每处理一帧图像,帧计数器加 1
                        self.frame_counter += 1
                        # 检查帧计数器是否是 5 的倍数
                        if self.frame_counter % 5 == 0:
                            # 如果是 5 的倍数,调用 _performOCR1 方法进行 OCR 识别
                            self._performOCR1()
                        # 检查帧计数器是否是 3 的倍数
                        if self.frame_counter % 3 == 0:
                            # 如果是 3 的倍数,调用 _performOCR2 方法进行 OCR 识别
                            self._performOCR2()

                        # 注释掉的代码,原可能用于调用 OCR 识别方法
                        # self._performOCR1()
                        # self._performOCR2()
                        # self._performOCR3()
                        # 再次检查帧计数器是否是 3 的倍数
                        if self.frame_counter % 3 == 0:
                            # 如果是 3 的倍数,调用 _performOCR3 方法进行 OCR 识别
                            self._performOCR3()
        except:
            # 捕获所有异常,不做具体处理,避免程序因异常而崩溃
            pass

        

    # 定义一个方法 w2set1,用于处理第一个相机的 OCR 识别结果,进行结果分析、显示和异常处理
    def w2set1(self):
        # 初始化最终答案为空字符串
        self.daan1 = ''
        # 检查存储第一个相机 OCR 识别结果的列表是否不为空
        if self.list_zong1:
            try:
                # 遍历存储第一个相机 OCR 识别结果的列表
                for i in self.list_zong1:
                    # 检查元素 i 在列表中出现的次数是否大于 1
                    if self.list_zong1.count(i) > 1:
                        # 如果出现次数大于 1,将该元素添加到第一个相机结果集合中
                        self.set_zong1.add(i)
                # 对第一个相机结果集合进行字符替换处理,得到处理后的集合
                set_len1 = set_bing(self.set_zong1)  # 当前的结果

                # 遍历处理后的集合
                for j in set_len1:
                    # 将集合中的元素添加到临时列表 list1 中
                    self.list1.append(j)

                # 找出临时列表 list1 中出现次数最多的元素,作为最终答案
                self.daan1 = most_common_element(self.list1)

                # 检查帧计数器是否是 16 的倍数,大约每 10 秒刷新一次临时列表
                if self.frame_counter % 16 == 0:
                    # 如果是 16 的倍数,清空临时列表 list1
                    self.list1.clear()

                # 将处理后的集合中的元素用逗号连接成一个字符串
                text = ','.join(set_len1)
                # 将连接后的字符串显示在名为 lineEdit 的文本框中
                self.lineEdit.setText(text)

                # 进行结果判断
                # 如果最终答案为空或者处理后的集合为空,不做任何处理
                if (not self.daan1) or (not set_len1):
                    pass
                # 如果处理后的集合中只有一个元素,且该元素等于最终答案,不做任何处理
                elif len(set_len1) == 1 and (list(set_len1)[0] == self.daan1):
                    pass
                else:
                    # 如果不满足上述条件,打印提示信息
                    print('一')
                    # 打印处理后的集合和最终答案
                    print(set_len1, self.daan1)

                    # 在第一个相机的图像副本上添加文本信息,文本内容为处理后的集合元素连接成的字符串
                    tu1 = cv2.putText(self.frame1.copy(), text, (100, 200), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 5,
                                      cv2.LINE_AA)

                    # 获取当前时间,格式化为指定的字符串
                    nam = time.strftime("%Y_%m_%d_%H_%M", time.localtime())
                    # 构建保存异常图像的文件路径
                    path = os.path.join(r'D:\MVS\Development\Samples\Python\shiyan\baojing\相机一',
                                        f'{nam}-{self.daan1}.jpg')
                    # 将添加文本后的图像保存到指定路径
                    cv2.imencode('.jpg', tu1)[1].tofile(path)

                    # 显示名为 label_4 的标签,可能用于提示异常信息
                    self.label_4.setVisible(True)
                    # 发送打开灯光的命令到第一个串口,可能用于触发灯光提示
                    # sendCmdToDevice(LIGHT_BUZZ1, self.ser1)
                    sendCmdToDevice(LIGHT, self.ser1)
                    # 5 秒后隐藏标签并发送关闭蜂鸣器的命令
                    QtCore.QTimer.singleShot(5000, lambda: hide_label_and_send_cmd(self.label_4, self.ser1))

                    # 清空存储第一个相机 OCR 识别结果的列表和集合
                    # self.list_zong1.clear()
                    # self.set_zong1.clear()
                    # 重置帧计数器为 0
                    self.frame_counter = 0
            except Exception as e:
                # 捕获异常并打印异常信息
                print(e)
            # 清空存储第一个相机 OCR 识别结果的列表和集合
            self.list_zong1.clear()
            self.set_zong1.clear()

    # 使用 execute_after_n_calls 装饰器,使得 _performOCR1 方法每调用 5 次后执行 w2set1 方法
    @execute_after_n_calls(5, w2set1)
    # 定义一个方法 _performOCR1,用于对第一个相机的图像进行 OCR 识别
    def _performOCR1(self):
        # 使用 ocr2 对第一个相机的图像进行 OCR 识别,不进行角度分类
        result = ocr2.ocr(self.frame1, cls=False)
        # 检查识别结果是否不为空,且结果中不包含 None
        if result and not None in result:
            try:
                # 遍历识别结果中的每一项
                for i in result[0]:
                    # 打印当前识别项
                    # print(i)
                    # 计算当前识别项所包围区域的面积
                    mianji = are(i)
                    # 打印当前识别项和其面积
                    # print('yi',i,mianji)
                    # 对当前识别项的文本内容进行处理,提取符合条件的部分
                    ma = process_string(i[1][0])
                    # 检查识别置信度是否大于 0.92,处理后的文本是否不为空,识别区域的左上角 x 坐标是否在 320 到 1060 之间,且面积是否大于 5000
                    # if i[1][1] > 0.92 and ma and 80<i[0][0][0]<550 and mianji>1000:
                    if i[1][1] > 0.92 and ma and 320 < i[0][0][0] < 1060 and mianji > 5000:
                        # 如果满足条件,将处理后的文本添加到存储第一个相机 OCR 识别结果的列表中
                        self.list_zong1.extend(ma)
                        # 打印当前识别项的文本内容和其面积
                        # print(i[1][0], are(i))
                # 打印分割线,用于区分不同帧的识别结果
                # print('******************')
            except:
                # 捕获异常,不做具体处理
                pass

    # 定义处理第二个相机OCR结果的方法,进行结果分析、显示和异常处理
    def w2set2(self):
        self.daan2 = ''  # 初始化最终答案为空字符串
        if self.list_zong2:  # 检查存储第二个相机OCR结果的列表是否有数据
            try:
                # 遍历列表,统计出现次数超过1次的元素并存入集合
                for i in self.list_zong2:
                    if self.list_zong2.count(i) > 1:
                        self.set_zong2.add(i)

                # 对集合中的字符进行替换处理
                set_len2 = set_bing(self.set_zong2)

                # 将处理后的字符添加到临时列表
                for j in set_len2:
                    self.list2.append(j)

                # 找出临时列表中出现次数最多的元素作为最终答案
                self.daan2 = most_common_element(self.list2)

                # 每25帧清空临时列表(约14秒刷新)
                if self.frame_counter % 25 == 0:
                    self.list2.clear()

                # 将结果显示在文本框中
                text2 = ','.join(set_len2)
                self.lineEdit_2.setText(text2)

                # 结果验证逻辑
                if (not self.daan2) or (not set_len2):
                    pass  # 结果为空时不处理
                elif len(set_len2) == 1 and (list(set_len2)[0] == self.daan2):
                    pass  # 唯一结果时不处理
                else:
                    print('二')  # 打印调试信息
                    print(set_len2, self.daan2)  # 输出当前结果和最终答案

                    # 在图像上添加文本标注
                    tu2 = cv2.putText(self.frame2.copy(), text2, (100, 200),
                                      cv2.FONT_HERSHEY_SIMPLEX, 3, (0, 0, 255), 6, cv2.LINE_AA)

                    # 保存异常图像
                    nam2 = time.strftime("%Y_%m_%d_%H_%M", time.localtime())
                    path = os.path.join(r'D:\MVS\Development\Samples\Python\shiyan\baojing\相机二',
                                        f'{nam2}-{self.daan2}.jpg')
                    cv2.imencode('.jpg', tu2)[1].tofile(path)

                    # 显示报警标签并控制灯光
                    self.label_5.setVisible(True)
                    sendCmdToDevice(LIGHT, self.ser2)  # 打开灯光
                    # 5秒后关闭灯光并隐藏标签
                    QtCore.QTimer.singleShot(5000, lambda: hide_label_and_send_cmd(self.label_5, self.ser2))

                    # 重置帧计数器
                    self.frame_counter = 0
            except Exception as e:
                print(e)  # 异常处理
            finally:
                # 清空临时数据
                self.list_zong2.clear()
                self.set_zong2.clear()

        # 合并两个相机的识别结果
        self.set_12hun = set([self.daan1, self.daan2])
        print('1:', self.daan1)  # 打印调试信息
        print('2:', self.daan2)
        print('合:', self.set_12hun)

    # 使用装饰器控制OCR执行频率(每3次调用后处理结果)
    @execute_after_n_calls(3, w2set2)
    def _performOCR2(self):
        result2 = ocr2.ocr(self.frame2, cls=False)  # 执行OCR识别
        if result2 and not None in result2:
            try:
                for i in result2[0]:
                    mianji = are(i)  # 计算识别区域面积
                    ma = process_string(i[1][0])  # 文本处理
                    # 置信度、文本有效性、区域位置和面积过滤条件
                    if i[1][1] > 0.84 and ma and mianji > 1600 and (350 < i[0][0][0] < 1060):
                        self.list_zong2.extend(ma)  # 保存有效识别结果
            except:
                pass
        elif None in result2:
            # 处理识别结果包含None的情况
            self.daan2 = ''
            self.set_12hun = set([self.daan1, self.daan2])
            print('1:', self.daan1)
            print('2:', self.daan2)
            print('合:', self.set_12hun)

    # 定义处理第三个相机OCR结果的方法
    def w2set3(self):
        maxx = 0  # 记录最大面积
        if self.list_zong3:
            try:
                # 提取所有识别文本并统计出现次数
                for i in self.list_zong3:
                    self.guo.append(i[1][0])
                for i in self.guo:
                    if self.guo.count(i) > 1:
                        self.set_zong3.add(i)

                # 找出最大面积的有效识别结果
                for j in self.list_zong3:
                    if (j[1][0] in self.set_zong3) and (are(j) > maxx):
                        aa = j
                        maxx = are(j)

                # 文本处理并显示
                text3 = aa[1][0].replace('0', 'O').replace('o', 'O').replace('S', '5').replace('I', '1').replace('L',
                                                                                                                 '1').replace(
                    'v', 'V').replace('B', '8').replace('p', 'P')
                self.lineEdit_3.setText(text3)

                # 结果验证逻辑
                if (not text3) or self.set_12hun == set() or self.set_12hun == {''}:
                    pass  # 无效结果不处理
                elif self.set_12hun and In_which(text3, self.set_12hun):
                    pass  # 匹配到合并结果不处理
                else:
                    print('三')  # 打印调试信息
                    print(text3)
                    print(self.set_12hun)

                    # 在图像上添加文本标注
                    tu3 = cv2.putText(self.frame3.copy(), text3, (100, 200),
                                      cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 5, cv2.LINE_AA)

                    # 保存异常图像
                    nam3 = time.strftime("%Y_%m_%d_%H_%M", time.localtime())
                    path = os.path.join(r'D:\MVS\Development\Samples\Python\shiyan\baojing\相机三',
                                        f'{nam3}-{self.set_12hun}.jpg')
                    cv2.imencode('.jpg', tu3)[1].tofile(path)

                    # 重置合并结果并触发报警
                    self.set_12hun.clear()
                    self.label_6.setVisible(True)
                    sendCmdToDevice(LIGHT, self.ser2)  # 打开灯光
                    QtCore.QTimer.singleShot(5000, lambda: hide_label_and_send_cmd(self.label_6, self.ser2))
            except:
                pass
            finally:
                # 清空临时数据
                self.guo.clear()
                self.list_zong3.clear()
                self.set_zong3.clear()

    # 使用装饰器控制OCR执行频率(每3次调用后处理结果)
    @execute_after_n_calls(3, w2set3)
    def _performOCR3(self):
        global text3
        try:
            # 颜色检测预处理
            hsv_image = cv2.cvtColor(self.frame3, cv2.COLOR_BGR2HSV)
            lower_brown = np.array([10, 30, 30])
            upper_brown = np.array([30, 255, 255])
            mask = cv2.inRange(hsv_image, lower_brown, upper_brown)
            white_pixels = np.sum(mask == 255)
            total_pixels = mask.shape[0] * mask.shape[1]
            color_percentage = (white_pixels / total_pixels) * 100

            # 当棕色区域超过20%时执行OCR
            if color_percentage >= 20:
                result2 = ocr2.ocr(self.frame3, cls=False)
                if result2 and not None in result2:
                    for j in result2[0]:
                        # 置信度和文本有效性过滤
                        if j[1][1] > 0.93 and process_string(j[1][0]):
                            self.list_zong3.append(j)
                else:
                    text3 = ''
        except:
            text3 = ''


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)  # 创建Qt应用程序实例
    window = PyQtMainEntry()  # 创建主窗口实例
    window.show()  # 显示主窗口
    sys.exit(app.exec_())  # 进入应用程序主循环
相关推荐
小鱼仙倌要会换位思考呀3 小时前
在 Colab 环境中自定义 YOLO 输入尺寸并取消 Letterbox 填充,实战网球高速检测
计算机视觉
xinxiangwangzhi_4 小时前
多视图几何--从线性变换到射影变换--2线性变换
人工智能·算法·计算机视觉
AI技术控4 小时前
计算机视觉算法实战——昆虫识别检测(主页有源码)
人工智能·算法·计算机视觉
蜡笔小新星5 小时前
OpenCV中文路径图片读写终极指南(Python实现)
开发语言·人工智能·python·opencv·计算机视觉
六月的翅膀5 小时前
C++/OpenCV:Mat初始化赋值误区
人工智能·opencv·计算机视觉
好评笔记5 小时前
AIGC视频生成模型:慕尼黑大学、NVIDIA等的Video LDMs模型
人工智能·深度学习·机器学习·计算机视觉·aigc·transformer·面试八股
Coovally AI模型快速验证6 小时前
DeepSeek引领端侧AI革命,边缘智能重构AI价值金字塔
人工智能·算法·目标检测·计算机视觉·边缘计算·deepseek
YuhsiHu7 小时前
【论文精读】ACE-Zero
人工智能·深度学习·计算机视觉·3d·机器人
蹦蹦跳跳真可爱5898 小时前
Python----计算机视觉处理(opencv:图片灰度化)
人工智能·python·opencv·计算机视觉
_zwy9 小时前
通义万相2.1 图生视频:为AI绘梦插上翅膀,开启ALGC算力领域新纪元
人工智能·深度学习·计算机视觉·ai作画