【问题标题】:Place a QToolButton on top of a video frame在视频帧的顶部放置一个 QToolButton
【发布时间】:2020-11-26 22:19:13
【问题描述】:

以下是视频监控系统的代码。我想在每个相机的右下角添加一个 QToolButton。

我对这个按钮有两个要求。首先是按钮只有在我将鼠标悬停在视频帧上时才可见。其次,我想要下拉功能而不是下拉。

编辑:如果可能,我还想删除 QToolButton 的小箭头符号。

我怎样才能做到这一点?

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

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, 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)

        # Slight offset is needed since PyQt layouts have a built in padding
        # So add offset to counter the padding 
        self.offset = 16
        self.screen_width = width - self.offset
        self.screen_height = height - self.offset
        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_frame = QtGui.QLabel()

        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)

    def get_video_frame(self):
        return self.video_frame


if __name__ == '__main__':

    # Create main application window
    app = QtGui.QApplication([])
    app.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
    mw = QtGui.QMainWindow()
    mw.setWindowTitle('Camera GUI')

    cw = QtGui.QWidget()
    ml = QtGui.QGridLayout()
    cw.setLayout(ml)
    mw.setCentralWidget(cw)
    mw.showMaximized()

    # Dynamically determine screen width/height
    screen_width = QtGui.QApplication.desktop().screenGeometry().width()
    screen_height = QtGui.QApplication.desktop().screenGeometry().height()

    # Create Camera Widgets 
    username = 'Your camera username!'
    password = 'Your camera password!'

    # Stream links
    camera0 = 'rtsp://{}:{}@192.168.1.43:554/cam/realmonitor?channel=1&subtype=0'.format(username, password)
    camera1 = 'rtsp://{}:{}@192.168.1.45/axis-media/media.amp'.format(username, password)
    camera2 = 'rtsp://{}:{}@192.168.1.47:554/cam/realmonitor?channel=1&subtype=0'.format(username, password)
    camera3 = 'rtsp://{}:{}@192.168.1.40:554/cam/realmonitor?channel=1&subtype=0'.format(username, password)
    camera4 = 'rtsp://{}:{}@192.168.1.44:554/cam/realmonitor?channel=1&subtype=0'.format(username, password)
    camera5 = 'rtsp://{}:{}@192.168.1.42:554/cam/realmonitor?channel=1&subtype=0'.format(username, password)
    camera6 = 'rtsp://{}:{}@192.168.1.46:554/cam/realmonitor?channel=1&subtype=0'.format(username, password)
    camera7 = 'rtsp://{}:{}@192.168.1.41:554/cam/realmonitor?channel=1&subtype=0'.format(username, password)

    # Create camera widgets
    print('Creating Camera Widgets...')
    zero = CameraWidget(screen_width//3, screen_height//3, camera0)
    one = CameraWidget(screen_width//3, screen_height//3, camera1)
    two = CameraWidget(screen_width//3, screen_height//3, camera2)
    three = CameraWidget(screen_width//3, screen_height//3, camera3)
    four = CameraWidget(screen_width//3, screen_height//3, camera4)
    five = CameraWidget(screen_width//3, screen_height//3, camera5)
    six = CameraWidget(screen_width//3, screen_height//3, camera6)
    seven = CameraWidget(screen_width//3, screen_height//3, camera7)

    # Add widgets to layout
    print('Adding widgets to layout...')
    ml.addWidget(zero.get_video_frame(),0,0,1,1)
    ml.addWidget(one.get_video_frame(),0,1,1,1)
    ml.addWidget(two.get_video_frame(),0,2,1,1)
    ml.addWidget(three.get_video_frame(),1,0,1,1)
    ml.addWidget(four.get_video_frame(),1,1,1,1)
    ml.addWidget(five.get_video_frame(),1,2,1,1)
    ml.addWidget(six.get_video_frame(),2,0,1,1)
    ml.addWidget(seven.get_video_frame(),2,1,1,1)

    print('Verifying camera credentials...')

    mw.show()

【问题讨论】:

  • 那么,你的“视频帧”,其实是一个QLabel,对吧?
  • @musicamante,是的,没错。
  • 那么,请再次始终尽量减少示例。您的问题与视频捕获无关,您的 95% 的代码完全没有必要。
  • @musicamante 实际上我不想错过任何可能使任何人都难以理解代码的细节,并且正如您在我的另一个问题上评论的那样,该示例应该采用一种形式在那里人们可以毫无困难地复制、粘贴和在他的机器上运行。
  • 它实际上使您的代码更加难以理解。你想在 QLabel 上显示一个按钮,就是这样。是的,该示例必须是可重现的,但也是最小。如果您无法减少当前代码以使其可重现,请创建一个仅实现必要部分的新脚本:在您的情况下,为标签设置一个临时 QPixmap 就足够了,因为您的代码就是这样做的。跨度>

标签: python user-interface pyqt video-streaming pyqt4


【解决方案1】:

您需要在标签中添加一个 child 按钮,方法是在构造函数中将标签用作父级(这也可以通过 setParent() 来完成)。

class VideoWidget(QtWidgets.QLabel):
    def __init__(self):
        super().__init__()
        # create a button that is a *child* of *this* widget ("self")
        self.button = QtWidgets.QToolButton(self)
        # hide the button, otherwise it will be automatically visible as soon
        # as this VideoWidget will be shown
        self.button.setVisible(False)
        self.buttonOffset = 5

        # just for testing
        self.setPixmap(QtGui.QPixmap('frame.png'))

    def updateButtonGeometry(self):
        # create a rectangle based on the sizeHint of the button, it will 
        # be used to set its geometry
        geo = QtCore.QRect(QtCore.QPoint(), self.button.sizeHint())
        # move the rectangle to the bottom right corner, using an offset 
        # as a margin from the border
        offset = QtCore.QPoint(self.buttonOffset, self.buttonOffset)
        geo.moveBottomRight(self.rect().bottomRight() - offset)
        # apply the geometry
        self.button.setGeometry(geo)

    def enterEvent(self, event):
        self.button.setVisible(True)
        self.updateButtonGeometry()

    def leaveEvent(self, event):
        self.button.setVisible(False)

    def resizeEvent(self, event):
        self.updateButtonGeometry()


class VideoContainer(QtWidgets.QWidget):
    '''
    An example class to display some widgets in a grid layout
    '''
    def __init__(self):
        super().__init__()
        self.videoWidgets = []
        layout = QtWidgets.QGridLayout(self)
        for row in range(3):
            for col in range(3):
                videoWidget = VideoWidget()
                layout.addWidget(videoWidget, row, col)
                self.videoWidgets.append(videoWidget)

关于您的代码的考虑(顺便说一句,这对于这件事来说是不必要的复杂):您正在创建 VideoWidget 类,但您从未使用它们,这使得它们几乎无用。只需从 QLabel 子类化并直接使用它的实例。

【讨论】:

  • 很抱歉给您带来了麻烦,但您能否说明一下我必须如何在构造函数中将标签作为父级以及我在哪里创建VideoWidget 类。
  • 标签成为父标签完全如代码所示self.button = QtWidgets.QToolButton(self)。从 QLabel 而不是 QWidget 的子类,将代码中与 self.label 相关的所有内容替换为 self,并使用 ml.addWidget(zero,0,0,1,1) 等将这些小部件添加到布局中。
  • 我还是不明白。
  • 什么是你不明白的?
  • VideoContainer 只是显示结果的示例。我说你是从 QWidget 子类化你没有真正使用的,因为你实际上是在显示 QLabel,所以你应该直接从 QLabel 子类化,而且,显然,你不需要创建self.video_frame,因为self(子类实例)将是实际标签,所以所有对self.video_frame的引用都应该单独替换为self
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-24
  • 2021-08-28
  • 1970-01-01
  • 2013-06-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多