1

I am trying to run a video in a pyqt5 app from an opencv video stream, where I can start and stop the video with 2 buttons.

This question is highly related to This. The issue is, that if I want to cancel the video, the Main Window is closing as well. Yet, I am not sure, why this is happening.

Edit: This issue appears on Windows 10, while the thread is running without any issues on a Mac.

from PyQt5.QtGui import * 
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
import cv2 

class MainWindow(QWidget):
    def __init__(self) -> None:
        super(MainWindow, self).__init__()
        self.VBL = QVBoxLayout()
        self.FeedLabel = QLabel()
        self.VBL.addWidget(self.FeedLabel)
        self.playBTN = QPushButton('Play')
        self.playBTN.clicked.connect(self.play)
        self.VBL.addWidget(self.playBTN)
        self.cancelBTN.clicked.connect(self.Cancel)
        self.VBL.addWidget(self.cancelBTN)
        self.setLayout(self.VBL)
    
    def ImageUpdateSlot(self, Image):
        Image = Image.scaled(640, 480, Qt.KeepAspectRatio)
        self.FeedLabel.setPixmap(QPixmap.fromImage(Image))
    
    def Cancel(self):
        self.worker1.stop()
    
    def play(self):
        self.worker1 = CompleteVideoRunner("Path/to/some/video")
        self.worker1.start()
        self.worker1.sigImage.connect(self.ImageUpdateSlot)
        self.cancelBTN.clicked.connect(self.Cancel)

class VideoSignal(QObject):
    Image = pyqtSignal(QImage)
    imageCount = pyqtSignal(int)

class CompleteVideoRunner(QThread):
    #signal = VideoSignal() 
    sigImage = pyqtSignal(QImage)
    sigCount =pyqtSignal(int)
    def __init__(self, path, curFrame = 0):
        super(CompleteVideoRunner, self).__init__()
        self.cap = cv2.VideoCapture(path)
        #self.signal = VideoSignal()
        self.curFrame = curFrame
        self.is_killed = False

    def run(self):
        self.is_killed = False
        j = self.curFrame
        self.cap.set(1, j)
        while (self.cap.isOpened() and not self.is_killed):
            rep, frame = self.cap.read()
            self.curFrame = j
            if not rep:
                break
            height, width, channel = frame.shape
            bytesPerLine = 3 * width
            jpg = frame.tobytes()
            jpg = QByteArray(jpg)
            QImg= QImage(frame.data, width, height, bytesPerLine,
                         QImage.Format_BGR888)
            self.sigImage.emit(QImg)
            self.sigCount.emit(j)
            j+=1
    

    def stop(self):
        self.is_killed = True
        print('finished thread')
        self.quit()

if __name__ == "__main__":
    App = QApplication(sys.argv)
    Root = MainWindow()
    Root.show()
    sys.exit(App.exec())

1 Answers1

1

To resolve the issue, it worked like in PyQt showing video stream from opencv thread. The important step was to resize the QImage in the run() function of the thread before emitting the signal and not in the main thread.

A follow up question is: Why do I need to rescale the QImage before emitting the signal?

For reproduce-ability consider the code below.

from PyQt5.QtGui import * 
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
import cv2 

class MainWindow(QWidget):
    def __init__(self) -> None:
        super(MainWindow, self).__init__()
        self.VBL = QVBoxLayout()
        self.FeedLabel = QLabel()
        self.VBL.addWidget(self.FeedLabel)
        self.playBTN = QPushButton('Play')
        self.playBTN.clicked.connect(self.play)
        self.VBL.addWidget(self.playBTN)
        self.cancelBTN.clicked.connect(self.Cancel)
        self.VBL.addWidget(self.cancelBTN)
        self.setLayout(self.VBL)
    
    def ImageUpdateSlot(self, Image):
        self.FeedLabel.setPixmap(QPixmap.fromImage(Image))
    
    def Cancel(self):
        self.worker1.stop()
    
    def play(self):
        self.worker1 = CompleteVideoRunner("Path/to/some/video")
        self.worker1.start()
        self.worker1.sigImage.connect(self.ImageUpdateSlot)
        self.cancelBTN.clicked.connect(self.Cancel)

class VideoSignal(QObject):
    Image = pyqtSignal(QImage)
    imageCount = pyqtSignal(int)

class CompleteVideoRunner(QThread):
    #signal = VideoSignal() 
    sigImage = pyqtSignal(QImage)
    sigCount =pyqtSignal(int)
    def __init__(self, path, curFrame = 0):
        super(CompleteVideoRunner, self).__init__()
        self.cap = cv2.VideoCapture(path)
        #self.signal = VideoSignal()
        self.curFrame = curFrame
        self.is_killed = False

    def run(self):
        self.is_killed = False
        j = self.curFrame
        self.cap.set(1, j)
        while (self.cap.isOpened() and not self.is_killed):
            rep, frame = self.cap.read()
            self.curFrame = j
            if not rep:
                break
            height, width, channel = frame.shape
            bytesPerLine = 3 * width
            QImg= QImage(frame.data, width, height, bytesPerLine,
                         QImage.Format_BGR888)
            QImg = QImg.scaled(640, 480, Qt.KeepAspectRatio)
            self.sigImage.emit(QImg)
            self.sigCount.emit(j)
            j+=1
    

    def stop(self):
        self.is_killed = True
        print('finished thread')
        self.quit()

if __name__ == "__main__":
    App = QApplication(sys.argv)
    Root = MainWindow()
    Root.show()
    sys.exit(App.exec())