Numpy a QImage se bloquea

Aug 29 2020

El siguiente código se bloquea al hacer clic en el botón o después de unos pocos clics cuando la señal se emite desde el hilo y se captura en la interfaz gráfica de usuario principal.

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QThread
import numpy as np
import time
from PyQt5.QtWidgets import QApplication, QDialog, QPushButton, QVBoxLayout


def convert_np_qimage(cv_img , width, height):
    h, w, ch = cv_img.shape
    bytes_per_line = ch * w
    qim = QtGui.QImage(cv_img.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
    print(qim.size())
    return qim

class VideoThread(QThread):
    change_qimage_signal = pyqtSignal(QImage)
    
    def __init__(self):
        super().__init__()

    def run(self):
        print("run")
        width = 1280
        height = 1280
        
        cv_img = np.zeros([height,width,3],dtype=np.uint8)
        cv_img.fill(255)
        print("image shape: ", cv_img.shape)

        qimg = convert_np_qimage(cv_img, width, height)
        self.change_qimage_signal.emit(qimg)
        print("emitted")

        
    def stop(self):
        self.wait()

import sys

class Dialog(QDialog):

    def __init__(self):
        super(Dialog, self).__init__()
        Dialog.resize(self, 640, 480)
        
        button=QPushButton("Click")
        button.clicked.connect(self.startThread)
        
        mainLayout = QVBoxLayout()
        mainLayout.addWidget(button)
        
        
        self.setLayout(mainLayout)
        self.setWindowTitle("QImage Example")
        
    def startThread(self):
        self.thread = VideoThread()
        self.thread.change_qimage_signal.connect(self.getPixmap)
        self.thread.start()      
        
    def getPixmap(self, qimg):
        print("got qimage")
        qpixmap = QPixmap.fromImage(qimg)
        

if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = Dialog()
    sys.exit(dialog.exec_())

El programa no se bloquea si la altura y el ancho se establecen en un número pequeño, digamos 3. El programa tampoco se bloquea si convertimos qimage a qpixmap antes de emitir y cambiamos el tipo de señal a QPixmap. El programa fue escrito originalmente para obtener imágenes de la cámara web usando opencv. La matriz numpy creada por opencv también se bloquea para tamaños de imagen grandes.

El sistema operativo utilizado es Windows10, la versión de pyqt es 5.12.3

¿Alguna idea de cuál podría ser la razón del accidente?

Respuestas

1 eyllanesc Aug 29 2020 at 12:13

En Linux con PyQt5 5.15 no reproduzco el problema, pero ese error es común y ocurre porque al pasar "datos" no se copia la información sino que se comparten los datos, por lo que en algún momento se destruyen cv_img y todos los objetos asociados incluyendo a " data "por lo que al transmitirlo a través de la señal y configurarlo en el QLabel se lee" data "pero ya no tiene memoria reservada. La solución en este caso es copiar "datos":

qim = QtGui.QImage(
    cv_img.data.tobytes(), w, h, bytes_per_line, QtGui.QImage.Format_RGB888
)

o copie QImage.

return qim.copy()