import cv2 import numpy as np import time from tkinter import * from tkinter import ttk from PIL import Image, ImageTk from opencv_utils import OpenCVUtils class MainWindow: def __init__(self, root: Tk) -> None: self.root = root self.font = ("Arial", 12, "bold") self.font_small = ("Arial", 10, "bold") self.colors = { "yellow": "#FDCE01", "black": "#1E1E1E", "white": "#FEFEFE", } self.congig_interface() self.root.bind("", self.close_application) self.functions = [] self.aplication = OpenCVUtils() self.fps_avg_frame_count = 30 self.COUNTER, self.FPS = 0, 0 self.START_TIME = time.time() # For optical flow self.prev_gray = None def close_application(self, event) -> None: """ Close the application :param event: The event that triggered the function """ # Libera a webcam e destrói todas as janelas do OpenCV self.cap.release() cv2.destroyAllWindows() self.root.destroy() def congig_interface(self) -> None: self.root.geometry("1500x1000") self.root.title("OpenCV + Tkinter") self.root.config(bg=self.colors["black"]) self.paned_window = PanedWindow(self.root, orient=HORIZONTAL) self.paned_window.pack(fill=BOTH, expand=1) # Cria a barra lateral com os sliders self.sidebar = Frame( self.paned_window, width=700, bg=self.colors["black"], background=self.colors["black"], padx=10, pady=10, ) self.paned_window.add(self.sidebar) # Create a scrollbar for the sidebar canvas = Canvas(self.sidebar, bg=self.colors["black"], highlightthickness=0) scrollbar = Scrollbar(self.sidebar, orient="vertical", command=canvas.yview) scrollable_frame = Frame( canvas, bg=self.colors["black"], ) scrollable_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # Cria as trackbars self.color_filter_var = IntVar() self.color_filter_var.trace_add( "write", lambda *args: self.add_function( self.aplication.apply_color_filter, self.color_filter_var ), ) Checkbutton( scrollable_frame, text="Color Filter", variable=self.color_filter_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() self.lower_hue = Scale( scrollable_frame, from_=0, to=180, orient=HORIZONTAL, label="Lower Hue", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.lower_hue.pack(anchor="center") self.upper_hue = Scale( scrollable_frame, from_=0, to=180, orient=HORIZONTAL, label="Upper Hue", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.upper_hue.pack(anchor="center") self.lower_saturation = Scale( scrollable_frame, from_=0, to=255, orient=HORIZONTAL, label="Lower Sat", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.lower_saturation.pack(anchor="center") self.upper_saturation = Scale( scrollable_frame, from_=0, to=255, orient=HORIZONTAL, label="Upper Sat", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.upper_saturation.pack(anchor="center") self.lower_value = Scale( scrollable_frame, from_=0, to=255, orient=HORIZONTAL, label="Lower Value", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.lower_value.pack(anchor="center") self.upper_value = Scale( scrollable_frame, from_=0, to=255, orient=HORIZONTAL, label="Upper Value", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.upper_value.pack(anchor="center") ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.canny_var = IntVar() self.canny_var.trace_add( "write", lambda *args: self.add_function( self.aplication.apply_edge_detection, self.canny_var ), ) Checkbutton( scrollable_frame, text="Canny", variable=self.canny_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() self.lower_canny = Scale( scrollable_frame, from_=0, to=255, orient=HORIZONTAL, label="Lower Canny", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.lower_canny.pack(anchor="center") self.upper_canny = Scale( scrollable_frame, from_=0, to=255, orient=HORIZONTAL, label="Upper Canny", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.upper_canny.pack(anchor="center") ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.blur_var = IntVar() self.blur_var.trace_add( "write", lambda *args: self.add_function(self.aplication.blur_image, self.blur_var), ) Checkbutton( scrollable_frame, text="Blur", variable=self.blur_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack(anchor="center") self.blur = Scale( scrollable_frame, from_=1, to=15, orient=HORIZONTAL, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.blur.pack(anchor="center") ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.rotation_var = IntVar() self.rotation_var.trace_add( "write", lambda *args: self.add_function( self.aplication.rotate_image, self.rotation_var ), ) Checkbutton( scrollable_frame, text="Rotation", variable=self.rotation_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack(anchor="center") self.rotation_angle = Scale( scrollable_frame, from_=0, to=360, orient=HORIZONTAL, label="Rotation Angle", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.rotation_angle.pack(anchor="center") ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.resize_var = IntVar() self.resize_var.trace_add( "write", lambda *args: self.add_function( self.aplication.resize_image, self.resize_var ), ) Checkbutton( scrollable_frame, text="Resize", variable=self.resize_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() Label( scrollable_frame, text="Height", bg=self.colors["black"], fg=self.colors["white"], ).pack() self.height = Scale( scrollable_frame, from_=100, to=1080, orient=HORIZONTAL, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.height.pack(anchor="center") self.width = Scale( scrollable_frame, from_=100, to=1920, orient=HORIZONTAL, label="Width", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.width.pack(anchor="center") ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.contour_var = IntVar() self.contour_var.trace_add( "write", lambda *args: self.add_function( self.aplication.apply_contour_detection, self.contour_var ), ) Checkbutton( scrollable_frame, text="Contour", variable=self.contour_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() # Add new OpenCV functions ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.hist_equal_var = IntVar() self.hist_equal_var.trace_add( "write", lambda *args: self.add_function( self.aplication.equalize_histogram, self.hist_equal_var ), ) Checkbutton( scrollable_frame, text="Histogram Equalization", variable=self.hist_equal_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.adaptive_threshold_var = IntVar() self.adaptive_threshold_var.trace_add( "write", lambda *args: self.add_function( self.aplication.adaptive_threshold, self.adaptive_threshold_var ), ) Checkbutton( scrollable_frame, text="Adaptive Threshold", variable=self.adaptive_threshold_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.morphology_var = IntVar() self.morphology_var.trace_add( "write", lambda *args: self.add_function( self.aplication.morphology, self.morphology_var ), ) Checkbutton( scrollable_frame, text="Morphology", variable=self.morphology_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() # Morphology operation options self.morph_op_var = StringVar(value="erode") Label( scrollable_frame, text="Operation", bg=self.colors["black"], fg=self.colors["white"], ).pack() for op in ["erode", "dilate", "open", "close"]: Radiobutton( scrollable_frame, text=op.capitalize(), variable=self.morph_op_var, value=op, bg=self.colors["black"], fg=self.colors["white"], selectcolor=self.colors["black"], highlightbackground=self.colors["black"], ).pack(anchor="w") self.morph_kernel_size = Scale( scrollable_frame, from_=1, to=31, orient=HORIZONTAL, label="Kernel Size", bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], ) self.morph_kernel_size.set(5) self.morph_kernel_size.pack(anchor="center") ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.sharpen_var = IntVar() self.sharpen_var.trace_add( "write", lambda *args: self.add_function(self.aplication.sharpen, self.sharpen_var), ) Checkbutton( scrollable_frame, text="Sharpen", variable=self.sharpen_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.hough_lines_var = IntVar() self.hough_lines_var.trace_add( "write", lambda *args: self.add_function( self.aplication.hough_lines, self.hough_lines_var ), ) Checkbutton( scrollable_frame, text="Hough Lines", variable=self.hough_lines_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.optical_flow_var = IntVar() self.optical_flow_var.trace_add( "write", lambda *args: self.add_function( self.process_optical_flow, self.optical_flow_var ), ) Checkbutton( scrollable_frame, text="Optical Flow", variable=self.optical_flow_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.pencil_sketch_var = IntVar() self.pencil_sketch_var.trace_add( "write", lambda *args: self.add_function( self.aplication.pencil_sketch, self.pencil_sketch_var ), ) Checkbutton( scrollable_frame, text="Pencil Sketch", variable=self.pencil_sketch_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.color_quantization_var = IntVar() self.color_quantization_var.trace_add( "write", lambda *args: self.add_function( self.aplication.color_quantization, self.color_quantization_var ), ) Checkbutton( scrollable_frame, text="Color Quantization", variable=self.color_quantization_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.hand_tracker_var = IntVar() self.hand_tracker_var.trace_add( "write", lambda *args: self.add_function( self.aplication.detect_hands, self.hand_tracker_var ), ) Checkbutton( scrollable_frame, text="Hand Tracker", variable=self.hand_tracker_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() ttk.Separator(scrollable_frame, orient=HORIZONTAL).pack(fill=X, padx=3, pady=3) self.face_tracker_var = IntVar() self.face_tracker_var.trace_add( "write", lambda *args: self.add_function( self.aplication.detect_faces, self.face_tracker_var ), ) Checkbutton( scrollable_frame, text="Face Tracker", variable=self.face_tracker_var, font=self.font, bg=self.colors["black"], fg=self.colors["white"], highlightbackground=self.colors["black"], selectcolor=self.colors["black"], ).pack() # Cria o label para exibir a imagem self.image_label = Label(self.paned_window, bg=self.colors["black"]) self.paned_window.add(self.image_label) def add_function(self, function: callable, var: IntVar) -> None: """ Add or remove a function from the list of functions to be applied to the image :param function: The function to be added or removed :param var: The variable that controls the function """ if var.get() == 1: self.functions.append(function) else: self.functions.remove(function) def process_optical_flow(self, frame: np.ndarray) -> np.ndarray: """ Special handler for optical flow which needs to track previous frames :param frame: The current frame :return: The processed frame with optical flow """ curr_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) if self.prev_gray is not None: frame = self.aplication.optical_flow(self.prev_gray, curr_gray, frame) self.prev_gray = curr_gray return frame def process_image(self, frame: np.ndarray) -> np.ndarray: """ Process the image with the functions selected by the user :param frame: The image to be processed :return: The processed image """ function_dict = { self.aplication.apply_color_filter: [ ( self.lower_hue.get(), self.lower_saturation.get(), self.lower_value.get(), ), ( self.upper_hue.get(), self.upper_saturation.get(), self.upper_value.get(), ), ], self.aplication.apply_edge_detection: [ self.lower_canny.get(), self.upper_canny.get(), ], self.aplication.blur_image: [self.blur.get()], self.aplication.rotate_image: [self.rotation_angle.get()], self.aplication.resize_image: [self.width.get(), self.height.get()], self.aplication.morphology: [ self.morph_op_var.get(), self.morph_kernel_size.get(), ], } for function in self.functions: args = function_dict.get(function, []) frame = function(frame, *args) return frame def run(self) -> None: """ Run the main loop of the tkinter application """ # Abre a webcam self.cap = cv2.VideoCapture(0) self.START_TIME = time.time() while True: # Lê um frame da webcam ret, frame = self.cap.read() if not ret: break # Aplica as funções do OpenCV frame = self.process_image(frame) output = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) if self.COUNTER % self.fps_avg_frame_count == 0: self.FPS = self.fps_avg_frame_count / (time.time() - self.START_TIME) self.START_TIME = time.time() self.COUNTER += 1 # Show the FPS fps_text = "FPS = {:.1f}".format(self.FPS) cv2.putText( output, fps_text, (24, 30), cv2.FONT_HERSHEY_DUPLEX, 1, (0, 0, 0), 1, cv2.LINE_AA, ) # Converte a imagem NumPy para uma imagem PIL pil_image = Image.fromarray(output) # Converte a imagem PIL para uma imagem Tkinter tk_image = ImageTk.PhotoImage(pil_image) # Exibe a imagem no label self.image_label.config(image=tk_image) self.image_label.image = tk_image # Atualiza a janela tkinter self.root.update() cv2.waitKey(1) def main(): # Cria a janela principal root = Tk() main_window = MainWindow(root) main_window.run() if __name__ == "__main__": main()