OpenCV imshow window cannot be reused when called within a thread - python

I'm having trouble using cv2 imshow when run in a separate python thread.
The code below works for the first call of start_cam, but the second call fails - the cv2 camera window does not reappear. This seems to have something to do with threading prevent that window being reused, because:
If the cv2 window is given a random name then it works indefinitely, although the window is not being reused as each window is new of course.
If _cam_loop() is called without threading this it also works and the same window can be reused indefinitely.
def start_cam(self):
self.cam_live = True
threading.Thread(target = self._cam_loop).start()
def stop_cam(self):
self.cam_live = False
def _cam_loop(self):
while self.cam_live:
img = self.cam.get_image()
cv2.imshow("cam", img)
cv2.waitKey(1)
self.start_cam() # live image window appears
self.stop_cam() # image window disappears (as thread is killed)
self.start_cam() # window fails to reappear
The window disappears when the thread finishes. Is there a way to keep a reference to the window after the thread finishes?

I didn't find a way to keep a reference to the named window, but if the window is destroyed it can be reused each time the thread is called. I simply added cv2.destroyAllWindows() to the end of the thread function and it worked. Curious to know why exactly.
def _cam_loop(self):
while self.cam_live:
img = self.cam.get_image()
cv2.imshow("cam", img)
cv2.waitKey(1)
cv2.destroyAllWindows()

Related

Display continuously change of an image in python

