How to remove video in apps of Tkinter Python? - python

I have created an apps to display video by Tkinter and Python. I can play, pause video in app and the problem is now I want to remove the video out of my app. I have tried a lot of solutions but It can not be removed. Code to display video in my app:
def load_vid(path):
global stop
global frame_image
global vlabel
video = imageio.get_reader(path)
frame = 0
stop = Button(tab1, text="Stop",command=stop)
stop.place(x=350,y=370,anchor="e")
for image in video.iter_data():
frame += 1
image_frame = PIL.Image.fromarray(image)
image_frame.thumbnail((500,500))
try:
frame_image = ImageTk.PhotoImage(image_frame)
vlabel = Label(tab1,image=frame_image)
#vlabel.config(image=frame_image)
vlabel.image = frame_image
vlabel.place(x=50, y=210, anchor="w")
if stop == True:
break
except:
sys.exit()
Function to remove the video:
def stop():
global stop
stop = True
print('stop')
vlabel.config(image = "")
I want to remove video out of the main frame of app.
With image, I can remove by using .config(image ="") but now it does not work. Is there any solution? Thanks for helping.

From the looks of it, you are creating a new Label during every frame of your video.
for image in video.iter_data():
...
try:
...
vlabel = Label(tab1,image=frame_image)
vlabel.place(x=50, y=210, anchor="w")
...
except:
...
There would be hundreds of Label stacking on top without knowing, and you are only setting the last created Label image to None.
Perhaps you should start by creating the Label outside of your loop, and only modify the image during your iteration:
def load_vid(path):
...
stop = Button(tab1, text="Stop",command=stop)
stop.place(x=350,y=370,anchor="e")
vlabel = Label(tab1)
vlabel.place(x=50, y=210, anchor="w")
for image in video.iter_data():
...
try:
frame_image = ImageTk.PhotoImage(image_frame)
vlabel.config(image=frame_image)
vlabel.image = frame_image
if stop == True:
break
except:
sys.exit()

Related

Break a loop externally with the button that started the loop

I want to break a loop with the same button that I started the loop with.
For all intents and purposes, it does what I need it to, up until I click the button again. If I hit the button again, it crashes. Right now with the debug visual setup, I can hit q to break the loop. But down the road I obviously want to turn that off and just have it all go through the button instead.
I should also note that the button does not update unless the window is interacted with/moved. This goes for when the button is pushed. It doesn't crash until you move the window.
def bttnup():
global is_on
#starts autoaccept
if is_on:
button.config(image = selected,
bg=selected_bg
)
start()
is_on = False
else:
#Stops autoaccept
button.config(image = unselected,
bg=unselected_bg,
)
is_on = True
#Object detection and keystroke
def start():
while(True):
screenshot = wincap.get_screenshot()
points = vision_accept.find(screenshot, .325, 'rectangles')
if cv.waitKey(1) == ord('q'):
cv.destroyAllWindows()
break
#Button
button = Button(window,
image=unselected,
command=bttnup)
button.pack()
I have also tried:
def bttnup():
#starts autoaccept
if button['bg'] == 'unselected_bg':
threading.join(1)
button.config(image=selected,
bg=selected_bg
)
start()
return
#Stops autoaccept
if button['bg'] == 'selected_bg':
threading.start()
button.config(image = unselected,
bg=unselected_bg,
)
return
#start object detection
def start():
i = 1
while button['bg'] == 'selected_bg':
i = i+1
# get an updated image of the game
screenshot = wincap.get_screenshot()
# screenshot = cv.resize(screenshot, (800, 600))
points = vision_accept.find(screenshot, .325, 'rectangles')
sleep(5)
#Button!
button = Button(window,
image=unselected,
command=lambda: bttnup())
So I went and changed a majority of the code to use .after instead of using a while statement. Its not as clean looking, but it does what it needs to.
created a function for on, and one for off and have the button cycle when the button is fixed. when the on button is pushed it calls to the start function which then cycles every second.
# initialize the WindowCapture class
wincap = WindowCapture()
# Initialize the Vision class
vision_accept = Vision('accept.jpg')
#window parameters
window = Tk()
window.resizable(False, False)
window.title('AutoAccept')
unselected = PhotoImage(file='Button_unselected.png')
selected = PhotoImage(file='matching.png')
unselected_bg='white'
selected_bg='black'
is_on = True
def start_on():
global is_on
#starts autoaccept
button.config(image = selected,
bg=selected_bg,
command = start_off
)
start()
is_on = True
def start_off():
global is_on
button.config(image = unselected,
bg=unselected_bg,
command = start_on
)
is_on = False
#start object detection
def start():
if is_on:
# get an updated image of the game
screenshot = wincap.get_screenshot()
points = vision_accept.find(screenshot, .32, 'rectangles')
window.after(500, start)
button = Button(window,
image=unselected,
command=start_on
)
button.pack()
window.mainloop()

