How to replace a widget with another in PyQt5 - python

In my GUI application, I'm displaying a camera stream to a user. Now the thing is that the user will be able to see stream from only one camera at a time and in order to see streams from other cameras he must enter the credentials of the new camera like username, password and camera IP.
I want to do this using a dialog box. I was able to do that but everytime a new window popped up. I do how to switch between different cameras using QStackedLayout but this time I can't use that because the camera objects are created at runtime.
All I want is that on press of a button a dialog box should appear and the camera must be replaced once the credentials are entered.
code:
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_())

Initial answer
Is this close to what you have in mind?
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_())
Without being able to actually see something it's hard to tell if something is missing, but this should be the right direction.
Answering comments
About mandatory fields
First of all this proposal is very rough.
You should add QLabels before each QLineEdit.
Then you should code some validation logic. You do this by removing the default "OK" button that I put in and put your own button. When this button is pressed, you check that each input self (the dialog) is valid.
If this is the case you can call accept(). Otherwise you can use setFocus() on the first input that is invalid.
Display previously entered data
In my proposal I created a dialog that is stored with the MainWindow.
It is never destroyed so all the data is still alive. When you display the dialog for the second time it still holds the previous data.
You can create a new dialog object each time if you prefer, or clear all inputs.

Related

Pyside6 Multi threading Opencv Webcam

In the below code I have a Pyside6 application that dynamically creates a list of labels and comboboxes paralel to each other.
To the right, each combobox has a list of available cameras and to the left(colored in black) I have a list of labels where in which I want to display the cameras.
This is what it looks like:
As I used the currentIndexChanged, whenevever you select any camera device, the thread will be launched and will be displayed at the last label.
Like so:
What I am struggling with, is:
Based on the index of the combobox I want to display the video stream on the same index of the label(as in this example i selected the combo box at index 0 but it displayed in label at index 4)
Pass the index of the selected camera(in the individual combobox) to the cv2.VideoCapture(index) so that the user can select what camera they want to display
How to be able to multi thread, so as to be able to display multiple cameras at the same time.
Here is the code:
import sys
from PySide6 import QtWidgets
from PySide6.QtCore import Qt, QThread, Signal, Slot
from PySide6.QtGui import QImage
from PySide6.QtGui import QIcon, QPixmap, QImage
from PySide6.QtMultimedia import QMediaDevices
import cv2
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.layout = QtWidgets.QGridLayout(self)
self.lists = ["1", "2", "3"]
self.availableCameras = []
self.th = Thread(self)
self.th.finished.connect(self.close)
self.th.updateFrame.connect(self.setImage)
for i in range(5):
self.label = QtWidgets.QLabel(self)
self.label.id_number = i
self.label.setStyleSheet(u"background-color: black;")
self.layout.addWidget(self.label, i, 0)
self.combobox = QtWidgets.QComboBox(self)
self.combobox.id_number = i
self.getAvailableCameras()
self.combobox.addItems(self.availableCameras)
self.layout.addWidget(self.combobox, i, 1)
self.combobox.currentIndexChanged.connect(self.runWebCam)
#Slot(QImage)
def runWebCam(self, idx):
combo = self.sender()
print(f"Selected the variable {idx} from combo {combo.id_number}")
self.th.start()
#Slot(QImage)
def setImage(self, image):
self.label.setPixmap(QPixmap.fromImage(image))
def getAvailableCameras(self):
cameras = QMediaDevices.videoInputs()
for cameraDevice in cameras:
self.availableCameras.append(cameraDevice.description())
class Thread(QThread):
updateFrame = Signal(QImage)
def __init__(self, parent=None):
QThread.__init__(self, parent)
self.status = True
self.cap = True
def run(self):
self.cap = cv2.VideoCapture(0)
while self.status:
ret, frame = self.cap.read()
if not ret:
continue
h, w, ch = frame.shape
img = QImage(frame.data, w, h, ch * w, QImage.Format_RGB888)
scaled_img = img.scaled(640, 480, Qt.KeepAspectRatio)
# Emit signal
self.updateFrame.emit(scaled_img)
sys.exit(-1)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
widget = MyWidget()
widget.resize(800, 600)
widget.show()
sys.exit(app.exec_())
def runWebCam(self, idx):
self.idx = idx
combo = self.sender()
print(f"Selected the variable {idx} from combo {combo.id_number}")
self.th.start()
def setImage(self, image):
self.label_list[self.idx].setPixmap(QPixmap.fromImage(image))
'''here self.label is the widget object of last label which you have created.
I tried storing the self.label into a list (self.label_list) and when you are selecting the item in the combo_box getting the idx value
Using the above idx value as list index, selecting the right label to display.
See the picture attached'''

