PyQt添加InferenceThread线程后界面闪退问题求助
PyQt多线程闪退问题排查与解决
问题描述
使用PyQt开发界面,程序包含CameraThread与InferenceThread两个线程。仅运行CameraThread时程序正常,加入InferenceThread后界面出现闪退。相关代码如下:
from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtGui import QImage, QPixmap from PyQt5 import uic import numpy as np import cv2 import sys global_parameter = '1' # 摄像头进程 class CameraThread(QThread): frameCaptured = pyqtSignal(np.ndarray) def run(self): camera = cv2.VideoCapture(0) ret, frame = camera.read() if ret: self.frameCaptured.emit(frame) # while True: # ret, frame = camera.read() # if ret: # self.frameCaptured.emit(frame) # 模型计算 class InferenceThread(QThread): resultReady = pyqtSignal(int) def __init__(self, image): super().__init__() self.image = image def run(self): # 图片预处理 # image_preprocess = preprocess_image(self.image) # 模型处理 # result = model.predict(np.expand_dims(image_preprocessed, axis=0)) result = 1 self.resultReady.emit(result) # 主窗口 class MainUi(QMainWindow): def __init__(self): super().__init__() self.ui = uic.loadUi('./ui/test.ui', self) self.ui.comboBox.addItems(['自动模式', '手动模式']) self.ui.action1.triggered.connect(self.open_port_dialog) self.ui.action2.triggered.connect(self.open_control_dialog) self.camera_thread = CameraThread() self.camera_thread.frameCaptured.connect(self.update_camera_feed) self.camera_thread.start() # 打开端口配置窗口 def open_port_dialog(self): self.portDialog = PortUi() self.portDialog.buttonbox.accepted.connect(self.handle_port_accept) self.portDialog.buttonbox.rejected.connect(self.handle_port_reject) self.portDialog.exec_() # 更新串口参数 def update_port_parameter(self): global global_parameter print(global_parameter) # 端口配置窗口 确认 响应方法 def handle_port_accept(self): self.update_port_parameter() def handle_port_reject(self): pass # 打开控制器参数窗口 def open_control_dialog(self): self.controlDialog = ControllerUi() self.controlDialog.buttonbox.accepted.connect(self.handle_control_accept) self.controlDialog.buttonbox.rejected.connect(self.handle_control_reject) self.controlDialog.exec_() # 更新控制器参数 def update_control_parameter(self): print('控制器参数已更新') # 控制器参数窗口 确认 响应方法 def handle_control_accept(self): self.update_control_parameter() def handle_control_reject(self): pass # 图像区 图像显示槽函数 def update_camera_feed(self, frame): frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) image = QImage( frame_rgb.data, frame_rgb.shape[1], frame_rgb.shape[0], QImage.Format_RGB888 ) pixmap = QPixmap.fromImage(image) self.ui.label_camera.setPixmap(pixmap.scaled(640, 380, aspectRatioMode=Qt.KeepAspectRatio)) self.ui.label_camera.setScaledContents(True) # 让图片自适应label大小 inference_thread = InferenceThread(frame_rgb) inference_thread.resultReady.connect(self.handle_inference_result) inference_thread.start() # 操作区,偏移坐标槽函数 def handle_inference_result(self, result): print(result) # 串口配置窗口 class PortUi(QDialog): def __init__(self): super().__init__() self.ui = uic.loadUi('./ui/port.ui', self) self.buttonbox = self.ui.buttonBox global global_parameter self.ui.lineEdit.setText(global_parameter) def accept(self): global global_parameter new_parameter = self.ui.lineEdit.text() global_parameter = new_parameter super().accept() # 控制器参数窗口 class ControllerUi(QDialog): def __init__(self): super().__init__() self.ui = uic.loadUi('./ui/controller.ui', self) self.buttonbox = self.ui.buttonBox def accept(self): super().accept() if __name__ =='__main__': app = QApplication([]) mainui = MainUi() mainui.ui.show() sys.exit(app.exec_())
问题根源分析
- 线程对象被垃圾回收:
update_camera_feed中创建的inference_thread是局部变量,函数执行完毕后该对象引用计数归零,Python垃圾回收机制会销毁线程对象。但此时线程可能仍在运行,导致Qt内部访问已销毁的对象,引发闪退。 - 图像数据生命周期问题:传入
InferenceThread的frame_rgb是局部numpy数组,函数执行完毕后数组内存被释放,线程运行时访问已失效的内存地址,导致崩溃。
解决方案
1. 将推理线程保存为成员变量,避免被回收
在MainUi类中维护推理线程的引用,确保线程运行期间对象不会被销毁:
class MainUi(QMainWindow): def __init__(self): super().__init__() # ... 原有初始化代码 self.inference_thread = None # 初始化推理线程成员变量 def update_camera_feed(self, frame): frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # ... 原有图像显示代码 # 停止并回收之前的线程(避免多线程并发) if self.inference_thread and self.inference_thread.isRunning(): self.inference_thread.quit() self.inference_thread.wait() # 创建新线程并保存为成员变量 self.inference_thread = InferenceThread(frame_rgb.copy()) self.inference_thread.resultReady.connect(self.handle_inference_result) self.inference_thread.start()
2. 复制图像数据,确保线程访问有效内存
修改InferenceThread的初始化方法,复制传入的图像数据,避免原数据被释放后线程访问无效内存:
class InferenceThread(QThread): resultReady = pyqtSignal(int) def __init__(self, image): super().__init__() self.image = image.copy() # 复制图像数据,保证线程内数据有效 def run(self): # ... 原有推理代码 result = 1 self.resultReady.emit(result)
3. 优化摄像头线程(可选)
如果启用CameraThread中的while循环,需添加延时控制帧率,避免短时间内创建大量推理线程:
class CameraThread(QThread): frameCaptured = pyqtSignal(np.ndarray) def run(self): camera = cv2.VideoCapture(0) while True: ret, frame = camera.read() if ret: self.frameCaptured.emit(frame) cv2.waitKey(1) # 添加延时,控制帧率
额外建议
对于此类短任务的线程场景,推荐使用QThreadPool+QRunnable替代手动创建QThread,Qt的线程池会自动管理线程生命周期,更高效且不易出错:
from PyQt5.QtCore import QRunnable, QThreadPool, pyqtSlot class InferenceRunnable(QRunnable): def __init__(self, image, callback): super().__init__() self.image = image.copy() self.callback = callback @pyqtSlot() def run(self): result = 1 self.callback(result) # 在MainUi的update_camera_feed中使用 def update_camera_feed(self, frame): # ... 原有图像显示代码 runnable = InferenceRunnable(frame_rgb, self.handle_inference_result) QThreadPool.globalInstance().start(runnable)
内容的提问来源于stack exchange,提问作者cv54321
相关产品推荐
相关产品推荐