How can you run loops alongside tkinter? - Python QR code reader GUI

I am trying to write a Tkinter application that will also process QR codes. For that to work, I need to have a loop checking if the QR code is valid and I need to make a post request. I'm fully aware that the way I have coded this is highly inefficient. Is there a better way to do this? Here is what I have so far:
import cv2
import tkinter as tk
from PIL import Image, ImageTk
import sys
import os
import pyzbar.pyzbar as zbar
import threading
import requests
import queue
result = []
decodedCode = ""
logo = "logo.png"
settings = "settings.png"
if os.environ.get('DISPLAY','') == '':
print('no display found. Using :0.0')
os.environ.__setitem__('DISPLAY', ':0.0')
#create main window
master = tk.Tk()
master.title("tester")
master.geometry("480x800")
master.configure(bg='white')
ttelogo = tk.PhotoImage(file = logo)
settingslogo = tk.PhotoImage(file = settings)
#settings button
settings_frame = tk.Frame(master,width=50,height=50,bg="white")
settings_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
settingsBtn = tk.Button(settings_frame, image=settingslogo).pack()
settings_frame.place(x=430,y=0)
#logo
img = tk.Label(master, image=ttelogo, bg='white')
img.image = ttelogo
img.place(x=176.5,y=10)
#Name Label
label_frame = tk.Frame(master,width=400,height=100,bg="white")
label_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
tk.Label(label_frame,bg="white",fg="black",text="John Smith Smithington III",font=("Calibri",22)).pack()
label_frame.place(x=40,y=140)
#Instructions Label
instructions_frame = tk.Frame(master,width=440,height=100,bg="white")
instructions_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
tk.Label(instructions_frame,bg="white",fg="black",text="Place your pass under the scanner below.",font=("Calibri",10)).pack()
instructions_frame.place(x=20,y=210)
#Camera Window
cameraFrame = tk.Frame(master, width=440, height=480)
cameraFrame.place(x=20, y=260)
#Camera Feed
lmain = tk.Label(cameraFrame)
lmain.place(x=0, y=0)
cap = cv2.VideoCapture(0)
def startScanning():
global cap
_, frame = cap.read()
frame = cv2.flip(frame, 1)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain.imgtk = imgtk
lmain.configure(image=imgtk)
lmain.after(10, startScanning)
def processScan():
global decodedCode
stopped = False
delay = 1
while(True):
ret = cv2.waitKey(delay) & 0xFF
if ret == ord('c'): # continue
stopped = False
delay = 1
if ret == ord('q'):
break
if stopped or (ret == ord('s')): # stop
stopped = True
delay = 30
continue
# Capture frame-by-frame
ret, frame = cap.read()
decodedObjects = zbar.decode(frame)
if len(decodedObjects) > 0:
stopped = True
for code in decodedObjects:
#print("Data", obj.data)
#API Calls
decodedCode = code.data.decode('utf-8')
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
def checkCode():
global decodedCode
while True:
if decodedCode != "":
print (decodedCode)
result = requests.post("https://example.com/login/index.php", data={'action': 'validate_scan', 'uuid': decodedCode}).text
print(result)
decodedCode = ""
startScanning() #Display 2
threading.Thread(name='background', target=processScan).start()
threading.Thread(name='background2', target=checkCode).start()
master.mainloop() #Starts GUI
Edit: New version in queue form:
# import all the necessary modules
from tkinter import *
from multiprocessing import Process, Queue
from queue import Empty # for excepting a specific error
import numpy as np
import cv2
from PIL import Image, ImageTk
import sys
import os
import pyzbar.pyzbar as zbar
import threading
import requests
# this is the function that will be run in a child process
def processScan(queue_): # pass the queue as an argument
stopped = False
delay = 1
while(True):
ret = cv2.waitKey(delay) & 0xFF
if ret == ord('c'): # continue
stopped = False
delay = 1
if ret == ord('q'):
break
if stopped or (ret == ord('s')): # stop
stopped = True
delay = 30
continue
# Capture frame-by-frame
ret, frame = cap.read()
decodedObjects = zbar.decode(frame)
if len(decodedObjects) > 0:
stopped = True
for code in decodedObjects:
#print("Data", obj.data)
#API Calls
queue_.put(code.data.decode('utf-8'))
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
#master.after(2000, processScan)
#return r
# just a function to not write a `lambda`, just easier
# to read code
def startScanning():
global cap
_, frame = cap.read()
frame = cv2.flip(frame, 1)
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain.imgtk = imgtk
lmain.configure(image=imgtk)
lmain.after(10, startScanning)
#processScan()
#threading.Thread(name='background', target=processScan).start()
# set process `daemon = True` so that it gets terminated with the
# main process, this approach is not suggested if you write to file
# but otherwise it shouldn't cause any issues (maybe an error but
# that probably can be handled with `try/except`)
Process(target=processScan, args=(queue, ), daemon=True).start()
# here is the loop for updating the label
# basically get the info from the queue
def update_label():
try:
# try getting data but since it is `block=False`
# if there is nothing in the queue it will not block
# this process waiting for data to appear in the queue
# but it will raise the Empty error
data = queue.get(block=False)
except Empty:
pass
else:
# if no error was raised just config
# label to new data
labelFrame.config(text=data)
finally:
# and simply schedule this function to run again in
# 100 milliseconds (this btw is not recursion)
master.after(100, update_label)
# crucial part is to use this if statement because
# child processes run this whole script again
if __name__ == '__main__':
master = Tk()
logo = "logo.png"
settings = "settings.png"
if os.environ.get('DISPLAY','') == '':
print('no display found. Using :0.0')
os.environ.__setitem__('DISPLAY', ':0.0')
#create main window
master.attributes("-fullscreen", True)
master.title("Title")
master.geometry("480x800")
master.configure(bg='white')
ttelogo = PhotoImage(file = logo)
settingslogo = PhotoImage(file = settings)
#settings button
settings_frame = Frame(master,width=50,height=50,bg="white")
settings_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
settingsBtn = Button(settings_frame, image=settingslogo).pack()
settings_frame.place(x=430,y=0)
#logo
img = Label(master, image=ttelogo, bg='white')
img.image = ttelogo
img.place(x=176.5,y=10)
#Name Label
label_frame = Frame(master,width=400,height=100,bg="white")
label_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
Label(label_frame,bg="white",fg="black",text="John Smith Smithington III",font=("Calibri",22)).pack()
label_frame.place(x=40,y=140)
#Instructions Label
instructions_frame = Frame(master,width=440,height=100,bg="white")
instructions_frame.pack_propagate(0) # Stops child widgets of label_frame from resizing it
Label(instructions_frame,bg="white",fg="black",text="Place your pass under the scanner below.",font=("Calibri",15)).pack()
instructions_frame.place(x=20,y=210)
#Camera Window
cameraFrame = Frame(master, width=440, height=480)
cameraFrame.place(x=20, y=260)
#Camera Feed
lmain = Label(cameraFrame)
lmain.place(x=0, y=0)
cap = cv2.VideoCapture(0)
# define queue (since it is a global variable now
# it can be easily used in the functions)
queue = Queue()
#label = Label(root)
#label.pack()
# initially start the update function
update_label()
# just a button for starting the process, but you can also simply
# call the function
#Button(root, text='Start', command=start_process).pack()
startScanning()
master.mainloop()
Still running into errors. Also am not sure if this is correct Queue syntax. The Camera feed is not live. Just a static image is showing up at the moment.
This is about as simple as it gets with multiprocessing (explanation in code comments (which is why the code may seem like it has a lot going on when if the comments were removed it would be approx only 40 lines of code for this example)):
# import all the necessary modules
from tkinter import Tk, Label, Button
from multiprocessing import Process, Queue
from queue import Empty # for excepting a specific error
# this is the function that will be run in a child process
def count(queue_): # pass the queue as an argument
# these modules are just to show the functionality
# but time might be necessary otherwise
# if theses are imported in the global namespace
# then you don't need to import them here again
import itertools
import time
for i in itertools.count():
# do the main looping
# put data in the queue
queue_.put(i)
# you may not need to use sleep here depending
# on how long it takes for the
# loop to finish the iteration
# the issue is that the loop may finish
# way faster than the update function runs
# and that will fill up the queue
# I think the queue size can be limited
# but putting sleep like this to allow
# the update loop to update will work too
time.sleep(0.2)
# just a function to not write a `lambda`, just easier
# to read code
def start_process():
# set process `daemon = True` so that it gets terminated with the
# main process, this approach is not suggested if you write to file
# but otherwise it shouldn't cause any issues (maybe an error but
# that probably can be handled with `try/except`)
Process(target=count, args=(queue, ), daemon=True).start()
# here is the loop for updating the label
# basically get the info from the queue
def update_label():
try:
# try getting data but since it is `block=False`
# if there is nothing in the queue it will not block
# this process waiting for data to appear in the queue
# but it will raise the Empty error
data = queue.get(block=False)
except Empty:
pass
else:
# if no error was raised just config
# label to new data
label.config(text=data)
finally:
# and simply schedule this function to run again in
# 100 milliseconds (this btw is not recursion)
root.after(100, update_label)
# crucial part is to use this if statement because
# child processes run this whole script again
if __name__ == '__main__':
root = Tk()
# define queue (since it is a global variable now
# it can be easily used in the functions)
queue = Queue()
label = Label(root)
label.pack()
# initially start the update function
update_label()
# just a button for starting the process, but you can also simply
# call the function
Button(root, text='Start', command=start_process).pack()
root.mainloop()
I am not too familiar with opencv so there might be a chance it won't work in a child process (tho I think I have done that), in that case you will have to either use threading or two alternatives using subprocess: put the opencv part in another file and run that file using python interpreter in the subprocess.Popen (won't work for computers that don't have Python installed) or convert that other python file with the opencv part to an .exe file or sth and run that executable file in the subprocess.Popen (both approaches tho are probably unnecessary and you probably will be fine with either multiprocessing or threading)
To use threading instead change these lines:
# from multiprocessing import Process, Queue
from threading import Thread
from queue import Empty, Queue
and
# Process(target=count, args=(queue, ), daemon=True).start()
Thread(target=count, args=(queue, ), daemon=True).start()