RuntimeError: wrapped C/C++ object of type QLabel has been deleted

I am trying to run the following piece of code but it gives me an error RuntimeError: wrapped C/C++ object of type QLabel has been deleted. I'm unable to figure out why is it happening.
I searched other answers but they all said it has something to do with setCentralWidget. But I'm not using it. So, why's the error occuring in my code?
from PyQt4 import QtGui, QtCore
from threading import Thread
from collections import deque
from datetime import datetime
import time
import sys
import cv2
import imutils
import os
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, btn_text=None, 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)
self.screen_width = width - 8
self.screen_height = height - 8
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_display_frame = QtGui.QFrame()
self.video_layout = QtGui.QVBoxLayout()
self.video_btn = QtGui.QPushButton(btn_text)
self.video_btn.setStyleSheet("background-color: rgb(128, 159, 255)")
self.video_btn.clicked.connect(self.message)
self.video_frame = QtGui.QLabel()
self.video_layout.addWidget(self.video_btn)
self.video_layout.addWidget(self.video_frame)
self.video_layout.setContentsMargins(0,0,0,0)
self.video_layout.setSpacing(0)
self.video_display_frame.setLayout(self.video_layout)
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) ### error comes in this line.
def get_video_frame(self):
return self.video_frame
def get_video_display_frame(self):
return self.video_display_frame
def message(self):
self.zone_config = ZoneConfig(self.camera_stream_link)
self.zone_config.show()
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.initUI()
def initUI(self):
self.setWindowTitle("VIDS")
titleBar_logo = os.path.join(dir, 'logos/logo.png')
self.setWindowIcon(QtGui.QIcon(titleBar_logo))
self.showMaximized()
self.screen_width = self.width()
self.screen_height = self.height()
# Layouts & frames
self.layout = QtGui.QVBoxLayout()
self.top_frame = QtGui.QFrame()
self.top_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.mid_frame = QtGui.QFrame()
self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
self.btm_frame = QtGui.QFrame()
self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.layout.addWidget(self.top_frame, 2)
self.layout.addWidget(self.mid_frame, 20)
self.layout.addWidget(self.btm_frame, 1)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.setLayout(self.layout)
# Top frame
label = QtGui.QLabel('VIDS Dashboard')
label.setFont(QtGui.QFont('Arial', 20))
label.setAlignment(QtCore.Qt.AlignCenter)
self.top_layout = QtGui.QHBoxLayout()
self.top_layout.addWidget(label)
self.top_frame.setLayout(self.top_layout)
# Middle frame
self.mid_layout = QtGui.QStackedLayout()
# Create camera widgets
print('Creating Camera Widgets...')
self.one = CameraWidget(int(self.screen_width), int(self.screen_height), camera1, 'Camera 1')
self.two = CameraWidget(int(self.screen_width), int(self.screen_height), camera2, 'Camera 2')
cam_widget = Cam2(self.one, self.two, self)
self.mid_layout.addWidget(cam_widget)
self.mid_layout.setCurrentWidget(cam_widget)
self.mid_frame.setLayout(self.mid_layout)
# Bottom frame
btn1 = QtGui.QPushButton('1 Cam')
btn1.clicked.connect(self.button1)
btn2 = QtGui.QPushButton('2 Cams')
btn2.clicked.connect(self.button2)
self.btm_layout = QtGui.QHBoxLayout()
self.btm_layout.addStretch()
self.btm_layout.addWidget(btn1)
self.btm_layout.addWidget(btn2)
self.btm_frame.setLayout(self.btm_layout)
self.show()
def button1(self):
cam1_widget = Cam1(self.one, self)
self.mid_layout.addWidget(cam1_widget)
self.mid_layout.setCurrentWidget(cam1_widget)
def button2(self):
cam2_widget = Cam2(self.one, self.two, self)
self.mid_layout.addWidget(cam2_widget)
self.mid_layout.setCurrentWidget(cam2_widget)
class Cam1(QtGui.QWidget):
def __init__(self, one, parent=None):
super(Cam1, self).__init__(parent)
# Add widgets to layout
print('Adding widgets to layout...')
layout = QtGui.QGridLayout()
layout.addWidget(one.get_video_display_frame(),0,0,1,1)
self.setLayout(layout)
class Cam2(QtGui.QWidget):
def __init__(self, one, two, parent=None):
super(Cam2, self).__init__(parent)
# Add widgets to layout
print('Adding widgets to layout...')
layout = QtGui.QGridLayout()
layout.addWidget(one.get_video_display_frame(),0,0,1,1)
layout.addWidget(two.get_video_display_frame(),0,1,1,1)
self.setLayout(layout)
class ZoneConfig(QtGui.QWidget):
def __init__(self, camera, parent=None):
super(ZoneConfig, self).__init__(parent)
self.initUI(camera)
def initUI(self, camera):
self.setWindowTitle("VIDS")
titleBar_logo = os.path.join(dir, 'logos/logo.png')
self.setWindowIcon(QtGui.QIcon(titleBar_logo))
self.showMaximized()
self.screen_width = self.width()
self.screen_height = self.height()
self.camera = camera
# Layouts & frames
self.layout = QtGui.QVBoxLayout()
self.top_frame = QtGui.QFrame()
self.top_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.mid_frame = QtGui.QFrame()
# self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
self.btm_frame = QtGui.QFrame()
self.btm_frame.setStyleSheet("background-color: rgb(208, 208, 225)")
self.layout.addWidget(self.top_frame, 2)
self.layout.addWidget(self.mid_frame, 20)
self.layout.addWidget(self.btm_frame, 1)
self.layout.setContentsMargins(0,0,0,0)
self.layout.setSpacing(0)
self.setLayout(self.layout)
# Top frame
self.label = QtGui.QLabel('VIDS - Zone Configuration')
self.label.setFont(QtGui.QFont('Arial', 20))
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.top_layout = QtGui.QHBoxLayout()
self.top_layout.addWidget(self.label)
self.top_frame.setLayout(self.top_layout)
# Middle frame
self.mid_layout = QtGui.QVBoxLayout()
self.mid_frame.setStyleSheet("background-color: rgb(153, 187, 255)")
# Create camera widgets
print('Creating Camera Widgets...')
self.cam = CameraWidget(self.screen_width, self.screen_height, self.camera)
self.mid_layout = QtGui.QVBoxLayout()
self.mid_layout.addWidget(self.cam.get_video_frame())
self.mid_frame.setLayout(self.mid_layout)
self.mid_layout.setContentsMargins(0,0,0,0)
self.mid_layout.setSpacing(0)
self.mid_frame.setLayout(self.mid_layout)
# Bottom frame
btn = QtGui.QPushButton('Dashboard')
btn.clicked.connect(self.goMainWindow)
self.btm_layout = QtGui.QHBoxLayout()
self.btm_layout.addStretch()
self.btm_layout.addWidget(btn)
self.btm_frame.setLayout(self.btm_layout)
def goMainWindow(self):
self.close()
dir = os.path.dirname(__file__)
window = None
camera1 = 'streams/Fog.avi'
camera2 = 'streams/Overspeed.avi'
if __name__ == '__main__':
app = QtGui.QApplication([])
app.setStyle(QtGui.QStyleFactory.create("plastique")) # applies to entire window
window = MainWindow()
# window.show()
sys.exit(app.exec_())
I am using python 3, pyqt 4 and windows 10.
It seems that in the handling of threads it does not synchronize the elimination of the objects correctly, so a possible solution is to use sip to verify if the object was eliminated or not:
from PyQt4 import QtGui, QtCore
import sip
# ...
self.pix = QtGui.QPixmap.fromImage(self.img)
if not sip.isdeleted(self.video_frame):
self.video_frame.setPixmap(self.pix)

