Opencv/pyqt python gui is crashing when i try to save an image

I have a video player GUI that is crashing when I try to save an image. I don’t get any error message in the terminal so I am not sure what is happening - this is only happening on one of the many PCs that I am running this code on and it typically happens when I spam the save image button at a high rate. Video player looks like below (camera plays on left, not currently plugged in)

The same issue was actually happening with start/stop videos but I added a small delay which fixed my issue. I tried the same thing in my saveImage function but it did not fix the crashes. I also tried completely stopping the video player while the save image process is happening but that did not seem to fix the issue either. Here is my code:

# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'untitled.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
import sys
from types import FrameType
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import cv2
import datetime
import numpy as np
import time

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1920, 1080)
        self.cameraViewer = QtWidgets.QWidget(MainWindow)
        self.cameraViewer.setObjectName("cameraViewer")

        self.cameraFeed = QtWidgets.QLabel(self.cameraViewer)
        self.cameraFeed.setGeometry(QtCore.QRect(10, 10, 1280, 960))
        self.cameraFeed.setObjectName("cameraFeed")

        self.startBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.startBTN.setGeometry(QtCore.QRect(1330, 10, 100, 100))
        self.startBTN.setObjectName("startBTN")
        self.startBTN.clicked.connect(self.StartFeed)

        self.stopBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.stopBTN.setGeometry(QtCore.QRect(1440, 10, 100, 100))
        self.stopBTN.setObjectName("stopBTN")
        self.stopBTN.clicked.connect(self.StopFeed)

        self.saveImageBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.saveImageBTN.setGeometry(QtCore.QRect(1550, 10, 100, 100))
        self.saveImageBTN.setObjectName("saveImageBTN")
        self.saveImageBTN.clicked.connect(self.SaveImage)

        self.startVideoBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.startVideoBTN.setGeometry(QtCore.QRect(1660, 10, 100, 100))
        self.startVideoBTN.setObjectName("startVideoBTN")
        self.startVideoBTN.clicked.connect(self.StartVideo)

        self.stopVideoBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.stopVideoBTN.setGeometry(QtCore.QRect(1770, 10, 100, 100))
        self.stopVideoBTN.setObjectName("stopVideoBTN")
        self.stopVideoBTN.clicked.connect(self.StopVideo)

        self.brightnessSlider = QtWidgets.QSlider(self.cameraViewer)
        self.brightnessSlider.setFocusPolicy(Qt.StrongFocus)
        self.brightnessSlider.setTickPosition(QSlider.TicksBothSides)
        self.brightnessSlider.setMinimum(-255)
        self.brightnessSlider.setMaximum(255)
        self.brightnessSlider.setTickInterval(11)
        self.brightnessSlider.setSingleStep(1)
        self.brightnessSlider.setGeometry(QtCore.QRect(1330, 120, 50, 100))
        self.brightnessSlider.setObjectName("brightnessSlider")
        self.brightnessSlider.valueChanged.connect(self.BrightnessSliderChange)

        self.brightnessLabel = QtWidgets.QLabel(self.cameraViewer)
        self.brightnessLabel.setGeometry(QtCore.QRect(1330, 230, 150, 50))
        self.brightnessLabel.setText("Brightness: " + str(self.brightnessSlider.value()))
        self.brightnessLabel.setObjectName("brightnessLabel")

        MainWindow.setCentralWidget(self.cameraViewer)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        self.Worker1 = Worker1()
        self.Worker1.start()
        self.Worker1.ImageUpdate.connect(self.ImageUpdateSlot)

    def ImageUpdateSlot(self, Image):
        self.cameraFeed.setPixmap(QPixmap.fromImage(Image))

    def StartFeed(self):
        self.Worker1.start()
    
    def StopFeed(self):
        self.Worker1.stop()

    def SaveImage(self):
        self.Worker1.saveImage()

    def StartVideo(self):
        self.Worker1.startVideo()
    
    def StopVideo(self):
        self.Worker1.stopVideo()
    
    def BrightnessSliderChange(self, value):
        self.brightnessLabel.setText("Brightness: " + str(value))
        self.Worker1.updateBrightness(value)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.startBTN.setText(_translate("MainWindow", "Start Feed"))
        self.stopBTN.setText(_translate("MainWindow", "Stop Feed"))
        self.saveImageBTN.setText(_translate("MainWindow", "Save Image"))
        self.startVideoBTN.setText(_translate("MainWindow", "Start Video"))
        self.stopVideoBTN.setText(_translate("MainWindow", "Stop Video"))

