Implementation of object detection in Tkinter GUI

Hi,
I want to implement the object detection in a Tkinter GUI on my Raspberry. For that I wrote following code. The GUI alone works just fine, but I have a problem to implement the code for the classification/detection. In the method “stream()” I tried to get back the “img” (with “yield”) from the for-loop for every iteration and display it on my GUI with “display()”, before a new Iteration starts… Without the yield-command the for-loop/classification starts, but of course doesn’t stop. With the yield-command, the hole function “stream()” doesn’t run at all.

Can anyone tell me what I do wrong or can help how I can solve that problem?

Thanks in advance!

Jonas

> #!/usr/bin/env python
> 
> import cv2
> import os
> import tkinter as tk
> from tkinter import ttk
> from PIL import Image
> from PIL import ImageTk
> from edge_impulse_linux.image import ImageImpulseRunner
> 
> class Application(tk.Frame):
> 	def __init__(self, master=None):
> 		super().__init__(master)
> 		self.master = master
> 		
> 		# Set up GUI window
> 		master.title("traffic sign detection")
> 		#master.attributes("-fullscreen", True)
> 		master.config(background="grey")
> 		self.subframe()
> 		
> 	def get_modelfile(self):
> 		model = "/home/pi/sign_detection/modelfile.eim"
> 		dir_path = os.path.dirname(os.path.realpath(__file__))
> 		self.modelfile = os.path.join(dir_path, model)  
> 		print('MODEL: ' + self.modelfile)
> 		
> 		# Load runner
> 		self.load_runner()
> 		
> 	def subframe(self):
> 		#Set up image frame
> 		self.imageFrame = tk.Frame(self.master, width=480, height=480)
> 		self.imageFrame.grid(row=0, column=0, padx=10, pady=2)
> 		self.create_widgets()
> 		
> 	def create_widgets(self):
> 		# Exit-Button widget 
> 		self.exit = tk.Button(self.master, text="EXIT", bg="red", padx=50, pady=50, command=self.master.destroy)
> 		self.exit.grid(row=0, column=1, padx=10, pady=2)
> 		
> 		# Display widget
> 		self.stream_label = tk.Label(self.imageFrame, text="traffic sign detection")
> 		self.stream_label.grid(row=0, column=0)
> 		
> 		# Load model
> 		self.get_modelfile()
> 		
> 	def load_runner(self):
> 		with ImageImpulseRunner(self.modelfile) as runner:
> 			self.runner = runner
> 			model_info = runner.init()
> 			print('Loaded runner for "' + model_info['project']['owner'] + ' / ' + model_info['project']['name'] + '"')
> 			labels = model_info['model_parameters']['labels']
> 			self.display()
> 
> 	def stream(self):	
> 		for res, img in self.runner.classifier(0):
> 			if "bounding_boxes" in res["result"].keys():
> 				print('Found %d bounding boxes (%d ms.)' % (len(res["result"]["bounding_boxes"]), res['timing']['dsp'] + res['timing']['classification']))
> 				for bb in res["result"]["bounding_boxes"]:
> 					val_bb = int(bb['value']*100)
> 					label_bb = bb['label']
> 					print('\t%s (%.2f): x=%d y=%d w=%d h=%d' % (bb['label'], bb['value'], bb['x'], bb['y'], bb['width'], bb['height']))
> 					img = cv2.rectangle(img, (bb['x'], bb['y']), (bb['x'] + bb['width'], bb['y'] + bb['height']), (255, 0, 0), 1)
> 					cv2.putText(img, '%s %s%%' % (label_bb, str(val_bb)), (bb['x'], bb['y']-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,0,0), 2)
> 					img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
> 					yield img
> 											
> 	def display(self):
> 		self.stream()
> 		classifier = self.stream()
> 		img = next(classifier)
> 		self.stream_resized = Image.fromarray(img).resize((320, 320))
> 		self.streamtk= ImageTk.PhotoImage(image=self.stream_resized)				
> 		self.stream_label.streamtk = self.streamtk
> 		self.stream_label.configure(image=self.streamtk)
> 		self.master.after(10, self.display)
> 
> def main():
> 	# Opens main window
> 	window = tk.Tk()
> 	GUI = Application(master=window)
> 	window.mainloop()
> 
> if __name__ == "__main__":
>    main()

Hello @HTWG_Elite,

I checked with Edge Impulse DevRel team and unfortunately we are not very familiar with Tkinter GUI. Maybe someone else from the community has tried that already?
In the meantime, if you managed to solve your issue, do not hesitate to explain here how you did it, I’m sure it can interest other people in the future.

Regards,

Louis

Hi, thanks for the answer. I solved the problem and came up with following Code.

#!/usr/bin/env python

