Search code examples
pythonpython-3.xpyqtpyqt4

RuntimeError: wrapped C/C++ object of type QLabel has been deleted


I am trying to run the following piece of code but it gives me an error RuntimeError: wrapped C/C++ object of type QLabel has been deleted. I'm unable to figure out why is it happening.

I searched other answers but they all said it has something to do with setCentralWidget. But I'm not using it. So, why's the error occuring in my code?

from PyQt4 import QtGui, QtCore
from threading import Thread
from collections import deque
from datetime import datetime
import time
import sys
import cv2
import imutils
import os


class CameraWidget(QtGui.QWidget):
    """Independent camera feed
    Uses threading to grab IP camera frames in the background

    @param width - Width of the video frame
    @param height - Height of the video frame
    @param stream_link - IP/RTSP/Webcam link
    @param aspect_ratio - Whether to maintain frame aspect ratio or force into fraame
    """

    def __init__(self, width, height, stream_link=0, btn_text=None, aspect_ratio=False, parent=None, deque_size=1):
        super(CameraWidget, self).__init__(parent)

        # Initialize deque used to store frames read from the stream
        self.deque = deque(maxlen=deque_size)

        self.screen_width = width - 8  
        self.screen_height = height - 8
        self.maintain_aspect_ratio = aspect_ratio

        self.camera_stream_link = stream_link

        # Flag to check if camera is valid/working
        self.online = False
        self.capture = None

        self.video_display_frame = QtGui.QFrame()
        self.video_layout = QtGui.QVBoxLayout()
        self.video_btn = QtGui.QPushButton(btn_text)
        self.video_btn.setStyleSheet("background-color: rgb(128, 159, 255)")
        self.video_btn.clicked.connect(self.message)
        self.video_frame = QtGui.QLabel()
        self.video_layout.addWidget(self.video_btn)
        self.video_layout.addWidget(self.video_frame)
        self.video_layout.setContentsMargins(0,0,0,0)
        self.video_layout.setSpacing(0)
        self.video_display_frame.setLayout(self.video_layout)

        self.load_network_stream()

        # Start background frame grabbing
        self.get_frame_thread = Thread(target=self.get_frame, args=())
        self.get_frame_thread.daemon = True
        self.get_frame_thread.start()

        # Periodically set video frame to display
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.set_frame)
        self.timer.start(.5)

        print('Started camera: {}'.format(self.camera_stream_link))

    def load_network_stream(self):
        """Verifies stream link and open new stream if valid"""

        def load_network_stream_thread():
            if self.verify_network_stream(self.camera_stream_link):
                self.capture = cv2.VideoCapture(self.camera_stream_link)
                self.online = True
        self.load_stream_thread = Thread(target=load_network_stream_thread, args=())
        self.load_stream_thread.daemon = True
        self.load_stream_thread.start()

    def verify_network_stream(self, link):
        """Attempts to receive a frame from given link"""

        cap = cv2.VideoCapture(link)
        if not cap.isOpened():
            return False
        cap.release()
        return True

    def get_frame(self):
        """Reads frame, resizes, and converts image to pixmap"""

        while True:
            try:
                if self.capture.isOpened() and self.online:
                    # Read next frame from stream and insert into deque
                    status, frame = self.capture.read()
                    if status:
                        self.deque.append(frame)
                    else:
                        self.capture.release()
                        self.online = False
                else:
                    # Attempt to reconnect
                    print('attempting to reconnect', self.camera_stream_link)
                    self.load_network_stream()
                    self.spin(2)
                self.spin(.001)
            except AttributeError:
                pass

    def spin(self, seconds):
        """Pause for set amount of seconds, replaces time.sleep so program doesnt stall"""

        time_end = time.time() + seconds
        while time.time() < time_end:
            QtGui.QApplication.processEvents()

    def set_frame(self):
        """Sets pixmap image to video frame"""

        if not self.online:
            self.spin(1)
            return

        if self.deque and self.online:
            # Grab latest frame
            frame = self.deque[-1]

            # Keep frame aspect ratio
            if self.maintain_aspect_ratio:
                self.frame = imutils.resize(frame, width=self.screen_width)
            # Force resize
            else:
                self.frame = cv2.resize(frame, (self.screen_width, self.screen_height))

            # Convert to pixmap and set to video frame
            self.img = QtGui.QImage(self.frame, self.frame.shape[1], self.frame.shape[0], QtGui.QImage.Format_RGB888).rgbSwapped()
            self.pix = QtGui.QPixmap.fromImage(self.img)
            self.video_frame.setPixmap(self.pix)                  ### error comes in this line.

    def get_video_frame(self):
        return self.video_frame

    def get_video_display_frame(self):
        return self.video_display_frame

    def message(self):
        self.zone_config = ZoneConfig(self.camera_stream_link) 
        self.zone_config.show()