How can I preview streaming images in tkinter with Allied Vision camera that uses Vimba SDK?

I want to display images from an Allied Vision camera inside a tkinter frame using OpenCV and the SDK for the camera, VimbaPython.
The only possible way to initialize the camera is with a Python with statement:
with Vimba.get_instance() as vimba:
cams = vimba.get_all_cameras()
with cams[0] as camera:
camera.get_frame()
# Convert frame to opencv image, then use Image.fromarray and ImageTk.PhotoImage to
# display it on the tkinter GUI
Everything works fine so far. But I don't only need a single frame. Instead, I need to continuously get frames and display them on the screen so that it is streaming.
I found that one way to do it is to call the .after(delay, function) method from a tkinter Label widget.
So, after obtaining one frame, I want to call the same function to get a new frame and display it again. The code would look like that:
with Vimba.get_instance() as vimba:
cams = vimba.get_all_cameras()
with cams[0] as camera:
def show_frame():
frame = camera.get_frame()
frame = frame.as_opencv_image()
im = Image.fromarray(frame)
img = Image.PhotoImage(im)
lblVideo.configure(image=img) # this is the Tkinter Label Widget
lblVideo.image = img
show_frame()
lblVideo.after(20, show_frame)
Then this shows the first frame and stops, throwing an error saying that Vimba needs to be initialized with a with statement. I don't know much about Python, but it looks like when I call the function with the .after() method it ends the with statement.
I would like to know if it is possible to execute this show_frame() function without ending the with. Also, I can't initialize the camera every time because the program goes really slow.
Thank you
I know this question is pretty old, but I ran into a similar problem with the Allied Vision cameras and found the solution to be relatively robust. So I hope this helps someone, even if not the OP.
An alternative to using with statements is using __enter__ and __exit__ (see sample here). With this, I created a class for the Vimba camera and during the __init__ I used these functions twice: once to initialize the Vimba instance, and once to open the camera itself. An example as follows...
vimba_handle = Vimba.get_instance().__enter__()
camera = vimba_handle.get_all_cameras()[0].__enter__()
I'll include a longer snippet as code as well, but please note my purpose was slightly different the OP's intent. Hopefully, it is still useful.
class VimbaCam:
def __init__(self, device_id=0):
# Variables
self.current_frame = np.array([])
self.device = None
self.device_id = device_id
self.vimba_handle = Vimba.get_instance().__enter__()
self.is_streaming = False
self.scale_window = 4
self.stream_thread = threading.Thread(target=self.thread_stream, daemon=True)
# Default settings
self.auto_exposure = "Off"
self.auto_gain = "Off"
self.acquisition = "Continuous"
self.exposure_us = 200000
self.fps = 6.763
self.gain = 0
self.gamma = 1
self.open()
def close(self):
if self.device is not None:
if self.is_streaming:
self.stop_stream()
time.sleep(1)
self.device.__exit__(None, None, None)
self.vimba_handle.__exit__(None, None, None)
def open(self):
cams = self.vimba_handle.get_all_cameras()
if not cams:
error_check(151, currentframe())
else:
self.device = cams[self.device_id].__enter__()
self.set_defaults()
self.start_stream()
def start_stream(self):
if self.device is not None:
self.is_streaming = True
self.stream_thread.start()
time.sleep(1)
def thread_stream(self):
while self.is_streaming:
current_frame = self.device.get_frame().as_opencv_image()
h, w, _ = current_frame.shape
self.current_frame = current_frame.reshape((h, w))
self.stream_thread = threading.Thread(target=self.thread_stream, daemon=True)
def stop_stream(self):
if self.device is not None:
self.is_streaming = False
def live_video(self):
if self.device is not None:
window_name = "Allied Vision"
h, w = self.current_frame.shape
w = int(w / self.scale_window)
h = int(h / self.scale_window)
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.resizeWindow(window_name, w, h)
while 1:
cv2.imshow(window_name, self.current_frame)
cv2.waitKey(1)
if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
break
cv2.destroyAllWindows()
def set_defaults(self):
if self.device is not None:
# Exposure time settings
self.device.ExposureAuto.set(self.auto_exposure)
self.device.ExposureTimeAbs.set(self.exposure_us)
# Gain settings
self.device.GainAuto.set(self.auto_gain)
self.device.Gain.set(self.gain)
# Gamma settings
self.device.Gamma.set(self.gamma)
self.device.AcquisitionMode.set(self.acquisition)
self.device.AcquisitionFrameRateAbs.set(self.fps)
# Try to adjust GeV packet size (available for GigE only)
try:
self.device.GVSPAdjustPacketSize.run()
while not self.device.GVSPAdjustPacketSize.is_done():
pass
except (AttributeError, VimbaFeatureError):
pass
# Color formatting (tries mono first, then color)
cv_formats = intersect_pixel_formats(self.device.get_pixel_formats(), OPENCV_PIXEL_FORMATS)
mono_formats = intersect_pixel_formats(cv_formats, MONO_PIXEL_FORMATS)
color_formats = intersect_pixel_formats(cv_formats, COLOR_PIXEL_FORMATS)
if mono_formats:
self.device.set_pixel_format(mono_formats[0])
elif color_formats:
self.device.set_pixel_format(color_formats[0])
if __name__ == "__main__":
dev = VimbaCam()
dev.live_video()
dev.close()
You need to use thread to run the capture code and pass the frames read via queue. Then the main tkinter application reads the queue and show the frames periodically using .after().
Below is an example based on your posted code:
import threading
from queue import SimpleQueue
import tkinter as tk
from PIL import Image, ImageTk
from vimba import Vimba
def camera_streaming(queue):
global is_streaming
is_streaming = True
print("streaming started")
with Vimba.get_instance() as vimba:
with vimba.get_all_cameras()[0] as camera:
while is_streaming:
frame = camera.get_frame()
frame = frame.as_opencv_image()
im = Image.fromarray(frame)
img = ImageTk.PhotoImage(im)
queue.put(img) # put the capture image into queue
print("streaming stopped")
def start_streaming():
start_btn["state"] = "disabled" # disable start button to avoid running the threaded task more than once
stop_btn["state"] = "normal" # enable stop button to allow user to stop the threaded task
show_streaming()
threading.Thread(target=camera_streaming, args=(queue,), daemon=True).start()
def stop_streaming():
global is_streaming, after_id
is_streaming = False # terminate the streaming thread
if after_id:
lblVideo.after_cancel(after_id) # cancel the showing task
after_id = None
stop_btn["state"] = "disabled" # disable stop button
start_btn["state"] = "normal" # enable start button
# periodical task to show frames in queue
def show_streaming():
global after_id
if not queue.empty():
image = queue.get()
lblVideo.config(image=image)
lblVideo.image = image
after_id = lblVideo.after(20, show_streaming)
queue = SimpleQueue() # queue for video frames
after_id = None
root = tk.Tk()
lblVideo = tk.Label(root, image=tk.PhotoImage(), width=640, height=480)
lblVideo.grid(row=0, column=0, columnspan=2)
start_btn = tk.Button(root, text="Start", width=10, command=start_streaming)
start_btn.grid(row=1, column=0)
stop_btn = tk.Button(root, text="Stop", width=10, command=stop_streaming, state="disabled")
stop_btn.grid(row=1, column=1)
root.mainloop()
Note that I don't have the camera and the SDK installed, the above code may not work for you. I just demonstrate how to use thread, queue and .after().
Below is a testing vimba module (saved as vimba.py) I use to simulate VimbaPython module using OpenCV and a webcam:
import cv2
class Frame:
def __init__(self, frame):
self.frame = frame
def as_opencv_image(self):
return self.frame
class Camera:
def __init__(self, cam_id=0):
self.cap = cv2.VideoCapture(cam_id, cv2.CAP_DSHOW)
def __enter__(self):
return self
def __exit__(self, *args):
self.cap.release()
return self
def get_frame(self):
ret, frame = self.cap.read()
if ret:
return Frame(frame)
class Vimba:
_instance = None
#classmethod
def get_instance(self):
if self._instance is None:
self._instance = Vimba()
return self._instance
def __enter__(self):
return self
def __exit__(self, *args):
return self
def get_all_cameras(self):
return (Camera(),)
I tried to read the frames in openCV and display them in tkinter label. I was able to do so using the below code:
import tkinter as tk
import cv2
from PIL import ImageTk, Image
video_path = "SAMPLE/STORED_VIDEO/PATH"
root = tk.Tk()
base_img = Image.open("PATH/TO/DEFAULT/LABLE/IMAGE")
img_obj = ImageTk.PhotoImage(base_img)
lblVideo = tk.Label(root, image=img_obj)
lblVideo.pack()
cap = cv2.VideoCapture(video_path)
if cap.isOpened():
def show_frame():
_, frame = cap.read()
im = Image.fromarray(frame)
img = ImageTk.PhotoImage(im)
lblVideo.configure(image=img)
lblVideo.image = img
lblVideo.after(1, show_frame) # Need to create callback here
show_frame()
root.mainloop()
Although this doesnot contain the with statement, you can try replacing the after() callback inside the show_frame function itself.