import cv2
import os
import time
import tkinter as tk
from tkinter import ttk
from PIL import Image
from PIL import ImageTk
from edge_impulse_linux.image import ImageImpulseRunner

class Application(tk.Frame):
	def __init__(self, master=None):
		super().__init__(master)
		self.master = master
		
		# Set up GUI window
		master.title("traffic sign detection")
		master.attributes("-fullscreen", True)
		master.config(background="grey")
		self.subframe()
		
	def get_modelfile(self):
		model = "/home/pi/sign_detection/modelfile2.eim"
		dir_path = os.path.dirname(os.path.realpath(__file__))
		self.modelfile = os.path.join(dir_path, model)  
		print('MODEL: ' + self.modelfile)
		
		# Load runner
		self.load_runner()
		
	def subframe(self):
		#Set up image frame
		self.imageFrame = tk.Frame(self.master, width=480, height=480)
		self.imageFrame.grid(row=0, column=0, rowspan=4, padx=10, pady=2)
		self.create_widgets()
		
	def create_widgets(self):
		# Exit-Button widget 
		self.exit = tk.Button(self.master, text="EXIT", font=("Helvetica", 15), bg="red", padx=50, pady=50, command=self.master.destroy)
		self.exit.grid(row=3, column=1, padx=20, pady=10)
		
		# Display widget
		self.stream_label = tk.Label(self.imageFrame, text="traffic sign detection")
		self.stream_label.grid(row=0, column=0)
		
		# Label 1 widget
		self.label1 = tk.Label(self.master, text="Schilderkennung", font=("Helvetica", 25), fg="white", bg="grey")
		self.label1.grid(row=0, column=1, padx=20, pady=2, sticky="w")
		
		# Label 2 widget
		self.label2 = tk.Label(self.master, text=" - Stop\n - Vorfahrt\n - Vorfahrt gewähren\n - Höchstgeschwindigkeit 30km/h ", font=("Helvetica", 12), fg="white", bg="grey", justify="left")
		self.label2.grid(row=1, column=1, padx=20, pady=2, sticky="w")
		
		# FPS-Label widget
		self.fps_label = tk.Label(self.master)
		self.fps_label.grid(row=2, column=1, padx=20, pady=2, sticky="w")
		
		# Load model
		self.get_modelfile()
		
	def load_runner(self):
		with ImageImpulseRunner(self.modelfile) as runner:
			self.runner = runner
			model_info = runner.init()
			print('Loaded runner for "' + model_info['project']['owner'] + ' / ' + model_info['project']['name'] + '"')
			labels = model_info['model_parameters']['labels']
			self.classifier = self.runner.classifier(0)
			self.stream()

	def stream(self):
		new_frame = 0
		prev_frame = 0
		while True:
			classifier_output = next(self.classifier)
			res, img = classifier_output
			
			for bb in res["result"]["bounding_boxes"]:
				val_bb = int(bb['value']*100)
				label_bb = bb['label']
				img = cv2.rectangle(img, (bb['x'], bb['y']), (bb['x'] + bb['width'], bb['y'] + bb['height']), (255, 0, 0), 1)
				cv2.putText(img, '%s %s%%' % (label_bb, str(val_bb)), (bb['x'], bb['y']+15), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,0,0), 2)
			
			new_frame = time.time()
			fps = (1/(new_frame-prev_frame))
			prev_frame = new_frame
			
			self.stream_resized = Image.fromarray(img).resize((470, 470))
			self.streamtk= ImageTk.PhotoImage(image=self.stream_resized)				
			self.stream_label.streamtk = self.streamtk
			self.stream_label.configure(image=self.streamtk)
			self.fps_label.configure(text="FPS: " + str(fps)[:4], font=("Helvetica", 12), fg="orange", bg="grey")
			self.update = self.master.update()

def main():
	# Opens main window
	window = tk.Tk()
	GUI = Application(master=window)
	window.mainloop()

if __name__ == "__main__":
   main()

The problem was the “.after()-method” in my privious code.

self.master.after(10, self.display)

The “.after()-method” is a common option to loop a single function. The problem is, that it has to be in the main-thread to work. For that reason I tried to split the stream and the display parts in two seperate functions, to get the .after() out of the for-loop. Unfortunately this approach, together with the yield-command didn’t work.

The solution is to use the .update()-method.

self.update = self.master.update()

This method can be used inside the loop of the stream-function and the implementation is much easier.

I also replaced the for-loop of the “classifier” with a while-loop for a bit higher framerate.

for res, img in self.runner.classifier(0):

… replaced with:

while True:
     classifier_output = next(self.classifier)
     res, img = classifier_output

The code opens a GUI with the object detection (in this case to detect traffic signs), an exit-button, a label that shows the current framerate and two Text labels.

1 Like