0

I am currently doing a machine learning project and I am trying to make a basic Tkinter UI that calls function from a file that performs gesture recognition.

Currently I have a button on my GUI that calls for the 'main' function in the gesture recognition file, every time I click that, it does run the function but then the GUI becomes unresponsive so I can't click anything else. I'm wondering if this is to do with nothing being returned perhaps? which is stopping the ui mainloop.

The functionality i'm aiming to build is for the UI to run the 'main function' from Camera.py (i'll link below) , and then have a button assigned to each of the three options from that main function "press 1 to train the model, 2 to detect hand gesture" etc. Rather than typing them in manually.

My current UI code is

import numpy as np
import matplotlib as plt
from sys import exit
from tkinter import *
from tkinter import ttk
import tkinter.simpledialog as sd
import camera as cam

modelobject = cam.Yolo_Model()

def runTraining():
    modelobject.main()

def getUserInput():
    options = {'minvalue':1, 'maxvalue':4}
    userInput = sd.askinteger('User Input', 'Select an Option', **options)
    print((f'You entered {userInput}'))

window=Tk()
window.title("Gesture Recognition")
window.geometry('600x350')

btn1 = ttk.Button(window, text="Train the Model", command=lambda: runTraining())
btn1.grid(column=0, row=0)


btn2 = ttk.Button(window, text="Detect Hand Gesture", command=lambda: getUserInput())
btn2.grid(column=1, row=0)


btn3 = ttk.Button(window, text="Clear Images Stored From Training")
btn3.grid(column=2, row=0)

def close_window():
    exit(0)
    window.destroy()

exitbtn = ttk.Button(window, text="Exit")
exitbtn.grid(column=0, row=4)
exitbtn.config(command=close_window)

window.mainloop()

The code for the file I want to run and call functions from within the UI is

import numpy as np
import os
from numpy import expand_dims
import cv2
from trainer import predicter
from keras.preprocessing.image import img_to_array
import time
from im_to_txt import m
from random import random
import shutil


class Yolo_Model:
    # Sets the intial parameters used within the definition of the class.
    def __init__(self):
        # Enables the video capture
        self.cap = cv2.VideoCapture(0)
        # Sets the temporary directory used for the captured frame
        self.filename = "img.jpg"
        # Sets the destination of the training directory
        self.train_dir = "new_training"
        try:
            os.mkdir(self.train_dir)
        except:
            print("directory exists")

    def func(self, curr):
        boo = ""
        image = cv2.imread(self.filename)
        os.remove(self.filename)
        print("Enter 'T' if the label is correct or 'F' is the label is incorrect!")
        boo = input(curr[0] + "\n")
        try:
            if boo == 'T':
                name = str(random()) + '.jpg'
                name = os.path.join(self.train_dir, name)
                try:
                    cv2.imwrite(name, image)
                except:
                    print("Could not save the image to " + str(name))
                m(name, curr)
            image = cv2
        except ValueError as e:
            print(e)
            print("Value not recognised")

    def model_detector(self):
        while (self.cap.isOpened()):
            ret, frame = self.cap.read()
            if ret == True:
                # time.sleep(1)
                frame = cv2.flip(frame, +1)
                cv2.imshow('window', frame)
                try:
                    cv2.imwrite(self.filename, frame)
                except:
                    print("Could not save the image to img.jpg")
                output = predicter(0.6, self.filename)

                os.remove(self.filename)
                # os.remove(photo_filename)
                while (len(output) >= 5):
                    curr = output[0:6]
                    output = output[6:]
                    label = curr[0]
                    try:
                        image = cv2.rectangle(frame, (curr[1], curr[4]), (curr[3], curr[2]), (0, 0, 255), 3)
                        image = cv2.putText(frame, label, (curr[1], (curr[2] - 10)), cv2.FONT_HERSHEY_SIMPLEX, 1,
                                            (0, 255, 0), 2)
                    except:
                        print("Could not annote the image. Please ensure the co-ordinates are correct")
                    cv2.imshow('window', image)

            # Used to detstroy the image capture window
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            else:
                break
                # This function is used to train the system. It detects a gesture being shown by the user and applies

    # a label. It then prompts the user, asking if the label identified was correct. If yes, the yolo format
    # text file is created, needed for training, and the image is saved into the training directory. The user
    # is also shown the detected image with the label and bounding box.
    def model_trainer(self):
        # Constant loop for capturing frames for the webcam.
        while (True):
            # Captures the image and frame number.
            ret, frame = self.cap.read()
            if ret == True:
                # Additional sleep timer can be used for waiting in between capturing frames. Turned
                # off for real time detection.
                # time.sleep(1)
                # Flips the frame so it appears the right way for the user.
                frame = cv2.flip(frame, +1)
                # Shows the user the frame that has been captured by the camera. This is constantly
                # updated each time a new frame is captured.
                cv2.imshow('window', frame)
                # Try and except block used incase the system cannot write the captured frame. The frame
                # is saved so it can be used by the prediction file.
                try:
                    cv2.imwrite(self.filename, frame)
                except:
                    print("Could not save the image to img.jpg")
                # The predict method from trainer.py is called. This will return the prediction of the captured
                # image along with the the parameters needed for the boudnding box and prediction score. The parameter
                # 0.6 corrosponds to threshold of how confident the model is about the label applied. Any lower and the
                # prediction will be discounted. 0.6 is lower than normal to allow for additonal classifcation to be made
                # during training.
                output = predicter(0.6, self.filename)
                # If the length of the array returned from the model prediction is greater than 5,i.e. a prediction was made
                # then the first 6 values, corrosponding to the length of one prediction, will be upacked until there are no more
                # predictions from that frame.
                while (len(output) >= 5):
                    # The first 6 values corrosponding to the first prediction are extracted
                    curr = output[0:6]
                    output = output[6:]
                    # The predicted label is the first value within the returned array
                    label = curr[0]
                    # A try block is used incase the given parameters for the bounding box are incorrect or cannot be labeled
                    # within the image.
                    try:
                        image = cv2.rectangle(frame, (curr[1], curr[4]), (curr[3], curr[2]), (0, 0, 255), 3)
                        image = cv2.putText(frame, label, (curr[1], (curr[2] - 10)), cv2.FONT_HERSHEY_SIMPLEX, 1,
                                            (0, 255, 0), 2)
                    except:
                        print("Could not annote the image. Please ensure the co-ordinates are correct")
                    cv2.imshow('window', image)
                    # This tunction is called to check if the predicted label was correct and if so use the image for training.
                    self.func(curr)
                    # Used to destroy the image capture window
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
            else:
                break

    # Adds additional ethics and security in removing the training directory
    # from the system.
    def removeFiles(self):
        # Try and except used incase the training directory could not be located.
        try:
            # Removes the training directory.
            shutil.rmtree(self.train_dir, ignore_errors=True)
        # Throws an OS error as the file cannot be found, printing user instructions on the error.
        except OSError as e:
            print("Error: ")
            print(e)

    # The main menu for the user to select the options for the system. This menu calls the functions
    # used for training, detecting, and removing stored images.
    def main(self):
        menu = input(
            "Press 1 to train the model: \nWarning training will store images of you!!\nPress 2 to detect the hand gesture: \nPress 3 to remove any images stored during training\n")
        if menu == '1':
            while (True):
                self.model_trainer()
        elif menu == '2':
            while (True):
                self.model_detector()
        elif menu == '3':
            self.removeFiles()
        # The final else statement catches the program should a unexpected value be entered.
        else:
            print("Error. Correct option not selected")


