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

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_())

问题根源分析

  1. 线程对象被垃圾回收:update_camera_feed中创建的inference_thread是局部变量,函数执行完毕后该对象引用计数归零,Python垃圾回收机制会销毁线程对象。但此时线程可能仍在运行,导致Qt内部访问已销毁的对象,引发闪退。
  2. 图像数据生命周期问题:传入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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 09:10:03