Search code examples
python-3.xpyqt5opencvpython

How do I draw a shape over a video using pyqt5 in python?


I am trying to have a shape on the video while still having that video as a label instead of having it as a background.

import os
import sys
import numpy as np
import cv2

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

 # https://stackoverflow.com/questions/67488022/how-do-i-run-a-video-in-a-pyqt-container
class ThreadOpenCV(QThread):
    
    changePixmap = pyqtSignal(QImage)

    def __init__(self, source):
        super().__init__()

        self.source = source

        self.running = True

    def run(self):
        print('start')

        cap = cv2.VideoCapture(self.source)

        self.running = True
        
        while self.running:
            ret, frame = cap.read()
            if ret:
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

                h, w, ch = frame.shape
                bytes_per_line = ch * w   # PEP8: `lower_case_names` for variables
                
                image = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
                image = image.scaled(640, 480, Qt.KeepAspectRatio)
                self.changePixmap.emit(image)
            
        cap.release()
        print('stop')
        
    def stop(self):
        self.running = False


class Widget(QtWidgets.QMainWindow):
    
    def __init__(self):
        super().__init__()
        
        PATH_TO_VIDEO = os.path.join(os.getcwd(), 'videoname.mp4')


        self.thread = ThreadOpenCV(PATH_TO_VIDEO)
        self.thread.changePixmap.connect(self.setImage)
        self.image =""
        
        # layout and adding buttons and images
        layout = QVBoxLayout()

        self.label_video = QLabel()
        layout.addWidget(self.label_video)

        self.btn1 = QPushButton("PLAY")
        self.btn1.clicked.connect(self.playVideo)
        layout.addWidget(self.btn1)

        self.btn_stop = QPushButton("STOP")
        self.btn_stop.clicked.connect(self.stopVideo)
        layout.addWidget(self.btn_stop)
        
        self.widget = QWidget()
        self.widget.setLayout(layout)

        self.setCentralWidget(self.widget)
        
    def playVideo(self):
        self.thread.start()

    def stopVideo(self):
        self.thread.running = False


    def setImage(self, image):
        
        self.image = QPixmap.fromImage(image)
        self.update()
        self.label_video.setPixmap(self.image)

# https://stackoverflow.com/questions/42769354/draw-on-top-of-image
    def paintEvent(self, event):
        painter = QPainter(self)
        pixmap = QPixmap(self.image)
        painter.drawPixmap(self.rect(), pixmap)
        pen = QPen(Qt.red, 3)
        painter.setPen(pen)
        painter.drawEllipse(int(self.frameGeometry().width()//2),int(self.frameGeometry().height()//2) ,50,50)
    
 
 



if __name__ == '__main__':

    app = QtWidgets.QApplication([])

    mw = Widget()
    mw.show()

    app.exec()

I was able to get the video to play with a shape on it however it seemed to set the video as a background which isn't what I wanted and the video is also playing but without the drawing Infront. (if self.label_video.setPixmap(self.image) is commented out you can see that the background image does have a drawing on it)

I was curious if this could be done by drawing something and then bringing the drawing to the front if this could be done then how so? (I looked into the raise() method but couldn't figure out how to use it)


Solution

  • I was able to fix it by changing the run function to this

        def run(self):
        # print('start')
        # try:
        #     self.count
        # except:
            
        cap = cv2.VideoCapture(self.source)
        fps = float(cap.get(cv2.CAP_PROP_FPS))
    
        self.running = True
        
        while self.running:
            time.sleep(1/fps)
            ret, frame = cap.read()
    
            if ret:
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    
                h, w, ch = frame.shape
                bytes_per_line = ch * w   # PEP8: `lower_case_names` for variables
                
                image = QImage(frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
                image = image.scaled(640, 480, Qt.KeepAspectRatio)
    
                painter = QPainter(image)
                pixmap = QPixmap(image)
                painter.drawPixmap(640,480, pixmap)
                pen = QPen(QColor(0, 0, 0), 1)
                painter.setPen(pen)
                painter.drawEllipse(285,45 ,30,50) 
    
                self.count +=1
                del painter
                del pen
    
                self.changePixmap.emit(image)