Cv2.imshow() screen turns black after a random amount of time

Hello. I am writing a program that displays a webcam feed live on the computer screen while also taking an image from that feed every 30 seconds or so to do some operation on that image (mainly slicing it up using numpy and opencv and reading numbers with Tesseract). Because I need to display the feed in a cv2.imshow() infinite loop that is uninterrupted, I have the loop in a thread so the image operation (scheduled every 30 seconds using a python scheduler) does not interrupt and freeze the display.

For some reason, the cv2.imshow() screen goes black after a random amount of time. This has happened after 15 minutes, and after 4 hours of functional operation, seemingly completely random. Not only that, but it saves an all black picture as well. The camera is on when it turns black (the light is anyway) and I have run a simple cv2.imshow() for hours to check for a hardware issue and there doesn’t seem to be one.

Below is my code. This is not the final code, but it is the smallest I can make it while keeping the same structure and reproducing the error.

import time
import gc
import cv2
import threading
import schedule
import numpy

class operator():
    def __init__(self):
        print("Start")
        
    def start_cam(self):
        is_blurry = True
        while is_blurry == True:
            print("Start of start_cam")
            self.camera = cv2.VideoCapture(0, cv2.CAP_DSHOW)                                                 # Make camera object with correct format, size, and autofocus. All of these appear to be necessary to get 4K resolution in OpenCV
            self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, 3840)  #3840
            self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 2160)  #2160
            self.camera.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('m','j','p','g'))
            self.camera.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('M','J','P','G'))                   
            self.camera.set(cv2.CAP_PROP_AUTOFOCUS, 1)
            self.camera.set(cv2.CAP_PROP_FPS, 60)

            i=10
            while i>0:                                                                                 # Takes 10 pictures and waits 1 sec in between each. This is done to make sure it has time to autofocus
                i=i-1
                cv2.waitKey(1000) #1000
                ret, self.frame = self.camera.read()
                if ret != True:
                    print("image error")                                                                # Check to make sure it actually took a picture and didn't fail
                    if ret != True and i == 1:
                        restart_loop = True
                        print("Image error, restarting loop")                                           #
                    continue
            laplace = cv2.Laplacian(self.frame, cv2.CV_64F).var()                                       # Checks if image is blurry. If it is, it restarts the camera.
            print(laplace)
            if laplace < 20:                        # Blurry
                print("Blurry image, reinitializing")
                self.camera.release()
                del(self.camera)
                cv2.destroyAllWindows()
                gc.collect()
                time.sleep(2)
            else:
                print("image not blurry")
                break


    def live_loop(self):
        loop_bool = True
        cv2.namedWindow("Live Feed", cv2.WINDOW_NORMAL)     # Creates a normal window so that the 4k image is scaled correctly for the live feed (I have a relatively small monitor)
        while loop_bool == True:
            ret, self.frame = self.camera.read()
            if ret != True:
                print("image error")
                break
            self.frame_rotate = cv2.rotate(self.frame, cv2.ROTATE_180)
            cv2.imshow("Live Feed", self.frame_rotate)
            k = cv2.waitKey(10)
        
        gc.collect()

    def data_operation(self):
        print("Start data operation")
        imgpath = "path where image is saved"
        cv2.imwrite(imgpath, self.frame)
        t = time.localtime()                                                                # If the image can't be read as a number, saves the image with a timestamp so that it can be examined later
        timestamp = time.strftime('%b-%d-%Y_%H;%M', t)
        print(timestamp)


if __name__== "__main__":
    op = operator()
    op.start_cam()
    x = threading.Thread(target=op.live_loop, daemon = True)
    x.start()
    schedule.every(30).seconds.do(op.data_operation)
    try:
        while True:
            schedule.run_pending()
            time.sleep(1)
    except KeyboardInterrupt:
        print("Ended Program")

    gc.collect()

The image operation starts by saving the image (using cv2.imwrite()) which is necessary because I need to upload the image to a google drive depending on certain variables. Interestingly, when I comment out the lines that save the image in the data_operation() function, the program seems to work without randomly turning black. However, I need to save the image as I said so I don’t know how to work around this.

Another interesting thing is that the program works fine on my laptop and never seems to go black, but the issue persists on the computer it needs to work on. I have done my best to replicate the exact conditions (external camera, same code, same python module version, etc.) and it never fails.

I realize this could be a python issue rather than an open issue, but I don’t know where else to turn. My StackOverflow question got no helpful answers, and I am totally stumped.

If there is a way to show a live webcam feed while grabbing an image from it and doing operations on that image (including saving it to the computer) without interrupting that live feed, please let me know. I don’t mind rebuilding this from the ground up if needed, I just want it to work. I am not a programmer by trade so I’ve gotten this far by Googling and problem solving, but this issue is so inconsistent that I don’t know what else to do.

Thanks in advance.

that’s a lot of code. do your best to trim it down to an example that is both minimal and reproducible.

what you currently have on stack overflow is certainly not minimal. minimal means anything goes out the window if omitting it doesn’t affect the issue. what is left, when added/removed, either breaks the code or affects the issue (causes it or fixes it)

this “exercise” isn’t about destroying that code you worked hard to write (you should copy the source file and edit that). this exercise is about learning what the issue is, so you can transfer your findings.

you received some responses but these people aren’t gods and they won’t do this work for you. your code is simply too complicated to be worth debugging by anyone other than yourself. your choices are to debug it yourself, simplify it, or throw it away and start from scratch with less complications.

and don’t be afraid of throwing it all away. your code looks like you’re new to many aspects of it, meaning you don’t know how to use many of these things. ask for approaches, don’t ask for someone to fix the approach you chose that got you in this situation.

This is the best I can do with my current circumstances. As far as I know, aside from some print statements, this is the minimal amount of code (which is quite trimmed from the original) that I can get. The initializing the camera, starting the loop, and then starting the schedule are all needed. I need to fix this for my job, so I am unable to sit around for 4 hours or more and see if changing something fixed it. The inconsistency of the issue is why I am on forums looking for answers instead of debugging myself. I’m sorry if it’s a lot of code but I am incapable of trimming and debugging myself given the circumstance of the issue.

If there’s a better approach to this then please tell me. I would love to learn what is causing this issue so I can write better code in the future, but I can’t ask for what I don’t know about. If this is the wrong place for a question like this then I’m sorry and I guess I’ll keep looking elsewhere.

start_cam

reopening the same camera doesn’t in itself fix any blur. at best it wastes time until the camera’s servo auto-focus has found what it wants to focus on. you might as well keep the VideoCapture open, read and check frames, and do that until you’re satisfied with the focus

waitKey to delay reading from the camera

bad idea. frames will be produced regardless. they’ll just queue up, so you aren’t getting a frame from a second later, but from one frame later (something around 16-40 milliseconds later).

messing with gc

I highly doubt that’s required to keep anything working. I have never in more than a decade experienced any issues with python that would require this, or where this would actually do anything noticeable. unless that provably does anything useful here, I’d call it superstition.

calling namedwindow/imshow/waitkey from a thread

that can be a bad idea. it may work, or it may cause trouble. depends on the GUI backend (win32/Qt/GTK/…). I haven’t had heisenbug issues with the win32 backend. either it works, or it fails noticeably (can’t remember if I had that problem).

it might be possible to run this, whatever it is, without threads entirely. that depends on what data_operation is specifically… because that there is an obvious placeholder. whatever code is really there might actually be at fault.

anyway, it should certainly be possible to keep all the imshow/waitkey stuff in the main loop.

worth a try to leave imshow/waitkey out entirely, and see if that changes the behavior. you won’t see anything, but it’s an experiment.

code style

calling a class operator, I find that misleading. first, the convention is to capitalize types/classes. second, the word “operator” usually refers to something like addition. what you do here is a container that holds a VideoCapture and a frame. call it a “capture thread” or whatever.

maybe consider deriving from threading.Thread (reimplement the run method, which is just the function that gets executed in the thread) or keep the code as is, with an instantiation of a threading.Thread given a target argument