如何在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
相关产品推荐
相关产品推荐