How to make screenshot while showing video from cam?

#Importing necessary libraries, mainly the OpenCV, and PyQt libraries
import cv2
import numpy as np
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSignal
class ShowVideo(QtCore.QObject):
#initiating the built in camera
camera_port = -1
camera = cv2.VideoCapture(camera_port)
VideoSignal = QtCore.pyqtSignal(QtGui.QImage)
def __init__(self, parent = None):
super(ShowVideo, self).__init__(parent)
#QtCore.pyqtSlot()
def startVideo(self):
run_video = True
while run_video:
ret, image = self.camera.read()
color_swapped_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
height, width, _ = color_swapped_image.shape
qt_image = QtGui.QImage(color_swapped_image.data,
width,
height,
color_swapped_image.strides[0],
QtGui.QImage.Format_RGB888)
pixmap = QtGui.QPixmap(qt_image)
qt_image = pixmap.scaled(640, 480, QtCore.Qt.KeepAspectRatio)
qt_image = QtGui.QImage(qt_image)
self.VideoSignal.emit(qt_image)
#QtCore.pyqtSlot()
def makeScreenshot(self):
#cv2.imwrite("test.jpg", self.image)
print("Screenshot saved")
#self.qt_image.save('test.jpg')
class ImageViewer(QtWidgets.QWidget):
def __init__(self, parent = None):
super(ImageViewer, self).__init__(parent)
self.image = QtGui.QImage()
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.drawImage(0,0, self.image)
self.image = QtGui.QImage()
def initUI(self):
self.setWindowTitle('Test')
#QtCore.pyqtSlot(QtGui.QImage)
def setImage(self, image):
if image.isNull():
print("viewer dropped frame!")
self.image = image
if image.size() != self.size():
self.setFixedSize(image.size())
self.update()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
thread = QtCore.QThread()
thread.start()
vid = ShowVideo()
vid.moveToThread(thread)
image_viewer = ImageViewer()
#image_viewer.resize(200,400)
vid.VideoSignal.connect(image_viewer.setImage)
#Button to start the videocapture:
push_button = QtWidgets.QPushButton('Start')
push_button.clicked.connect(vid.startVideo)
push_button2 = QtWidgets.QPushButton('Screenshot')
push_button2.clicked.connect(vid.makeScreenshot)
vertical_layout = QtWidgets.QVBoxLayout()
vertical_layout.addWidget(image_viewer)
vertical_layout.addWidget(push_button)
vertical_layout.addWidget(push_button2)
layout_widget = QtWidgets.QWidget()
layout_widget.setLayout(vertical_layout)
main_window = QtWidgets.QMainWindow()
main_window.setCentralWidget(layout_widget)
main_window.resize(640,480)
main_window.show()
sys.exit(app.exec_())
This code showing video from camera in endless loop using OpenCV and PyQt5. But how to make screenshot and don't stop showing video. I think it needs to be stop loop for a little, make screnshot, and run loop again.
You can use cv2.waitKey() for the same, as shown below:
while run_video:
ret, image = self.camera.read()
if(cv2.waitKey(10) & 0xFF == ord('s')):
cv2.imwrite("screenshot.jpg",image)
(I'm guessing that by the term "screenshot", you mean the camera frame, and not the image of the entire screen.)
When you press 's' on the keyboard, it'll perform imwrite.
Note that if you wish to save multiple images, you'd have to vary the filename. The above code will overwrite screenshot.jpg to save only the latest frame.

pyqt5: qMainWindow does not expand to centralWidget Size

I'm integrating openCV 3.0 with Qt5 in Python 3.4.3 using pyqt5. I've been trying to build an app to process videos from files, but ran into some trouble with pyqt. Specifically, I will be loading videos through a file dialog multiple times and these videos will not be the same size. Therefore, I want the main window in my app to wrap/expand to the size of the video being played.
Below is a simplified version of my code with the 3 core classes for showing the video. One for the Main Window, one for a frame viewer widget to show each video frame in the GUI, and one for a video processor to read and process the video through opencv, transform it to a QImage then send it to the viewer.
class videoProcessor(QtCore.QObject):
filename = None
cap = None
videoSignal = QtCore.pyqtSignal(QtGui.QImage)
def __init__(self):
super().__init__()
self.filename = "test.mp4"
#QtCore.pyqtSlot()
def runVideoProcessor(self):
self.cap = cv2.VideoCapture(self.filename)
while self.cap.isOpened():
ret, frame = self.cap.read()
if ret:
outimg = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
imgh, imgw, bytesPerComponent = outimg.shape
bytesPerLine = bytesPerComponent * imgw;
qtimg = QtGui.QImage(outimg.data,imgw,imgh,bytesPerLine,QtGui.QImage.Format_RGB888)
self.videoSignal.emit(qtimg)
else:
break
self.cap.release()
class frameViewer(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.image = QtGui.QImage()
self.imageAvailable = False
def paintEvent(self,event):
painter = QtGui.QPainter(self)
painter.drawImage(0,0,self.image)
self.image = QtGui.QImage()
painter.end()
#QtCore.pyqtSlot(QtGui.QImage)
def setFrame(self,image):
self.image = image
self.setFixedSize(self.image.size())
self.repaint()
class mainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.fview = frameViewer()
self.vproc = videoProcessor()
self.vproc.videoSignal.connect(self.fview.setFrame)
self.startButton = QtWidgets.QPushButton("Start")
self.startButton.clicked.connect(self.vproc.runVideoProcessor)
self.mainLayout = QtWidgets.QVBoxLayout()
self.mainLayout.addWidget(self.fview)
self.mainLayout.addWidget(self.startButton)
self.mainWidget = QtWidgets.QWidget()
self.mainWidget.setLayout(self.mainLayout)
self.mainWidget.setSizePolicy(QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Expanding)
self.setCentralWidget(self.mainWidget)
self.statusBar().showMessage('Ready')
self.setGeometry(50, 50, 300, 300)
self.setWindowTitle('OpenCV PyQt Test')
self.show()
if __name__=='__main__':
app = QtWidgets.QApplication(sys.argv)
mw = mainWindow()
sys.exit(app.exec_())
So far, the program can run videos but there are two main problems:
The window does not adjust to the size of the video frame until the end of the video. However, any repetition of the same video play will be in the correct size.
If I don't set self.image=QtGui.QImage() in paintEvent after drawing the image, the program crashes. However, if I put that line in, at the end of the video, the window will go blank because an empty QImage will be drawn in the last frame's place whenever the window is updated.
Any ideas on how to solve these issues? Thank you.

Display OpenCv window on top of PyQt's main window

I have made a opencv project which processes input stream from the video and displays the processed output. I have used PyQt buttons to switch from one output to another. My PyQt window covvers almost the entire screen and when i click on my buttons, the opencv window remains behind the PyQt window. Also, I have made the main window of PyQt my parent window. How can I bring the opencv window on top of PyQt window. I searched for cvGetWindowHandle(), but didn't find it's implementation for python.
I have used PyQt4 and opencv2, and the PyQt window has not been designed using a QtDesigner.
You can always wrap OpenCV window in Qt widget...
class QtCapture(QtGui.QWidget):
def __init__(self, *args):
super(QtGui.QWidget, self).__init__()
self.fps = 24
self.cap = cv2.VideoCapture(*args)
self.video_frame = QtGui.QLabel()
lay = QtGui.QVBoxLayout()
lay.setMargin(0)
lay.addWidget(self.video_frame)
self.setLayout(lay)
def setFPS(self, fps):
self.fps = fps
def nextFrameSlot(self):
ret, frame = self.cap.read()
# OpenCV yields frames in BGR format
frame = cv2.cvtColor(frame, cv2.cv.CV_BGR2RGB)
img = QtGui.QImage(frame, frame.shape[1], frame.shape[0], QtGui.QImage.Format_RGB888)
pix = QtGui.QPixmap.fromImage(img)
self.video_frame.setPixmap(pix)
def start(self):
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.nextFrameSlot)
self.timer.start(1000./self.fps)
def stop(self):
self.timer.stop()
def deleteLater(self):
self.cap.release()
super(QtGui.QWidget, self).deleteLater()
...and do with it whatever you will:
class ControlWindow(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.capture = None
self.start_button = QtGui.QPushButton('Start')
self.start_button.clicked.connect(self.startCapture)
self.quit_button = QtGui.QPushButton('End')
self.quit_button.clicked.connect(self.endCapture)
self.end_button = QtGui.QPushButton('Stop')
vbox = QtGui.QVBoxLayout(self)
vbox.addWidget(self.start_button)
vbox.addWidget(self.end_button)
vbox.addWidget(self.quit_button)
self.setLayout(vbox)
self.setWindowTitle('Control Panel')
self.setGeometry(100,100,200,200)
self.show()
def startCapture(self):
if not self.capture:
self.capture = QtCapture(0)
self.end_button.clicked.connect(self.capture.stop)
self.capture.setFPS(30)
self.capture.setParent(self)
self.capture.setWindowFlags(QtCore.Qt.Tool)
self.capture.start()
self.capture.show()
def endCapture(self):
self.capture.deleteLater()
self.capture = None
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = ControlWindow()
sys.exit(app.exec_())

Categories