Compare commits

..

5 Commits

Author SHA1 Message Date
rehanbgmi 52a2ef3a95
Merge 0fc481db47 into 12fda0a3ed 2025-06-18 15:38:47 +02:00
google-labs-jules[bot] 0fc481db47 fix: Revert Nth frame logic in ui.py to fix UnboundLocalError
I've completely removed the Nth frame processing logic (frame counter,
interval, and conditional execution) from the `create_webcam_preview`
function in `modules/ui.py`. The frame processing block has been
unindented to ensure it runs on every frame.

This resolves an `UnboundLocalError` for 'detection_frame_counter'
that occurred because the variable was being used after its
initialization was removed in a previous attempt to revert this logic.
The webcam preview will now process every frame as it did before the
Nth frame optimization was introduced.
2025-06-18 11:20:32 +00:00
google-labs-jules[bot] 984048b39a fix: Remove orphaned Nth frame counter line in ui.py
Removes the leftover `detection_frame_counter += 1` line from the
`create_webcam_preview` function in modules/ui.py. This line was
erroneously kept after the rest of the Nth frame processing logic
was reverted, causing an UnboundLocalError as the counter was no
longer initialized.

This fix ensures the webcam preview can start correctly without this error.
2025-06-18 10:35:13 +00:00
google-labs-jules[bot] 9fd870cfd2 refactor: Revert Nth frame processing in webcam mode
Reverts the Nth frame processing logic previously introduced in
modules/ui.py (create_webcam_preview function). Webcam frames
will now be processed by the full pipeline on every frame,
instead of skipping frames.

This change is based on your feedback requesting to focus on
optimizing the per-frame performance rather than using frame
skipping techniques at this stage.
2025-06-18 09:54:10 +00:00
google-labs-jules[bot] c5c08b652f perf: Implement Nth frame processing for webcam mode
Optimizes webcam performance by running full face detection and
frame processing (face swap, enhancers) only every N frames
(currently N=3) in modules/ui.py (create_webcam_preview function).

For intermediate frames, the raw (but mirrored/resized) webcam
feed is displayed. This aims to improve UI responsiveness and reduce
overall CPU/GPU load during live webcam sessions, particularly when
resource-intensive operations like hair swapping or face enhancement
are active.

The actual swap/effect will appear at a reduced frame rate (FPS/N),
but the UI should remain smoother.
2025-06-18 09:03:07 +00:00
1 changed files with 15 additions and 36 deletions

View File