tkinter + OpenCV WebCam Very Slow Video Stream

I am using tkinter and opencv for the first time and have successfully built a GUI for my project, however, I cannot figure out why my video stream is updating so extremely slow. I am grabbing frames very quickly but it seems that the update on the screen gets exponentially slower. I am seeing somewhere around 30 seconds of lag when I first launch the program but it eventually slows to a halt. I am connecting to three cameras but only displaying one at a time. The cameras all display and the selection buttons work. My only issue is the display refresh rate.
This is running in Python3.7 on a raspberry pi4. I can connect to the camera via web browser and it appears to have no lag.
I have been searching for answers but cannot seem to find anything that helps. Can anyone offer some help with this?
Here's my program (I have removed unrelated code):
#!/usr/bin/env python3
import time
from tkinter import *
import cv2
from PIL import Image, ImageTk
#GUI
class robotGUI:
def __init__(self):
self.selectedCam = "front"
self.window = Tk()
#Setup the window to fit the Raspberry Pi Touch Display = 800x400 and align top left
self.window.geometry("800x480+0+0")
self.window.overrideredirect(True)
self.window.fullScreenState = False
#Create Frame for Video Window
self.videoFrame = Frame(self.window, relief=SUNKEN, bd=2)
self.videoFrame.place(x=0, y=0, height=457, width=650)
#Create the Video Window
self.video = Label(self.videoFrame, bd=0, relief=FLAT, width=644, height=451)
self.video.place(x=0, y=0)
self.vid = VideoCapture()
self.camUpdateFreq = 250
self.updateCams()
#Create the Button Frame
self.buttonFrame = Frame(self.window, relief=FLAT)
self.buttonFrame.place(x=651, y=0, height=457, width=149)
#Create Buttons
#Select Front Camera Button
self.frontCamButton = Button(self.buttonFrame, text="Front Camera", command=lambda: self.selectCam("front"))
self.frontCamButton.place(x=24, y=50, height=30, width=100)
#Select Boom Camera Button
self.boomCamButton = Button(self.buttonFrame, text="Boom Camera", command=lambda: self.selectCam("boom"))
self.boomCamButton.place(x=24, y=130, height=30, width=100)
#Select Rear Camera Button
self.rearCamButton = Button(self.buttonFrame, text="Rear Camera", command=lambda: self.selectCam("rear"))
self.rearCamButton.place(x=24, y=210, height=30, width=100)
#Close Button
self.exitButton = Button(self.buttonFrame, text="Close", command=self.window.destroy)
self.exitButton.place(x=24, y=400, height=30, width=100)
#Start the main loop for the gui
self.window.mainloop()
def selectCam(self, cam):
if (cam.lower() == "front"):
self.selectedCam = "front"
self.statusBarLeft['text'] = "Front Camera Selected"
elif (cam.lower() == "boom"):
self.selectedCam = "boom"
self.statusBarLeft['text'] = "Boom Camera Selected"
elif (cam.lower() == "rear"):
self.selectedCam = "rear"
self.statusBarLeft['text'] = "Rear Camera Selected"
def updateCams(self):
#Get a frame from the selected camera
ret, frame = self.vid.get_frame(self.selectedCam)
if ret:
imageCV2 = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(imageCV2)
imgPhoto = ImageTk.PhotoImage(image=img)
self.video.imgPhoto = imgPhoto
self.video.configure(image=imgPhoto)
self.window.after(self.camUpdateFreq, self.updateCams)
#Video Camera Class
class VideoCapture:
def __init__(self):
#Define Locals
FrontCameraAddress = "rtsp://admin:password#192.168.5.20:8554/12"
BoomCameraAddress = "rtsp://admin:password#192.168.5.21:8554/12"
RearCameraAddress = "rtsp://admin:password#192.168.5.22:8554/12"
#Open Front Video Camera Source
self.vidFront = cv2.VideoCapture(FrontCameraAddress)
self.vidBoom = cv2.VideoCapture(BoomCameraAddress)
self.vidRear = cv2.VideoCapture(RearCameraAddress)
#Verify that the Camera Streams Opened
if not self.vidFront.isOpened():
raise ValueError("Unable to open video source to Front Camera")
if not self.vidBoom.isOpened():
raise ValueError("Unable to open video source to Boom Camera")
if not self.vidRear.isOpened():
raise ValueError("Unable to open video source to Rear Camera")
#Get One Frame from the Selected Camera
def get_frame(self, camera="front"):
#Attempt to Get Front Camera Frame
if (camera.lower() == "front"):
#If Stream Still Open Return a Frame
if self.vidFront.isOpened():
ret, frame = self.vidFront.read()
if ret:
#Return a boolean success flag and the current frame converted to BGR
return (ret, frame)
else:
return (ret, None)
else:
return (ret, None)
#Attempt to Get Boom Camera Frame
elif (camera.lower() == "boom"):
#If Stream Still Open Return a Frame
if self.vidBoom.isOpened():
ret, frame = self.vidBoom.read()
if ret:
#Return a boolean success flag and the current frame converted to BGR
return (ret, frame)
else:
return (ret, None)
else:
return (ret, None)
#Attempt to Get Rear Camera Frame
elif (camera.lower() == "rear"):
#If Stream Still Open Return a Frame
if self.vidRear.isOpened():
ret, frame = self.vidRear.read()
if ret:
#Return a boolean success flag and the current frame converted to BGR
return (ret, frame)
else:
return (ret, None)
else:
return (ret, None)
else:
return (False, None)
#Release the video sources when the object is destroyed
def __del__(self):
if self.vidFront.isOpened():
self.vidFront.release()
if self.vidBoom.isOpened():
self.vidBoom.release()
if self.vidRear.isOpened():
self.vidRear.release()
#Main Routine - Only run if called from main program instance
if __name__ == '__main__':
try:
#Create GUI Object
app = robotGUI()
except Exception as e:
print("Exception: " + str(e))
finally:
print("Cleaning Up")
NOTE: In this copy of the program, I am updating every 250ms but I have tried smaller numbers down to around 3 but the frames still seem to be behind. Is there a better way to do this?
NOTE2: After working with this more today, I realize that openCV is definitely buffering frames for each camera starting when the cv2.VideoCapture() function is called for each camera. The read() function does seem to be pulling the next frame from the buffer which explains why it is taking so long to update and why the image I see on the screen never catches up to reality. I changed my test code to only connect to one camera at a time and use the cv2.release() function any time I am not actively viewing a camera. This improved things quite a bit. I also set the update function to run every 1ms and I am using the grab() function to grab a frame every cycle but I am only processing and displaying every 10th cycle which has also improved some. I still have some lag that I would love to remove if anyone has any suggestions.
My RTSP stream shows with zero noticeable lag when viewed in a web browser. Does anyone know how I can get the same effect in tkinter? I am not married to openCV.

