OpenCV Python: How to detect if a window is closed? - python

I have the following code:
total_frames = 50
cv2.cv.NamedWindow("Dragonfly Simulation")
cv2.cv.StartWindowThread()
for i in range(total_frames):
# do stuff
img_name = # something
img = cv2.cv.LoadImage(img_name)
cv2.cv.ShowImage("Dragonfly Simulation", img)
cv2.cv.WaitKey(2)
cv2.cv.DestroyWindow("Dragonfly Simulation")
cv2.cv.WaitKey(1)
# rest of code
So what does it do:
Opens a window
In a loop, shows an image on the window
Once finished, closes the window
Runs the rest of the code
However in this case I have the total_frame given before. I don't want that.
Instead, I want a code that does the following:
Opens a window
In a loop, shows an image on the window
Waits for the user to close that window
When the user closes that window, exit loop, goes on with the rest of the code.
However, I cannot find a function in OpenCV that can detect when user closes a window. Can anyone suggest a workaround please?

I was just looking for a way to detect when the window has been closed using the X button of the window in addition to waiting for a key press, but I couldn't find an answer anywhere (IsWindowVisible and cvGetWindowHandle are not available in the Python cv2 module).
So I played around and this is how it works:
while cv2.getWindowProperty('window-name', 0) >= 0:
keyCode = cv2.waitKey(50)
# ...
cv2.getWindowProperty() returns -1 as soon as the window is closed.
For explanation, see the documentation for the enumeration of cv::WindowPropertyFlags: getting the flag with index 0 is the fullscreen property, but actually it doesn't matter which flag to use, they all become -1 as soon as the window is closed.
Note: This might only work for certain GUI backends. Notably, it will not work with the GTK backend used in Debian/Ubuntu packages. To use the Qt backend instead, you have to install opencv-python via pip.

As of version 2.2 there is a simple solution (this is modified from the loop in hist.py):
cv2.imshow('image',im)
while True:
k = cv2.waitKey(100) # change the value from the original 0 (wait forever) to something appropriate
...
elif k == 27:
print('ESC')
cv2.destroyAllWindows()
break
if cv2.getWindowProperty('image',cv2.WND_PROP_VISIBLE) < 1:
break
cv2.destroyAllWindows()

I tested on C++ using the getWindowProperty('image', WND_PROP_VISIBLE), but it does not work. So I used the WND_PROP_AUTOSIZE and it works.
I did like this:
cv::namedWindow("myTitle", WINDOW_AUTOSIZE);
while(1)
{
cv::imshow("myTitle", myImage);
if (cv::getWindowProperty("myTitle", WND_PROP_AUTOSIZE) == -1)
break;
}

if cv2.getWindowProperty('windowname',1) == -1 :
break
cv2.imshow('windowname', image)

import cv2
import numpy as np
# total_frames = 50
cv2.namedWindow("Dragonfly Simulation")
cv2.startWindowThread()
# for i in range(total_frames):
while True:
# do stuff
img = np.random.randint(0,255,(200,300)).astype(np.uint8)
cv2.imshow("Dragonfly Simulation", img)
key = cv2.waitKey(200)
print key
if key in [ord('a'), 1048673]:
print 'a pressed!'
elif key in [27, 1048603]: # ESC key to abort, close window
cv2.destroyAllWindows()
break
# do the rest of processing after break
print 'results:'
Sure, you can check user inputs using waitKey and here is a small example based on your code. I changed old cv interface to cv2. I think cv is obsolete.
(Edit) I moved cv2.destroyAllWindows() to inside the while loop to make it clear that the window closes when the user pressed ESC key (which you can assign a key of your choice). I do not think opencv has a proper event handler to catch the window close event like in other GUI toolkit (wxPython etc). So you will need to define how your users should close the window and watch out for that.

I used the following code t check if a key is pressed or the window is closed at the same time.
while cv2.getWindowProperty(camera.get_name(), cv2.WND_PROP_VISIBLE) > 0:
if cv2.waitKey(100) > 0:
break
cv2.destroyAllWindows()

I made a simple function based on this post. It works in opencv 4.5.2
def wait_with_check_closing(win_name):
"""
https://stackoverflow.com/questions/35003476/"
"opencv-python-how-to-detect-if-a-window-is-closed/37881722
"""
while True:
keyCode = cv2.waitKey(50)
if keyCode != -1:
break
win_prop = cv2.getWindowProperty(win_name, cv2.WND_PROP_VISIBLE)
if win_prop <= 0:
break
It can be used instead of cv2.waitKey(0) like following example.
# Usage
cv2.imshow(title, img_resized)
# cv2.waitKey(0)
wait_with_check_closing(title)
cv2.destroyAllWindows()

