PyQt5 在 QLabel 的视频上绘制圆圈

PyQt5 Paint Circle Over Video in QLabel

我想在按下鼠标时在光标位置的视频上绘制一个圆圈。视频在 MainWindow 中的 QLabel 对象中播放。我正在使用 OpenCV 以每秒 10 帧的速度从网络摄像头读取帧。我正在将帧转换为 QPixmap 并将它们显示在 QLabel 对象中 (self.vidWindow)。

在下面的代码中,当 MainWindow 启动时(不是我想要的)立即绘制圆圈,然后被视频流覆盖。当按下鼠标按钮时,文本显示在掩码 Qlabel 对象中,并在主窗口中打印一条消息。

我可以在 QLabel 对象中画一个圆吗?如果是这样,我应该使用遮罩 QLabel 对象还是有办法直接覆盖 self.vidWindow 中的视频?

在代码的最小化版本中,显示了视频,但是当我尝试绘制椭圆时触发了错误。

import sys, cv2
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel
from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QFont
from PyQt5.QtCore import QTimer, Qt, QCoreApplication

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()        
        self.initUI()        

    def initUI(self):                       
        self.statusBar().showMessage('Ready')        
        self.setGeometry(50, 50, 800, 600)
        self.setWindowTitle('Statusbar')
        self.vidWindow = QLabel(self)
        self.vidWindow.setGeometry(20, 20, 640, 480)
        self.maskWindow = QLabel(self)
        self.maskWindow.setGeometry(20, 20, 640, 480)
        self.maskWindow.setStyleSheet('background-color: rgba(0,0,0,0%)')
        font = QFont()
        font.setPointSize(18)
        font.setBold(True)
        font.setWeight(75)
        self.maskWindow.setFont(font)
        self.maskWindow.setText('Message is on the mask Qlabel object')
        self.msgLabel = QLabel(self)
        self.msgLabel.setGeometry(675, 300, 100, 20)
        self.cap = cv2.VideoCapture(0)
        self.pix = QImage()
        self.timer = QTimer()
        self.frame_rate = 5
        self.show()
        self.start()

    def nextFrameSlot(self):
        ret, frame = self.cap.read()
        if ret == True:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QImage(frame,frame.shape[1], frame.shape[0], QImage.Format_RGB888)
            img = img.scaled(640, 480, Qt.KeepAspectRatio)
            self.pix = QPixmap.fromImage(img)
            self.vidWindow.setPixmap(self.pix)

    def mousePressEvent(self, QMouseEvent):        
        self.msgLabel.setText('Mouse Clicked!')

    def paintEvent(self, QMouseEvent):
        e = QMouseEvent
        painter = QPainter(self)
        painter.setPen(QPen(Qt.green,  4, Qt.SolidLine))
        painter.drawEllipse(e.x(), e.y(), 100)

    def start(self):
        rate = int(1000.0 / self.frame_rate)        
        self.timer.setTimerType(Qt.PreciseTimer)
        self.timer.timeout.connect(self.nextFrameSlot)
        self.timer.start(rate)

    def closeEvent(self, event):
        if self.cap.isOpened():
            self.cap.release()
            self.vidWindow.clear()        
        QCoreApplication.quit()

if __name__ == '__main__':    
    app = QApplication(sys.argv)
    ex = MainWindow()
sys.exit(app.exec_())

虽然 QPainter 用于绘制小部件,但它不适用于这种情况,因为它会将其子项下方的 "MainWindow" 绘制为 QLabel。至少有 2 种可能的解决方案:

  • 创建自定义 QLabel 并检测点击并绘制圆圈,

  • 创建一个显示有圆圈的QPixmap的QLabel,并根据鼠标信息移动。

在这种情况下,我将实现第二种方法:

import sys, cv2
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel
from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QFont
from PyQt5.QtCore import QTimer, Qt


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.statusBar().showMessage("Ready")
        self.setGeometry(50, 50, 800, 600)
        self.setWindowTitle("Statusbar")
        self.vidWindow = QLabel(self)
        self.vidWindow.setGeometry(20, 20, 640, 480)
        self.maskWindow = QLabel(self)
        self.maskWindow.setGeometry(20, 20, 640, 480)
        self.maskWindow.setStyleSheet("background-color: rgba(0,0,0,0%)")
        font = QFont()
        font.setPointSize(18)
        font.setBold(True)
        font.setWeight(75)
        self.maskWindow.setFont(font)
        self.maskWindow.setText("Message is on the mask Qlabel object")
        self.msgLabel = QLabel(self)
        self.msgLabel.setGeometry(675, 300, 100, 20)

        self.marker_label = QLabel(self)

        pixmap = QPixmap(100, 100)
        pixmap.fill(Qt.transparent)

        painter = QPainter(pixmap)
        painter.setPen(QPen(Qt.green, 4, Qt.SolidLine))
        painter.drawEllipse(pixmap.rect().adjusted(4, 4, -4, -4))
        painter.end()

        self.marker_label.setPixmap(pixmap)
        self.marker_label.adjustSize()
        self.marker_label.hide()
        self.marker_label.raise_()

        self.cap = cv2.VideoCapture(0)
        self.timer = QTimer()
        self.frame_rate = 5
        self.show()
        self.start()

    def nextFrameSlot(self):
        ret, frame = self.cap.read()
        if ret == True:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = QImage(frame, frame.shape[1], frame.shape[0], QImage.Format_RGB888)
            img = img.scaled(640, 480, Qt.KeepAspectRatio)
            pix = QPixmap.fromImage(img)
            self.vidWindow.setPixmap(pix)

    def mousePressEvent(self, event):
        self.msgLabel.setText("Mouse Clicked!")
        if self.vidWindow.rect().contains(event.pos()):
            self.marker_label.move(event.pos() - self.marker_label.rect().center())
            self.marker_label.show()
        super().mousePressEvent(event)

    def start(self):
        rate = int(1000.0 / self.frame_rate)
        self.timer.setTimerType(Qt.PreciseTimer)
        self.timer.timeout.connect(self.nextFrameSlot)
        self.timer.start(rate)

    def closeEvent(self, event):
        if self.cap.isOpened():
            self.cap.release()
        super().closeEvent(event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    ex = MainWindow()
    sys.exit(app.exec_())