0

I have a custom QLabel item that allows users to track mouse points, draw rectangles on the pixmap. Also, I have a video stream provided by another class that inherits QThread and use OpenCV. My aim is to display the video stream on this custom QLabel. A user can pause the stream and display the last frame on the QLabel to draw rectangles.

However, the paintEvent in my QLabel item causes the black image. How can I display a video stream on my QLabel item and pause on a frame to draw rectangles?

My QLabel class:

class ImageLabel(QLabel):
    def __init__(self, width=720, height=540):
        super(ImageLabel, self).__init__()
        
        self.setMouseTracking(True)
        #### initialize coordinates ####
        self.x1 = 0
        self.y1 = 0

        self.x_curr = 0
        self.y_curr = 0

        self.x2 = 0
        self.y2 = 0
        
        ######
        self.enable_labelling = False
        self.enable_cor = False

        ################################
        self.source = Communicate()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.drawPixmap(self.rect(), self.pixmap)
        if self.enable_labelling == True:
            self.paintRect(event, qp)
            qp.end()

    def paintRect(self, event, qp):
        br = QtGui.QBrush(QtGui.QColor(50, 255, 255, 40))
        qp.setBrush(br)

        if self.enable_cor == True:
            qp.drawRect(QtCore.QRect(QPoint(self.x1, self.y1), QSize(self.x_curr - self.x1, self.y_curr - self.y1)))
        else:
            qp.drawRect(QtCore.QRect(QPoint(self.x1, self.y1), QSize(self.x2 - self.x1, self.y2 - self.y1)))

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self.enable_labelling == True:
            self.x1 = event.x()
            self.y1 = event.y()

            self.enable_cor = True

            self.source.cor_update.emit()

    def mouseMoveEvent(self, event):
        self.x_curr = event.x()
        self.y_curr = event.y()

        self.source.cor_curr.emit()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.enable_labelling == True:
            self.x2 = event.x()
            self.y2 = event.y()

            self.source.cor_update.emit()

            self.enable_cor = False

Main class:

class MainWindowDesign(QWidget):
    def __init__(self, parent, cam_id):
        super(MainWindowDesign, self).__init__(parent)
        self.parent = parent
        self.cam_id = cam_id
        self.init_UI()

    def init_UI(self):
        self.image_lbl = ImageLabel()
        self.image_lbl.setStyleSheet(ss.style.image_box)
        self.image_lbl.setFixedSize(self.image_width, self.image_height)

        th = Stream_Thread()
        th.set_index(self.cam_id)
        th.changePixmap.connect(self.setImage)
        th.start()


    @pyqtSlot(QImage)
    def setImage(self, image):
       self.image_lbl.setPixmap(QPixmap.fromImage(image).scaled(720, 540))

Stream class: -based on https://stackoverflow.com/a/44404713/13080899

