From 95742c8fd5dbb87ec4f5dd93a6e2fce8ede89f71 Mon Sep 17 00:00:00 2001 From: Kenneth Estanislao Date: Fri, 4 Oct 2024 14:41:22 +0800 Subject: [PATCH] Merge pull request #686 from GhoulBoii/main BOUNTY - Webcam Merged (tested) --- modules/globals.py | 3 +- modules/ui.py | 147 +++++++++++++++++++++++++++++++++++---------- requirements.txt | 1 + 3 files changed, 118 insertions(+), 33 deletions(-) diff --git a/modules/globals.py b/modules/globals.py index 16ed2b5..f1bed22 100644 --- a/modules/globals.py +++ b/modules/globals.py @@ -34,4 +34,5 @@ headless = None log_level = 'error' fp_ui: Dict[str, bool] = {} camera_input_combobox = None -webcam_preview_running = False \ No newline at end of file +webcam_preview_running = False +opacity = 100 diff --git a/modules/ui.py b/modules/ui.py index ec2210a..7750c40 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -3,6 +3,7 @@ import webbrowser import customtkinter as ctk from typing import Callable, Tuple import cv2 +from cv2_enumerate_cameras import enumerate_cameras from PIL import Image, ImageOps import tkinterdnd2 as tkdnd import time @@ -25,6 +26,7 @@ from modules.utilities import ( has_image_extension, ) +modules.globals.face_opacity = 100 os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" os.environ["QT_SCREEN_SCALE_FACTORS"] = "1" os.environ["QT_SCALE_FACTOR"] = "1" @@ -402,11 +404,33 @@ def create_root( ) preview_button.pack(side="left", padx=10, expand=True) + # --- Camera Selection --- + camera_label = ctk.CTkLabel(root, text="Select Camera:") + camera_label.place(relx=0.4, rely=0.86, relwidth=0.2, relheight=0.05) + available_cameras = get_available_cameras() + # Convert camera indices to strings for CTkOptionMenu + available_camera_indices, available_camera_strings = available_cameras + camera_variable = ctk.StringVar( + value=available_camera_strings[0] + if available_camera_strings + else "No cameras found" + ) + camera_optionmenu = ctk.CTkOptionMenu( + root, variable=camera_variable, values=available_camera_strings + ) + camera_optionmenu.place(relx=0.65, rely=0.86, relwidth=0.2, relheight=0.05) + # --- End Camera Selection --- + live_button = ModernButton( button_frame, text="Live", cursor="hand2", - command=lambda: webcam_preview(root), + command=lambda: webcam_preview( + root, + available_camera_indices[ + available_camera_strings.index(camera_variable.get()) + ], + ), ) live_button.pack(side="left", padx=10, expand=True) @@ -454,6 +478,29 @@ def create_root( ) remove_donate_button.pack(side="right", padx=(10, 0)) + # Add opacity slider + opacity_frame = ctk.CTkFrame(options_column, fg_color="#2a2d2e") + opacity_frame.pack(pady=5, anchor="w", fill="x") + + opacity_label = ctk.CTkLabel( + opacity_frame, text="Face Opacity:", font=("Roboto", 14, "bold") + ) + opacity_label.pack(side="left", padx=(0, 10)) + + opacity_slider = ctk.CTkSlider( + opacity_frame, + from_=0, + to=100, + number_of_steps=100, + command=update_opacity, + fg_color=("gray75", "gray25"), + progress_color="#3a7ebf", + button_color="#3a7ebf", + button_hover_color="#2b5d8b", + ) + opacity_slider.pack(side="left", fill="x", expand=True) + opacity_slider.set(modules.globals.face_opacity) + main_frame.grid_columnconfigure((0, 2), weight=1) main_frame.grid_rowconfigure((0, 1, 2), weight=1) @@ -959,75 +1006,111 @@ def update_preview(frame_number: int = 0) -> None: PREVIEW.deiconify() -def webcam_preview(root: ctk.CTk): +def webcam_preview(root: ctk.CTk, camera_index: int): if not modules.globals.map_faces: if modules.globals.source_path is None: # No image selected return - create_webcam_preview() + create_webcam_preview(camera_index) else: modules.globals.souce_target_map = [] create_source_target_popup_for_webcam(root, modules.globals.souce_target_map) -def create_webcam_preview(): +def get_available_cameras(): + """Returns a list of available camera names and indices.""" + camera_indices = [] + camera_names = [] + + for camera in enumerate_cameras(): + cap = cv2.VideoCapture(camera.index) + if cap.isOpened(): + camera_indices.append(camera.index) + camera_names.append(camera.name) + cap.release() + return (camera_indices, camera_names) + + +# Add this function to update the opacity value +def update_opacity(value): + modules.globals.face_opacity = int(value) + + +# Modify the create_webcam_preview function to include the slider +def create_webcam_preview(camera_index): global preview_label, PREVIEW - camera = cv2.VideoCapture( - 0 - ) # Use index for the webcam (adjust the index accordingly if necessary) - camera.set( - cv2.CAP_PROP_FRAME_WIDTH, PREVIEW_DEFAULT_WIDTH - ) # Set the width of the resolution - camera.set( - cv2.CAP_PROP_FRAME_HEIGHT, PREVIEW_DEFAULT_HEIGHT - ) # Set the height of the resolution - camera.set(cv2.CAP_PROP_FPS, 60) # Set the frame rate of the webcam + camera = cv2.VideoCapture(camera_index) + if not camera.isOpened(): + update_status(f"Error: Could not open camera with index {camera_index}") + return + camera.set(cv2.CAP_PROP_FRAME_WIDTH, PREVIEW_DEFAULT_WIDTH) + camera.set(cv2.CAP_PROP_FRAME_HEIGHT, PREVIEW_DEFAULT_HEIGHT) + camera.set(cv2.CAP_PROP_FPS, 60) - preview_label.configure( - width=PREVIEW_DEFAULT_WIDTH, height=PREVIEW_DEFAULT_HEIGHT - ) # Reset the preview image before startup + PREVIEW.deiconify() - PREVIEW.deiconify() # Open preview window + # Remove any existing widgets in PREVIEW window + for widget in PREVIEW.winfo_children(): + widget.destroy() + + # Create a main frame to hold all widgets + main_frame = ctk.CTkFrame(PREVIEW) + main_frame.pack(fill="both", expand=True) + + # Create a frame for the preview label + preview_frame = ctk.CTkFrame(main_frame) + preview_frame.pack(fill="both", expand=True, padx=10, pady=10) + + preview_label = ctk.CTkLabel(preview_frame, text="") + preview_label.pack(fill="both", expand=True) frame_processors = get_frame_processors_modules(modules.globals.frame_processors) - source_image = None # Initialize variable for the selected face image + source_image = None + + def update_frame_size(event): + nonlocal temp_frame + if modules.globals.live_resizable: + temp_frame = fit_image_to_size(temp_frame, event.width, event.height) + + preview_frame.bind("", update_frame_size) while camera: ret, frame = camera.read() if not ret: break - temp_frame = frame.copy() # Create a copy of the frame + temp_frame = frame.copy() if modules.globals.live_mirror: - temp_frame = cv2.flip(temp_frame, 1) # horizontal flipping - - if modules.globals.live_resizable: - temp_frame = fit_image_to_size( - temp_frame, PREVIEW.winfo_width(), PREVIEW.winfo_height() - ) + temp_frame = cv2.flip(temp_frame, 1) if not modules.globals.map_faces: - # Select and save face image only once if source_image is None and modules.globals.source_path: source_image = get_one_face(cv2.imread(modules.globals.source_path)) + original_frame = temp_frame.copy() for frame_processor in frame_processors: temp_frame = frame_processor.process_frame(source_image, temp_frame) + + # Apply opacity + opacity = modules.globals.face_opacity / 100 + temp_frame = cv2.addWeighted( + temp_frame, opacity, original_frame, 1 - opacity, 0 + ) else: modules.globals.target_path = None for frame_processor in frame_processors: temp_frame = frame_processor.process_frame_v2(temp_frame) - image = cv2.cvtColor( - temp_frame, cv2.COLOR_BGR2RGB - ) # Convert the image to RGB format to display it with Tkinter + image = cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB) image = Image.fromarray(image) image = ImageOps.contain( - image, (temp_frame.shape[1], temp_frame.shape[0]), Image.LANCZOS + image, + (preview_frame.winfo_width(), preview_frame.winfo_height()), + Image.LANCZOS, ) image = ctk.CTkImage(image, size=image.size) preview_label.configure(image=image) @@ -1037,7 +1120,7 @@ def create_webcam_preview(): break camera.release() - PREVIEW.withdraw() # Close preview window when loop is finished + PREVIEW.withdraw() def create_source_target_popup_for_webcam(root: ctk.CTk, map: list) -> None: diff --git a/requirements.txt b/requirements.txt index 34731f8..970ef97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ numpy>=1.23.5,<2 opencv-python==4.8.1.78 +cv2_enumerate_cameras==1.1.15 onnx==1.16.0 insightface==0.7.3 psutil==5.9.8