""" Create a Python program to build a Tkinter-based GUI named "Yiking" for controlling OpenCV parameters. The GUI must meet the following requirements: 1. **General Layout**: - **Root Window**: - Background color: Black. - Window title: "Yiking". - **Widgets Layout**: - **Row 0**: - A dropdown (`ttk.Combobox`) to select the camera, with the background and field background set to black and text in gray. - **Row 1**: - Left column: A group of sliders controlling OpenCV parameters, each with a black background, black borders, and gray text. - Right column: A canvas to display images, with a black background and no border. - **Row 2**: - Left column: A "Run" button styled with a black background, gray text, no border, and no highlights. - Right column: A text box for displaying results or error messages, with a black background, gray text, no border, and gray insertion point color. 2. **Widgets Behavior**: - **Camera Dropdown**: - Detects up to 5 connected cameras using OpenCV. - Lists detected cameras as "Camera 0", "Camera 1", etc. - Default selection is the first detected camera. - **Sliders**: - Sliders for the following OpenCV parameters: - `minDist`: Range (0, 500), default 100. - `param1`: Range (0, 500), default 30. - `param2`: Range (0, 400), default 25. - `minRadius`: Range (0, 100), default 5. - `maxRadius`: Range (0, 1000), default 1000. - `color1_R_min`, `color1_R_max`, `color1_V_min`, `color1_V_max`, `color1_B_min`, `color1_B_max`: Range (0, 64), default 5. - Each slider synchronizes with a text input field for precise value entry. - Sliders must use a black background, black trough color, no border, and gray text. - **Run Button**: - Triggers a `run_process` function. - Styled with a black background, gray text, no border, and no highlights. - **Image Display Canvas**: - Displays an image returned by the `process_frame` function. - Rescales images to fit within 1024x768 pixels, preserving the aspect ratio. - **Result Text Box**: - Displays the results or error messages. - Styled with a black background, gray text, no border, and gray insertion point color. 3. **Implementation Details**: - **Functionality**: - The `run_process` function: - Collects current slider values. - Retrieves the selected camera ID. - Passes the values to a `process_frame` function (assume this function exists in a separate `process.py` file). - Handles exceptions and displays error messages in the result text box. - The `process_frame` function is expected to return an OpenCV image (NumPy array) and a result string. - **Styling**: - Use `ttk.Style` for consistent dropdown styling. - Replace `ttk` widgets with `tk` widgets where custom styling is required (e.g., sliders, buttons, text input). 4. **Output**: - Provide a complete Python script, including imports, the `OpenCVInterface` class, and the `__main__` block to run the GUI. """ import tkinter as tk from tkinter import ttk from process import process_frame from PIL import Image, ImageTk # Required for displaying images import cv2 # OpenCV for numpy array to image conversion class OpenCVInterface: def __init__(self, root): self.root = root self.root.title("Yiking") # Set black background color self.root.configure(bg="black") # Variables for sliders with min, max, and default values self.variables_config = { "minDist": (0, 500, 100), "param1": (0, 500, 30), "param2": (0, 400, 25), "minRadius": (0, 100, 5), "maxRadius": (0, 1000, 1000), "color1_R_min": (0, 64, 5), "color1_R_max": (0, 64, 5), "color1_V_min": (0, 64, 5), "color1_V_max": (0, 64, 5), "color1_B_min": (0, 64, 5), "color1_B_max": (0, 64, 5), } self.variables = { name: tk.IntVar(value=config[2]) for name, config in self.variables_config.items() } # GUI Layout self.setup_gui() def setup_gui(self): # Root grid layout self.root.rowconfigure(1, weight=1) self.root.columnconfigure(0, weight=1) self.root.columnconfigure(1, weight=1) # Dropdown for camera selection camera_frame = tk.Frame(self.root, bg="black") camera_frame.grid(row=0, column=0, sticky="nsew") self.camera_selection = tk.StringVar() self.camera_dropdown = ttk.Combobox(camera_frame, textvariable=self.camera_selection) style = ttk.Style() style.configure("TCombobox", fieldbackground="black", background="black", foreground="gray") self.camera_dropdown.grid(row=0, column=0, padx=5, pady=5) self.populate_camera_dropdown() # Left Column: Sliders left_frame = tk.Frame(self.root, bg="black") left_frame.grid(row=1, column=0, sticky="nswe") for var_name, var in self.variables.items(): min_val, max_val, _ = self.variables_config[var_name] self.create_slider(left_frame, var_name, var, min_val, max_val) # Right Column: Image Placeholder self.image_canvas = tk.Canvas(self.root, bg="black", width=1024, height=768, highlightthickness=0) self.image_canvas.grid(row=0, column=1, rowspan=2, sticky="nswe") # Bottom Row: Run Button and Result run_button = tk.Button(self.root, text="Run", command=self.run_process, bg="black", fg="gray", activebackground="gray", activeforeground="gray", bd=0, highlightthickness=0) run_button.grid(row=2, column=0, sticky="we") self.result_text = tk.Text(self.root, height=5, width=40, bg="black", fg="gray", bd=0, highlightthickness=0, insertbackground="gray") self.result_text.grid(row=2, column=1, sticky="nswe") def populate_camera_dropdown(self): # Detect connected cameras using OpenCV cameras = [] for i in range(5): # Check first 5 indexes for cameras cap = cv2.VideoCapture(i) if cap.read()[0]: cameras.append(f"Camera {i}") cap.release() self.camera_dropdown["values"] = cameras if cameras: self.camera_dropdown.current(0) def create_slider(self, parent, name, variable, min_val, max_val): frame = tk.Frame(parent, bg="black") frame.pack(fill="x", padx=5, pady=2) # Label label = tk.Label(frame, text=name, bg="black", fg="gray") label.pack(side="left") def on_slide(value): # Round value to nearest multiple of 5 rounded_value = round(float(value) / 5) * 5 variable.set(int(rounded_value)) # Update the variable with the rounded value # Slider slider = tk.Scale( frame, from_=min_val, to=max_val, variable=variable, orient="horizontal", bg="black", fg="gray", troughcolor="black", highlightthickness=0, bd=0) slider.pack(side="left", fill="x", expand=True, padx=5) # Entry box entry = tk.Entry(frame, textvariable=variable, width=5, bg="black", fg="gray", insertbackground="gray", bd=0, highlightthickness=0) entry.pack(side="left") def run_process(self): # Collect slider values parameters = {key: var.get() for key, var in self.variables.items()} try: # Get selected camera ID cam_id = int(self.camera_selection.get().split()[-1]) # Call process function image, result_text = process_frame(parameters, cam_id=cam_id) # Convert OpenCV image (numpy array) to PIL Image for Tkinter display if image is not None: image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # Convert BGR to RGB pil_image = Image.fromarray(image) # Convert numpy array to PIL Image # Rescale image to fit within 1024x768 while preserving aspect ratio max_width, max_height = 1024, 768 original_width, original_height = pil_image.size aspect_ratio = min(max_width / original_width, max_height / original_height) new_width = int(original_width * aspect_ratio) new_height = int(original_height * aspect_ratio) pil_image = pil_image.resize((new_width, new_height), Image.Resampling.LANCZOS) tk_image = ImageTk.PhotoImage(pil_image) # Convert PIL Image to Tkinter Image # Clear canvas and display the image self.image_canvas.delete("all") self.image_canvas.create_image(512, 384, image=tk_image, anchor="center") self.image_canvas.image = tk_image # Keep a reference to prevent garbage collection # Update result text self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, result_text) except Exception as exc: # Handle and display exceptions self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, str(exc)) self.image_canvas.delete("all") if __name__ == "__main__": root = tk.Tk() app = OpenCVInterface(root) root.mainloop()