https://www.programcreek.com/python/example/110676/cv2.WND_PROP_VISIBLE
For my scenario, it had to be the following. I had to call k = cv2.waitKey(1) & 0xFF before checking status of the window for it to work
def show_webcam(mirror=False):
cam = cv2.VideoCapture(0)
# cv2.startWindowThread()
window_name="A_star webcam"
while True:
ret_val, img = cam.read()
if mirror:
img = cv2.flip(img, 1)
cv2.imshow(window_name, img)
k = cv2.waitKey(1) & 0xFF
if not (cv2.getWindowProperty(window_name,cv2.WND_PROP_VISIBLE)):
break
if cv2.waitKey(1) == 27:
break # esc to quit
cv2.destroyAllWindows()
The libraries installed are
autopep8==1.6.0
numpy==1.22.1
opencv-python==4.5.5.62
pycodestyle==2.8.0
toml==0.10.2

#David Kohen's answer is working great for me. But if you have many images to show its better to convert it to a function
def waitUntilX(window):
while True:
k = cv2.waitKey(100) # change the value from the original 0 (wait forever) to something appropriate
if k == 27:
print('ESC')
cv2.destroyAllWindows()
break
if cv2.getWindowProperty(window,cv2.WND_PROP_VISIBLE) < 1:
break
cv2.destroyAllWindows()
Usage
invertedmask = cv2.bitwise_not(mask)
cv2.imshow('inverted mask', invertedmask)
waitUntilX('inverted mask')

Related

While loop doesn't perfom second iteration

my code is:
cap = cv2.VideoCapture(2)
frame_width = int(cap.get(3))
frame_height = int(cap.get(4))
while True:
ret, frame = cap.read()
if ret == True:
cv2.imshow('frame',frame)
time.sleep(0.001)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
print("you suck")
break
cap.release()
cv2.destroyAllWindows()
The problem is it doesn't do the second iteration, it just performs the first one and then gets stuck. interesting is that when I delete import:
from model.return_model import return_model, process_frames which basically imports pytorch model, it works perfectly. I am trying to debug it, but can't find any solution
Guys I resolved the issue. I used sudo to run the script, and it worked

using multiple waitKey() in an if-elif test, second key test always fails, even when that key is pressed

I made a simple code to take snapshots from my webcam when prompted by pressing 's', and quit if I pressed 'q'. Code runs fine but the elif statement for the quitting part alone is ignored.
import cv2
cap = cv2.VideoCapture(0)
counter = 0
while(True):
ret, frame = cap.read()
cv2.imshow('Webcam', frame)
if (cv2.waitKey(1) & 0xFF) == ord('s'):
print("s working")
counter = counter + 1
cv2.imwrite("Snapshot_" + str(counter) + ".png", frame)
elif (cv2.waitKey(1) & 0xFF) == ord('q'):
print("q working")
break
cap.release()
cv2.destroyAllWindows()
I also tried swapping their order, the if statement being the 'q' condition and the elif being 's', the second statement is always the one skipped.
Update: it only works if I hold q for a while
Why isn't it working normally?
As I learned from the comments, the problem was caused by the waitKey delay. So to fix it I put the pressed button in a variable and compared the condition statements to that variable instead.
import cv2
cap = cv2.VideoCapture(0)
counter = 0
while(True):
ret, frame = cap.read()
cv2.imshow('Webcam', frame)
k = cv2.waitKey(1) & 0xFF
if k == ord('s'):
print("s working")
counter = counter + 1
cv2.imwrite("Screenshot_" + str(counter) + ".png", frame)
elif k == ord('q'):
print("q working")
break
cap.release()
cv2.destroyAllWindows()
The issue is not caused by any delays, nor by any blocking.
After cap.read() returns a new frame, there may be key events waiting because you pressed some keys.
Each waitKey() consumes one key press event and returns it, if there was a key event.
If you pressed a key, the first waitKey() receives that event, and if it was the wrong key, the if doesn't run its consequent code.
Then, the second waitKey() has no key event to handle, so it returns -1.
If you held the key down, you might have had a chance to create enough key press events to get one of them into the second waitKey().
The proper solution is to call waitKey() once per loop, and store its returned value in a variable.
Then you test that variable against as many key codes as you like.
while True:
...
keycode = cv.waitKey()
if keycode == ord('s'):
...
elif keycode == ord('q'):
...
That &0xFF stuff is superfluous and outdated since 2016. waitKey() does that already. It is superfluous.