Play an Animated GIF in python with tkinter

I am wanting to create a virtual pet style game using python3 and tkinter. So far I have the main window and have started putting labels in, but the issue I am having is playing an animated gif. I have searched on here and have found some answers, but they keep throwing errors. The result I found has the index position of the gif using PhotoImage continue through a certain range.
# Loop through the index of the animated gif
frame2 = [PhotoImage(file='images/ball-1.gif', format = 'gif -index %i' %i) for i in range(100)]
def update(ind):
frame = frame2[ind]
ind += 1
img.configure(image=frame)
ms.after(100, update, ind)
img = Label(ms)
img.place(x=250, y=250, anchor="center")
ms.after(0, update, 0)
ms.mainloop()
When I run this in terminal with "pyhton3 main.py" I get the following error:
_tkinter.TclError: no image data for this index
What am I overlooking or completely leaving out?
Here is the link to the GitHub repository to see the full project:VirtPet_Python
Thanks in advance!
The error means that you tried to load 100 frames, but the gif has less than that.
Animated gifs in tkinter are notoriously bad. I wrote this code an age ago that you can steal from, but will get laggy with anything but small gifs:
import tkinter as tk
from PIL import Image, ImageTk
from itertools import count
class ImageLabel(tk.Label):
"""a label that displays images, and plays them if they are gifs"""
def load(self, im):
if isinstance(im, str):
im = Image.open(im)
self.loc = 0
self.frames = []
try:
for i in count(1):
self.frames.append(ImageTk.PhotoImage(im.copy()))
im.seek(i)
except EOFError:
pass
try:
self.delay = im.info['duration']
except:
self.delay = 100
if len(self.frames) == 1:
self.config(image=self.frames[0])
else:
self.next_frame()
def unload(self):
self.config(image="")
self.frames = None
def next_frame(self):
if self.frames:
self.loc += 1
self.loc %= len(self.frames)
self.config(image=self.frames[self.loc])
self.after(self.delay, self.next_frame)
root = tk.Tk()
lbl = ImageLabel(root)
lbl.pack()
lbl.load('ball-1.gif')
root.mainloop()
First of all, you need to know what is the last range of your GIF file. so by changing the different value of i, you will get it.For my condition is 31.
then just need to put the condition.So it will play gif infinitely.
from tkinter import *
import time
import os
root = Tk()
frames = [PhotoImage(file='./images/play.gif',format = 'gif -index %i' %(i)) for i in range(31)]
def update(ind):
frame = frames[ind]
ind += 1
print(ind)
if ind>30: #With this condition it will play gif infinitely
ind = 0
label.configure(image=frame)
root.after(100, update, ind)
label = Label(root)
label.pack()
root.after(0, update, 0)
root.mainloop()
A very simple approach would be to use multithreading.
To run the GIF infinitely in a Tkinter window you should follow the following:
Create a function to run the GIF.
Put your code to run the GIF inside while True inside the function.
Create a thread to run the function.
Run root.mainloop() in the primary flow of the program.
Use time.sleep() to control the speed of your animation.
Refer to my code below:
i=0
ph = ImageTk.PhotoImage(Image.fromarray(imageframes[i]))
imglabel=Label(f2,image=ph)
imglabel.grid(row=0,column=0)
def runthegif(root,i):
while True:
i = i + 7
i= i % 150
ph=ImageTk.PhotoImage(PhotoImage(file='images/ball.gif',format='gif -index %i' %i))
imagelabel=Label(f2,image=ph)
imagelabel.grid(row=0,column=0)
time.sleep(0.1)
t1=threading.Thread(target=runthegif,args=(root,i))
t1.start()
root.mainloop()

Categories

Resources