class MainWindow(QtGui.QWidget):    

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.initUI()

    def initUI(self):
        self.setWindowTitle("VIDS")
        titleBar_logo = os.path.join(dir, 'logos/logo.png')
        self.setWindowIcon(QtGui.QIcon(titleBar_logo))
        self.showMaximized()

        self.screen_width = self.width()
        self.screen_height = self.height()

        # Layouts & frames
        self.layout = QtGui.QVBoxLayout()

        self.top_frame = QtGui.QFrame()
        self.top_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
        self.mid_frame = QtGui.QFrame()   
        self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
        self.btm_frame = QtGui.QFrame()
        self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")

        self.layout.addWidget(self.top_frame, 2)
        self.layout.addWidget(self.mid_frame, 20)
        self.layout.addWidget(self.btm_frame, 1)

        self.layout.setContentsMargins(0,0,0,0)
        self.layout.setSpacing(0)
        self.setLayout(self.layout)

        # Top frame
        label = QtGui.QLabel('VIDS Dashboard')
        label.setFont(QtGui.QFont('Arial', 20))
        label.setAlignment(QtCore.Qt.AlignCenter)

        self.top_layout = QtGui.QHBoxLayout()
        self.top_layout.addWidget(label)
        self.top_frame.setLayout(self.top_layout)

        # Middle frame
        self.mid_layout = QtGui.QStackedLayout()

        # Create camera widgets
        print('Creating Camera Widgets...')
        self.one = CameraWidget(int(self.screen_width), int(self.screen_height), camera1, 'Camera 1')
        self.two = CameraWidget(int(self.screen_width), int(self.screen_height), camera2, 'Camera 2')

        cam_widget = Cam2(self.one, self.two, self)
        self.mid_layout.addWidget(cam_widget)
        self.mid_layout.setCurrentWidget(cam_widget)
        self.mid_frame.setLayout(self.mid_layout)  

        # Bottom frame
        btn1 = QtGui.QPushButton('1 Cam')
        btn1.clicked.connect(self.button1)
        btn2 = QtGui.QPushButton('2 Cams')
        btn2.clicked.connect(self.button2)

        self.btm_layout = QtGui.QHBoxLayout()
        self.btm_layout.addStretch()
        self.btm_layout.addWidget(btn1)
        self.btm_layout.addWidget(btn2)
        self.btm_frame.setLayout(self.btm_layout)
        self.show()

    def button1(self):
        cam1_widget = Cam1(self.one, self)
        self.mid_layout.addWidget(cam1_widget)
        self.mid_layout.setCurrentWidget(cam1_widget)

    def button2(self):
        cam2_widget = Cam2(self.one, self.two, self)
        self.mid_layout.addWidget(cam2_widget)
        self.mid_layout.setCurrentWidget(cam2_widget)


