You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PyQt5应用中集成cv2.VideoCapture并传递条码数据

实现方法

核心注意点:PyQt所有UI操作必须在主线程执行,你原来的OpenCV条码识别是阻塞式while循环,直接放到UI主线程会把界面卡死,所以要把摄像头读取、条码解码逻辑放到独立子线程,通过Qt自带的信号槽机制把识别到的条码值传回主线程,再填充到序列号输入框即可。

具体实现步骤

  • 替换不合理的控件:序列号是单行输入,把原来的QTextEdit换成QLineEdit更适配,当然保留QTextEdit也能正常用。
  • 编写独立的条码识别工作线程:继承QThread实现工作类,定义传递条码字符串的信号,识别到未重复的新条码时触发信号;去掉原来的time.sleep()阻塞逻辑,改用时间戳判断3秒内重复条码直接跳过,避免线程卡住;去掉OpenCV自带的imshow弹窗,需要显示摄像头画面的话可以把帧转成QImage通过信号传回主线程显示。
  • 主线程绑定信号槽:在主窗口类里启动工作线程,把线程的条码识别信号绑定到自定义槽函数,槽函数里直接把收到的条码值设置到序列号输入框即可。

整合后的完整代码

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import cv2
import numpy as np
from pyzbar.pyzbar import decode
import time


# 条码识别工作线程,跑在子线程不卡UI
class BarcodeScanThread(QThread):
    # 定义信号:识别到新条码时发送字符串内容
    barcode_detected = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.running = False
        self.used_codes = []
        self.last_detect_time = 0
        self.detect_interval = 3  # 同一条码3秒内不重复触发

    def run(self):
        self.running = True
        cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        while self.running:
            ret, frame = cap.read()
            if not ret:
                break
            # 解码条码
            gray_img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            barcodes = decode(gray_img)
            for obj in barcodes:
                points = obj.polygon
                (x, y, w, h) = obj.rect
                pts = np.array(points, np.int32)
                pts = pts.reshape((-1, 1, 2))
                cv2.polylines(frame, [pts], True, (0, 255, 0), 3)
                barcodeData = obj.data.decode("utf-8")
                barcodeType = obj.type
                string = f"Data {barcodeData} | Type {barcodeType}"
                cv2.putText(frame, string, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 2)
                
                # 判定是否是有效新条码
                now = time.time()
                if barcodeData not in self.used_codes and (now - self.last_detect_time) > self.detect_interval:
                    self.used_codes.append(barcodeData)
                    self.last_detect_time = now
                    # 发信号把条码值传回主线程
                    self.barcode_detected.emit(barcodeData)
            
            # 控制帧率,不要跑满CPU
            cv2.waitKey(1)
        cap.release()

    def stop(self):
        self.running = False
        self.wait()


class Window(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("条码录入工具")
        self.setGeometry(100, 100, 1000, 600)
        self.UiComponents()
        # 初始化并启动扫码线程
        self.scan_thread = BarcodeScanThread()
        self.scan_thread.barcode_detected.connect(self.fill_sn_input)
        self.scan_thread.start()
        self.show()

    def UiComponents(self):
        self.radio_button_fail = QRadioButton(self)
        self.radio_button_pass = QRadioButton(self)
        self.text_batch = QLineEdit(self)  # 单行输入更合适
        self.text_sn = QLineEdit(self)     # 序列号输入框
        self.text_problem = QTextEdit(self)
        self.label1 = QLabel(self)
        self.label2 = QLabel(self)
        self.label3 = QLabel(self)
        self.label4 = QLabel(self)
        self.button_save = QPushButton(self)

        self.radio_button_fail.setGeometry(450, 170, 82, 17)
        self.radio_button_pass.setGeometry(450, 140, 82, 17)
        self.text_batch.setGeometry(80, 140, 150, 41)
        self.text_sn.setGeometry(250, 140, 150, 41)
        self.text_problem.setGeometry(550, 140, 200, 41)
        self.label1.setGeometry(110, 100, 101, 41)
        self.label2.setGeometry(260, 100, 130, 41)
        self.label3.setGeometry(450, 100, 101, 41)
        self.label4.setGeometry(570, 100, 170, 41)
        self.button_save.setGeometry(780, 140, 70, 41)
        
        self.label1.setText("Batch")
        self.label2.setText("Serial number")
        self.label3.setText("Status")
        self.label4.setText("Problem description")
        self.button_save.setText("Save")
                                    
        font = QFont()
        font.setPointSize(14)
        self.label1.setFont(font)
        self.label2.setFont(font)
        self.label3.setFont(font)
        self.label4.setFont(font)
    
        self.radio_button_fail.setText("FAIL")
        self.radio_button_pass.setText("PASS")
        self.radio_button_pass.setChecked(True)
        self.text_problem.setDisabled(True)

        self.radio_button_fail.clicked.connect(self.check)
        self.radio_button_pass.clicked.connect(self.check)
        self.button_save.clicked.connect(self.save_record)

    def fill_sn_input(self, sn_value):
        # 收到子线程发的条码值,填充到序列号输入框
        self.text_sn.setText(sn_value)
        # 可选:填充后自动触发保存或者切换焦点,按需加逻辑

    def check(self):
        if self.radio_button_fail.isChecked():
            self.text_problem.setDisabled(False)
        else:
            self.text_problem.setDisabled(True)

    def save_record(self):
        # 这里加你原来的保存逻辑即可
        batch = self.text_batch.text()
        sn = self.text_sn.text()
        status = "PASS" if self.radio_button_pass.isChecked() else "FAIL"
        problem = self.text_problem.toPlainText()
        print(f"保存记录:批次{batch}, 序列号{sn}, 状态{status}, 问题{problem}")
        # 保存完清空序列号输入框,准备扫下一个
        self.text_sn.clear()

    def closeEvent(self, event):
        # 窗口关闭时停掉扫码线程,释放摄像头
        self.scan_thread.stop()
        event.accept()


if __name__ == "__main__":
    App = QApplication(sys.argv)
    window = Window()
    sys.exit(App.exec())

额外说明

  • 原来代码里的time.sleep(3)会直接阻塞线程,导致摄像头画面卡顿、信号延迟,换成时间戳判断间隔的方式不会阻塞,流畅度好很多。
  • 如果你想清空已扫描的条码记录、重复识别同一个码,直接清空工作线程里的used_codes列表即可。
  • 如果需要在界面显示摄像头画面,可以在工作线程加帧传输信号,主线程收到帧后转成QPixmap显示到QLabel上,不影响条码识别和填充功能。

内容的提问来源于stack exchange,提问作者gajun5

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 01:42:36