如何在PyQt5中用另一个小部件替换小部件

2024-05-16 14:59:14 发布

您现在位置:Python中文网/ 问答频道 /正文

在我的GUI应用程序中,我向用户显示一个摄像头流。现在的问题是,用户一次只能看到来自一个摄像机的流,为了看到来自其他摄像机的流,他必须输入新摄像机的凭证,如usernamepasswordcamera IP

我想使用对话框执行此操作。我能做到,但每次都会有一扇新窗户弹出。我知道如何使用QStackedLayout在不同的相机之间切换,但这次我不能使用,因为相机对象是在运行时创建的

我想要的是,按下一个按钮,就会出现一个对话框,输入凭证后必须更换相机

代码:

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

class CameraWidget(QtWidgets.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, username, password, camera_ip, width=0, height=0, 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.screen_width = 640
        self.screen_height = 480
        self.maintain_aspect_ratio = aspect_ratio

        self.camera_stream_link = 'rtsp://{}:{}@{}/Streaming/Channels/2'.format(username, password, camera_ip)

        # Flag to check if camera is valid/working
        self.online = False
        self.capture = None
        self.video_frame = QtWidgets.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:
            QtWidgets.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))
                self.frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                h, w, ch = self.frame.shape
                bytesPerLine = ch * w

            # Convert to pixmap and set to video frame
            self.img = QtGui.QImage(self.frame, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
            self.pix = QtGui.QPixmap.fromImage(self.img)
            self.video_frame.setPixmap(self.pix)

    def get_video_frame(self):
        return self.video_frame


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, username, password, camera_ip, parent=None):
        super(MainWindow, self).__init__(parent)

        # Top frame
        self.top_frame = QtWidgets.QFrame()
        self.top_frame.setStyleSheet("background-color: rgb(153, 187, 255)")

        self.camera = CameraWidget(username, password, camera_ip)
        self.top_layout = QtWidgets.QHBoxLayout()
        self.top_layout.addWidget(self.camera.get_video_frame())
        self.top_frame.setLayout(self.top_layout)


        # Bottom frame
        self.btm_frame = QtWidgets.QFrame()
        self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")

        self.button = QtWidgets.QPushButton('Change Camera')
        self.button.clicked.connect(self.onClick)
        self.btm_layout = QtWidgets.QHBoxLayout()
        self.btm_layout.addStretch()
        self.btm_layout.addWidget(self.button)
        self.btm_layout.setContentsMargins(5, 5, 5, 5)
        self.btm_frame.setLayout(self.btm_layout)


        self.widget = QtWidgets.QWidget()
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.top_frame, 20)
        self.layout.addWidget(self.btm_frame,1)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.widget.setLayout(self.layout)
        self.setCentralWidget(self.widget)

    def onClick(self):
        """
        I want this function to open a dialog box
        asking user to enter new cameras credentials
        and display it.
        """


if __name__ == '__main__':

    # Create main application window
    app = QtWidgets.QApplication([])
    app.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
    w = MainWindow('admin', 'vaaan@123', '192.168.1.51')
    w.showMaximized()
    sys.exit(app.exec_())

Tags: toimportselfstreamiftimedefvideo
1条回答
网友
1楼 · 发布于 2024-05-16 14:59:14

初步答复

这和你的想法接近吗

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

class CameraWidget(QtWidgets.QWidget):
    # no change
    ...


class ChangeDialog(QtWidgets.QDialog):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, *kwargs)
        QBtn = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
        
        buttonBox = QtWidgets.QDialogButtonBox(QBtn)
        buttonBox.accepted.connect(self.accept)
        buttonBox.rejected.connect(self.reject)

        self.layout = QtWidgets.QVBoxLayout()

        self.setLayout(self.layout)

        vlayout = QtWidgets.QVBoxLayout()
        self.usernameEdit = QtWidgets.QLineEdit()
        self.passwordEdit = QtWidgets.QLineEdit()
        self.passwordEdit.setEchoMode(QtWidgets.QLineEdit.Password)
        self.ipAddrEdit = QtWidgets.QLineEdit()
        vlayout.addWidget(self.usernameEdit)
        vlayout.addWidget(self.passwordEdit)
        vlayout.addWidget(self.ipAddrEdit)

        self.layout.addLayout(vlayout)
        self.layout.addWidget(buttonBox)

    @property
    def username(self):
        return self.usernameEdit.text()

    @property
    def password(self):
        return self.passwordEdit.text()

    @property
    def ipAddress(self):
        return self.ipAddrEdit.text()


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, username, password, camera_ip, parent=None):
        super(MainWindow, self).__init__(parent)

        # Top frame
        self.top_frame = QtWidgets.QFrame()
        self.top_frame.setStyleSheet("background-color: rgb(153, 187, 255)")

        self.camera = CameraWidget(username, password, camera_ip)
        self.top_layout = QtWidgets.QHBoxLayout()
        self.top_layout.addWidget(self.camera.get_video_frame())
        self.top_frame.setLayout(self.top_layout)


        # Bottom frame
        self.btm_frame = QtWidgets.QFrame()
        self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")

        self.button = QtWidgets.QPushButton('Change Camera')
        self.button.clicked.connect(self.onClick)
        self.btm_layout = QtWidgets.QHBoxLayout()
        self.btm_layout.addStretch()
        self.btm_layout.addWidget(self.button)
        self.btm_layout.setContentsMargins(5, 5, 5, 5)
        self.btm_frame.setLayout(self.btm_layout)


        self.widget = QtWidgets.QWidget()
        self.layout = QtWidgets.QVBoxLayout()
        self.layout.addWidget(self.top_frame, 20)
        self.layout.addWidget(self.btm_frame,1)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.widget.setLayout(self.layout)
        self.setCentralWidget(self.widget)

        self.changeDialog = ChangeDialog()
        self.changeDialog.accepted.connect(self.changeCamera)


    def changeCamera(self):
        self.camera = CameraWidget(
            self.changeDialog.username,
            self.changeDialog.password,
            self.changeDialog.ipAddress)
        # not sure if this is necessary
        self.top_layout.takeAt(0)
        self.top_layout.addWidget(self.camera.get_video_frame())

    def onClick(self):
        """
        I want this function to open a dialog box
        asking user to enter new cameras credentials
        and display it.
        """
        self.changeDialog.exec()


if __name__ == '__main__':

    # Create main application window
    app = QtWidgets.QApplication([])
    app.setStyle(QtWidgets.QStyleFactory.create("Fusion"))
    w = MainWindow('admin', 'vaaan@123', '192.168.1.51')
    w.showMaximized()
    sys.exit(app.exec_())

如果看不到什么东西,很难判断是否有什么东西丢失了,但这应该是正确的方向

答复意见

关于必填字段

首先,这个建议很粗糙

您应该在每次QLineEdit之前添加QLabel

然后,您应该编写一些验证逻辑。您可以通过删除我放入的默认“OK”按钮并放入您自己的按钮来完成此操作。按下此按钮时,检查每个输入self(对话框)是否有效

如果是这种情况,您可以调用^{}。否则,您可以对无效的第一个输入使用^{}

显示以前输入的数据

在我的提案中,我创建了一个与主窗口一起存储的对话框

它从未被销毁,因此所有数据仍然有效。当您第二次显示该对话框时,它仍然保存以前的数据

如果愿意,您可以每次创建一个新的对话框对象,或者清除所有输入

相关问题 更多 >