# Automatically calls the main method within the Yolo_Model class
if __name__ == "__main__":
    model = Yolo_Model()
    # Call the main method
    model.main()
    print("Closing application")
    # Try and except should the camera not have been used within the system.
    try:
        # Stop the frame capture
        cap.release()
        # Destroy the all the CV2 windows
        cv2.destroyAllWindows()
    except:
        print("Image capture not used")

To clarify, when running the UI, the window pops up just fine, then as soon as I click the button that is currently assigned to run 'main' from camera.py, it runs but the UI becomes unresponsive, so I have to complete the process from the console, which is what i'm trying to replace with UI functionality.

I hope this is clear, and there is an obvious fix that i'm missing, as i'm not very experienced. Cheers

desertnaut
  • 57,590
  • 26
  • 140
  • 166
  • It is possible that the crashing comes down due to system limitations – Thomas Apr 16 '20 at 15:47
  • First you have to understand [Event-driven_programming](https://en.m.wikipedia.org/wiki/Event-driven_programming). Read through [`[tkinter] event driven programming`](https://stackoverflow.com/search?q=is%3Aanswer+%5Btkinter%5D+event+driven+programming+entry) – stovfl Apr 16 '20 at 16:02
  • The button calls a function with an infinite loop. As long as that loop is running, tkinter can't respond to events. Not only that, but that function itself seems to have an infinite loop, and at least one path where it waits for a key to be pressed. You should do more debugging before posting a question, such as adding some print statements to see what the last statement to get executed is before the GUI seems to crash. – Bryan Oakley Apr 16 '20 at 16:12

1 Answers1

0

Your UI will be as long unresponsive as long as your runTraining() function is running. You have to run your long running task in a separate Thread or Process. If you want to change UI elements from your long running task you have to set-up a Queue which is polled in your main loop and can change the UI in context of the main loop.

Here is an example: http://zetcode.com/articles/tkinterlongruntask/

See also Python Tkinter: How do I make my GUI responsive as long as a thread runs? (there are some links to related questions in the comments)

And here is an example which uses PySimpleGui instead of TKinter directly: https://github.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Multithreaded_Long_Tasks.py

rtrrtr
  • 563
  • 5
  • 7