class Worker1(QThread):
    def __init__(self):
        super(Worker1, self).__init__()
        self.ThreadActive = None
        self.Capture = None
        self.frame = None
        self.videoCapture = None
        self.videoFile = None
        self.brightnessValue = 0

    ImageUpdate = pyqtSignal(QImage)
    def run(self):
        self.ThreadActive = True
        self.Capture = cv2.VideoCapture(0)
        while self.ThreadActive:
            ret, self.frame = self.Capture.read()
            if ret:
                #self.FlippedImage = cv2.flip(frame, 1)
                Image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                BrightnessMatrix = np.ones(Image.shape, dtype = "uint8") * abs(self.brightnessValue)
                if (self.brightnessValue > 0): 
                    Image = cv2.add(Image, BrightnessMatrix)
                elif (self.brightnessValue < 0):
                    Image = cv2.subtract(Image, BrightnessMatrix)
                ConvertToQtFormat = QImage(Image.data, Image.shape[1], Image.shape[0], QImage.Format_RGB888)
                Pic = ConvertToQtFormat.scaled(1280, 960, Qt.KeepAspectRatio)
                self.ImageUpdate.emit(Pic)
                if self.videoCapture:
                    self.videoFile.write(self.frame)
        self.Capture.release()
    def stop(self):
        self.ThreadActive = False
        self.quit()
    def saveImage(self):
        time = datetime.datetime.now()      
        img_name = "image_{}.png".format(time.strftime("%Hh%Mm%Ss"))
        if self.Capture.isOpened():
            cv2.imwrite(img_name, self.frame)
    def startVideo(self):
        time = datetime.datetime.now()      
        video_name = "video_{}.avi".format(time.strftime("%Hh%Mm%Ss"))
        codec = cv2.VideoWriter_fourcc(*'MJPG')
        self.videoFile = cv2.VideoWriter(video_name, codec, 30.0, (640, 480))
        self.videoCapture = True
    def stopVideo(self):
        self.videoCapture = False
        time.sleep(0.1)
        self.videoFile.release()
    def updateBrightness(self, value):
        self.brightnessValue = value

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

does that actually cause the worker thread to run that code? or does it simply execute in the main thread, synchronously from the event loop? you’d want to send the worker a signal instead.

I suspect that you have synchronization issues. something (object) isn’t safe to call methods on from another thread while it’s used in one thread already.

I believe it is all running on the main thread. I switched my code so that a second worker thread gets called when the Save Image button is pressed but now I am struggling to figure out to pass my variables from Worker 1 into Worker 2 so that I can save the image out. This is my code change:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'untitled.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.
#
# Author: Colin O'Dowd
#
# Description: Video laryngoscope visualization platform
#


