基于PyQt5+OpenCV+PaddleOCR的多摄像头识别报警系统实现
在工业检测、安防监控等场景中,多摄像头协同识别+异常报警是常见的需求。本文将详细介绍一个基于PyQt5、OpenCV、PaddleOCR和串口通信的多摄像头识别报警系统,该系统可实现3路摄像头(2路工业相机+1路USB相机)的实时采集、OCR型号识别、异常情况判断及报警器联动控制,完整还原项目实现过程,供大家参考学习。
一、项目核心需求
本项目主要实现以下核心功能,适配工业场景下的自动化识别与报警需求:
-
多摄像头协同采集:2路HK工业相机(固定IP)+1路USB相机,实时获取画面并在PyQt5界面展示;
-
OCR型号识别:基于PaddleOCR识别画面中的字符/型号,筛选有效信息并处理易错字符,提升识别准确率;
-
异常判断与报警:对比识别结果与基准值,出现异常时触发报警器(闪光+声音),同时隐藏警告标识、保存异常画面;
-
辅助功能:摄像头资源定期释放、界面控制(打开/关闭摄像头、复位、打开报警日志文件夹)、后台线程联动,确保系统稳定运行。
二、核心技术栈
项目依赖的技术框架与第三方库如下,需提前安装配置:
-
界面开发:PyQt5(实现主窗口、摄像头画面展示、按钮控制、输入框显示);
-
摄像头采集:OpenCV(USB相机采集与画面处理)、HKCamera_class(HK工业相机控制,需导入对应SDK);
-
字符识别:PaddleOCR(中英文识别,启用GPU加速提升识别速度);
-
串口通信:pyserial(与报警器联动,发送控制指令);
-
其他:numpy(数值计算)、collections.Counter(统计识别结果频次)、threading(后台线程,释放摄像头资源)、logging(日志控制)。
三、项目整体结构
项目代码采用模块化设计,核心分为5个部分,逻辑清晰、易于维护,整体结构如下:
-
导入依赖与参数配置:导入所需库、配置命令行参数(串口、置信度、摄像头编号等);
-
工具函数定义:实现报警器控制、识别结果处理、字符面积计算等通用功能;
-
OCR初始化:创建PaddleOCR实例,配置GPU加速与语言类型;
-
主窗口类(PyQtMainEntry):核心模块,实现摄像头初始化、画面采集、OCR识别、报警联动、界面交互;
-
程序入口:初始化PyQt应用,启动主窗口。
四、核心代码解析
以下针对关键模块、核心函数进行详细解析,结合代码逻辑说明实现思路,重点讲解难点与易错点。
4.1 参数配置与指令声明
通过argparse模块配置可自定义参数,方便后续部署时调整,无需修改代码;同时声明报警器控制指令(十六进制),用于串口通信。
import cv2
from collections import Counter
import os
from numpy import ndarray
import sys
sys.path.append(r"D:\MVS\MVS\Development\Samples\Python\MvImport") # 导入HK相机SDK路径
from HKCamera_class import HKCamera
from PyQt5 import QtCore, QtGui, QtWidgets
# 省略其他导入...
# 命令行参数配置
parser = argparse.ArgumentParser()
parser.add_argument("--SERIAL_PORT1", type=str, default='COM5', help='第一个报警器的串口号')
parser.add_argument("--SERIAL_PORT2", type=str, default='COM4', 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" # 闪光+声音2(注:代码中注释与指令一致,可根据实际需求调整)
LIGHT = "0110001A0001028E19" # 仅闪光
BUZZ1 = "0110001A0001034FD9" # 仅声音1
BUZZ2 = "0110001A000105CFDB" # 仅声音2
BUZZ_CMD_CLOSE = "0110001A0001000FD8" # 关闭声音和闪光
易错点:SDK路径需根据实际安装位置调整,否则会导致HKCamera_class导入失败;报警器指令需与硬件对应,错误指令会导致报警器无响应。
4.2 核心工具函数
工具函数封装通用功能,降低代码冗余,主要包括以下4个关键函数:
4.2.1 报警器控制函数(sendCmdToDevice)
将十六进制指令转换为字节,通过串口发送给报警器,实现报警控制。
def sendCmdToDevice(cmd, ser): # 控制警报器
cmdd = bytes.fromhex(cmd) # 十六进制指令转字节
ser.write(cmdd) # 串口发送指令
4.2.2 识别结果处理函数(process_string、set_bing)
process_string:筛选OCR识别结果中的有效型号(含字母+数字、或4-7位纯数字);set_bing:替换易错字符(如0/O、s/5等),提升识别准确率。
def process_string(input_string): # 在ocr的结果中筛选出对应型号
aa=[]
parts = input_string.split() # 空格切分识别结果
# 正则筛选:含字母+数字 或 4-7位纯数字
pattern_alphanumeric = re.compile(r'^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d-]{2,10}$')
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
4.2.3 字符面积计算函数(are)
计算OCR识别到的字符区域面积,用于过滤无效识别结果(面积过小的字符可能是干扰项)。
def are(i): # 计算字符面积(基于OCR返回的字符坐标)
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
return width * height # 返回面积
4.2.4 后台线程函数(release_capture3)
USB相机长时间运行易出现资源占用问题,通过后台守护线程,每30分钟释放一次USB相机资源并重新打开,确保系统稳定。
def release_capture3(cap): # 定期释放usb摄像头资源
while True:
time.sleep(1800) # 每30分钟(1800秒)执行一次
cap.release() # 释放资源
cap.open(opt.cap_numb3) # 重新打开相机
4.3 主窗口类核心逻辑(PyQtMainEntry)
主窗口类是项目的核心,集成了摄像头初始化、画面采集、OCR识别、报警联动、界面交互等所有功能,重点解析以下关键方法。
4.3.1 初始化方法(__init__)
完成界面初始化、摄像头配置、串口初始化、线程启动等操作,是程序启动的入口。
class PyQtMainEntry(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self) # 初始化PyQt界面(由Qt Designer生成)
# 初始化串口(连接报警器)
self.ser1 = serial.Serial(opt.SERIAL_PORT1, 9600, timeout=2.5)
self.ser2 = serial.Serial(opt.SERIAL_PORT2, 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 = []
# 初始化HK工业相机(1号、2号相机,固定IP)
self.camera1 = HKCamera(CameraIp='192.168.20.30')
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.start_camera() # 启动相机
self.camera2 = HKCamera(CameraIp='192.168.20.20')
# 2号相机配置与1号一致,省略重复代码...
self.camera2.start_camera()
# 初始化USB相机(3号相机)
self.camera3 = cv2.VideoCapture(opt.cap_numb3)
# 启动后台线程,定期释放USB相机资源
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 # 统计画面帧数(用于控制OCR识别频率)
关键说明:HK工业相机的参数(PixelFormat、GainAuto等)需根据相机型号调整;定时器间隔(frame_delay)决定画面采集频率,影响系统性能与识别实时性。
4.3.2 画面采集与OCR识别(_queryFrame)
定时器触发的核心方法,实现3路摄像头画面采集、缩放、显示,并控制OCR识别频率(每隔一定帧数执行一次OCR,避免占用过多资源)。
@QtCore.pyqtSlot()
def _queryFrame(self):
try:
if not self.camera3.grab(): # 检查USB相机是否能获取画面
print("No frame grabbed.")
self.camera3.release()
self.close()
else:
# 采集3路相机画面
self.frame1: ndarray = self.camera1.get_image()
self.frame2: ndarray = self.camera2.get_image()
ret3, self.frame3 = self.camera3.read()
if not ret3: # USB相机采集失败处理
print("No frame retrieved.")
self.camera3.release()
self.close()
else:
# 缩放画面(适配界面显示,640*480)
self.frame11 = cv2.resize(self.frame1, (640, 480))
self.frame22 = cv2.resize(self.frame2, (640, 480))
self.frame33 = cv2.resize(self.frame3, (640, 480))
# 画面转换为PyQt可显示的格式,并显示在界面标签上
qimage = cv2.cvtColor(self.frame11.copy(), cv2.COLOR_BGR2RGB)
qimage = QtGui.QImage(qimage.data, qimage.shape[1], qimage.shape[0], QtGui.QImage.Format_RGB888)
self.label.setPixmap(QtGui.QPixmap.fromImage(qimage))
# 2号、3号相机画面显示,省略重复代码...
# 控制OCR识别频率(每隔指定帧数执行,避免频繁识别)
self.frame_counter += 1
if self.frame_counter % 5 == 0:
self._performOCR1() # 1号相机OCR识别
if self.frame_counter % 3 == 0:
self._performOCR2() # 2号相机OCR识别
self._performOCR3() # 3号相机OCR识别
except:
pass
4.3.3 OCR识别与异常判断(_performOCR系列+w2set系列)
_performOCR1/2/3:分别对3路相机画面执行OCR识别,筛选有效结果并存储;w2set1/2/3:对识别结果进行统计分析,判断是否异常,触发报警。
以1号相机为例(2号、3号逻辑类似,仅参数不同):
# 装饰器:控制OCR识别后,每执行3次调用w2set1(统计分析+异常判断)
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
@execute_after_n_calls(3, w2set1)
def _performOCR1(self):
result = ocr2.ocr(self.frame1, cls=False) # OCR识别(不进行角度校正)
if result and not None in result:
try:
for i in result[0]:
mianji = are(i) # 计算字符面积
ma = process_string(i[1][0]) # 筛选有效型号
# 过滤条件:置信度>0.92、有有效型号、字符坐标在指定范围、面积>1000(过滤干扰)
if i[1][1] > 0.92 and ma and 320 < i[0][0][0]< 1060 and mianji > 1000:
self.list_zong1.extend(ma) # 存储有效识别结果
except:
pass
def w2set1(self):
if self.list_zong1:
try:
# 筛选出现次数>1的识别结果(降低误识别概率)
for i in self.list_zong1:
if self.list_zong1.count(i) > 1:
self.set_zong1.add(i)
set_len1 = set_bing(self.set_zong1) # 替换易错字符
self.list1.extend(list(set_len1))
self.daan1 = most_common_element(self.list1) # 取出现次数最多的作为基准值
# 定期清空识别结果列表,避免数据冗余
if self.frame_counter % 16 == 0:
self.list1.clear()
# 在界面输入框显示识别结果
text = ','.join(set_len1)
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:
# 异常处理:保存异常画面、显示警告标识、触发报警器
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\MVS\Development\Samples\Python\shiyan\baojing\镜头一', f'{nam}-{self.daan1}.jpg')
cv2.imencode('.jpg', tu1)[1].tofile(path) # 保存异常画面
self.label_4.setVisible(True) # 显示警告标识
sendCmdToDevice(LIGHT, self.ser1) # 触发闪光报警
# 5秒后隐藏警告标识、关闭报警
QtCore.QTimer.singleShot(5000, lambda: hide_label_and_send_cmd(self.label_4,self.ser1))
self.frame_counter = 0 # 重置帧数计数器
except Exception as e:
print(e)
# 清空当前识别结果容器,准备下一轮识别
self.list_zong1.clear()
self.set_zong1.clear()
关键说明:3号相机额外增加了颜色检测(牛皮纸颜色),只有当颜色占比≥20%时才执行OCR识别,进一步过滤无效画面,提升识别准确性。
4.4 界面交互方法
实现界面按钮的点击事件,包括摄像头打开/关闭、复位、打开报警日志文件夹等功能,提升系统易用性。
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)
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): # 1号相机复位按钮
self.set_zong1.clear()
self.list1.clear()
sendCmdToDevice(BUZZ_CMD_CLOSE, self.ser1)
self.label_4.setVisible(False)
# 2号相机复位按钮(clearSet2),逻辑与clearSet1一致,省略代码...
五、项目测试与注意事项
5.1 测试步骤
-
配置依赖环境,安装所需第三方库,确保HK相机SDK路径正确;
-
连接硬件(工业相机、USB相机、报警器),确认串口端口、相机IP与代码一致;
-
运行代码,点击“打开”按钮,查看3路相机画面是否正常显示;
-
放置含目标型号的物体,测试OCR识别是否准确,异常情况是否触发报警;
-
测试复位、关闭摄像头、打开日志文件夹等功能是否正常。
5.2 注意事项
-
SDK路径、相机IP、串口端口、报警日志路径等,需根据实际环境修改,否则会导致程序运行失败;
-
PaddleOCR启用GPU加速需配置对应环境(如CUDA),无GPU可将use_gpu=False,识别速度会略有下降;
-
OCR识别的置信度、字符面积阈值、帧数间隔等参数,需根据实际场景调试,提升识别准确率;
-
串口通信时,需确保报警器与电脑串口连接正常,波特率(9600)与代码一致;
-
长时间运行时,需注意电脑资源占用,可适当调整画面分辨率、OCR识别频率。
六、项目总结
本项目实现了多摄像头协同识别与报警的完整功能,基于PyQt5打造了简洁易用的界面,结合OpenCV、PaddleOCR实现了高效的画面采集与字符识别,通过串口通信实现了报警器联动,可直接适配工业检测、安防监控等场景。
项目的核心亮点的是:多摄像头协同工作、易错字符处理提升识别准确率、后台线程释放资源确保系统稳定、界面交互友好且可灵活配置参数。后续可根据实际需求,优化识别算法、增加画面标注、完善日志记录等功能。
后续将补充硬件信息,完善项目测试细节,如有疑问或优化建议,欢迎在评论区交流~
更多推荐



所有评论(0)