I'm trying to learn how to use threading and specifically concurrent.futures.ThreadPoolExecutor
this is because I need to return a numpy.array
from a function I want to run concurrently.
The end goal is to have one process running a video loop of an application, while another process does object detection and GUI interactions. The result()
keyword from the concurrent.futures library allows me to do this.
The issue is my code runs once, and then seems to lock up. I'm actually unsure what happens as when I step through it in the debugger it runs once, then the debugger goes blank and I literally cannot step through and no error is thrown.
The code appears to lock up on the line: notepadWindow = pygetwindow.getWindowsWithTitle('Notepad')[0]
I get exactly one loop, the print
statement prints once the loop restarts and then it halts at pygetwindow
I don't know much about the GIL but I have tried using the max_workers=1
argument on ThreadPoolExecutor()
which doesn't make a difference either way and I was under the impression concurrent.futures
allows me to bypass the lock.
How do I run videoLoop
as a single thread making sure to return DetectionWindow
every iteration?
import cv2 as cv
import numpy as np
import concurrent.futures
from PIL import ImageGrab
import pygetwindow
def videoLoop():
notepadWindow = pygetwindow.getWindowsWithTitle('Notepad')[0]
x1 = notepadWindow.left
y1 = notepadWindow.top
height = notepadWindow.height
width = notepadWindow.width
x2 = x1 width
y2 = y1 height
haystack_img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
haystack_img_np = np.array(haystack_img)
DetectionWindow= cv.cvtColor(haystack_img_np, cv.COLOR_BGR2GRAY)
return DetectionWindow
def f1():
with concurrent.futures.ThreadPoolExecutor() as executor:
f1 = executor.submit(videoLoop)
notepadWindow = f1.result()
cv.imshow("Video Loop", notepadWindow)
cv.waitKey(1)
print(f1.result())
while True:
f1()
CodePudding user response:
A ThreadPoolExecutor won't help you an awful lot here, if you want a continuous stream of frames.
Here's a reworking of your code that uses a regular old threading.Thread
and puts frames (and their capture timestamps, since this is asynchronous) in a queue.Queue
you can then read in another (or the main) thread.
The thread has an otherwise infinite loop that can be stopped by setting the thread's exit_signal
.
(I didn't test this, since I'm presently on a Mac, so there may be typos or other problems.)
import queue
import time
import cv2 as cv
import numpy as np
import threading
from PIL import ImageGrab
import pygetwindow
def do_capture():
notepadWindow = pygetwindow.getWindowsWithTitle("Notepad")[0]
x1 = notepadWindow.left
y1 = notepadWindow.top
height = notepadWindow.height
width = notepadWindow.width
x2 = x1 width
y2 = y1 height
haystack_img = ImageGrab.grab(bbox=(x1, y1, x2, y2))
return cv.cvtColor(np.array(haystack_img), cv.COLOR_BGR2GRAY)
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 = do_capture()
self.result_queue.put((time.time(), result))
except Exception as exc:
print(f"Failed capture: {exc}")
def main():
result_queue = queue.Queue()
thread = VideoCaptureThread(result_queue=result_queue)
thread.start()
start_time = time.time()
while time.time() - start_time < 5: # Run for five seconds
frame = result_queue.get()
print(frame)
thread.exit_signal.set()
thread.join()
if __name__ == "__main__":
main()