from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from types import FrameType
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import cv2
import datetime
import numpy as np
import time

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1920, 1080)
        self.cameraViewer = QtWidgets.QWidget(MainWindow)
        self.cameraViewer.setObjectName("cameraViewer")

        self.cameraFeed = QtWidgets.QLabel(self.cameraViewer)
        self.cameraFeed.setGeometry(QtCore.QRect(10, 10, 1280, 960))
        self.cameraFeed.setObjectName("cameraFeed")

        self.startBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.startBTN.setGeometry(QtCore.QRect(1330, 10, 100, 100))
        self.startBTN.setObjectName("startBTN")
        self.startBTN.clicked.connect(self.StartFeed)

        self.stopBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.stopBTN.setGeometry(QtCore.QRect(1440, 10, 100, 100))
        self.stopBTN.setObjectName("stopBTN")
        self.stopBTN.clicked.connect(self.StopFeed)

        self.saveImageBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.saveImageBTN.setGeometry(QtCore.QRect(1550, 10, 100, 100))
        self.saveImageBTN.setObjectName("saveImageBTN")
        self.saveImageBTN.clicked.connect(self.SaveImage)

        self.startVideoBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.startVideoBTN.setGeometry(QtCore.QRect(1660, 10, 100, 100))
        self.startVideoBTN.setObjectName("startVideoBTN")
        self.startVideoBTN.clicked.connect(self.StartVideo)

        self.stopVideoBTN = QtWidgets.QPushButton(self.cameraViewer)
        self.stopVideoBTN.setGeometry(QtCore.QRect(1770, 10, 100, 100))
        self.stopVideoBTN.setObjectName("stopVideoBTN")
        self.stopVideoBTN.clicked.connect(self.StopVideo)

        self.brightnessSlider = QtWidgets.QSlider(self.cameraViewer)
        self.brightnessSlider.setFocusPolicy(Qt.StrongFocus)
        self.brightnessSlider.setTickPosition(QSlider.TicksBothSides)
        self.brightnessSlider.setMinimum(-255)
        self.brightnessSlider.setMaximum(255)
        self.brightnessSlider.setTickInterval(11)
        self.brightnessSlider.setSingleStep(1)
        self.brightnessSlider.setGeometry(QtCore.QRect(1330, 120, 50, 100))
        self.brightnessSlider.setObjectName("brightnessSlider")
        self.brightnessSlider.valueChanged.connect(self.BrightnessSliderChange)

        self.brightnessLabel = QtWidgets.QLabel(self.cameraViewer)
        self.brightnessLabel.setGeometry(QtCore.QRect(1330, 230, 150, 50))
        self.brightnessLabel.setText("Brightness: " + str(self.brightnessSlider.value()))
        self.brightnessLabel.setObjectName("brightnessLabel")

        MainWindow.setCentralWidget(self.cameraViewer)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        self.Worker1 = Worker1()
        self.Worker1.start()
        self.Worker1.ImageUpdate.connect(self.ImageUpdateSlot)

    def ImageUpdateSlot(self, Image):
        self.cameraFeed.setPixmap(QPixmap.fromImage(Image))

    def StartFeed(self):
        self.Worker1.start()
    
    def StopFeed(self):
        self.Worker1.stop()

    def SaveImage(self):
        self.Worker1.saveImage()

    def StartVideo(self):
        self.Worker1.startVideo()
    
    def StopVideo(self):
        self.Worker1.stopVideo()
    
    def BrightnessSliderChange(self, value):
        self.brightnessLabel.setText("Brightness: " + str(value))
        self.Worker1.updateBrightness(value)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.startBTN.setText(_translate("MainWindow", "Start Feed"))
        self.stopBTN.setText(_translate("MainWindow", "Stop Feed"))
        self.saveImageBTN.setText(_translate("MainWindow", "Save Image"))
        self.startVideoBTN.setText(_translate("MainWindow", "Start Video"))
        self.stopVideoBTN.setText(_translate("MainWindow", "Stop Video"))

class Worker1(QThread):
    def __init__(self):
        super(Worker1, self).__init__()
        self.ThreadActive = None
        self.Capture = None
        self.frame = None
        self.videoCapture = None
        self.videoFile = None
        self.brightnessValue = 0
        self.Worker2 = Worker2()

    ImageUpdate = pyqtSignal(QImage)
    def run(self):
        self.ThreadActive = True
        self.Capture = cv2.VideoCapture(0)
        while self.ThreadActive:
            ret, self.frame = self.Capture.read()
            if ret:
                #self.FlippedImage = cv2.flip(frame, 1)
                Image = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                BrightnessMatrix = np.ones(Image.shape, dtype = "uint8") * abs(self.brightnessValue)
                if (self.brightnessValue > 0): 
                    Image = cv2.add(Image, BrightnessMatrix)
                elif (self.brightnessValue < 0):
                    Image = cv2.subtract(Image, BrightnessMatrix)
                ConvertToQtFormat = QImage(Image.data, Image.shape[1], Image.shape[0], QImage.Format_RGB888)
                Pic = ConvertToQtFormat.scaled(1280, 960, Qt.KeepAspectRatio)
                self.ImageUpdate.emit(Pic)
                if self.videoCapture:
                    self.videoFile.write(self.frame)
        self.Capture.release()
    def stop(self):
        self.ThreadActive = False
        self.quit()
    def saveImage(self):
        """ time = datetime.datetime.now()      
        img_name = "image_{}.png".format(time.strftime("%Hh%Mm%Ss"))
        if self.Capture.isOpened():
            cv2.imwrite(img_name, self.frame) """
        self.Worker2.start(Worker1)
    def startVideo(self):
        time = datetime.datetime.now()      
        video_name = "video_{}.avi".format(time.strftime("%Hh%Mm%Ss"))
        codec = cv2.VideoWriter_fourcc(*'MJPG')
        self.videoFile = cv2.VideoWriter(video_name, codec, 30.0, (640, 480))
        self.videoCapture = True
    def stopVideo(self):
        self.videoCapture = False
        time.sleep(0.1)
        self.videoFile.release()
    def updateBrightness(self, value):
        self.brightnessValue = value

class Worker2(QThread):
    def __init__(self):
        super(Worker2, self).__init__()
        self.Capture = Worker1.Capture
        self.frame = Worker1.frame
    
    def run(self):
        time = datetime.datetime.now()      
        img_name = "image_{}.png".format(time.strftime("%Hh%Mm%Ss"))
        if self.Capture.isOpened():
            cv2.imwrite(img_name, self.frame)
    

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())