使用PyQt6+摄像头+pyqtgraph多进程遇pickle错误,求解决方案
问题描述
正尝试用PyQt6开发GUI,实现摄像头与pyqtgraph Viewer的交互。创建了三个类:QT界面类(CharacViewer)、摄像头类(cameraClass)、pyqtgraph Viewer类(imageViewer),希望将摄像头和Viewer作为独立进程运行:摄像头采集帧后通过队列发送给Viewer进程显示。但启动进程时遇到pickle错误,无法序列化cv2.VideoCapture对象。不想使用Pathos的Pool,仅需两个独立进程,求解决方法。
错误信息
TypeError: cannot pickle 'cv2.VideoCapture' object
问题原因
- 摄像头对象序列化问题:当前代码在主进程中调用
charac_viewer.initialiseCamera(),会在主进程内创建cv2.VideoCapture实例。启动子进程时,multiprocessing需要序列化整个camera对象,而cv2.VideoCapture是C语言实现的资源句柄,无法被Python的pickle机制序列化。 - GUI控件跨进程问题:主进程中调用
charac_viewer.initialiseViewer()创建了pyqtgraph的ImageView,GUI控件只能在所属的Qt事件循环进程中运行,跨进程操作会导致异常,且ImageView对象同样无法被序列化。
解决方案
核心思路:所有进程专属的资源(摄像头、GUI控件)都在各自的子进程内部初始化,避免在主进程创建后跨进程传递。
修改cameraClass.py
将摄像头的初始化逻辑移到子进程的run()方法中,同时把宽高参数通过类初始化传入:
import cv2 import multiprocessing import numpy as np class camera(multiprocessing.Process): def __init__(self, queue, width, height): multiprocessing.Process.__init__(self) self.queue = queue self.width = width self.height = height print('Camera class initialized!') def run(self): # 在子进程内部初始化摄像头 self.vid = cv2.VideoCapture(0) self.vid.set(cv2.CAP_PROP_FRAME_WIDTH, self.width) self.vid.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height) print('Camera initialized!') while True: # 限制队列最大缓存5帧,避免内存占用过高 if self.queue.qsize() < 5: ret, frame = self.vid.read() if ret: arr = np.array(frame) # 打包帧形状与扁平化数据 self.queue.put((arr.shape[0], arr.shape[1], arr.shape[2], arr.flatten())) # 退出时释放摄像头资源 self.vid.release()
修改imageViewer.py
在子进程内部初始化ImageView,并启动独立的Qt事件循环(子进程无主进程的QApplication支持):
import multiprocessing import pyqtgraph as pg import numpy as np from PyQt6.QtWidgets import QApplication class imageViewer(multiprocessing.Process): def __init__(self, queue): multiprocessing.Process.__init__(self) self.queue = queue self.counter = 0 print('Image Viewer Class initialized!') def run(self): # 子进程必须创建独立的QApplication app = QApplication([]) # 在子进程内部初始化Viewer self.imv = pg.ImageView() self.imv.show() self.imv.resize(800, 600) print('Image Viewer initialized!') # 使用QTimer轮询队列,避免死循环阻塞事件循环 timer = pg.QtCore.QTimer() def update_frame(): while not self.queue.empty(): width, height, colors, data_flat = self.queue.get() frame = np.reshape(data_flat, (width, height, colors)) self.imv.setImage(frame) timer.timeout.connect(update_frame) timer.start(30) # 每30ms更新一次,对应约30fps # 启动子进程的Qt事件循环 app.exec()
修改CharacViewer.py
移除主进程中对摄像头和Viewer的初始化调用,仅保留进程启动逻辑:
from PyQt6 import QtWidgets, uic class CharacViewer(QtWidgets.QMainWindow): def __init__(self): super(CharacViewer, self).__init__() self.ui = uic.loadUi("gui_interface.ui", self) self.ui.pushButton_RunCamera.clicked.connect(self.startImagingProcess) self.width = 720 self.height = 1280 self.counter = 0 def setImageViewer(self, imv): self.imv = imv def setCamera(self, cam): self.cam = cam def startImagingProcess(self): print('Start Imaging...') self.cam.start() self.imv.start()
修改main.py
调整摄像头实例的创建,传入宽高参数,不再调用主进程内的初始化方法:
import sys from PyQt6.QtWidgets import QApplication from CharacViewer import CharacViewer from cameraClass import camera from imageViewer import imageViewer import multiprocessing as mp if __name__ == '__main__': app = QApplication([]) app.setStyle('Fusion') queue = mp.Queue() # 创建摄像头实例时传入宽高参数 camera_proc = camera(queue, 720, 1280) imageViewer_proc = imageViewer(queue) charac_viewer = CharacViewer() charac_viewer.move(900,0) charac_viewer.resize(650,830) charac_viewer.show() charac_viewer.setImageViewer(imageViewer_proc) charac_viewer.setCamera(camera_proc) print('All set!') sys.exit(app.exec())
关键说明
- 摄像头初始化移到子进程:确保
cv2.VideoCapture在子进程内创建,避免跨进程序列化资源句柄。 - Viewer进程独立事件循环:pyqtgraph依赖Qt事件循环,子进程必须创建自己的
QApplication并启动循环,否则GUI控件无法正常显示。 - 队列数据优化:将帧形状与数据分开传递,同时限制队列缓存数量,避免内存占用过高。
内容的提问来源于stack exchange,提问作者Paul J. M.
相关产品推荐
相关产品推荐

