微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

PyQt5:当通过来自另一个线程的信号发送像素图时,Python 会因 SIGSEGV *有时* 崩溃

如何解决PyQt5:当通过来自另一个线程的信号发送像素图时,Python 会因 SIGSEGV *有时* 崩溃

背景和问题

我正在尝试处理来自相机的流数据。 Python 不断因此消息而崩溃:

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

崩溃发生有时同时发出包含图像的信号。

我的代码,如下所示,遵循这个过程:

  1. 名为 QObjectCameraThread 在 GUI 中实例化并由 QThread 运行。
  2. CameraThread 实例化一个IngestManager 并将其提供给数据源。数据源会反复调用IngestManager的{​​{1}}方法,提供数据。
  3. 工作线程处理数据并通过回调方法IngestManager将其发送回IngestManager
  4. frame_callback 发出信号。这是它有时崩溃的地方。

我的尝试/观察

我尝试了几种方法来修复它,包括IngestManager 传递给工作线程本身。我也认为有时线程会同时完成并发出,但我不知道如何解决这个问题。

我与 GUI 交互越多,崩溃发生得越快,例如快速按下虚拟按钮。如果我不与 UI 交互,它几乎不会发生(但它仍然会发生)。我想我可能需要某种锁。

我该如何开始解决这个问题?


这是连接到数据源、处理数据和发出信号的代码

pyqtSignal

这就是上面代码用法

import io
import threading

from PIL import Image
from PIL.ImageQt import ImageQt
from PyQt5.QtCore import QObject,pyqtSignal,pyqtSlot
from PyQt5.QtGui import Qpixmap


class CameraThread(QObject):
    signal_new_frame = pyqtSignal(Qpixmap)

    def __init__(self,parent=None):
        QObject.__init__(self,parent)

    @pyqtSlot()
    def run(self):
        with DataSource() as source:
            output = IngestManager(frame_signal=self.signal_new_frame)

            # The source continuously calls IngestManager.write() with new data
            source.set_ingest(output)


class IngestManager(object):
    """Manages incoming data stream from camera."""

    def __init__(self,frame_signal):
        self.frame_signal = frame_signal

        # Construct a pool of 4 image processors along with a lock to control access between threads
        self.lock = threading.Lock()
        self.pool = [ImageProcessor(self) for _ in range(4)]
        self.processor = None  # First "frame" intentionally dropped,else thread would see garbled data at start

    def write(self,buf):
        if buf.startswith(b'\xff\xd8'):  # Frame detected
            if self.processor:
                self.processor.event.set()  # Let waiting processor thread kNow a frame is here

            with self.lock:
                if self.pool:
                    self.processor = self.pool.pop()
                else:
                    # All threads popped and busy. No choice but to skip frame.
                    self.processor = None

        if self.processor:
            self.processor.stream.write(buf)  # Feed frame data to current processor

    def frame_callback(self,image):
        print('Frame processed. Emitting.')
        self.frame_signal.emit(image)


class ImageProcessor(threading.Thread):
    def __init__(self,owner: IngestManager):
        super(ImageProcessor,self).__init__()

        # Data Stuff
        self.stream = io.BytesIO()

        # Thread stuff
        self.event = threading.Event()
        self.owner = owner
        self.start()

    def run(self):
        while True:
            if self.event.wait(1):
                pil_image = Image.open(self.stream)
        
                # Image is processed here,then sent back
                # ...
                # ...
        
                q_image = ImageQt(pil_image)
                q_pixmap = Qpixmap.fromImage(q_image)
        
                self.owner.frame_callback(q_pixmap)

                # Reset the stream and event
                self.stream.seek(0)
                self.stream.truncate()
                self.event.clear()
                
                # Return to available pool
                with self.owner.lock:
                    self.owner.pool.append(self)

解决方法

首先,在主线程之外使用 QPixmap 是不安全的。所以你应该改用 QImage

其次,ImageQt 共享传递给它的 Image 的缓冲区。因此,如果在 Qt 图像仍然存在时删除缓冲区,则很可能会发生崩溃。如果您不能长时间保持 PIL 图像,您可能需要 copy the Qt image 来防止这种情况发生。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。