class Stream_Thread(QThread):
    changePixmap = pyqtSignal(QImage)

    def __init__(self):
        super(Stream_Thread, self).__init__()
        self.ref = False        #refresh flag

    def set_index(self, index = 0):
        self.index = int(index)

    def refresh(self):
        self.ref = True
        self.capt.open(self.index)


    def run(self):
        self.capt = cv2.VideoCapture(self.index, cv2.CAP_DSHOW)

        while(True):
            ret, frame = self.capt.read()

            if ret:
                rbgImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                h, w, ch = rbgImage.shape
                bytesPerLine = ch*w
                convertToQtFormat = QImage(rbgImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
                p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
                self.changePixmap.emit(p)

EDIT: I created a reproducible code. Here it is:

Main class:

import sys

from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QHBoxLayout


from ImageLabel import ImageLabel
from Stream_Thread import Stream_Thread

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_UI()


    def init_UI(self):
        self.image_lbl = ImageLabel()

        th = Stream_Thread()
        th.set_index(1)
        th.changePixmap.connect(self.setImage, Qt.QueuedConnection)
        th.start()


        btn_cnt = QPushButton("Continue")
        btn_pa = QPushButton("Pause")

        hbox = QHBoxLayout()
        hbox.addWidget(btn_cnt)
        hbox.addWidget(btn_pa)

        vbox = QVBoxLayout()
        vbox.addWidget(self.image_lbl)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

        self.show()


    @pyqtSlot(QImage)
    def setImage(self, image):
       self.image_lbl.setPixmap(QPixmap.fromImage(image).scaled(720, 540))



def main():
    app = QApplication(sys.argv)
    main_form = MainWindow()
    sys.exit(app.exec_())



if __name__ == '__main__':
    main()

Stream_Thread class:

import cv2
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QImage

#https://stackoverflow.com/a/44404713/13080899
class Stream_Thread(QThread):
    changePixmap = pyqtSignal(QImage)

    def __init__(self):
        super(Stream_Thread, self).__init__()
        self.ref = False        #refresh flag

    def set_index(self, index = 0):
        self.index = int(index)

    def refresh(self):
        self.ref = True
        self.capt.open(self.index)


    def run(self):
        self.capt = cv2.VideoCapture(self.index, cv2.CAP_DSHOW)

        while(True):
            ret, frame = self.capt.read()

            if ret:
                rbgImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                h, w, ch = rbgImage.shape
                bytesPerLine = ch*w
                convertToQtFormat = QImage(rbgImage.data, w, h, bytesPerLine, QImage.Format_RGB888)
                p = convertToQtFormat.scaled(640, 480, Qt.KeepAspectRatio)
                self.changePixmap.emit(p)

ImageLabel class:

from PyQt5 import QtGui, QtCore
from PyQt5.QtCore import Qt, QSize, pyqtSignal, QObject, QPoint, pyqtSlot
from PyQt5.QtWidgets import QLabel

class Communicate(QObject):
    cor_update = pyqtSignal()
    cor_curr = pyqtSignal()

class ImageLabel(QLabel):
    def __init__(self, width=720, height=540):
        super(ImageLabel, self).__init__()
        self.setMouseTracking(True)
        self.pixmap = QtGui.QPixmap("img/logo.jpg")

        #### initialize coordinates ####
        self.x1 = 0
        self.y1 = 0

        self.x_curr = 0
        self.y_curr = 0

        self.x2 = 0
        self.y2 = 0

        ##### enable state to allow user for drawing
        self.enable_labelling = False

        ##### enable state to track coordinates for drawing
        self.enable_cor = False

        ################################
        self.source = Communicate()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.drawPixmap(self.rect(), self.pixmap)
        if self.enable_labelling == True:
            self.paintRect(event, qp)
            qp.end()

    def paintRect(self, event, qp):
        br = QtGui.QBrush(QtGui.QColor(50, 255, 255, 40))
        qp.setBrush(br)

        if self.enable_cor == True:
            qp.drawRect(QtCore.QRect(QPoint(self.x1, self.y1), QSize(self.x_curr - self.x1, self.y_curr - self.y1)))
        else:
            qp.drawRect(QtCore.QRect(QPoint(self.x1, self.y1), QSize(self.x2 - self.x1, self.y2 - self.y1)))

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self.enable_labelling == True:
            self.x1 = event.x()
            self.y1 = event.y()

            self.enable_cor = True

            self.source.cor_update.emit()

    def mouseMoveEvent(self, event):
        self.x_curr = event.x()
        self.y_curr = event.y()

        self.source.cor_curr.emit()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.enable_labelling == True:
            self.x2 = event.x()
            self.y2 = event.y()

            self.source.cor_update.emit()

            self.enable_cor = False
Ugurcan
  • 366
  • 1
  • 4
  • 17
  • Where is the opencv stream functions ? Do you read the stream via on thread ? Share the rest of code. – Yunus Temurlenk Dec 02 '20 at 08:07
  • @YunusTemurlenk I shared. – Ugurcan Dec 02 '20 at 08:11
  • ``` @pyqtSlot(QImage) def setImage(self, image): self.image_lbl.pixmap = QPixmap.fromImage(image).scaled(720, 540) self.image_lbl.setPixmap(QPixmap.fromImage(image).scaled(720, 540))``` The problem is about setPixmap. When I assign the pixmap in this way it worked but it doesn't look reasonable. If I remove the last line QLabel is frozen.. – Ugurcan Dec 02 '20 at 08:43
  • You can not manage ui things on thread, all the ui things should connect to main thread – Yunus Temurlenk Dec 02 '20 at 08:46
  • But the added answer says if I don't manage QLabel update process without thread it will block my MainWindow. https://stackoverflow.com/questions/44404349/pyqt-showing-video-stream-from-opencv/44404713#44404713 What am I missing? – Ugurcan Dec 02 '20 at 08:51
  • Can you try making the connection explicitly queued? So connect to the `changePixmap` signal using `th.changePixmap.connect(self.setImage, Qt.QueuedConnection)` . – G.M. Dec 02 '20 at 10:24
  • @G.M. It didn't work. ```qp = QtGui.QPainter(self) qp.drawPixmap(self.rect(), self.pixmap) ``` I am just playing with the code and it seems the problem is in here or about the logic. – Ugurcan Dec 02 '20 at 10:40
  • @Ugurcan: Please add all the import statements in order to make it easier to run your code. – Maurice Meyer Dec 02 '20 at 11:15
  • "The problem is about setPixmap. When I assign the pixmap in this way it worked but it doesn't look reasonable." So, it actually works, but you thought it wasn't ok? Please clarify. – musicamante Dec 02 '20 at 11:18
  • The implementation of `ImageLabel::paintEvent` looks odd. You call `qp.begin(self)` *after* `the `QPainter` `qp` is already active -- not sure what effect (if any) that might have. – G.M. Dec 02 '20 at 11:28
  • @musicamante Yes, it is working now. ``` @pyqtSlot(QImage) def setImage(self, image): self.image_lbl.pixmap = QPixmap.fromImage(image).scaled(720, 540) self.image_lbl.setPixmap(QPixmap.fromImage(image).scaled(720, 540)) ``` but setImage() method is liket that. I added the first line into the code. – Ugurcan Dec 02 '20 at 12:05
  • @G.M.Thank you. I didn't affect anything but when I run the code on command line it throw error. So I removed it. – Ugurcan Dec 02 '20 at 12:13
  • 1
    QPainter ignores a new `begin()` call if it's already initialized and throws an error, but it's not fatal (and, even if in this case the editing was trivial for the matter, you shouldn't modify the code in the question based on suggestions in the comments, unless it's due to formatting problems). In any case, I don't understand: if it works, what is the problem? Also, what is `Communicate()`? Please always ensure that the example is both minimal **and** reproducible. – musicamante Dec 02 '20 at 12:36
  • @musicamante I add the minimal code. – Ugurcan Dec 03 '20 at 09:03

0 Answers0