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'''
Related
I have a simple user interface where I want to dynamically add frames and labels in a widget (as I will use these labels to transmit a video feed from my webcams).
In the following code I set a function where the user selects an integer which represents the number of labels(webcams) they want to see and then dynamically adds these labels& frames to the widget:
def loopCamFeed(self,n):
if (n % 2) == 0:
dividnd = n / 2
for i in range(2):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.horizontalLayout_14 = QHBoxLayout(self.frame_12)
self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
for i in range(int(dividnd)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.horizontalLayout_14.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
Which displays the labels as in the image below:
--By adding a value of 2:
--By adding a value of 4):
By adding a value of 8:
The challenge that I am facing is how to handle an odd number selection. For example, if a user selects 3 or 7 webcams/labels.
If a user selects 3 labels/webcams, I'd want to show one on the top frame and two at the bottom.
MAIN.PY (Where this piece of code was written):
from ui_interface import *
import sys
from Custom_Widgets.Widgets import *
import cv2
import numpy as np
from PyQt5.QtCore import pyqtSignal, QObject, QThread
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
loadJsonStyle(self, self.ui)
self.show()
#Expand Center Menu Widget
self.ui.settingsBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
self.ui.infoBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
self.ui.helpBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
#Close Center Menu Widget
self.ui.closeCenterMenuButton.clicked.connect(lambda: self.ui.centerMenuContainer.collapseMenu())
#Close Notification Menu Widget
self.ui.closeNotificationBtn.clicked.connect(lambda: self.ui.popUpNotificationContainer.collapseMenu())
self.loopCamFeed(4)
def ImageUpdateSlot(self, Image):
self.ui.label_5.setPixmap(QPixmap.fromImage(Image))
def CancelFeed(self):
self.worker1.stop()
def startVideo(self):
self.worker1 = Worker1()
self.worker1.start()
self.worker1.ImageUpdate.connect(self.ImageUpdateSlot)
def loopCamFeed(self,n):
if (n % 2) == 0:
dividnd = n / 2
for i in range(2):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.horizontalLayout_14 = QHBoxLayout(self.frame_12)
self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
for i in range(int(dividnd)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.horizontalLayout_14.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
class Worker1(QThread):
ImageUpdate = pyqtSignal(QImage)
def __init__(self):
super().__init__()
def run(self):
self.ThreadActive = True
Capture = cv2.VideoCapture(0)
while self.ThreadActive:
ret, frame = Capture.read()
if ret:
Image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
FlippedImage = cv2.flip(Image, 1)
ConvertToQtFormat = QImage(FlippedImage.data, FlippedImage.shape[1], FlippedImage.shape[0], QImage.Format_RGB888)
Pic = ConvertToQtFormat.scaled(1200, 900, Qt.KeepAspectRatio)
self.ImageUpdate.emit(Pic)
def stop(self):
self.ThreadActive = False
self.quit()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Use a grid layout instead of a horizontal layout:
def loopCamFeed(self,n):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.grid_layout = QGridLayout(self.frame_12)
self.grid_layout.setObjectName(u"grid_layout")
for i in range(int(n)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.grid_layout.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
As Medhat mentioned, applying the GridLayout was the best solution.
I applied the following code:
def loopCamFeed(self,n):
w = 0
if n > 0:
# if ( n % 2) == 0:
for i in range(int(n)):
if (i%2) == 0:
w +=1
print(int(w / 2), (i%2))
self.label = QtWidgets.QLabel()
self.label.setText("Screen " +str(i))
self.label.setStyleSheet("background-color: black5;")
self.label.setObjectName(u"label")
self.gridLayout.addWidget(self.label, (i%2) ,int(w),Qt.AlignHCenter )
This works perfectly! Thanks #Medhat
I am building a GUI for a hand tracking app using PyQT5.
There are two windows: one 'MainWindow' which displays the camera view, and one called 'Window 2' which holds multiple checkboxes that correspond to actions that can be enabled/disabled while the program is running.
The code for the two classes is as follows:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi("window1.ui", self)
self.setWindowTitle("Tracker")
self.display_width = 640
self.display_height = 480
# start the thread
self.gesture.clicked.connect(self.gotowindow2)
self.startbutton.clicked.connect(self.startvideo)
def gotowindow2(self):
widget.setCurrentIndex(widget.currentIndex() + 1) # changes index by 1 to change page
def startvideo(self):
# Change label color to light blue
self.startbutton.clicked.disconnect(self.startvideo)
# Change button to stop
self.startbutton.setText('Stop video')
values = Window2()
hello = values.returnvalues()
print(hello)
self.thread = VideoThread(1,1,0)
self.thread.change_pixmap_signal.connect(self.update_image)
# start the thread
self.thread.start()
self.startbutton.clicked.connect(self.thread.stop) # Stop the video if button clicked
self.startbutton.clicked.connect(self.stopvideo)
def stopvideo(self):
self.thread.change_pixmap_signal.disconnect()
self.startbutton.setText('Start video')
self.startbutton.clicked.disconnect(self.stopvideo)
self.startbutton.clicked.disconnect(self.thread.stop)
self.startbutton.clicked.connect(self.startvideo)
def closeEvent(self, event):
self.thread.stop()
event.accept()
#pyqtSlot(np.ndarray)
def update_image(self, img):
"""Updates the image_label with a new opencv image"""
qt_img = self.convert_cv_qt(img)
self.image_label.setPixmap(qt_img)
def convert_cv_qt(self, img):
"""Convert from an opencv image to QPixmap"""
rgb_image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_image.shape
bytes_per_line = ch * w
convert_to_Qt_format = QtGui.QImage(rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
p = convert_to_Qt_format.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio)
return QPixmap.fromImage(p)
class Window2(QWidget):
def __init__(self):
super(Window2, self).__init__()
loadUi("window2.ui", self)
self.backbutton.clicked.connect(self.returnvalues)
self.backbutton.clicked.connect(self.gotowindow1)
self.outputbutton.clicked.connect(self.printvalues)
def printvalues(self):
print(self.returnvalues())
def gotowindow1(self):
widget.setCurrentIndex(widget.currentIndex() - 1)
def returnvalues(self):
left = self.leftclickbutton.isChecked()
scrollup = self.scrollupbutton.isChecked()
scrolldown = self.scrolldownbutton.isChecked()
checkboxvalues = [left, scrollup, scrolldown]
return checkboxvalues
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
a = MainWindow()
b = Window2()
widget.resize(1000, 600)
widget.addWidget(a)
widget.addWidget(b)
widget.show()
sys.exit(app.exec_())
My problem is that when I check the checkboxes and press the "outputbutton", it correctly displays the three values.
When these values are fetched by hello = values.returnvalues(), it is always [False, False, False]
How can the correct values get passed into class "MainWindow" stored under variable "hello"
It seems like you are not familiar with Object Oriented Programming.
values = Window2()
hello = values.returnvalues()
This block of code actually creates a new object Window2, and its checkboxes are indeed unchecked, which is why you always get [False, False, False].
The simplest (but not the best) way to solve your problem would be to give your Window2 instance to your mainWindow.
So instead of:
a = MainWindow()
b = Window2()
you do:
b = Window2()
a = MainWindow(b)
Instead of:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
you should do:
class MainWindow(QMainWindow):
def __init__(self, window_2):
super(MainWindow, self).__init__()
self.window_2 = window_2
and instead of:
values = Window2()
hello = values.returnvalues()
you should do:
hello = self.window_2.returnvalues()
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.
#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.
I'm attempting to get webcam data from a camera using opencv and then display that in a PyQt gui. I have done this before with Tkinter by gaining access to Tkinter main window loop with the .after function. However, PyQt doesn't seem to have the same usability and in order to have another loop running with an application you need to use a separate thread. So this is what I have come up with:
import sys
import cv2
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtGui import QImage
import time
class VideoCapture(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget().__init__()
self.camera = None
self.camera = cv2.VideoCapture(0)
b, self.frame = self.camera.read()
self.label = QtGui.QLabel()
self.workThread = WorkThread(self)
self.connect(self.workThread, QtCore.SIGNAL('update_Camera'), self.draw)
self.workThread.start()
def closeEvent(self, event):
self.workThread.stop()
def draw(self):
print "I should Redraw"
height, width, channel = self.frame.shape
bpl = 3 * width
self.qImg = QImage(self.frame.data, width, height, bpl, QImage.Format_RGB888)
pix = QtGui.QPixmap(self.qImg)
self.label.setPixmap(pix)
self.label.show()
class WorkThread(QtCore.QThread):
def __init__(self, parent):
QtCore.QThread.__init__(self)
self.parent = parent
def __del__(self):
self.wait()
def run(self):
while True:
self.emit(QtCore.SIGNAL('update_Camera'), "_")
self.terminate()
app = QtGui.QApplication(sys.argv)
test = VideoCapture()
test.draw()
sys.exit(app.exec_())
My idea was simple: I'll just create a thread with a loop which emits a signal telling the main application to update. (Obviously I don't I want a thread with a while True loop but I just used it for convenience and planned on replacing it once I could guarantee this idea would work). However, the signal doesn't appear to be registering because the draw() function is never called. Any idea what i'm doing wrong?
I don't know anything about OpenCV, so I can only guess at the problems.
My guess is that you are only reading the video data once. If it is a video stream then you have to continually read and interpret the data.
import sys
import cv2
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtGui import QImage
import time
class VideoCapture(QtGui.QWidget):
update_video = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QWidget().__init__()
self.camera = cv2.VideoCapture(0)
self.label = QtGui.QLabel()
layout = QtGui.QHBoxLayout()
self.setLayout(layout)
layout.addWidget(self.label)
# Create the worker Thread
self.workThread = WorkThread(self.readVideo)
self.update_video.connect(self.draw)
def start(self):
self.workerThread.start()
def stop(self):
self.workThread.alive = False
self.workThread.stop()
def readVideo(self):
"""Note this method is executed in a thread. No drawing can happen in a thread. Emit a signal to draw items."""
b, self.frame = self.camera.read()
self.update_video.emit() # Signals are slow this may happen too fast
def closeEvent(self, event):
self.stop()
return QtGui.QWidget.closeEvent(self, event)
#self.workThread.alive = False
#self.workThread.stop()
def draw(self):
print "I should Redraw"
height, width, channel = self.frame.shape
bpl = 3 * width
qImg = QImage(self.frame.data, width, height, bpl, QImage.Format_RGB888)
pix = QtGui.QPixmap(qImg)
self.label.setPixmap(pix)
# self.label.show() # The label is now a part of the widget layout
class WorkThread(QtCore.QThread):
def __init__(self, target=None, args=(), kwargs={}):
QtCore.QThread.__init__(self)
# I don't know how Qt's threads work, so I am treating it like a python thread
self.target = target
self.args = args
self.kwargs = kwargs
self.alive = True
def run(self):
while self.alive:
self.target(*self.args, **self.kwargs)
app = QtGui.QApplication(sys.argv)
test = VideoCapture()
test.start()
sys.exit(app.exec_())
Since you are only updating so many times per second you could probably use a timer for this instead of a thread. The timer is probably easier and safer to use.
import sys
import cv2
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtGui import QImage
import time
class VideoCapture(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget().__init__()
self.camera = cv2.VideoCapture(0)
self.label = QtGui.QLabel()
layout = QtGui.QHBoxLayout()
self.setLayout(layout)
layout.addWidget(self.label)
# Create the worker Thread
self.timer= QtCore.QTimer()
self.timer.setInterval(300)
self.timer.timeout.connect(self.draw_camera)
def start(self):
self.timer.start()
def stop(self):
self.timer.stop()
def draw_camera(self):
"""You can draw in a timer, so just read the data and draw however fast you want."""
print "I should Redraw"
b, frame = self.camera.read()
height, width, channel = frame.shape
bpl = 3 * width
qImg = QImage(frame.data, width, height, bpl, QImage.Format_RGB888)
pix = QtGui.QPixmap(qImg)
self.label.setPixmap(pix)
def closeEvent(self, event):
self.stop()
return QtGui.QWidget.closeEvent(self, event)
app = QtGui.QApplication(sys.argv)
test = VideoCapture()
test.start()
sys.exit(app.exec_())
I've been working on things very similar to your problem. I modified your code and tested it on my Windows PC.
The key point here is that you have to put the cv2 camera object in the WorkThread, read each frame in the main while loop in the run() method, and finally emit the image to the QWidget object to display it. In this way you get a continuous iteration of image capturing and display.
import sys
import cv2
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtGui import QImage
import time
class VideoCapture(QtGui.QWidget):
def __init__(self, parent = None):
# Use super() to call __init__() methods in the parent classes
super(VideoCapture, self).__init__()
# The instantiated QLabel object should belong to the 'self' QWidget object
self.label = QtGui.QLabel(self) # <- So put 'self' in the parenthesis
# Set the QLabel geometry to fit the image dimension (640, 480)
# The top left corner (0, 0) is the position within the QWidget main window
self.label.setGeometry(0,0,640,480)
# Instantiate a QThread object. No need to pass in the parent QWidget object.
self.workThread = WorkThread()
# Connect signal from self.workThread to the slot self.draw
self.connect(self.workThread, QtCore.SIGNAL('update_Camera'), self.draw)
self.workThread.start()
def closeEvent(self, event):
self.workThread.stop()
event.accept()
def draw(self, img):
print "I should Redraw"
height, width, channel = img.shape
bpl = 3 * width
self.qImg = QImage(img, width, height, bpl, QImage.Format_RGB888)
pix = QtGui.QPixmap(self.qImg)
self.label.setPixmap(pix)
self.label.show()
class WorkThread(QtCore.QThread):
def __init__(self):
# Use super() to call __init__() methods in the parent classes
super(WorkThread, self).__init__()
# Place the camera object in the WorkThread
self.camera = cv2.VideoCapture(0)
# The boolean variable to break the while loop in self.run() method
self.running = True
def run(self):
while self.running:
# Read one frame
b, self.frame = self.camera.read()
# Emit self.frame to the QWidget object
self.emit(QtCore.SIGNAL('update_Camera'), self.frame)
def stop(self):
# Terminate the while loop in self.run() method
self.running = False
app = QtGui.QApplication(sys.argv)
video_capture_widget = VideoCapture()
video_capture_widget.show()
sys.exit(app.exec_())