I currently write a Python program that is used to control a camera in a laboratory. I use tkinter for the GUI and the label that displays the image of the camera (10 FPS, but could be changed) changes to its background color for a fraction of a second while the new image is given to it. The occurance of the flickering does not happen at a set frequency and doesn't change with FPS. It flickers between 3 and 5 times per second. Since the window holds additional GUI elements that have to be accessible during the live stream, I gave the task of refreshing the image to a different thread. There are 3-4 parallel threads for similar tasks running, depending on the state of the program. Down below there are some relevant parts of the code but I can't give you a real MRE since the software for the camera is missing:
import tkinter as tk
from tkinter import *
from tkinter import ttk
import threading
import time
import imagePreprocessing
#Thread function to update the displayed picture according to the FPS defined above
def liveFeedThread():
global timestamp, labelImageFeed
while True:
currentTime = time.perf_counter_ns() #gets current time in nano seconds
if currentTime - timestamp > 1000000000/fps: #one second/fps -> time between pictures
image = imagePreprocessing.takeImage() #different module controlling the camera, images are always 640x480
timestamp = time.perf_counter_ns() #update timestamp before processing picture to ensure timing
labelImageFeed['image'] = image
else:
time.sleep(1E-2)
#Window
window = tk.Tk()
window.resizable(False, False)
window.geometry('640x580') #640x480 for downscaled livestream and 100px height for window components
frameTop = ttk.Frame(window, width=640, height=480)
frameTop.pack(side=TOP, fill=BOTH)
labelImageFeed = tk.Label(frameTop, width=640, height=480, bg='black')
labelImageFeed.pack(expand=True, fill=BOTH)
frameBottom = ttk.Frame(window, width=640, height=100, padding=10)
frameBottom.pack(side=BOTTOM, fill=BOTH)
frameBottom.columnconfigure(0, weight=10)
frameBottom.columnconfigure(2, weight=10)
frameBottom.columnconfigure(4, weight=10)
frameBottom.columnconfigure(6, weight=10)
frameBottom.rowconfigure(0, weight=10)
frameBottom.rowconfigure(1, weight=10)
frameBottom.rowconfigure(2, weight=10)
#more code for the GUI elements in the bottom frame is left out of this example
#Live-feed
liveFeed = threading.Thread(target=liveFeedThread, daemon=True)
liveFeed.start()
#more code for different threads is also left out
window.mainloop()
I searched for the problem and found someone telling me that tkinter doesn't like threading. Since I can't let all tasks run in the main thread, I hope it is not that. I tried to save the picture in a global variable to ensure it is not only saved in the label. I changed the label to a canvas. I used different FPS. I have a different, older program that uses the same camera but doesn't have any other GUI elements. In this program there are no flickers, so I am pretty sure the problem is about something I don't know about layout managing of tkinter or Python in general since I normally code in Java/C++.
I think we can solve the problem with "queue" and "threading" modules. Once I modified your code arbitrarily. First, import the required modules.
import time
import tkinter as tk
from tkinter import ttk
from threading import Thread
from queue import Queue
import imagePreprocessing
And create gui elements and variables.(convert to class)
class CamWindow(tk.Tk):
def __init__(self):
super().__init__()
self.resizable(False, False)
self.geometry('640x580')
self.frameTop = ttk.Frame(self, width=640, height=480)
self.frameTop.pack(side=tk.TOP, fill=tk.BOTH)
self.labelImageFeed = tk.Label(self.frameTop, width=640, height=480, bg='black')
self.labelImageFeed.pack(expand=True, fill=tk.BOTH)
self.frameBottom = ttk.Frame(self, width=640, height=100, padding=10)
self.frameBottom.pack(side=tk.BOTTOM, fill=tk.BOTH)
self.frameBottom.columnconfigure(0, weight=10)
self.frameBottom.columnconfigure(2, weight=10)
self.frameBottom.columnconfigure(4, weight=10)
self.frameBottom.columnconfigure(6, weight=10)
self.frameBottom.rowconfigure(0, weight=10)
self.frameBottom.rowconfigure(1, weight=10)
self.frameBottom.rowconfigure(2, weight=10)
self.timestamp = time.perf_counter_ns()
self.fps = 10
self.camImage = Queue()
self.t1 = Thread(target=self.liveFeedThread)
self.t1.start()
self.image_update()
Here, we determine the initial values of self.timestamp and self.fps, and declare a queue called self.camImage.
And now, we define a function to put the processed image into a queue and a function to update the queued image to the GUI.
def liveFeedThread(self):
while True:
currentTime = time.perf_counter_ns() # gets current time in nano seconds
if currentTime - self.timestamp > 1000000000 / self.fps: # one second/fps -> time between pictures
# image = tk.PhotoImage(file='screenshot.png')
image = imagePreprocessing.takeImage() # different module controlling the camera, images are always 640x480
self.camImage.put(image)
self.timestamp = time.perf_counter_ns() # update timestamp before processing picture to ensure timing
else:
time.sleep(1E-2)
def image_update(self):
try:
self.labelImageFeed['image'] = self.camImage.get_nowait()
except:
pass
self.after(1, self.image_update)
and,
if __name__ == "__main__":
app = CamWindow()
app.mainloop()
I'm not sure since I haven't seen the imagePreprocessing module, but I hope this solves your problem.