当前位置: 首页 > news >正文

基于机器视觉的工业产品型号识别与报警系统实现

在工业生产检测场景中,快速、准确识别产品型号并对异常型号及时报警,是保障产品质量和生产效率的关键环节。本文将分享一套基于 Python、OpenCV、PaddleOCR 和 PyQt5 实现的多摄像头产品型号识别与报警系统,该系统可实时采集多路摄像头画面,通过 OCR 识别产品型号,对比标准型号后触发声光报警,并保存异常画面。

一、系统整体架构

系统核心功能分为三大部分:

  1. 摄像头数据采集:支持海康工业相机(网口)和 USB 摄像头,实时获取多路画面;
  2. OCR 型号识别:基于 PaddleOCR 实现字符识别,针对工业场景字符易混淆问题做专项优化;
  3. 异常检测与报警:对比识别结果与基准型号,异常时触发声光报警并保存取证画面。

系统整体流程:

摄像头画面采集 → 帧间隔OCR识别 → 字符清洗与标准化 → 型号对比 → 异常则声光报警+画面存档

二、核心技术选型

表格

技术 / 库用途
PyQt5可视化界面搭建,摄像头画面展示,按钮交互
OpenCV图像预处理、摄像头数据读取、画面绘制
PaddleOCR光学字符识别,支持中英文、角度分类
serial串口通信,控制声光报警器
threading后台线程管理,避免界面卡顿
numpy图像数据处理、字符区域面积计算

三、核心代码实现

1. 环境准备

首先安装依赖包:

pip install opencv-python pyqt5 paddleocr numpy pyserial

2. 系统初始化(核心类定义)

import cv2 from collections import Counter import os from numpy import ndarray import sys import logging import numpy as np from paddleocr import PaddleOCR import re import serial import time import argparse import threading from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import QFileDialog, QMainWindow # 报警指令定义 LIGHT_BUZZ1 = "0110001A000101CE18" # 闪光+声音1 LIGHT = "0110001A0001028E19" # 仅闪光 BUZZ_CMD_CLOSE = "0110001A0001000FD8" # 关闭报警 # 初始化OCR引擎 ocr = PaddleOCR(use_angle_cls=True,use_gpu=True, lang='en') ocr2 = PaddleOCR(use_angle_cls=False, use_gpu=True, lang='en') # 命令行参数解析 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() 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) # 初始化UI元素(隐藏报警标签) self.label_4.setVisible(False) # 摄像头1异常标签 self.label_5.setVisible(False) # 摄像头2异常标签 self.label_6.setVisible(False) # 摄像头3异常标签 # 初始化数据存储容器 self.list_zong1 = [] # 摄像头1识别结果列表 self.list_zong2 = [] # 摄像头2识别结果列表 self.list_zong3 = [] # 摄像头3识别结果列表 self.set_zong1 = set() self.set_zong2 = set() self.set_zong3 = set() self.set_12hun = set() # 摄像头1+2基准型号集合 # 初始化摄像头 # 海康工业相机1 self.camera1 = HKCamera(CameraIp='192.168.20.20') 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() # 海康工业相机2 self.camera2 = HKCamera(CameraIp='192.168.20.40') 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.start_camera() # USB摄像头3 self.camera3 = cv2.VideoCapture(opt.cap_numb3) # 启动后台线程定期释放USB摄像头资源(防止内存泄漏) release_thread2 = threading.Thread(target=self.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 # 帧数计数器

3. 关键工具函数

(1)串口指令发送(控制报警器)
def sendCmdToDevice(cmd, ser): """向报警器发送串口指令""" cmdd = bytes.fromhex(cmd) ser.write(cmdd) def hide_label_and_send_cmd(label,ser=None): """隐藏报警标签并停止报警""" label.setVisible(False) if ser: sendCmdToDevice(BUZZ_CMD_CLOSE, ser)
(2)字符标准化(解决易混淆字符问题)
def set_bing(set_a): """替换工业场景易混淆字符:0→O、s→5、I→1等""" 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 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: if pattern_alphanumeric.match(part) or pattern_at_least_two_digits.match(part): aa.append(part) return aa
(3)字符区域面积计算(筛选最大字符区域)
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)) width = (width_A + width_B) / 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)) height = (height_A + height_B) / 2 area = width * height return area

4. 摄像头画面采集与 OCR 识别