How to keep cv2.imwrite() from overwriting the image files?

I am using Jupyter Notebook for practicing coding (if it's relevant) and trying to use cv2 in order to take pictures using my webcam. However, whenever I take a picture and have that picture saved, the previous picture gets overwritten because of having the same file name. How do I keep this from not happening?
import cv2
key = cv2.waitKey(1)
webcam = cv2.VideoCapture(0)
while True:
try:
check, frame = webcam.read()
print(check)
print(frame)
cv2.imshow("Capturing", frame)
key = cv2.waitKey(1)
if key == ord('s'):
cv2.imwrite(filename='saved_img.jpg', img=frame)
webcam.release()
cv2.waitKey(1650)
cv2.destroyAllWindows()
break
elif key == ord('q'):
print("Turning off camera.")
webcam.release()
print("Camera off.")
print("Program ended.")
cv2.destroyAllWindows()
break
except(KeyboardInterrupt):
print("Turning off camera.")
webcam.release()
print("Camera off.")
print("Program ended.")
cv2.destroyAllWindows()
break
'saved_img' image file always gets overwritten whenever I capture...
You can use the isfile function from the os module:
import os
if os.isfile("saved_img.jpg"):
if input("do you want to overwrite saved_img.jpg? ") == "yes":
cv2.imwrite(filename='saved_img.jpg', img=frame)
else:
cv2.imwrite(filename='saved_img.jpg', img=frame)
If you need to restart-proof, you can check how many files have been saved before, so that code allows (as you said in comment) one image per run of th program
nb_files = len(list(Path(".").glob("saved_img_*.jpg")))
filename = f"saved_img_{nb_files}.jpg"
import cv2
key = cv2.waitKey(1)
webcam = cv2.VideoCapture(0)
while True:
try:
check, frame = webcam.read()
print(check)
print(frame)
cv2.imshow("Capturing", frame)
key = cv2.waitKey(1)
if key == ord('s'):
cv2.imwrite(filename=filename, img=frame)
webcam.release()
cv2.waitKey(1650)
cv2.destroyAllWindows()
break
...
Here's a variation if one run of the program may create multiple images
nb_files = len(list(Path(".").glob("saved_img_*.jpg")))
i = 0
import cv2
key = cv2.waitKey(1)
webcam = cv2.VideoCapture(0)
while True:
try:
check, frame = webcam.read()
print(check)
print(frame)
cv2.imshow("Capturing", frame)
key = cv2.waitKey(1)
if key == ord('s'):
cv2.imwrite(filename=f"saved_img_{nb_files + i}.jpg", img=frame)
i += 1

how to end a while loop by pressing a key

I'm trying to write a code so that a webcam will take a picture every hour for an undetermined amount of time (2 weeks for now). Here's what I have so far (which works) :
t0=time.perf_counter()
time_max=3600*24*14
time_step= 3600
while(True):
tc=time.perf_counter()
crit = tc-t0
if (crit>time_max) :
cap.release()
break
cap.open(num_cam,api)
cap.set(cv2.CAP_PROP_FOCUS,70)
ret=cap.grab() #takes a picture
tst,frm=cap.retrieve()
cv2.imwrite('test1h_'+str(np.round(crit,2))+'_f70.png',frm)
cap.release()
time.sleep(time_step)
pass
I would like for this loop to stop if I press 'q' for example, and for the webcam to take a picture if I press 'p', how could I implement that ? I read you can you can use cv2.waitKey but I don't know where to put that in the code. I also read that you can use the nodule "keyboard" but that it requires a root in linux and I work on windows.
You can just use this:
if cv.waitKey(20) % 0xFF == ord("d"):
break
So for example if you want to display a video while not pressing the "d" key, this should work:
while True:
isTrue, frame = capture.read()
cv.imshow("Video", frame)
if cv.waitKey(20) % 0xFF == ord("d"):
break
capture.realease()
cv.destroyAllWindows()
Edit: Had mixed up 'char' and 'key', fixed it now.
If you're okay with not using cv2, I would suggest this using pynput:
from pynput import keyboard
def on_press(key):
print(key)
listener = keyboard.Listener(on_press=on_press)
listener.start()
In your case, you could implement it like this:
import time
from pynput import keyboard
t0=time.perf_counter()
time_max=3600*24*14
time_step= 3600
def on_press(key):
if key.char=='p':
#Take picture
cap.open(num_cam,api)
cap.set(cv2.CAP_PROP_FOCUS,70)
ret=cap.grab()
tst,frm=cap.retrieve()
cv2.imwrite('test1h_'+str(np.round(crit,2))+'_f70.png',frm)
cap.release()
elif key.char=='q':
#Interrupt
exit()
listener = keyboard.Listener(on_press=on_press)
listener.start()
while(True):
tc=time.perf_counter()
crit = tc-t0
if (crit>time_max) :
cap.release()
break
cap.open(num_cam,api)
cap.set(cv2.CAP_PROP_FOCUS,70)
ret=cap.grab() #takes a picture
tst,frm=cap.retrieve()
cv2.imwrite('test1h_'+str(np.round(crit,2))+'_f70.png',frm)
cap.release()
time.sleep(time_step)
pass

Time.Sleep Alternative? Need to be able to break loop/return to main

import time
import pyautogui
from pyautogui import *
import keyboard
from PIL import ImageGrab, Image
from PIL import *
import os
import win32api, win32con
import cv2
import pyperclip
from pynput.keyboard import Key, Listener
import pytesseract
import numpy as np
from numpy import *
from threading import Event
##1340,182, 1777, 213
test = 0
src_path = os.getcwd()
pytesseract.pytesseract.tesseract_cmd = r'C:\\Program Files\\Tesseract-OCR\\tesseract'
def get_region(box):
im = ImageGrab.grab(box)
im = im.save(os.getcwd() + "\\logs.png", 'PNG')
def get_string(img_path):
img = cv2.imread(img_path)
kernel = np.ones((1, 1), np.uint8)
img = cv2.dilate(img, kernel, iterations=1)
img = cv2.erode(img, kernel, iterations=1)
red = [0,0,255]
img[img != red] = 255
cv2.imwrite(src_path + "\\logs.png", img)
result = pytesseract.image_to_string(Image.open(src_path + "\\logs.png"))
return result
def alertf1(c):
global test
time.sleep(0.1)
while c == 1:
if keyboard.is_pressed('f1'): # if key 'f1' is pressed
print('f1 has been pressed')
c = 0
break
else:
result = get_string(src_path + "\\logs.png")
print('checking if anything is destroyed')
if result.find('destroved') != -1:
print('something got destroyed!')
pyautogui.keyDown('ctrl')
time.sleep(0.1)
pyautogui.keyDown('tab')
pyautogui.keyUp('ctrl')
pyautogui.keyUp('tab')
time.sleep(0.5)
for char in '#':
pyperclip.copy(char)
pyautogui.hotkey('ctrl', 'v', interval=0.1)
pyautogui.write('everyone Enemies/Red Logs! Something has been destroyed.')
pyautogui.press('enter')
pyautogui.click(x=1860, y=50)
else:
if keyboard.is_pressed('f1'): # if key 'f1' is pressed
print('f1 has been pressed')
c = 0
break
def on_press(key):
global test
test = test + 1
region = (1341,185, 1778, 215)
get_region(region)
if key == Key.f1 and test == 1:
print('before alert f1 pressed')
test = test + 1
time.sleep(0.1)
alertf1(1)
if key == Key.f1 and test == 3:
test = 0
with Listener(on_press=on_press) as listener:
listener.join()
def main():
while True:
print('on press going now')
on_press(key)
if __name__ == '__main__':
main()
Thats my current code, a small explanation of what it does and what I want:
Upon start, if user presses 'F1' it will take a screenshot of a small area and turn the image into text, it will then run an infinite loop, to check if the text I got from the image is = to 'destroyed' if it is then user gets notified and if it isn't it will be on a infinite loop getting new screenshots and checking it again.
I also added the "if keyboard.is_pressed('f1'):" to break the infinite loop.
The 2 issues I have right now:
1- After user gets notified I want the program to not proceed into the infinite loop for 5 minutes, time.sleep (300) works but the issue is that if the user wants to cancel the current function and presses F1 to break the loop back into Main() it will not get detected, thus I need another alternative to time.sleep because it just freezes the code completely. I heard that threading.event could work but I have honestly no idea how it works, and even after checking a couple examples I am not sure how I could make it work still.
2- I found that 'if keyboard.is_pressed('f1'):' Isn't very effective as it sometimes doesn't trigger and have to click quite a few times until it triggers and breaks loop. What would be a good alternative?

Categories