class Cam1(QtGui.QWidget):
    def __init__(self, one, parent=None):
        super(Cam1, self).__init__(parent)

        # Add widgets to layout
        print('Adding widgets to layout...')
        layout = QtGui.QGridLayout()
        layout.addWidget(one.get_video_display_frame(),0,0,1,1)
        self.setLayout(layout)


class Cam2(QtGui.QWidget):
    def __init__(self, one, two, parent=None):
        super(Cam2, self).__init__(parent)

        # Add widgets to layout
        print('Adding widgets to layout...')
        layout = QtGui.QGridLayout()
        layout.addWidget(one.get_video_display_frame(),0,0,1,1)
        layout.addWidget(two.get_video_display_frame(),0,1,1,1)
        self.setLayout(layout)


class ZoneConfig(QtGui.QWidget):
    def __init__(self, camera, parent=None):
        super(ZoneConfig, self).__init__(parent)

        self.initUI(camera)

    def initUI(self, camera):

        self.setWindowTitle("VIDS")
        titleBar_logo = os.path.join(dir, 'logos/logo.png')
        self.setWindowIcon(QtGui.QIcon(titleBar_logo))
        self.showMaximized()

        self.screen_width = self.width()
        self.screen_height = self.height()
        
        self.camera = camera

        # Layouts & frames
        self.layout = QtGui.QVBoxLayout()

        self.top_frame = QtGui.QFrame()
        self.top_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
        self.mid_frame = QtGui.QFrame()   
        # self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
        self.btm_frame = QtGui.QFrame()
        self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")

        self.layout.addWidget(self.top_frame, 2)
        self.layout.addWidget(self.mid_frame, 20)
        self.layout.addWidget(self.btm_frame, 1)

        self.layout.setContentsMargins(0,0,0,0)
        self.layout.setSpacing(0)
        self.setLayout(self.layout)        

        # Top frame
        self.label = QtGui.QLabel('VIDS - Zone Configuration')
        self.label.setFont(QtGui.QFont('Arial', 20))
        self.label.setAlignment(QtCore.Qt.AlignCenter)

        self.top_layout = QtGui.QHBoxLayout()
        self.top_layout.addWidget(self.label)
        self.top_frame.setLayout(self.top_layout)

        # Middle frame
        self.mid_layout = QtGui.QVBoxLayout()
        self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")

        # Create camera widgets
        print('Creating Camera Widgets...')
        self.cam = CameraWidget(self.screen_width, self.screen_height, self.camera)

        self.mid_layout = QtGui.QVBoxLayout()
        self.mid_layout.addWidget(self.cam.get_video_frame())
        self.mid_frame.setLayout(self.mid_layout)

        self.mid_layout.setContentsMargins(0,0,0,0)
        self.mid_layout.setSpacing(0)
        self.mid_frame.setLayout(self.mid_layout)   
        
        # Bottom frame
        btn = QtGui.QPushButton('Dashboard')
        btn.clicked.connect(self.goMainWindow)

        self.btm_layout = QtGui.QHBoxLayout()
        self.btm_layout.addStretch()
        self.btm_layout.addWidget(btn)
        self.btm_frame.setLayout(self.btm_layout)

    def goMainWindow(self):
        self.close()


dir = os.path.dirname(__file__)
window = None
camera1 = 'streams/Fog.avi'
camera2 = 'streams/Overspeed.avi'


if __name__ == '__main__':
    app = QtGui.QApplication([])
    app.setStyle(QtGui.QStyleFactory.create("plastique"))                            # applies to entire window 
    window = MainWindow()
    # window.show()
    sys.exit(app.exec_())

I am using python 3, pyqt 4 and windows 10.


Solution

  • It seems that in the handling of threads it does not synchronize the elimination of the objects correctly, so a possible solution is to use sip to verify if the object was eliminated or not:

    from PyQt4 import QtGui, QtCore
    import sip
    
    # ...
    self.pix = QtGui.QPixmap.fromImage(self.img)
    if not sip.isdeleted(self.video_frame):
        self.video_frame.setPixmap(self.pix)