210 lines
9.2 KiB
Python
210 lines
9.2 KiB
Python
|
|
"""
|
|
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()
|