I am writing a python program that gradually changes an image step by step, adjusting each pixel by a small amount in each step. To get a visualization of what the program is doing during runtime, I want it to display the image at each step, always overwriting the currently shown image so that it doesen't open bunch of display windows.
I already tried matplotlib, opencv and skimage, with their according possibilities to display an image and update the frame content in the course of the program:
# using scimage
viewer = ImageViewer(image)
viewer.show(main_window=False) # set the parameter to false so that the code doesn't block here but continues computation
..other code..
viewer.update_image(new_image)
# using matplotlib
myplot = plt.imshow(image)
plt.show(block=False)
.. other code..
myplot.set_data(new_image)
plt.show()
# using opencv
cv2.imshow('image',image)
.. other code ..
cv2.imshow('image', new_image)
I always ran into the problem that when it was supposed to open a frame with an image, it did not display the image but only a black screen. Weirdly enough, when I ran the code in IntelliJ in debug-mode and hit a breakpoint after the display-function, it worked.
What can I do so that it is displayed correctly when running the program normally and not with a breakpoint?
Here's the thing, I think your program does work, except it does and finishes unless you tell it to pause, which is why your breakpoint strategy is working.
Try pausing after showing image -
You can ask for user input. It'll pause until you enter some input to the program.
Put the program thread to sleep for some specified amount of time. This'll freeze your program for some given specified time, but you'll be able to see the image if it's already rendered.
Edit -
Since opencv's waitKey method is working for you now, you can use this method again to prevent the program from closing image window. Use waitKey(0) as your last program statement. It waits for a key press indefinitely, and returns the pressed key's code. Press any key to continue (but remember to have your image window in focus or it won't work), and your program should close if it's used in the end.
Also, I've striked earlier suggested options for pausing a program, because I'm unsure if it would've helped. I think waitKey method is more complex, and helps pause the program without freezing it.
Well, I am still not sure what exactly your goal is but here is a code piece that modifies an image inside of a window whenever the upper button is pressed.
from tkinter import Tk, Canvas, Button, Label, PhotoImage, mainloop
import random
WIDTH, HEIGHT = 800, 600
def modify_image():
print ("modifiying image...")
for x in range(1000):
img.put ( '#%06x' % random.randint(0, 16777215), # 6 char hex color
( random.randint(0, WIDTH), random.randint(0, HEIGHT) ) # (x, y)
)
canvas.update_idletasks()
print ("done")
canvas = Canvas(Tk(), width=WIDTH, height=HEIGHT, bg="#000000")
canvas.pack()
Button(canvas,text="modifiying image",command=modify_image).pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
Label(canvas,image=img).pack()
mainloop()
The function modify_image() adds 1000 random pixels to the image within the main window. Note the tkinter module is a default python module.

Python Image Window Not Closing Properly on Mac

I'm working with OpenCV and trying to figure this out. When I want to read and show an image:
import cv2
img = cv2.imread('baboon.jpg', 0)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow('image')
I write this. But whenever the image display window pops up, I'm not able to close it and execute everything after
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow('image')
because the window will not close unless I quit it.
For example, if I run this code
import cv2
img = cv2.imread('baboon.jpg', 0)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow('image')
#Nothing after this will get executed
print('Hello')
I get no output in my console, but the image window does pop up.
How can I fix this?
(I'm using Pycharm if that helps)
EDIT:
It seems that whenever I press any key the window closes and it works as intended (code after the comment is executed). Not sure why this is happening.
I believe the quit is acting as an interrupt, which quits the entire python program. This is different than pressing a key, which is incorporated into the logic of the actual program and continues to completion:
cv2.waitKey(0) # waits for any key to be pressed to continue the logic of the program
# this is what actually allows you to "display" an image without it immediately disappearing.
I believe you can click on anywhere in the pic and it would return to the code window

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

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()

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.

Trying to display opencv video in QGraphicsScene and QGraphicsView but Nothing shows up

I have a GUI app that I wrote in Python and I'm trying to display video in Qt app.
It uses a QGraphicsScene and QGraphicsView to display images but now I have to display a video. When I try to display a video here is what I do:
First, I create VideoCapture from cv2 package. After that I run loop and read frame in every iteration which is then casted into QPixmap (this is done correctly, I checked on a single frame). I return that QPixmap object to the Class which holds QGraphicsScene and QGraphicsView and try to add it to the scene.
Thing is, only when the video ends, the last frame is displayed, throughout video playing time I'm welcomed with this sighting
The loop that I mentioned earlier looks like this:
self.imageViewer.start_capturing()
self.timer = QtCore.QTimer()
self.fps = 24
while True:
retVal = self.imageViewer.read_frame()
if not retVal:
self.timer.stop()
break
self.timer.start(1000./self.fps)
self.imageViewer.stop_capturing()
self.ImageViewer is a component which holds the QGraphicsScene and QGraphicsView
I also put timer here so that it displays proper ammount of fps but it doesn't help.
The GUI app itself shows QGraphicsView with some buttons
middleLayout.addWidget(self.imageViewer.view)
This is what I mean. So it doesn't show imageViewer but it shows a subclass of QGraphicsView
Here is the code from read_frame method in ImageViewer class.
def read_frame(self):
"""
Reads a frame from the video, if video ended, then False is returned.
If there is a successful reading then True is returned
:return: True if video is read successfully, False otherwise.
"""
retVal, self.current_frame = self.capture.getFrame()
if not retVal:
return False
else:
self.pixmap = retVal
self.scene.addPixmap(self.pixmap)
return True
The method self.capture.getFrame() just returns QPixmap item and CV::mat item.
This whole process is done correctly because I tried manually reading frame by frame and everything worked OK, but when I try to display video the app freezed like shown in image above. So I manually tried to do the whole described process by manually clicking a button which loads me frame and puts it onto the QGraphicsScene so I assume that the core of the app works correctly (and I even tried to get roughly 5-7 fps by clicking fast :D )
I hope I made my problem clear and included all code that is needed.
I think your timing logic is wrong.. you do not use the timer correctly.. you could either use connect or you can use sleep.
I would do something like this:
def grabFrame
retVal = self.imageViewer.read_frame()
if not retVal:
self.timer.stop()
self.imageViewer.stop_capturing()
And somewhere in your main logic or some init function of your class:
yourObject.connect(timer,SIGNAL("timeout()"),yourObject,SLOT("grabFrame()"))
timer.start(1000./self.fps)
Or you can use some kind of sleep of time module after
import time
...
while True:
retVal = self.imageViewer.read_frame()
if not retVal:
break
time.sleep(1./self.fps)//in fraction of second
self.imageViewer.stop_capturing()

Categories