Compare commits

...

4 Commits

Author SHA1 Message Date
google-labs-jules[bot] 44ef1fdcac criticalfix: Correct IndentationError in create_lower_mouth_mask
Replaces the create_lower_mouth_mask function in
modules/processors/frame/face_swapper.py with a version that has
corrected indentation. This resolves an "IndentationError: unexpected indent"
that was preventing the application from starting.

The replaced block also includes minor robustness improvements for
ROI calculations and Gaussian blur kernel sizes within this function
and implicitly updated other related utility functions that were part of the
provided code block.
2025-06-18 20:43:53 +00:00
google-labs-jules[bot] 8a03fccb59 fix: Resolve circular import between core and face_swapper
Refactors the usage of the update_status function to break a
circular import dependency.

- In modules/processors/frame/face_swapper.py:
  - Removed direct import of update_status from modules.core.
  - Modified pre_start(), process_image(), and process_video()
    to accept update_status as a Callable parameter
    (status_fn_callback).
  - Internal calls now use this passed callback.
- In modules/core.py:
  - Updated the calls to pre_start(), process_image(), and
    process_video() for frame processors (specifically face_swapper)
    to pass the core.update_status function as the
    status_fn_callback argument.

This change ensures that face_swapper.py no longer needs to import
modules.core directly for status updates, resolving the ImportError.
2025-06-18 16:53:21 +00:00
google-labs-jules[bot] d7139d5c6e fix: Correct IndentationError and type hint in create_lower_mouth_mask
I resolved an IndentationError in the create_lower_mouth_mask function
in modules/processors/frame/face_swapper.py by correcting the
indentation of the lower_lip_order list definition and the subsequent
try-except block.

Additionally, I updated the function's return type hint to use
typing.Tuple and typing.Optional for Python 3.9+ compatibility.

This fixes a crash that prevented your application from running.
2025-06-18 16:34:46 +00:00
google-labs-jules[bot] 4e36622a47 feat: Implement Optical Flow KPS tracking for webcam performance
Introduces Nth-frame full face detection combined with KCF bounding
box tracking and Lucas-Kanade (LK) optical flow for keypoint (KPS)
tracking on intermediate frames. This is primarily for single-face
webcam mode to improve performance while maintaining per-frame swaps.

Key Changes:
- Modified `face_swapper.py` (`process_frame`):
    - Full `insightface.FaceAnalysis` runs every N frames (default 5)
      or if tracking is lost.
    - KCF tracker updates bounding box on intermediate frames.
    - Optical flow (`cv2.calcOpticalFlowPyrLK`) tracks the 5 keypoints
      from the previous frame to the current intermediate frame.
    - A `Face` object is constructed with tracked bbox and KPS for
      swapping on intermediate frames (detailed landmarks like
      `landmark_2d_106` are None for these).
    - Experimental similar logic added to `_process_live_target_v2`
      for `map_faces=True` live mode (non-many_faces path).
- Robustness:
    - Mouth masking and face mask creation functions in `face_swapper.py`
      now handle cases where `landmark_2d_106` is `None` (e.g., by
      skipping mouth mask or using bbox for face mask).
    - Added division-by-zero check in `apply_color_transfer`.
- State Management:
    - Introduced `reset_tracker_state()` in `face_swapper.py` to clear
      all tracking-related global variables.
    - `ui.py` now calls `reset_tracker_state()` at appropriate points
      (webcam start, mode changes, new source image selection) to ensure
      clean tracking for new sessions.
- `DETECTION_INTERVAL` in `face_swapper.py` increased to 5.

This aims to provide you with a smoother face swap experience with better FPS
by reducing the frequency of expensive full face analysis, while the
actual swap operation continues on every frame using tracked data.
2025-06-18 16:16:52 +00:00
4 changed files with 487 additions and 519 deletions

View File

@ -176,9 +176,12 @@ def update_status(message: str, scope: str = 'DLC.CORE') -> None:
ui.update_status(message)
def start() -> None:
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
if not frame_processor.pre_start():
return
# Note: pre_start is called in run() before start() now.
# If it were to be called here, it would also need the status_fn_callback.
# For example:
# for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
# if not frame_processor.pre_start(status_fn_callback=update_status): # If pre_start was here
# return
update_status('Processing...')
# process image to image
if has_image_extension(modules.globals.target_path):
@ -190,7 +193,7 @@ def start() -> None:
print("Error copying file:", str(e))
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
update_status('Progressing...', frame_processor.NAME)
frame_processor.process_image(modules.globals.source_path, modules.globals.output_path, modules.globals.output_path)
frame_processor.process_image(modules.globals.source_path, modules.globals.output_path, modules.globals.output_path, status_fn_callback=update_status)
release_resources()
if is_image(modules.globals.target_path):
update_status('Processing to image succeed!')
@ -210,7 +213,7 @@ def start() -> None:
temp_frame_paths = get_temp_frame_paths(modules.globals.target_path)
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
update_status('Progressing...', frame_processor.NAME)
frame_processor.process_video(modules.globals.source_path, temp_frame_paths)
frame_processor.process_video(modules.globals.source_path, temp_frame_paths, status_fn_callback=update_status)
release_resources()
# handles fps
if modules.globals.keep_fps:
@ -249,7 +252,9 @@ def run() -> None:
if not pre_check():
return
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
if not frame_processor.pre_check():
if not frame_processor.pre_check(): # pre_check in face_swapper does not use update_status
return
if hasattr(frame_processor, 'pre_start') and not frame_processor.pre_start(status_fn_callback=update_status): # Pass callback here
return
limit_resources()
if modules.globals.headless:

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ from modules.face_analyser import (
)
from modules.capturer import get_video_frame, get_video_frame_total
from modules.processors.frame.core import get_frame_processors_modules
from modules.processors.frame.face_swapper import reset_tracker_state # Added import
from modules.utilities import (
is_image,
is_video,
@ -240,6 +241,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
command=lambda: (
setattr(modules.globals, "many_faces", many_faces_value.get()),
save_switch_states(),
reset_tracker_state() # Added reset call
),
)
many_faces_switch.place(relx=0.6, rely=0.65)
@ -266,7 +268,8 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
command=lambda: (
setattr(modules.globals, "map_faces", map_faces.get()),
save_switch_states(),
close_mapper_window() if not map_faces.get() else None
close_mapper_window() if not map_faces.get() else None,
reset_tracker_state() # Added reset call
),
)
map_faces_switch.place(relx=0.1, rely=0.75)
@ -604,9 +607,11 @@ def select_source_path() -> None:
RECENT_DIRECTORY_SOURCE = os.path.dirname(modules.globals.source_path)
image = render_image_preview(modules.globals.source_path, (200, 200))
source_label.configure(image=image)
reset_tracker_state() # Added reset call
else:
modules.globals.source_path = None
source_label.configure(image=None)
reset_tracker_state() # Added reset call even if source is cleared
def swap_faces_paths() -> None:
@ -979,6 +984,8 @@ def create_webcam_preview(camera_index: int):
frame_count = 0
fps = 0
reset_tracker_state() # Ensure tracker is reset before starting webcam loop
while True:
ret, frame = cap.read()
if not ret:

View File

@ -2,7 +2,7 @@
numpy>=1.23.5,<2
typing-extensions>=4.8.0
opencv-python==4.10.0.84
opencv-contrib-python==4.10.0.84
cv2_enumerate_cameras==1.1.15
onnx==1.16.0
insightface==0.7.3