Webcam in PqQt5 window
-
Hi all ,
I'm working on an ap which does some Face-Reco. First i try to show the webcam feed in a Qt window.
When i press the button the feed is started (button changes text and color) , when i click again the feed should stop.
Problem is that when close the feed sometime the entire Qt window closes.
Here is my code:
import os
import timeit
import cv2
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import *
from PyQt5.uic import loadUiclass Worker1(QThread): ImageUpdate = pyqtSignal(QImage) def run(self): self.ThreadActive = True self.Capture = cv2.VideoCapture(0,cv2.CAP_DSHOW) while self.ThreadActive: ret, frame = self.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(640, 480, Qt.KeepAspectRatio) self.ImageUpdate.emit(Pic) def stop(self): self.Capture.release() self.ThreadActive = False cv2.destroyAllWindows() class FaceIdWindow(QtWidgets.QMainWindow): def __init__(self): super(FaceIdWindow, self).__init__() self.ui = loadUi("uidesign/facereco/FaceId.ui", self) self.ui.cmdStartCamera.clicked.connect(self.StartCamera) self.status_camera = "STOPPED" def StartCamera(self): start = timeit.default_timer() start = timeit.default_timer() print("Start StartCamera\n") print(self.status_camera) if self.status_camera == "STOPPED": self.status_camera = "STARTED" self.ui.cmdStartCamera.setStyleSheet("background-color: green") self.ui.cmdStartCamera.setText("Stop camera") self.Worker1 = Worker1() self.Worker1.start() self.Worker1.ImageUpdate.connect(self.ImageUpdateSlot) else: cv2.destroyAllWindows() image_path = str(os.getcwd()) image_path = image_path + "/assets/clipart/clipartfaceid2.png" self.lblPicture.setPixmap(QtGui.QPixmap(image_path)) self.status_camera = "STOPPED" self.ui.cmdStartCamera.setStyleSheet("background-color: ") self.ui.cmdStartCamera.setText("Start camera") self.Worker1.stop() print("Stop StartCamera\n") end = timeit.default_timer() print("Process Time: ", (end - start))
Can anyone give a hand ,
Cheers , John
-
@Johnson9070 You should start here: https://doc.qt.io/qt-5/thread-basics.html
-
@Johnson9070 said in Webcam in PqQt5 window:
Problem is that when close the feed sometime the entire Qt window closes.
[I know nothing about cameras or OpenCV, this is just a comment inspecting the code.]
Did you run it from Qt Creator Debug? When "the entire Qt window closes" is it possible the code has actually crashed/gone wrong?
Just looking at your code, I wonder whether something you have is actually "safe". When you execute
self.Worker1.stop()
from main/UI thread, it seems to me yourWorker
thread could be anywhere insiderun()
. It might be anywhere in the middle of thewhile ... if ...
code. Is it "safe" to execute the statements instop()
at an unknown point? For example, if you executeself.Capture.release()
while/just before it is doingself.Capture.read()
, is that good??My thought would be:
def stop(self): self.ThreadActive = False def run(self): self.ThreadActive = True self.Capture = cv2.VideoCapture(0,cv2.CAP_DSHOW) while self.ThreadActive: ... self.Capture.release() cv2.destroyAllWindows() print("Stop StartCamera\n") end = timeit.default_timer() print("Process Time: ", (end - start))
I have made
stop()
only doself.ThreadActive = False
, and then exit. Sorun()
completes its currentwhile self.ThreadActive:
iteration, then sees the flag has been cleared, exits the loop, clears up, and returns cleanly.Any improvement?
-
Hi,
In addition to @JonB, QThread already provides the requestInterruption method and corresponding getter to help manage infinite loops.
-
Hi Guys ,
Thanks for your replies.
This is working stable:import os import threading import timeit import cv2 from PyQt5 import QtGui from PyQt5 import QtWidgets from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import * from PyQt5.uic import loadUi class Worker1(QThread): ImageUpdate = pyqtSignal(QImage) def run(self): self.ThreadActive = True self.Capture = cv2.VideoCapture(0) while self.ThreadActive: ret, frame = self.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(640, 480, Qt.KeepAspectRatio) self.ImageUpdate.emit(Pic) self.Capture.release() cv2.destroyAllWindows() def stop(self): self.ThreadActive = False self.terminate() class FaceIdWindow(QtWidgets.QMainWindow): def __init__(self): super(FaceIdWindow, self).__init__() self.ui = loadUi("uidesign/facereco/FaceId.ui", self) self.ui.cmdChoosePicture.clicked.connect(self.ChoosePicture) self.ui.cmdStartCamera.clicked.connect(self.StartCamera) self.ui.cmdTrainFace.clicked.connect(self.TrainFace) self.ui.cmdProcess.clicked.connect(self.Process) self.status_camera = "STOPPED" def StartCamera(self): start = timeit.default_timer() start = timeit.default_timer() print("Start StartCamera\n") print(self.status_camera) if self.status_camera == "STOPPED": self.status_camera = "STARTED" self.ui.cmdStartCamera.setStyleSheet("background-color: green") self.ui.cmdStartCamera.setText("Stop camera") self.Worker1 = Worker1() self.Worker1.start() self.Worker1.ImageUpdate.connect(self.ImageUpdateSlot) else: self.status_camera = "STOPPED" self.Worker1.stop() image_path = str(os.getcwd()) image_path = image_path + "/assets/clipart/clipartfaceid2.png" self.lblPicture.setPixmap(QtGui.QPixmap(image_path)) self.ui.cmdStartCamera.setStyleSheet("background-color: ") self.ui.cmdStartCamera.setText("Start camera") print("Stop StartCamera\n") end = timeit.default_timer() print("Process Time: ", (end - start)) def ImageUpdateSlot(self, Image): self.lblPicture.setPixmap(QPixmap.fromImage(Image))
@SGaist : is "requestInterruption" a cleaner way to close the thread ?
-
@Johnson9070 said in Webcam in PqQt5 window:
def stop(self): self.ThreadActive = False self.terminate()
Why have you picked to use QThread.terminate()?
Warning: This function is dangerous and its use is discouraged. The thread can be terminated at any point in its code path. Threads can be terminated while modifying data. There is no chance for the thread to clean up after itself, unlock any held mutexes, etc. In short, use this function only if absolutely necessary.
Given that, your thread is no longer going to do e.g.
self.Capture.release() cv2.destroyAllWindows()
So what state does it leave things in?
Why did you put that in at all?? I said all you needed was:
def stop(self): self.ThreadActive = False
Did you try that, did that not suffice to work?
@SGaist : is "requestInterruption" a cleaner way to close the thread ?
This does not "close" the thread. It is an alternative way of doing your
self.ThreadActive
flag and yourself.Worker1.stop()
call- In thread get rid of your
self.ThreadActive
variable. Make the loop readwhile not self.isInterruptionRequested():
. - In caller replace
self.Worker1.stop()
withself.Worker1.requestInterruption()
.
Whether you use your
stop()
or inbuiltrequestInterruption()
they come to the same thing/utilise the same approach. - In thread get rid of your
-
@JonB said in Webcam in PqQt5 window:
ef stop(self):
self.ThreadActive = False
self.terminate()Without the terminate() my window closed when the camera feed stopped.
Here is the code of my Gui:# -*- coding: utf-8 -*- # Form implementation generated from reading ui file 'C:\DominoSport\uidesign\facereco\FaceId2.ui' # # Created by: PyQt5 UI code generator 5.15.6 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(899, 739) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("C:\\DominoSport\\uidesign\\facereco\\../../assets/clipart/clipartfaceid2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) MainWindow.setStyleSheet("QPushButton{\n" " color:rgb(17,17,17);\n" " border-width: 1px;\n" " border-radius: 6px;\n" " border-bottom-color: rgb(56, 84, 164);\n" " border-right-color: rgb(56, 84, 164);\n" " border-left-color: rgb(56, 84, 164);\n" " border-top-color: rgb(56, 84, 164);\n" " border-style: solid;\n" " padding: 4px;\n" " background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));\n" "}\n" "QPushButton:hover{\n" " color:rgb(17,17,17);\n" " border-width: 1px;\n" " border-radius:6px;\n" " border-top-color: rgb(56, 84, 164);\n" " border-right-color: rgb(56, 84, 164);\n" " border-left-color: rgb(56, 84, 164);\n" " border-bottom-color: rgb(56, 84, 164);\n" " border-style: solid;\n" " padding: 2px;\n" " background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));\n" "}\n" "QPushButton:default{\n" " color:rgb(17,17,17);\n" " border-width: 1px;\n" " border-radius:6px;\n" " border-top-color: rgb(255,150,60);\n" " border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 255));\n" " border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 255));\n" " border-bottom-color: rgb(200,70,20);\n" " border-style: solid;\n" " padding: 2px;\n" " background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));\n" "}\n" "QPushButton:pressed{\n" " color:rgb(17,17,17);\n" " border-width: 1px;\n" " border-radius: 6px;\n" " border-width: 1px;\n" " border-top-color: rgba(255,150,60,200);\n" " border-right-color: qlineargradient(spread:pad, x1:0, y1:1, x2:1, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 200));\n" " border-left-color: qlineargradient(spread:pad, x1:1, y1:0, x2:0, y2:0, stop:0 rgba(200, 70, 20, 255), stop:1 rgba(255,150,60, 200));\n" " border-bottom-color: rgba(200,70,20,200);\n" " border-style: solid;\n" " padding: 2px;\n" " background-color: qlineargradient(spread:pad, x1:0.5, y1:0, x2:0.5, y2:1, stop:0 rgba(220, 220, 220, 255), stop:1 rgba(255, 255, 255, 255));\n" "}\n" "QPushButton:disabled{\n" " color:rgb(174,167,159);\n" " border-width: 1px;\n" " border-radius: 6px;\n" " background-color: qlineargradient(spread:pad, x1:0.5, y1:1, x2:0.5, y2:0, stop:0 rgba(200, 200, 200, 255), stop:1 rgba(230, 230, 230, 255));\n" "}") self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.cmdProcess = QtWidgets.QPushButton(self.centralwidget) self.cmdProcess.setEnabled(True) self.cmdProcess.setGeometry(QtCore.QRect(300, 340, 90, 30)) icon1 = QtGui.QIcon() icon1.addPixmap(QtGui.QPixmap("C:\\DominoSport\\uidesign\\facereco\\../../assets/icons/iconprocessing.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.cmdProcess.setIcon(icon1) self.cmdProcess.setIconSize(QtCore.QSize(20, 20)) self.cmdProcess.setObjectName("cmdProcess") self.tableImportData = QtWidgets.QTableView(self.centralwidget) self.tableImportData.setGeometry(QtCore.QRect(20, 390, 851, 310)) self.tableImportData.setObjectName("tableImportData") self.cmdStartCamera = QtWidgets.QPushButton(self.centralwidget) self.cmdStartCamera.setEnabled(True) self.cmdStartCamera.setGeometry(QtCore.QRect(120, 340, 100, 31)) icon2 = QtGui.QIcon() icon2.addPixmap(QtGui.QPixmap("C:\\DominoSport\\uidesign\\facereco\\../../assets/icons/output-onlinepngtools (10).png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.cmdStartCamera.setIcon(icon2) self.cmdStartCamera.setCheckable(False) self.cmdStartCamera.setObjectName("cmdStartCamera") self.lblStatus2 = QtWidgets.QLabel(self.centralwidget) self.lblStatus2.setGeometry(QtCore.QRect(400, 20, 50, 22)) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblStatus2.sizePolicy().hasHeightForWidth()) self.lblStatus2.setSizePolicy(sizePolicy) self.lblStatus2.setStyleSheet("QLabel {\n" " background-color: rgb(240, 240, 240);\n" " border:0px solid rgb(#rgb(56, 84, 164));\n" " border-radius:0px;\n" " padding: 0px;\n" " font: 10pt \"Segoe UI\";\n" "}") self.lblStatus2.setScaledContents(False) self.lblStatus2.setAlignment(QtCore.Qt.AlignCenter) self.lblStatus2.setWordWrap(False) self.lblStatus2.setObjectName("lblStatus2") self.cmdTrainFace = QtWidgets.QPushButton(self.centralwidget) self.cmdTrainFace.setEnabled(True) self.cmdTrainFace.setGeometry(QtCore.QRect(230, 340, 60, 30)) icon3 = QtGui.QIcon() icon3.addPixmap(QtGui.QPixmap("C:\\DominoSport\\uidesign\\facereco\\../../assets/icons/iconlearning.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.cmdTrainFace.setIcon(icon3) self.cmdTrainFace.setIconSize(QtCore.QSize(25, 20)) self.cmdTrainFace.setObjectName("cmdTrainFace") self.lblPicture = QtWidgets.QLabel(self.centralwidget) self.lblPicture.setGeometry(QtCore.QRect(40, 20, 300, 300)) self.lblPicture.setStyleSheet("\n" " border:2px solid rgb(#rgb(56, 84, 164));\n" " border-color: rgb(56, 84, 164);\n" " border-radius:10px;\n" " padding: 3px;\n" " font: 8pt \"Segoe UI\";") self.lblPicture.setText("") self.lblPicture.setPixmap(QtGui.QPixmap("C:\\DominoSport\\uidesign\\facereco\\../../assets/clipart/clipartfaceid2.png")) self.lblPicture.setScaledContents(True) self.lblPicture.setAlignment(QtCore.Qt.AlignCenter) self.lblPicture.setIndent(-3) self.lblPicture.setObjectName("lblPicture") self.cmdChoosePicture = QtWidgets.QPushButton(self.centralwidget) self.cmdChoosePicture.setEnabled(True) self.cmdChoosePicture.setGeometry(QtCore.QRect(30, 340, 81, 30)) icon4 = QtGui.QIcon() icon4.addPixmap(QtGui.QPixmap("C:\\DominoSport\\uidesign\\facereco\\../../assets/icons/iconfoto.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.cmdChoosePicture.setIcon(icon4) self.cmdChoosePicture.setIconSize(QtCore.QSize(20, 20)) self.cmdChoosePicture.setObjectName("cmdChoosePicture") self.progressBar = QtWidgets.QProgressBar(self.centralwidget) self.progressBar.setGeometry(QtCore.QRect(350, 20, 40, 300)) self.progressBar.setLayoutDirection(QtCore.Qt.LeftToRight) self.progressBar.setProperty("value", 10) self.progressBar.setOrientation(QtCore.Qt.Vertical) self.progressBar.setInvertedAppearance(False) self.progressBar.setObjectName("progressBar") self.lblStatus1 = QtWidgets.QLabel(self.centralwidget) self.lblStatus1.setGeometry(QtCore.QRect(450, 20, 430, 170)) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.lblStatus1.sizePolicy().hasHeightForWidth()) self.lblStatus1.setSizePolicy(sizePolicy) self.lblStatus1.setStyleSheet(" background-color: rgb(255, 255, 255);\n" " border:2px solid rgb(#rgb(56, 84, 164));\n" " border-color: rgb(56, 84, 164);\n" " border-radius:10px;\n" " padding: 1px;\n" " font: 8pt \"Segoe UI\";") self.lblStatus1.setText("") self.lblStatus1.setScaledContents(False) self.lblStatus1.setAlignment(QtCore.Qt.AlignCenter) self.lblStatus1.setWordWrap(False) self.lblStatus1.setObjectName("lblStatus1") self.cmdStopCamera = QtWidgets.QPushButton(self.centralwidget) self.cmdStopCamera.setEnabled(True) self.cmdStopCamera.setGeometry(QtCore.QRect(410, 340, 100, 31)) self.cmdStopCamera.setIcon(icon2) self.cmdStopCamera.setCheckable(False) self.cmdStopCamera.setObjectName("cmdStopCamera") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 899, 22)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Domino gezichtsherkenning")) self.cmdProcess.setText(_translate("MainWindow", "Verwerken")) self.cmdStartCamera.setText(_translate("MainWindow", "Start Camera")) self.lblStatus2.setText(_translate("MainWindow", "Status:")) self.cmdTrainFace.setText(_translate("MainWindow", "Train")) self.cmdChoosePicture.setText(_translate("MainWindow", "Kies foto")) self.cmdStopCamera.setText(_translate("MainWindow", "Stop Camera"))
-
@Johnson9070 said in Webcam in PqQt5 window:
Without the terminate() my window closed when the camera feed stopped.
I have no idea why
terminate()
would affect that, it's just a rude interruption of the thread which exiting it cleanly should have the same effect as anyway.I asked you before whether you had run it under a debugger to see whether possibly your program actually "crashes" causes termination/the UI windows to close.
-
I'm using pycharm , i get no error from the debugger when the main window closes after closing the camera-feed.
-
You where right , had to remove the terminate , the camera feed kept running but the image in the label changed so i tought it was working.
-
@Johnson9070 said in Webcam in PqQt5 window:
cv2.destroyAllWindows()
Also removed: cv2.destroyAllWindows() , stille working fine. @SGaist : i'll adapt the code with your input
-
Hi guys , question about the threading.
I need to run differtent functions in threads. A second function starts the camera feed but with differtent settings.How do i go about this? Do i add a second Thread class to run the function or is there a better way ?
Also how do i detect when the thread is finished ? I would like to set a clipart in my window but it keeps getting overwritten. -
@Johnson9070 You should start here: https://doc.qt.io/qt-5/thread-basics.html
-
I've been reading up on threading , but with openCV i find different recommendations, it seems easiest to close the running thread en duplicate the class wit a different "run" function. But there are different ways of threading depending on the app , therefore i like the opinion of a pro to set me on the right path. ;-)