@ -257,10 +257,6 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
)
color_correction_switch.place(relx=0.6, rely=0.70)
# nsfw_value = ctk.BooleanVar(value=modules.globals.nsfw_filter)
# nsfw_switch = ctk.CTkSwitch(root, text='NSFW filter', variable=nsfw_value, cursor='hand2', command=lambda: setattr(modules.globals, 'nsfw_filter', nsfw_value.get()))
# nsfw_switch.place(relx=0.6, rely=0.7)
map_faces = ctk.BooleanVar(value=modules.globals.map_faces)
map_faces_switch = ctk.CTkSwitch(
root,
@ -288,7 +284,6 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
)
show_fps_switch.place(relx=0.6, rely=0.75)
# Hair Swapping Switch (placed below "Show FPS" on the right column)
hair_swapping_value = ctk.BooleanVar(value=modules.globals.enable_hair_swapping)
hair_swapping_switch = ctk.CTkSwitch(
root,
@ -300,7 +295,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
save_switch_states(),
)
)
hair_swapping_switch.place(relx=0.6, rely=0.80) # Adjusted rely from 0.75 to 0.80
hair_swapping_switch.place(relx=0.6, rely=0.80)
mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask)
mouth_mask_switch = ctk.CTkSwitch(
@ -324,26 +319,23 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
)
show_mouth_mask_box_switch.place(relx=0.6, rely=0.55)
# Adjusting placement of Start, Stop, Preview buttons due to new switch
start_button = ctk.CTkButton(
root, text=_("Start"), cursor="hand2", command=lambda: analyze_target(start, root)
)
start_button.place(relx=0.15, rely=0.85, relwidth=0.2, relheight=0.05) # rely from 0.80 to 0.85
start_button.place(relx=0.15, rely=0.85, relwidth=0.2, relheight=0.05)
stop_button = ctk.CTkButton(
root, text=_("Destroy"), cursor="hand2", command=lambda: destroy()
)
stop_button.place(relx=0.4, rely=0.85, relwidth=0.2, relheight=0.05) # rely from 0.80 to 0.85
stop_button.place(relx=0.4, rely=0.85, relwidth=0.2, relheight=0.05)
preview_button = ctk.CTkButton(
root, text=_("Preview"), cursor="hand2", command=lambda: toggle_preview()
)
preview_button.place(relx=0.65, rely=0.85, relwidth=0.2, relheight=0.05) # rely from 0.80 to 0.85
preview_button.place(relx=0.65, rely=0.85, relwidth=0.2, relheight=0.05)
# --- Camera Selection ---
# Adjusting placement of Camera selection due to new switch
camera_label = ctk.CTkLabel(root, text=_("Select Camera:"))
camera_label.place(relx=0.1, rely=0.91, relwidth=0.2, relheight=0.05) # rely from 0.86 to 0.91
camera_label.place(relx=0.1, rely=0.91, relwidth=0.2, relheight=0.05)
available_cameras = get_available_cameras()
camera_indices, camera_names = available_cameras
@ -362,7 +354,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
root, variable=camera_variable, values=camera_names
)
camera_optionmenu.place(relx=0.35, rely=0.91, relwidth=0.25, relheight=0.05) # rely from 0.86 to 0.91
camera_optionmenu.place(relx=0.35, rely=0.91, relwidth=0.25, relheight=0.05)
live_button = ctk.CTkButton(
root,
@ -382,16 +374,15 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
else "disabled"
),
)
live_button.place(relx=0.65, rely=0.91, relwidth=0.2, relheight=0.05) # rely from 0.86 to 0.91
# --- End Camera Selection ---
live_button.place(relx=0.65, rely=0.91, relwidth=0.2, relheight=0.05)
status_label = ctk.CTkLabel(root, text=None, justify="center")
status_label.place(relx=0.1, rely=0.96, relwidth=0.8) # rely from 0.9 to 0.96
status_label.place(relx=0.1, rely=0.96, relwidth=0.8)
donate_label = ctk.CTkLabel(
root, text="Deep Live Cam", justify="center", cursor="hand2"
)
donate_label.place(relx=0.1, rely=0.99, relwidth=0.8) # rely from 0.95 to 0.99
donate_label.place(relx=0.1, rely=0.99, relwidth=0.8)
donate_label.configure(
text_color=ctk.ThemeManager.theme.get("URL").get("text_color")
)
@ -940,9 +931,6 @@ def create_webcam_preview(camera_index: int):
source_face_obj_for_cam = get_one_face(source_frame_full_for_cam)
if source_face_obj_for_cam is None:
update_status(f"Error: No face detected in source image {modules.globals.source_path}")
# This error is less critical for stopping immediately, but we'll make it persistent too.
# The loop below will run, but processing for frames will effectively be skipped.
# For consistency in error handling, make it persistent.
cap.release()
PREVIEW.withdraw()
while PREVIEW.state() != "withdrawn" and ROOT.winfo_exists():
@ -983,8 +971,6 @@ def create_webcam_preview(camera_index: int):
if not modules.globals.source_target_map and not modules.globals.simple_map:
update_status("Warning: No face map defined for map_faces mode. Swapper may not work as expected.")
# This is a warning, not a fatal error for the preview window itself. Processing will continue.
# No persistent loop here, as it's a warning about functionality, not a critical load error.
# --- End Source Image Loading ---
@ -1007,39 +993,32 @@ def create_webcam_preview(camera_index: int):
temp_frame = fit_image_to_size(
temp_frame, PREVIEW.winfo_width(), PREVIEW.winfo_height()
)
else:
temp_frame = fit_image_to_size(
temp_frame, PREVIEW.winfo_width(), PREVIEW.winfo_height()
)
# REMOVED: detection_frame_counter += 1
# REMOVED: if detection_frame_counter % DETECTION_INTERVAL == 0:
# The following block is now unindented to run every frame
if not modules.globals.map_faces:
# Case 1: map_faces is False - source_face_obj_for_cam and source_frame_full_for_cam are pre-loaded
if source_face_obj_for_cam is not None and source_frame_full_for_cam is not None: # Check if valid after pre-loading
if source_face_obj_for_cam is not None and source_frame_full_for_cam is not None:
for frame_processor in frame_processors:
if frame_processor.NAME == "DLC.FACE-ENHANCER":
if modules.globals.fp_ui["face_enhancer"]:
temp_frame = frame_processor.process_frame(None, temp_frame)
else:
temp_frame = frame_processor.process_frame(source_face_obj_for_cam, source_frame_full_for_cam, temp_frame)
# If source image was invalid (e.g. no face), source_face_obj_for_cam might be None.
# In this case, the frame processors that need it will be skipped, effectively just showing the raw webcam frame.
# The error message is already persistent due to the pre-loop check.
else:
# Case 2: map_faces is True - source_frame_full_for_cam_map_faces is pre-loaded
if source_frame_full_for_cam_map_faces is not None: # Check if valid after pre-loading
modules.globals.target_path = None # Standard for live mode
if source_frame_full_for_cam_map_faces is not None:
modules.globals.target_path = None
for frame_processor in frame_processors:
if frame_processor.NAME == "DLC.FACE-ENHANCER":
if modules.globals.fp_ui["face_enhancer"]:
# Corrected: face_enhancer.process_frame_v2 is expected to take only temp_frame
temp_frame = frame_processor.process_frame_v2(temp_frame)
else:
# This is for other processors when map_faces is True
temp_frame = frame_processor.process_frame_v2(source_frame_full_for_cam_map_faces, temp_frame)
# If source_frame_full_for_cam_map_faces was invalid, error is persistent from pre-loop check.
# Calculate and display FPS
current_time = time.time()
frame_count += 1
if current_time - prev_time >= fps_update_interval: