CV2.imshow() window won't reopen - Live Video Capture - python

I am trying to create a python program that does automatic pupil detection from a live camera feed. My program has multiple threads to obtain images from my camera, analyze the code and display an edited version of the camera feed.
As I am new to threading, my current code just displays the negative of the camera feed. When run, the program works as expected. However, if I try to run the code a second time after closing the cv2 window the program does not work as intended. My camera turns on (as expected) however, a new cv2 window does not open. I need to re open my ide (spyder) in order to get the program working properly again.
I think this may due to my threads not terminating properly, however, given my lack of experience in the area, I am not certain. If I run
threading.current_thread()
after I close the window I get
<_MainThread(MainThread, started 2468)>
I would appreciate any insight as to where the problem lies.
My Code:
frame_to_detect = None
filtering = True
filter_frame = None
view = True
stopper = None
class Filter(Thread):
#indenting got mess up here
def __init_(self):
Thread.__init__(self)
def run(self):
global frame_to_detect
global filter_frame
while view:
if frame_to_detect is not None:
filter_frame = 255-frame_to_detect
class displayFrame(Thread):
#indenting got messed up here
def __init__(self):
Thread.__init__(self)
def run(self):
global view
while view:
if filter_frame is not None:
cv2.imshow('frame',filter_frame)
if (cv2.waitKey(1) & 0xFF == ord('q')):
view = False
Filter_thread = Filter()
Filter_thread.daemon = True
Filter_thread.start()
display = displayFrame()
display.daemon = True
display.start()
video_capture = cv2.VideoCapture(filepath)
while view:
ret,frame_to_detect = video_capture.read()
filtering = False
video_capture.release()
cv2.destroyAllWindows()

When you close the cv2 window the thread continues to run in the background. The threads for the cv2.imshow will time out eventually. However to speed up things up you can have the threads close with an exception. Such as
thread.raise_exception()
thread.join()

Related

How do you run two programs in python simultaneously at different frame rates?

I have been experimenting with OpenCV in python and I want to make a game with it in pygame. But it is way too slow, OpenCV is running at 8 fps but I want the pygame game to be running at 60 fps. I don't need the tracking to be fast but I want pygame to be running at a normal frame rate. I have tried to use the built-in sockets in python but it would make both programs run at 8 fps. Then I tried to stop the socket from pausing at waiting for data but changed nothing. Thanks.
pose.py
import cv2
import mediapipe as mp
import time
import numpy as np
cap = cv2.VideoCapture(0)
mpHands = mp.solutions.hands
hands = mpHands.Hands(static_image_mode=False,
max_num_hands=8,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
mpDraw = mp.solutions.drawing_utils
pTime = 0
cTime = 0
while True:
success, img = cap.read()
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
results = hands.process(imgRGB)
#print(results.multi_hand_landmarks)
if results.multi_hand_landmarks:
handId = 0
for handLms in results.multi_hand_landmarks:
avX = avY = l = 0
for id, lm in enumerate(handLms.landmark):
#print(id,lm)
h, w, c = img.shape
l += 1
cx, cy = int(lm.x *w), int(lm.y*h)
avX += cx
avY += cy
#if id ==0:
cv2.circle(img, (cx,cy), 3, (255,0,2555), cv2.FILLED)
avX = avX / l
avY = avY / l
mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)
handId += 1
cTime = time.time()
fps = 1/(cTime-pTime)
pTime = cTime
cv2.putText(img,str(int(fps)), (10,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,255), 3)
#cv2.imshow("Image", img)
cv2.waitKey(1)
poseGame.py
import pygame
pygame.init()
screen = pygame.display.set_mode((960, 540))
clock = pygame.time.Clock()
while True:
screen.fill((0, 0, 0))
# ...
clock.tick(60)
pygame.display.update()
you have three options.
first option is to have the game or the app running as the main python application and the other one running as a child process using the multiprocessing module and use a manager.queue for communication between the two processes, where the game will check if there is something in the queue on each frame (and pop the message if it finds one).
second (harder) option is to use sockets in non-blocking mode using socket.setblocking, so the game will not block until the other app finishes the transmission, but you will need to incrementally update an internal "message buffer" on each frame of the game as the messages will be received on multiple frames, the advantage of this approach is that the game and the commanding application will be two separate applications with no shared code, so the communication doesn't even have to be within python.
third option is to employ threading with blocking sockets where a child thread will be monitoring the received messages from the other application and putting it on a deque or the main thread to read.
one disadvantage of both approaches is that the application has to constantly send messages to the game, as both applications will be running at different rates, therefore the game needs to save the last message and keep using it until it receives the next message.
You need threads.
Do not mess around with sockets for this, it's needlessly complicated. You also don't necessarily need multiprocessing. Threads are just fine. All the heavy lifting (OpenCV, mediapipe, ...) will release the GIL. If you don't know what that means, it's okay. I'm saying that for those that think threading isn't okay to use.
You need a thread for the mediapipe stuff and another thread for reading the camera. And you can do your game logic in the main thread (the one you didn't start yourself).
The thread for the camera reading is required because if you don't read from the camera as quickly as it can produce frames (say you're processing in the same loop), produced frames will queue up in the driver and you'll get latency. Have this thread read from the camera and keep the most recent frame (in a variable), and do nothing else.
Whenever the mediapipe thread is ready for (more) work, it gets the current frame from the camera thread. Do that by simply accessing the variable. It can then work and send its conclusions to the game, either by setting variables or by posting to Queue objects (python comes with a queue module). When reading from a queue that may not contain an object, but you don't wanna block until there is an element added to it, you need to specify a zero timeout in the "get" call and catch the "empty" exception.
Here's a code skeleton. It's simple. I won't show you how to use classes. Global variables are fine for this demo. However, I'll show you how to use a Condition object, to wait for fresh frames and signal a fresh frame. No, this is not about guarding access (although it does that too), it's about making sure you actually have new data to process.
import threading
import numpy as np
import cv2 as cv
# and pygame? and mediapipe?
running = True # global flag. when False, threads end
latest_frame = None
latest_frame_available = threading.Condition()
def camera_worker_function():
global latest_frame
camera = cv.VideoCapture(0)
assert camera.isOpened()
while running:
(rv, frame) = camera.read()
if not rv: break
with latest_frame_available:
latest_frame = frame
latest_frame_available.notify_all()
camera.release()
def mediapipe_worker_function():
# globals for values you need in the game
while running:
with latest_frame_available:
if latest_frame_available.wait(timeout=1.0):
frame = latest_frame
else: # False means timeout
continue # -> recheck `running`
if frame is None: break # sentinel value to shut down
# do whatever you need here, using `frame`
...
camera_thread = threading.Thread(target=camera_worker_function)
camera_thread.start()
mediapipe_thread = threading.Thread(target=mediapipe_worker_function)
mediapipe_thread.start()
... # game logic
# end threads
running = False
with latest_frame_available:
latest_frame = None # sentinel value
latest_frame_available.notify_all()
mediapipe_thread.join()
camera_thread.join()
I didn't test this code. Take it as guidance.

What do I need to do to get these both to run in different threads?

I've got two functions main and detection. I need them to run at the same time, I'm trying to do this via two different threads.
main is made up of multiple small functions and is responsible for GUI interactions on an application window. This runs in an loop, it constantly checks to see if it wants to perform GUI interactions. Almost all of them are simply clicking to close notifications within the application window.
def main():
while True:
if check():
doSomething1()
doSomething2()
doSomething3()
detection loads a few thousand images and then checks to see if it's ready to run image detection via a pixel colour match. I realise it's loading the images on every iteration of it's while loop. That's important as doSomething4 appends and sorts images for it to load and try to detect. The performance is fine, they're very simple images.
class VideoCaptureThread(threading.Thread):
def __init__(self, result_queue: queue.Queue) -> None:
super().__init__()
self.exit_signal = threading.Event()
self.result_queue = result_queue
def run(self) -> None:
while not self.exit_signal.wait(0.05):
try:
result = videoLoop()
self.result_queue.put(result)
except Exception as exc:
print(f"Failed capture: {exc}")
def detection():
result_queue = queue.Queue()
thread = VideoCaptureThread(result_queue=result_queue)
thread.start()
while True:
window = result_queue.get()
# load images to detect
images = loadImages(avoid)
# check if ready to run detection
x, y = 1742, 979
r,g,b = py.pixel(x, y)
if b == 106:
if shipDetection(images , 0.98, window, 50):
continue
else:
doSomething4()
Someone on here helped get the detection thread running correctly but I can't seem to get main to start. I was told I need to initiate main before detection as it's loop will block/lock my code.
t = Thread(name='bot', target=main)
t.start()
if __name__ == "__main__":
detection()
If I run t.start in a separate script I can get a thread to start without issue, but here detection runs correctly while main appears to start running (a window appears when it starts) but nothing updates from there.
doSomething1 should return 11 frames of a specific section of the application window and display it (find is a separate function loaded from another script that displays things).
Instead, I'm only getting 1 frame from doSomething1.
def doSomething1(object, y_0, y_1, x_0, x_1, x_adjustment, y_adjustment):
for z in range (0, 10):
haystack = app_window.get_haystack()
location = haystack[y_0:y_1, x_0:x_1]
img = object.find(location, 0.92, 'points1','Refit')
if img:
return True
What do I need to do to get these both to run in different threads?

Python cv2 VideoCapture() in daemon thread turns black randomly

I have a webcam that captures something I want to be able to monitor live. Every 30 seconds an operation needs to be done on the most recent frame of the video. To do this, I've set up a cv2.NamedWindow() in a daemon thread that allows me to see the webcam feed. Then using a scheduler, I can do the operation I need every 30 seconds. The problem is, the camera feed will randomly go black, and save an all black image. Sometimes this happens after 15 minutes of operation, sometimes after 4 hours of operation., so it's very inconsistent and there seems to be no pattern. Here is the code:
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()
This is the smallest amount of code I can reproduce the issue with. There is more going on just this, but this code block has been run and the issue persists. I have found that when I remove the line that saves the image in the data_operation function, which is the cv2.imwrite(imgpath, self.frame), the program seems to work and the camera never goes black and the issue is gone. So I think that is the problem, but I don't know how else to save the frame as an image every 30 seconds while also keeping the live feed up and running. As far as I know, the rest of the code works perfectly except for this, and I do need to be able to save and access the image.
I am not a programmer by trade so I've just been figuring this out as I go. If something is glaringly stupid let me know. If there is a much easier/safer way to display a live webcam feed while also automatically grabbing a picture every 30 seconds and doing stuff with that picture while keeping the live loop going, I would be happy to hear that too.
Any help would be appreciated, and let me know if you have further questions.
EDIT: I have updated the code to simplify and continue shrinking the smallest reproduceable sample. I realized the start_cam function was unnecessary, as all of the cv2 initialization can be done at the start of the live_loop function. The issue persists. I have tried adding a mutex, however that seems to not help (though it's possible I am implementing it wrong). Any info or working examples would be greatly appreciated.
EDIT 2: Another thing to note is that the exact same program running on my laptop (using the built-in webcam rather than the 4k Logitech Brio) doe not seem to have the same issue. Is the frame rate or resolution causing the issue perhaps? Testing is difficult since I need to keep a working program running while working on this one, but I will do more tests when I am able and keep this post updated if anything develops. Still, feel free to toss any ideas you have to me.

python opencv and tkinter capture webcam problem

hello everyone i have a code for read video from webcam and show it into the tkinter window with timer threading.
when user click show button, app make a thread and run it every x second to show frame by frame.
here's the problem : every several frame app shows first frame that captured from video source.
that's weird i know but i cant find out why!!!
here's my code:
import tkinter as tk
from tkinter import ttk
from tkinter import *
import threading
import cv2
import PIL.Image, PIL.ImageTk
from PIL import Image
from PIL import ImageTk
import time
class App(threading.Thread):
def __init__(self, root, window_title, video_source=0):
self.root = root
self.root.title(window_title)
self.video_source = video_source
self.show_switch=False
self.showButton = Button(self.root, text="PlayStream",command=self.showStram,width=15, padx="2", pady="3",compound=LEFT)
self.showButton.pack()
# Create a canvas that can fit the above video source size
self.canvas = tk.Canvas(root, width = 530, height = 397, cursor="crosshair")
self.canvas.pack()
self.root.mainloop()
def updateShow(self):
# Get a frame from the video source
cap=cv2.VideoCapture(0)
while True:
if(cap.isOpened()):
#read the frame from cap
ret, frame = cap.read()
if ret:
#show frame in main window
self.photo = PIL.ImageTk.PhotoImage(image = PIL.Image.fromarray(frame))
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
else:
break
raise ValueError("Unable to open video source", video_source)
if self.show_switch==False:
cap.release()
return False
time.sleep(0.0416666666666667)
#release the cap
cap.release()
def showStram(self):
if self.show_switch:
self.showButton["text"]="StartStream"
# self.showButton.configure(image=self.playIcon)
self.show_switch=False
else:
self.showButton["text"]="StopStream"
self.show_switch=True
# self.showButton.configure(image=self.stopIcon)
self.showTimer=threading.Thread(target=self.updateShow,args=())
#self.showTimer.daemon=True
self.showTimer.start()
App(tk.Tk(), "Main")
This is a complicated question, because your example code is extensive and combines multiple things that can go wrong. I cannot test your code in its current form.
First off, you are accessing your Tk instance from a thread that is not the MainThread. This can cause all sorts of issues. There are also bugs present in the implementation of supposed thread-safety in Tkinter, and the solution has not yet been merged. Checkout mtTkinter if you really need to use multiple threads with Tkinter, and even then, it is better not to, especially not if you are building a new application and have the option to use Queues or some other system instead.
Second, you create a (subclass) instance of threading.Thread, but you never call threading.Thread.__init__ in App.__init__ (which is an absolute requirement if you want to use it as a Thread!). Then you create a new Thread in def showStream(self), while you actually already had a thread. Now, this does not break your code, but you do not need to subclass threading.Thread if you do not intend to use your class as a Thread. As you create a Thread in your class, there is no need to make a Thread out of your class.
Then, moving on through your code, you do start the Thread, so updateShow gets run. However, in your while loop, there is a problem:
while True:
if (cap.isOpened()):
ret, frame = cap.read()
if ret:
...
else:
break
# Error will never be raised because of break
# break exits the loop immediately, so the next line is never evaluated
raise ValueError()
cap.release()
# No notification of the loop having ended
There may be two things going wrong here. The first is that maybe your loop just ends because of the break in the else-clause. Break immediately exits the loop code. Anything following it will never be executed. If it does exit the loop because it failed to get the next frame, you cannot know for sure as you do not check whether the thread is still alive (threading.Thread.is_alive) or have any print statements to indicate that the loop has ended.
Secondly, your program might actually be hard-crashing on you accessing Tkinter from a second thread. Doing this causes undefined behaviour, including weird errors and Python-interpreter lockups because the Tk interpreter and Python interpreter are fighting for flow control in a deadlock (simply put).
Last but not least, there is a problem with the way you create your images:
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
In this line, you create a new image on the Canvas. However, if an image already exists on the Canvas in the location where you are creating a new image, it will appear under the image already shown. If you do not delete your old image (which is in any case advisable to prevent a huge memory leak), it will remain visible on the Canvas.
def __init__(...):
...
self.im = None
def updateShow(self):
...
while True:
if (cap.isOpened()):
...
if ret:
if self.im is not None:
self.canvas.delete(self.im)
...
self.im: str = self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)
Summarizing: With your current posted code, there are multiple things that can be going wrong. Without additional information, it is impossible to know. However, if you fix accessing Tkinter from a different Thread, adjust your while loop to not break but raise the error, you adjust your Canvas image creation code and your video source actually does work properly, it should work out.

OpenCV VideoCapture only updates after 5 read()s

I have a very strange error that has been plaguing my research for a few years now. I'm using OpenCV2 with Python to read image data from a webcam. However, the image is lagged by 5 frames. In other words, each call to read() is 5 frame behind real-time.
One bandage fix I have been using is to grab() 4 frames and then read the 5th every time I need an updated image, but that absolutely murders my performance.
Here's the code that I am using to display the images from the webcam
frame = self.getGoodFrame()
if self.DEBUG:
window = cv2.namedWindow("Angles")
imgHSV = cv2.cvtColor(frame, cv2.cv.CV_BGR2HSV)
... Reseach specific code I shouldn't be giving out here ...
... It finds the center of a few bright colors in an image
if self.DEBUG:
debugImage = numpy.zeros((self.CAMERA_HEIGHT, self.CAMERA_WIDTH), numpy.uint8) #blank image
... Then we draw some stuff to the image to be displayed ...
cv2.imshow("Angles", debugImage)
cv2.waitKey(1)
raw_input()
and getGoodFrame()
def getGoodFrame(self):
MIN_READS_FOR_GOOD_FRAME = 4
for i in xrange(MIN_READS_FOR_GOOD_FRAME):
successful_read = self.capture.grab()
successful_read, frame = self.capture.read()
if not successful_read:
print "Unable to read from webcam, exiting."
return frame
You'll notice that I have a raw_input() call. That makes it so I can see how many reads need to happen by pressing enter a few times in console. This shows that there is exactly 5 frames of lag.
I don't think it's a hardware issue, I've had this happen with multiple webcams and multiple USB cords. I haven't tried reproducing the error on another machine though.
So the problem was something to do with the way that my hardware was buffering the frames. I never quite got to the bottom of it but the solution I found was very simple paralellization. I modified my code to open up a thread and constantly update a variable holding the current frame. Then, when I need the current frame I just ask for whatever that variable is at the moment.
Note that this is still 5 read() calls behind because of the ever-present buffering, however because I am doing read() calls continuously and it's all on its own thread, it is very up to date. This is because I can call many read() calls per second. The image I get is not noticeably behind real-time at all.
The Python class I made to do this is below. It is not very nice code but it works. To do this properly, I would (and will) add graceful ways to exit the infinite loop. It will work for me though and it has improved the speed of my image detection code by well over 100 fold, which is extremely awesome and exciting for me :)
class webcamImageGetter:
def __init__(self):
self.currentFrame = None
self.CAMERA_WIDTH = #webcam width
self.CAMERA_HEIGHT = #webcam height
self.CAMERA_NUM = 0
self.capture = cv2.VideoCapture(0) #Put in correct capture number here
#OpenCV by default gets a half resolution image so we manually set the correct resolution
self.capture.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH,self.CAMERA_WIDTH)
self.capture.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT,self.CAMERA_HEIGHT)
#Starts updating the images in a thread
def start(self):
Thread(target=self.updateFrame, args=()).start()
#Continually updates the frame
def updateFrame(self):
while(True):
ret, self.currentFrame = self.capture.read()
while (self.currentFrame == None): #Continually grab frames until we get a good one
ret, frame = self.capture.read()
def getFrame(self):
return self.currentFrame
To use this, you initialize it then called start on the instance. This will make it so when you later called getFrame(), it will have the most up to date frame from the webcam. Woohoo!
Default buffer is set to 4.0 instead of 1.0.
To Fix it:
cap.set(38,1)
reference: https://docs.opencv.org/4.x/d4/d15/group__videoio__flags__base.html#ga41c5cfa7859ae542b71b1d33bbd4d2b4

Categories