def _queryFrame(self): """定时器回调:采集摄像头画面并触发OCR""" try: if not self.camera3.grab(): print("USB摄像头无画面") 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 ret3: # 调整画面尺寸并显示到UI self.frame11 = cv2.resize(self.frame1, (640, 480)) self.frame22 = cv2.resize(self.frame2, (640, 480)) self.frame33 = cv2.resize(self.frame3, (640, 480)) # 转换为Qt图像格式并显示 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)) qimage2 = cv2.cvtColor(self.frame22.copy(), cv2.COLOR_BGR2RGB) qimage2 = QtGui.QImage(qimage2.data, qimage2.shape[1], qimage2.shape[0], QtGui.QImage.Format_RGB888) self.label_3.setPixmap(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) self.label_2.setPixmap(QtGui.QPixmap.fromImage(qimage3)) # 帧间隔触发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 Exception as e: print(f"画面采集异常:{e}") # 装饰器:指定调用次数后执行数据处理 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, lambda self: self.w2set1()) def _performOCR1(self): """摄像头1 OCR识别""" result = ocr2.ocr(self.frame1, cls=False) 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): """摄像头1识别结果处理:异常判断+报警""" 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) # 取出现次数最多的作为基准 # 10秒刷新一次基准列表 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(f"摄像头1处理异常:{e}") finally: self.list_zong1.clear() self.set_zong1.clear()

5. 系统运行入口

if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = PyQtMainEntry() window.show() sys.exit(app.exec_())

四、系统亮点

  1. 多摄像头适配:同时支持工业网口相机和 USB 摄像头,满足不同场景需求;
  2. 字符鲁棒识别:针对工业场景易混淆字符(0/O、1/I/L、5/S 等)做专项替换,提升识别准确率;
  3. 性能优化:通过帧间隔 OCR、字符面积筛选、出现次数统计,降低计算量同时提升识别稳定性;
  4. 异常处理完善:异常时自动保存取证画面、触发声光报警,5 秒后自动复位,无需人工干预;
  5. 线程安全:后台线程管理摄像头资源,避免 UI 卡顿和资源泄漏。

五、扩展方向

  1. 模型优化:针对特定产品型号定制 PaddleOCR 训练数据集,进一步提升识别准确率;
  2. 远程监控:增加网络推送功能,将异常画面和报警信息推送到手机 / 电脑端;
  3. 数据统计:增加数据库存储功能,统计异常型号出现频次、报警时间等,辅助生产分析;
  4. 自适应调节:根据环境光照自动调整相机曝光参数,提升复杂环境下的识别稳定性。

该系统已在实际工业检测场景中落地应用,能够有效替代人工肉眼识别,提升检测效率和准确率,降低漏检、错检率。核心代码具备良好的可扩展性,可根据不同工业场景的需求快速适配调整。

http://www.cnnetsun.cn/news/2485287.html

相关文章:

  • Tokio运行时Worker挂死原理剖析与防御实践
  • 从 WebGPT 到 WebAgent:搜索增强型智能体演进
  • ARM Cortex-A53缓存策略实战:手把手教你配置MMU页表优化程序性能
  • AI写论文必备攻略!4款AI论文写作工具,开启高效论文创作之旅!
  • MATLAB R2026a安装教程
  • 从零开始学习AI Agent的实战路线图
  • 告别Gym,拥抱Gymnasium:从Atari游戏安装到代码迁移的完整避坑指南
  • AI Agent 输出格式的隐形瓶颈
  • VL53L0X激光测距模块在STM32上的应用:除了测距,还能玩出什么花样?
  • 用Field II和MATLAB搞定超声波声场仿真:从理论推导到代码实战(附源码)
  • 读研读博,教你3招搞定文献调研
  • HarmonyOS 图片缩放没想象中简单——detailEnhance 四档质量深度解析
  • 【DeepSeek API接入实战指南】:20年AI架构师亲授5大避坑要点与3分钟快速调通秘籍
  • 别再只盯着Encoder模式了!STM32F4通用IO口+外部中断搞定EC11旋转编码器(附代码)
  • 基于STM32F105系列使用CAN总线实现双机通信代码
  • 鸿蒙支付模块构建:快捷充值选项与缴费记录的时间线设计
  • VSCode Mermaid Preview:面向技术团队的实时图表协作解决方案
  • [明道云实战] 流程一多就开始乱,怎样把明道云工作流整理成可维护的工程系统?
  • 深度测评2026年日本工程塑料厂家最佳代理服务排行榜,解锁高精尖材料新选择
  • 告别Keil!在VSCode里用PlatformIO+CubeMX+HAL库玩转STM32(保姆级配置流程)
  • 从CUDA_VISIBLE_DEVICES到Docker:聊聊GPU资源隔离的几种‘姿势’
  • MiniMax-M2.7-W8A8 双机 DP=2 部署
  • 树莓派摄像头detected=0?别急着重装系统,先检查这个新手常插错的接口
  • 考前终极口诀合集,30秒过一遍
  • 错过申报期等于白干:政策信息平台的时效性保障技术方案
  • 从Multisim仿真到理论验证:一个实际案例带你吃透结点电压法的‘自导’与‘互导’
  • 从IMC层到应力点:手把手教你用SEM/EDS给BGA焊点做一次‘体检’
  • 从6DOF到近场动力学:多物理场耦合仿真的技术跃迁与工程实践
  • 创业公司如何利用Taotoken以可控成本开展每日AI创意生成活动
  • k8s集群网络层碎碎念