Some edits on the UI
- Grouped the face enhancers - Make the mouth mask just a slider - Removed the redundant switches
This commit is contained in:
@@ -28,7 +28,7 @@ def get_face_analyser() -> Any:
|
|||||||
FACE_ANALYSER = insightface.app.FaceAnalysis(
|
FACE_ANALYSER = insightface.app.FaceAnalysis(
|
||||||
name='buffalo_l',
|
name='buffalo_l',
|
||||||
providers=modules.globals.execution_providers,
|
providers=modules.globals.execution_providers,
|
||||||
allowed_modules=['detection', 'recognition']
|
allowed_modules=['detection', 'recognition', 'landmark_2d_106']
|
||||||
)
|
)
|
||||||
FACE_ANALYSER.prepare(ctx_id=0, det_size=(320, 320))
|
FACE_ANALYSER.prepare(ctx_id=0, det_size=(320, 320))
|
||||||
return FACE_ANALYSER
|
return FACE_ANALYSER
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ show_mouth_mask_box: bool = False # Visualize the mouth mask area (for debuggin
|
|||||||
mask_feather_ratio: int = 12 # Denominator for feathering calculation (higher = smaller feather)
|
mask_feather_ratio: int = 12 # Denominator for feathering calculation (higher = smaller feather)
|
||||||
mask_down_size: float = 0.1 # Expansion factor for lower lip mask (relative)
|
mask_down_size: float = 0.1 # Expansion factor for lower lip mask (relative)
|
||||||
mask_size: float = 1.0 # Expansion factor for upper lip mask (relative)
|
mask_size: float = 1.0 # Expansion factor for upper lip mask (relative)
|
||||||
|
mouth_mask_size: float = 0.0 # Mouth mask size (0-100; 0=off, 100=mouth to chin)
|
||||||
|
|
||||||
# --- START: Added for Frame Interpolation ---
|
# --- START: Added for Frame Interpolation ---
|
||||||
enable_interpolation: bool = True # Toggle temporal smoothing
|
enable_interpolation: bool = True # Toggle temporal smoothing
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ def create_lower_mouth_mask(
|
|||||||
|
|
||||||
landmarks = face.landmark_2d_106
|
landmarks = face.landmark_2d_106
|
||||||
if landmarks is not None:
|
if landmarks is not None:
|
||||||
# Use outer mouth landmarks (52-63) to capture the lips only
|
# Use outer mouth landmarks (52-71) to capture the full mouth area
|
||||||
lower_lip_order = list(range(52, 64))
|
lower_lip_order = list(range(52, 72))
|
||||||
|
|
||||||
if max(lower_lip_order) >= landmarks.shape[0]:
|
if max(lower_lip_order) >= landmarks.shape[0]:
|
||||||
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
||||||
@@ -94,13 +94,16 @@ def create_lower_mouth_mask(
|
|||||||
center = np.mean(lower_lip_landmarks, axis=0)
|
center = np.mean(lower_lip_landmarks, axis=0)
|
||||||
|
|
||||||
# Expand the landmarks outward using the mouth_mask_size
|
# Expand the landmarks outward using the mouth_mask_size
|
||||||
# Use a more conservative expansion to avoid affecting face shape
|
mouth_mask_size = getattr(modules.globals, "mouth_mask_size", 0.0) # 0-100 slider
|
||||||
expansion_factor = (
|
expansion_factor = 1 + (mouth_mask_size / 100.0) * 2.5
|
||||||
1 + modules.globals.mask_down_size * modules.globals.mouth_mask_size
|
|
||||||
)
|
|
||||||
expanded_landmarks = (lower_lip_landmarks - center) * expansion_factor + center
|
|
||||||
|
|
||||||
# Removed specific top/chin extensions to preserve face shape
|
# Expand with extra downward bias toward chin
|
||||||
|
offsets = lower_lip_landmarks - center
|
||||||
|
chin_bias = 1 + (mouth_mask_size / 100.0) * 1.5
|
||||||
|
scale_y = np.where(offsets[:, 1] > 0, expansion_factor * chin_bias, expansion_factor)
|
||||||
|
expanded_landmarks = lower_lip_landmarks.copy()
|
||||||
|
expanded_landmarks[:, 0] = center[0] + offsets[:, 0] * expansion_factor
|
||||||
|
expanded_landmarks[:, 1] = center[1] + offsets[:, 1] * scale_y
|
||||||
|
|
||||||
# Convert back to integer coordinates
|
# Convert back to integer coordinates
|
||||||
expanded_landmarks = expanded_landmarks.astype(np.int32)
|
expanded_landmarks = expanded_landmarks.astype(np.int32)
|
||||||
|
|||||||
@@ -136,10 +136,12 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
|
|||||||
if not hasattr(source_face, 'normed_embedding') or source_face.normed_embedding is None:
|
if not hasattr(source_face, 'normed_embedding') or source_face.normed_embedding is None:
|
||||||
return temp_frame
|
return temp_frame
|
||||||
|
|
||||||
# Store a copy of the original frame before swapping for opacity blending
|
# Store a copy of the original frame before swapping for opacity blending and mouth mask
|
||||||
opacity = getattr(modules.globals, "opacity", 1.0)
|
opacity = getattr(modules.globals, "opacity", 1.0)
|
||||||
opacity = max(0.0, min(1.0, opacity))
|
opacity = max(0.0, min(1.0, opacity))
|
||||||
original_frame = temp_frame if opacity >= 1.0 else temp_frame.copy()
|
mouth_mask_enabled = getattr(modules.globals, "mouth_mask", False)
|
||||||
|
# Always copy if mouth mask is enabled (we need the unmodified original for mouth cutout)
|
||||||
|
original_frame = temp_frame.copy() if (opacity < 1.0 or mouth_mask_enabled) else temp_frame
|
||||||
|
|
||||||
# Pre-swap Input Check with optimization
|
# Pre-swap Input Check with optimization
|
||||||
if temp_frame.dtype != np.uint8:
|
if temp_frame.dtype != np.uint8:
|
||||||
@@ -190,25 +192,25 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
|
|||||||
# --- Post-swap Processing (Masking, Opacity, etc.) ---
|
# --- Post-swap Processing (Masking, Opacity, etc.) ---
|
||||||
# Now, work with the guaranteed uint8 'swapped_frame'
|
# Now, work with the guaranteed uint8 'swapped_frame'
|
||||||
|
|
||||||
if getattr(modules.globals, "mouth_mask", False): # Check if mouth_mask is enabled
|
if mouth_mask_enabled: # Check if mouth_mask is enabled
|
||||||
# Create a mask for the target face
|
# Create a mask for the target face
|
||||||
face_mask = create_face_mask(target_face, temp_frame) # Use temp_frame (original shape) for mask creation geometry
|
face_mask = create_face_mask(target_face, original_frame) # Use original_frame for mask creation geometry
|
||||||
|
|
||||||
# Create the mouth mask using original geometry
|
# Create the mouth mask using the ORIGINAL frame (before swap) for cutout
|
||||||
mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon = (
|
mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon = (
|
||||||
create_lower_mouth_mask(target_face, temp_frame) # Use temp_frame (original) for cutout
|
create_lower_mouth_mask(target_face, original_frame) # Use original_frame for real mouth cutout
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply the mouth area only if mouth_cutout exists
|
# Apply the mouth area only if mouth_cutout exists
|
||||||
if mouth_cutout is not None and mouth_box != (0,0,0,0): # Add check for valid box
|
if mouth_cutout is not None and mouth_box != (0,0,0,0):
|
||||||
# Apply mouth area (from original) onto the 'swapped_frame'
|
# Apply mouth area (from original) onto the 'swapped_frame'
|
||||||
swapped_frame = apply_mouth_area(
|
swapped_frame = apply_mouth_area(
|
||||||
swapped_frame, mouth_cutout, mouth_box, face_mask, lower_lip_polygon
|
swapped_frame, mouth_cutout, mouth_box, face_mask, lower_lip_polygon
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Draw bounding box only while slider is being dragged
|
||||||
if getattr(modules.globals, "show_mouth_mask_box", False):
|
if getattr(modules.globals, "show_mouth_mask_box", False):
|
||||||
mouth_mask_data = (mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon)
|
mouth_mask_data = (mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon)
|
||||||
# Draw visualization on the swapped_frame *before* opacity blending
|
|
||||||
swapped_frame = draw_mouth_mask_visualization(
|
swapped_frame = draw_mouth_mask_visualization(
|
||||||
swapped_frame, target_face, mouth_mask_data
|
swapped_frame, target_face, mouth_mask_data
|
||||||
)
|
)
|
||||||
@@ -750,9 +752,9 @@ def create_lower_mouth_mask(
|
|||||||
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
||||||
|
|
||||||
try: # Wrap main logic in try-except
|
try: # Wrap main logic in try-except
|
||||||
# Use outer mouth landmarks (52-63) to capture the lips only
|
# Use outer mouth landmarks (52-71) to capture the full mouth area
|
||||||
# This avoids including the chin/jawline, preserving the face shape from the swap
|
# This covers both upper and lower lips for proper mouth preservation
|
||||||
lower_lip_order = list(range(52, 64))
|
lower_lip_order = list(range(52, 72))
|
||||||
|
|
||||||
# Check if all indices are valid for the loaded landmarks (already partially done by < 106 check)
|
# Check if all indices are valid for the loaded landmarks (already partially done by < 106 check)
|
||||||
if max(lower_lip_order) >= landmarks.shape[0]:
|
if max(lower_lip_order) >= landmarks.shape[0]:
|
||||||
@@ -772,9 +774,18 @@ def create_lower_mouth_mask(
|
|||||||
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
||||||
|
|
||||||
|
|
||||||
mask_down_size = getattr(modules.globals, "mask_down_size", 0.1) # Default 0.1
|
mouth_mask_size = getattr(modules.globals, "mouth_mask_size", 0.0) # 0-100 slider
|
||||||
expansion_factor = 1 + mask_down_size
|
# 0=tight lip outline, 50=covers mouth area, 100=mouth to chin
|
||||||
expanded_landmarks = (lower_lip_landmarks - center) * expansion_factor + center
|
expansion_factor = 1 + (mouth_mask_size / 100.0) * 2.5
|
||||||
|
|
||||||
|
# Expand landmarks from center, with extra downward bias toward chin
|
||||||
|
offsets = lower_lip_landmarks - center
|
||||||
|
# Add extra downward expansion for points below center (toward chin)
|
||||||
|
chin_bias = 1 + (mouth_mask_size / 100.0) * 1.5 # extra vertical stretch downward
|
||||||
|
scale_y = np.where(offsets[:, 1] > 0, expansion_factor * chin_bias, expansion_factor)
|
||||||
|
expanded_landmarks = lower_lip_landmarks.copy()
|
||||||
|
expanded_landmarks[:, 0] = center[0] + offsets[:, 0] * expansion_factor
|
||||||
|
expanded_landmarks[:, 1] = center[1] + offsets[:, 1] * scale_y
|
||||||
|
|
||||||
# Ensure landmarks are finite after adjustments
|
# Ensure landmarks are finite after adjustments
|
||||||
if not np.all(np.isfinite(expanded_landmarks)):
|
if not np.all(np.isfinite(expanded_landmarks)):
|
||||||
@@ -881,8 +892,8 @@ def draw_mouth_mask_visualization(
|
|||||||
print(f"Error drawing polygon for visualization: {e}") # Optional debug
|
print(f"Error drawing polygon for visualization: {e}") # Optional debug
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Optional: Draw bounding box (red rectangle)
|
# Draw bounding box (red rectangle)
|
||||||
# cv2.rectangle(vis_frame, (min_x, min_y), (max_x, max_y), (0, 0, 255), 1)
|
cv2.rectangle(vis_frame, (min_x, min_y), (max_x, max_y), (0, 0, 255), 2)
|
||||||
|
|
||||||
# Optional: Add labels
|
# Optional: Add labels
|
||||||
label_pos_y = min_y - 10 if min_y > 20 else max_y + 15 # Adjust position based on box location
|
label_pos_y = min_y - 10 if min_y > 20 else max_y + 15 # Adjust position based on box location
|
||||||
@@ -962,85 +973,34 @@ def apply_mouth_area(
|
|||||||
# print("Warning: Mouth cutout is invalid after resize attempt.")
|
# print("Warning: Mouth cutout is invalid after resize attempt.")
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
# --- Color Correction Step ---
|
|
||||||
# Apply color transfer from ROI (swapped face region) to the original mouth cutout
|
|
||||||
# This helps match lighting/color before blending
|
|
||||||
color_corrected_mouth = resized_mouth_cutout # Default to resized if correction fails
|
|
||||||
try:
|
|
||||||
# Ensure both images are 3 channels for color transfer
|
|
||||||
if len(resized_mouth_cutout.shape) == 3 and resized_mouth_cutout.shape[2] == 3 and \
|
|
||||||
len(roi.shape) == 3 and roi.shape[2] == 3:
|
|
||||||
color_corrected_mouth = apply_color_transfer(resized_mouth_cutout, roi)
|
|
||||||
else:
|
|
||||||
# print("Warning: Cannot apply color transfer, images not BGR.")
|
|
||||||
pass
|
|
||||||
except cv2.error as ct_e: # Handle potential errors in color transfer
|
|
||||||
# print(f"Warning: Color transfer failed: {ct_e}. Using uncorrected mouth cutout.") # Optional debug
|
|
||||||
pass
|
|
||||||
except Exception as ct_gen_e:
|
|
||||||
# print(f"Warning: Unexpected error during color transfer: {ct_gen_e}")
|
|
||||||
pass
|
|
||||||
# --- End Color Correction ---
|
|
||||||
|
|
||||||
|
|
||||||
# --- Mask Creation ---
|
# --- Mask Creation ---
|
||||||
# Create a mask based *specifically* on the mouth_polygon, relative to the ROI
|
# Create a mask based on the mouth_polygon, relative to the ROI
|
||||||
polygon_mask_roi = np.zeros(roi.shape[:2], dtype=np.uint8)
|
polygon_mask_roi = np.zeros(roi.shape[:2], dtype=np.uint8)
|
||||||
# Adjust polygon coordinates relative to the ROI's top-left corner
|
|
||||||
adjusted_polygon = mouth_polygon - [min_x, min_y]
|
adjusted_polygon = mouth_polygon - [min_x, min_y]
|
||||||
# Draw the filled polygon on the ROI mask
|
|
||||||
cv2.fillPoly(polygon_mask_roi, [adjusted_polygon.astype(np.int32)], 255)
|
cv2.fillPoly(polygon_mask_roi, [adjusted_polygon.astype(np.int32)], 255)
|
||||||
|
|
||||||
# Feather the polygon mask (Gaussian blur)
|
# Feather the edges with Gaussian blur for smooth blending
|
||||||
mask_feather_ratio = getattr(modules.globals, "mask_feather_ratio", 12) # Default 12
|
feather_amount = max(1, min(30, min(box_width, box_height) // 8))
|
||||||
# Calculate feather amount based on the smaller dimension of the box
|
|
||||||
feather_base_dim = min(box_width, box_height)
|
|
||||||
feather_amount = max(1, min(30, feather_base_dim // max(1, mask_feather_ratio))) # Avoid div by zero
|
|
||||||
# Ensure kernel size is odd and positive
|
|
||||||
kernel_size = 2 * feather_amount + 1
|
kernel_size = 2 * feather_amount + 1
|
||||||
feathered_polygon_mask = cv2.GaussianBlur(polygon_mask_roi.astype(np.float32), (kernel_size, kernel_size), 0)
|
feathered_mask = cv2.GaussianBlur(polygon_mask_roi.astype(np.float32), (kernel_size, kernel_size), 0)
|
||||||
|
|
||||||
# Normalize feathered mask to [0.0, 1.0] range
|
# Normalize to [0.0, 1.0]
|
||||||
max_val = feathered_polygon_mask.max()
|
max_val = feathered_mask.max()
|
||||||
if max_val > 1e-6: # Avoid division by zero
|
if max_val > 1e-6:
|
||||||
feathered_polygon_mask = feathered_polygon_mask / max_val
|
feathered_mask = feathered_mask / max_val
|
||||||
else:
|
else:
|
||||||
feathered_polygon_mask.fill(0.0) # Mask is all black if max is near zero
|
feathered_mask.fill(0.0)
|
||||||
# --- End Mask Creation ---
|
|
||||||
|
|
||||||
|
# --- Blending: paste original mouth onto swapped face ---
|
||||||
# --- Refined Blending ---
|
|
||||||
# Get the corresponding ROI from the *full face mask* (already blurred)
|
|
||||||
# Ensure face_mask is float and normalized [0.0, 1.0]
|
|
||||||
if face_mask.dtype != np.float64 and face_mask.dtype != np.float32:
|
|
||||||
face_mask_float = face_mask.astype(np.float32) / 255.0
|
|
||||||
else: # Assume already float [0,1] if type is float
|
|
||||||
face_mask_float = face_mask.astype(np.float32) if face_mask.dtype == np.float64 else face_mask
|
|
||||||
face_mask_roi = face_mask_float[min_y:max_y, min_x:max_x]
|
|
||||||
|
|
||||||
# Combine the feathered mouth polygon mask with the face mask ROI
|
|
||||||
# Use minimum to ensure we only affect area inside both masks (mouth area within face)
|
|
||||||
# This helps blend the edges smoothly with the surrounding swapped face region
|
|
||||||
combined_mask = np.minimum(feathered_polygon_mask, face_mask_roi)
|
|
||||||
|
|
||||||
# Expand mask to 3 channels for blending (ensure it matches image channels)
|
|
||||||
if len(frame.shape) == 3 and frame.shape[2] == 3:
|
if len(frame.shape) == 3 and frame.shape[2] == 3:
|
||||||
combined_mask_3channel = combined_mask[:, :, np.newaxis]
|
mask_3ch = feathered_mask[:, :, np.newaxis].astype(np.float32)
|
||||||
|
inv_mask = 1.0 - mask_3ch
|
||||||
|
|
||||||
# Ensure data types are compatible for blending
|
# Blend: (original_mouth * mask) + (swapped_face * (1 - mask))
|
||||||
# float32 provides sufficient precision for 8-bit image blending
|
blended_roi = (resized_mouth_cutout.astype(np.float32) * mask_3ch +
|
||||||
combined_mask_f32 = combined_mask_3channel.astype(np.float32)
|
roi.astype(np.float32) * inv_mask)
|
||||||
inv_mask = np.float32(1.0) - combined_mask_f32
|
|
||||||
|
|
||||||
# Blend: (original_mouth * combined_mask) + (swapped_face_roi * (1 - combined_mask))
|
frame[min_y:max_y, min_x:max_x] = np.clip(blended_roi, 0, 255).astype(np.uint8)
|
||||||
blended_roi = (color_corrected_mouth * combined_mask_f32 +
|
|
||||||
roi * inv_mask)
|
|
||||||
|
|
||||||
# Place the blended ROI back into the frame
|
|
||||||
frame[min_y:max_y, min_x:max_x] = blended_roi.astype(np.uint8)
|
|
||||||
else:
|
|
||||||
# print("Warning: Cannot apply mouth mask blending, frame is not 3-channel BGR.")
|
|
||||||
pass # Don't modify frame if it's not BGR
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error applying mouth area: {e}") # Optional debug
|
print(f"Error applying mouth area: {e}") # Optional debug
|
||||||
|
|||||||
+115
-88
@@ -135,6 +135,7 @@ def save_switch_states():
|
|||||||
"show_fps": modules.globals.show_fps,
|
"show_fps": modules.globals.show_fps,
|
||||||
"mouth_mask": modules.globals.mouth_mask,
|
"mouth_mask": modules.globals.mouth_mask,
|
||||||
"show_mouth_mask_box": modules.globals.show_mouth_mask_box,
|
"show_mouth_mask_box": modules.globals.show_mouth_mask_box,
|
||||||
|
"mouth_mask_size": modules.globals.mouth_mask_size,
|
||||||
}
|
}
|
||||||
with open("switch_states.json", "w") as f:
|
with open("switch_states.json", "w") as f:
|
||||||
json.dump(switch_states, f)
|
json.dump(switch_states, f)
|
||||||
@@ -156,10 +157,10 @@ def load_switch_states():
|
|||||||
modules.globals.live_resizable = switch_states.get("live_resizable", False)
|
modules.globals.live_resizable = switch_states.get("live_resizable", False)
|
||||||
modules.globals.fp_ui = switch_states.get("fp_ui", {"face_enhancer": False})
|
modules.globals.fp_ui = switch_states.get("fp_ui", {"face_enhancer": False})
|
||||||
modules.globals.show_fps = switch_states.get("show_fps", False)
|
modules.globals.show_fps = switch_states.get("show_fps", False)
|
||||||
modules.globals.mouth_mask = switch_states.get("mouth_mask", False)
|
modules.globals.mouth_mask_size = switch_states.get("mouth_mask_size", 0.0)
|
||||||
modules.globals.show_mouth_mask_box = switch_states.get(
|
# mouth_mask is driven by the slider: on if size > 0, off if 0
|
||||||
"show_mouth_mask_box", False
|
modules.globals.mouth_mask = modules.globals.mouth_mask_size > 0
|
||||||
)
|
modules.globals.show_mouth_mask_box = False # always start hidden
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# If the file doesn't exist, use default values
|
# If the file doesn't exist, use default values
|
||||||
pass
|
pass
|
||||||
@@ -220,7 +221,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
save_switch_states(),
|
save_switch_states(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
keep_fps_checkbox.place(relx=0.1, rely=0.5)
|
keep_fps_checkbox.place(relx=0.1, rely=0.42)
|
||||||
ToolTip(keep_fps_checkbox, _("Output video keeps the original frame rate"))
|
ToolTip(keep_fps_checkbox, _("Output video keeps the original frame rate"))
|
||||||
|
|
||||||
keep_frames_value = ctk.BooleanVar(value=modules.globals.keep_frames)
|
keep_frames_value = ctk.BooleanVar(value=modules.globals.keep_frames)
|
||||||
@@ -234,51 +235,9 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
save_switch_states(),
|
save_switch_states(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
keep_frames_switch.place(relx=0.1, rely=0.55)
|
keep_frames_switch.place(relx=0.1, rely=0.47)
|
||||||
ToolTip(keep_frames_switch, _("Keep extracted frames on disk after processing"))
|
ToolTip(keep_frames_switch, _("Keep extracted frames on disk after processing"))
|
||||||
|
|
||||||
enhancer_value = ctk.BooleanVar(value=modules.globals.fp_ui["face_enhancer"])
|
|
||||||
enhancer_switch = ctk.CTkSwitch(
|
|
||||||
root,
|
|
||||||
text=_("Face Enhancer"),
|
|
||||||
variable=enhancer_value,
|
|
||||||
cursor="hand2",
|
|
||||||
command=lambda: (
|
|
||||||
update_tumbler("face_enhancer", enhancer_value.get()),
|
|
||||||
save_switch_states(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
enhancer_switch.place(relx=0.1, rely=0.6)
|
|
||||||
ToolTip(enhancer_switch, _("Improve face quality using the GFPGAN restoration model"))
|
|
||||||
|
|
||||||
gpen256_value = ctk.BooleanVar(value=modules.globals.fp_ui.get("face_enhancer_gpen256", False))
|
|
||||||
gpen256_switch = ctk.CTkSwitch(
|
|
||||||
root,
|
|
||||||
text=_("GPEN Enhancer 256"),
|
|
||||||
variable=gpen256_value,
|
|
||||||
cursor="hand2",
|
|
||||||
command=lambda: (
|
|
||||||
update_tumbler("face_enhancer_gpen256", gpen256_value.get()),
|
|
||||||
save_switch_states(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
gpen256_switch.place(relx=0.1, rely=0.65)
|
|
||||||
ToolTip(gpen256_switch, _("Use GPEN face enhancement model at 256px resolution (faster)"))
|
|
||||||
|
|
||||||
gpen512_value = ctk.BooleanVar(value=modules.globals.fp_ui.get("face_enhancer_gpen512", False))
|
|
||||||
gpen512_switch = ctk.CTkSwitch(
|
|
||||||
root,
|
|
||||||
text=_("GPEN Enhancer 512"),
|
|
||||||
variable=gpen512_value,
|
|
||||||
cursor="hand2",
|
|
||||||
command=lambda: (
|
|
||||||
update_tumbler("face_enhancer_gpen512", gpen512_value.get()),
|
|
||||||
save_switch_states(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
gpen512_switch.place(relx=0.1, rely=0.7)
|
|
||||||
ToolTip(gpen512_switch, _("Use GPEN face enhancement model at 512px resolution (higher quality)"))
|
|
||||||
|
|
||||||
keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio)
|
keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio)
|
||||||
keep_audio_switch = ctk.CTkSwitch(
|
keep_audio_switch = ctk.CTkSwitch(
|
||||||
root,
|
root,
|
||||||
@@ -290,7 +249,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
save_switch_states(),
|
save_switch_states(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
keep_audio_switch.place(relx=0.6, rely=0.5)
|
keep_audio_switch.place(relx=0.6, rely=0.42)
|
||||||
ToolTip(keep_audio_switch, _("Copy audio track from the source video to output"))
|
ToolTip(keep_audio_switch, _("Copy audio track from the source video to output"))
|
||||||
|
|
||||||
many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces)
|
many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces)
|
||||||
@@ -304,7 +263,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
save_switch_states(),
|
save_switch_states(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
many_faces_switch.place(relx=0.6, rely=0.55)
|
many_faces_switch.place(relx=0.6, rely=0.47)
|
||||||
ToolTip(many_faces_switch, _("Swap every detected face, not just the primary one"))
|
ToolTip(many_faces_switch, _("Swap every detected face, not just the primary one"))
|
||||||
|
|
||||||
color_correction_value = ctk.BooleanVar(value=modules.globals.color_correction)
|
color_correction_value = ctk.BooleanVar(value=modules.globals.color_correction)
|
||||||
@@ -318,7 +277,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
save_switch_states(),
|
save_switch_states(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
color_correction_switch.place(relx=0.6, rely=0.6)
|
color_correction_switch.place(relx=0.6, rely=0.57)
|
||||||
ToolTip(color_correction_switch, _("Fix blue/green color cast from some webcams"))
|
ToolTip(color_correction_switch, _("Fix blue/green color cast from some webcams"))
|
||||||
|
|
||||||
# nsfw_value = ctk.BooleanVar(value=modules.globals.nsfw_filter)
|
# nsfw_value = ctk.BooleanVar(value=modules.globals.nsfw_filter)
|
||||||
@@ -337,7 +296,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
close_mapper_window() if not map_faces.get() else None
|
close_mapper_window() if not map_faces.get() else None
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
map_faces_switch.place(relx=0.1, rely=0.75)
|
map_faces_switch.place(relx=0.1, rely=0.52)
|
||||||
ToolTip(map_faces_switch, _("Manually assign which source face maps to which target face"))
|
ToolTip(map_faces_switch, _("Manually assign which source face maps to which target face"))
|
||||||
|
|
||||||
poisson_blend_value = ctk.BooleanVar(value=modules.globals.poisson_blend)
|
poisson_blend_value = ctk.BooleanVar(value=modules.globals.poisson_blend)
|
||||||
@@ -351,7 +310,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
save_switch_states(),
|
save_switch_states(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
poisson_blend_switch.place(relx=0.1, rely=0.8)
|
poisson_blend_switch.place(relx=0.1, rely=0.57)
|
||||||
ToolTip(poisson_blend_switch, _("Blend face edges smoothly using Poisson blending"))
|
ToolTip(poisson_blend_switch, _("Blend face edges smoothly using Poisson blending"))
|
||||||
|
|
||||||
show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps)
|
show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps)
|
||||||
@@ -365,54 +324,34 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
save_switch_states(),
|
save_switch_states(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
show_fps_switch.place(relx=0.6, rely=0.65)
|
show_fps_switch.place(relx=0.6, rely=0.52)
|
||||||
ToolTip(show_fps_switch, _("Display frames-per-second counter on the live preview"))
|
ToolTip(show_fps_switch, _("Display frames-per-second counter on the live preview"))
|
||||||
|
|
||||||
|
# mouth_mask and show_mouth_mask_box are auto-controlled by the Mouth Mask slider
|
||||||
mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask)
|
mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask)
|
||||||
mouth_mask_switch = ctk.CTkSwitch(
|
|
||||||
root,
|
|
||||||
text=_("Mouth Mask"),
|
|
||||||
variable=mouth_mask_var,
|
|
||||||
cursor="hand2",
|
|
||||||
command=lambda: setattr(modules.globals, "mouth_mask", mouth_mask_var.get()),
|
|
||||||
)
|
|
||||||
mouth_mask_switch.place(relx=0.1, rely=0.45)
|
|
||||||
ToolTip(mouth_mask_switch, _("Preserve original mouth movement in the swapped face"))
|
|
||||||
|
|
||||||
show_mouth_mask_box_var = ctk.BooleanVar(value=modules.globals.show_mouth_mask_box)
|
show_mouth_mask_box_var = ctk.BooleanVar(value=modules.globals.show_mouth_mask_box)
|
||||||
show_mouth_mask_box_switch = ctk.CTkSwitch(
|
|
||||||
root,
|
|
||||||
text=_("Show Mouth Mask Box"),
|
|
||||||
variable=show_mouth_mask_box_var,
|
|
||||||
cursor="hand2",
|
|
||||||
command=lambda: setattr(
|
|
||||||
modules.globals, "show_mouth_mask_box", show_mouth_mask_box_var.get()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
show_mouth_mask_box_switch.place(relx=0.6, rely=0.45)
|
|
||||||
ToolTip(show_mouth_mask_box_switch, _("Display the mouth mask boundary for debugging"))
|
|
||||||
|
|
||||||
start_button = ctk.CTkButton(
|
start_button = ctk.CTkButton(
|
||||||
root, text=_("Start"), cursor="hand2", command=lambda: analyze_target(start, root)
|
root, text=_("Start"), cursor="hand2", command=lambda: analyze_target(start, root)
|
||||||
)
|
)
|
||||||
start_button.place(relx=0.15, rely=0.86, relwidth=0.2, relheight=0.05)
|
start_button.place(relx=0.15, rely=0.78, relwidth=0.2, relheight=0.04)
|
||||||
ToolTip(start_button, _("Begin processing the target image/video with selected face"))
|
ToolTip(start_button, _("Begin processing the target image/video with selected face"))
|
||||||
|
|
||||||
stop_button = ctk.CTkButton(
|
stop_button = ctk.CTkButton(
|
||||||
root, text=_("Destroy"), cursor="hand2", command=lambda: destroy()
|
root, text=_("Destroy"), cursor="hand2", command=lambda: destroy()
|
||||||
)
|
)
|
||||||
stop_button.place(relx=0.4, rely=0.86, relwidth=0.2, relheight=0.05)
|
stop_button.place(relx=0.4, rely=0.78, relwidth=0.2, relheight=0.04)
|
||||||
ToolTip(stop_button, _("Stop processing and close the application"))
|
ToolTip(stop_button, _("Stop processing and close the application"))
|
||||||
|
|
||||||
preview_button = ctk.CTkButton(
|
preview_button = ctk.CTkButton(
|
||||||
root, text=_("Preview"), cursor="hand2", command=lambda: toggle_preview()
|
root, text=_("Preview"), cursor="hand2", command=lambda: toggle_preview()
|
||||||
)
|
)
|
||||||
preview_button.place(relx=0.65, rely=0.86, relwidth=0.2, relheight=0.05)
|
preview_button.place(relx=0.65, rely=0.78, relwidth=0.2, relheight=0.04)
|
||||||
ToolTip(preview_button, _("Show/hide a preview of the processed output"))
|
ToolTip(preview_button, _("Show/hide a preview of the processed output"))
|
||||||
|
|
||||||
# --- Camera Selection ---
|
# --- Camera Selection ---
|
||||||
camera_label = ctk.CTkLabel(root, text=_("Select Camera:"))
|
camera_label = ctk.CTkLabel(root, text=_("Select Camera:"))
|
||||||
camera_label.place(relx=0.1, rely=0.92, relwidth=0.2, relheight=0.05)
|
camera_label.place(relx=0.1, rely=0.83, relwidth=0.2, relheight=0.03)
|
||||||
|
|
||||||
available_cameras = get_available_cameras()
|
available_cameras = get_available_cameras()
|
||||||
camera_indices, camera_names = available_cameras
|
camera_indices, camera_names = available_cameras
|
||||||
@@ -431,7 +370,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
root, variable=camera_variable, values=camera_names
|
root, variable=camera_variable, values=camera_names
|
||||||
)
|
)
|
||||||
|
|
||||||
camera_optionmenu.place(relx=0.35, rely=0.92, relwidth=0.25, relheight=0.05)
|
camera_optionmenu.place(relx=0.35, rely=0.83, relwidth=0.25, relheight=0.03)
|
||||||
ToolTip(camera_optionmenu, _("Select which camera to use for live mode"))
|
ToolTip(camera_optionmenu, _("Select which camera to use for live mode"))
|
||||||
|
|
||||||
live_button = ctk.CTkButton(
|
live_button = ctk.CTkButton(
|
||||||
@@ -452,10 +391,52 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
else "disabled"
|
else "disabled"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
live_button.place(relx=0.65, rely=0.92, relwidth=0.2, relheight=0.05)
|
live_button.place(relx=0.65, rely=0.83, relwidth=0.2, relheight=0.03)
|
||||||
ToolTip(live_button, _("Start real-time face swap using webcam"))
|
ToolTip(live_button, _("Start real-time face swap using webcam"))
|
||||||
# --- End Camera Selection ---
|
# --- End Camera Selection ---
|
||||||
|
|
||||||
|
# --- Face Enhancer Dropdown ---
|
||||||
|
enhancer_options = ["None", "GFPGAN", "GPEN-512", "GPEN-256"]
|
||||||
|
enhancer_key_map = {
|
||||||
|
"None": None,
|
||||||
|
"GFPGAN": "face_enhancer",
|
||||||
|
"GPEN-512": "face_enhancer_gpen512",
|
||||||
|
"GPEN-256": "face_enhancer_gpen256",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine initial value from current fp_ui state
|
||||||
|
initial_enhancer = "None"
|
||||||
|
if modules.globals.fp_ui.get("face_enhancer", False):
|
||||||
|
initial_enhancer = "GFPGAN"
|
||||||
|
elif modules.globals.fp_ui.get("face_enhancer_gpen512", False):
|
||||||
|
initial_enhancer = "GPEN-512"
|
||||||
|
elif modules.globals.fp_ui.get("face_enhancer_gpen256", False):
|
||||||
|
initial_enhancer = "GPEN-256"
|
||||||
|
|
||||||
|
enhancer_variable = ctk.StringVar(value=initial_enhancer)
|
||||||
|
|
||||||
|
def on_enhancer_change(choice: str):
|
||||||
|
# Disable all enhancers first
|
||||||
|
for key in ["face_enhancer", "face_enhancer_gpen256", "face_enhancer_gpen512"]:
|
||||||
|
update_tumbler(key, False)
|
||||||
|
# Enable the selected one
|
||||||
|
selected_key = enhancer_key_map.get(choice)
|
||||||
|
if selected_key:
|
||||||
|
update_tumbler(selected_key, True)
|
||||||
|
save_switch_states()
|
||||||
|
|
||||||
|
enhancer_label = ctk.CTkLabel(root, text="Face Enhancer:")
|
||||||
|
enhancer_label.place(relx=0.1, rely=0.62, relwidth=0.2, relheight=0.03)
|
||||||
|
|
||||||
|
enhancer_dropdown = ctk.CTkOptionMenu(
|
||||||
|
root,
|
||||||
|
variable=enhancer_variable,
|
||||||
|
values=enhancer_options,
|
||||||
|
command=on_enhancer_change,
|
||||||
|
)
|
||||||
|
enhancer_dropdown.place(relx=0.35, rely=0.62, relwidth=0.3, relheight=0.03)
|
||||||
|
ToolTip(enhancer_dropdown, _("Select a face enhancement model (None = no enhancement)"))
|
||||||
|
|
||||||
# 1) Define a DoubleVar for transparency (0 = fully transparent, 1 = fully opaque)
|
# 1) Define a DoubleVar for transparency (0 = fully transparent, 1 = fully opaque)
|
||||||
transparency_var = ctk.DoubleVar(value=1.0)
|
transparency_var = ctk.DoubleVar(value=1.0)
|
||||||
|
|
||||||
@@ -475,9 +456,9 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
modules.globals.face_swapper_enabled = True
|
modules.globals.face_swapper_enabled = True
|
||||||
update_status(f"Transparency set to {percentage}%")
|
update_status(f"Transparency set to {percentage}%")
|
||||||
|
|
||||||
# 2) Transparency label and slider (placed ABOVE sharpness)
|
# 2) Transparency label and slider
|
||||||
transparency_label = ctk.CTkLabel(root, text="Transparency:")
|
transparency_label = ctk.CTkLabel(root, text="Transparency:")
|
||||||
transparency_label.place(relx=0.15, rely=0.75, relwidth=0.2, relheight=0.05)
|
transparency_label.place(relx=0.15, rely=0.66, relwidth=0.2, relheight=0.03)
|
||||||
|
|
||||||
transparency_slider = ctk.CTkSlider(
|
transparency_slider = ctk.CTkSlider(
|
||||||
root,
|
root,
|
||||||
@@ -493,7 +474,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
border_width=1,
|
border_width=1,
|
||||||
corner_radius=3,
|
corner_radius=3,
|
||||||
)
|
)
|
||||||
transparency_slider.place(relx=0.35, rely=0.77, relwidth=0.5, relheight=0.02)
|
transparency_slider.place(relx=0.35, rely=0.67, relwidth=0.5, relheight=0.02)
|
||||||
ToolTip(transparency_slider, _("Blend between original and swapped face (0% = original, 100% = fully swapped)"))
|
ToolTip(transparency_slider, _("Blend between original and swapped face (0% = original, 100% = fully swapped)"))
|
||||||
|
|
||||||
# 3) Sharpness label & slider
|
# 3) Sharpness label & slider
|
||||||
@@ -503,7 +484,7 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
update_status(f"Sharpness set to {value:.1f}")
|
update_status(f"Sharpness set to {value:.1f}")
|
||||||
|
|
||||||
sharpness_label = ctk.CTkLabel(root, text="Sharpness:")
|
sharpness_label = ctk.CTkLabel(root, text="Sharpness:")
|
||||||
sharpness_label.place(relx=0.15, rely=0.80, relwidth=0.2, relheight=0.05)
|
sharpness_label.place(relx=0.15, rely=0.69, relwidth=0.2, relheight=0.03)
|
||||||
|
|
||||||
sharpness_slider = ctk.CTkSlider(
|
sharpness_slider = ctk.CTkSlider(
|
||||||
root,
|
root,
|
||||||
@@ -519,18 +500,64 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C
|
|||||||
border_width=1,
|
border_width=1,
|
||||||
corner_radius=3,
|
corner_radius=3,
|
||||||
)
|
)
|
||||||
sharpness_slider.place(relx=0.35, rely=0.82, relwidth=0.5, relheight=0.02)
|
sharpness_slider.place(relx=0.35, rely=0.70, relwidth=0.5, relheight=0.02)
|
||||||
ToolTip(sharpness_slider, _("Sharpen the enhanced face output"))
|
ToolTip(sharpness_slider, _("Sharpen the enhanced face output"))
|
||||||
|
|
||||||
|
# 4) Mouth Mask Size slider
|
||||||
|
mouth_mask_size_var = ctk.DoubleVar(value=modules.globals.mouth_mask_size)
|
||||||
|
|
||||||
|
def on_mouth_mask_size_change(value: float):
|
||||||
|
val = float(value)
|
||||||
|
modules.globals.mouth_mask_size = val
|
||||||
|
# Auto-enable/disable mouth mask based on slider position
|
||||||
|
if val > 0:
|
||||||
|
modules.globals.mouth_mask = True
|
||||||
|
mouth_mask_var.set(True)
|
||||||
|
else:
|
||||||
|
modules.globals.mouth_mask = False
|
||||||
|
mouth_mask_var.set(False)
|
||||||
|
modules.globals.show_mouth_mask_box = False
|
||||||
|
|
||||||
|
def on_mouth_mask_slider_release(event):
|
||||||
|
# Hide bounding box when user releases the slider
|
||||||
|
modules.globals.show_mouth_mask_box = False
|
||||||
|
|
||||||
|
def on_mouth_mask_slider_press(event):
|
||||||
|
# Show bounding box while dragging
|
||||||
|
if modules.globals.mouth_mask_size > 0:
|
||||||
|
modules.globals.show_mouth_mask_box = True
|
||||||
|
|
||||||
|
mouth_mask_size_label = ctk.CTkLabel(root, text="Mouth Mask:")
|
||||||
|
mouth_mask_size_label.place(relx=0.15, rely=0.72, relwidth=0.2, relheight=0.03)
|
||||||
|
|
||||||
|
mouth_mask_size_slider = ctk.CTkSlider(
|
||||||
|
root,
|
||||||
|
from_=0.0,
|
||||||
|
to=100.0,
|
||||||
|
variable=mouth_mask_size_var,
|
||||||
|
command=on_mouth_mask_size_change,
|
||||||
|
fg_color="#E0E0E0",
|
||||||
|
progress_color="#007BFF",
|
||||||
|
button_color="#FFFFFF",
|
||||||
|
button_hover_color="#CCCCCC",
|
||||||
|
height=5,
|
||||||
|
border_width=1,
|
||||||
|
corner_radius=3,
|
||||||
|
)
|
||||||
|
mouth_mask_size_slider.place(relx=0.35, rely=0.73, relwidth=0.5, relheight=0.02)
|
||||||
|
mouth_mask_size_slider.bind("<ButtonPress-1>", on_mouth_mask_slider_press)
|
||||||
|
mouth_mask_size_slider.bind("<ButtonRelease-1>", on_mouth_mask_slider_release)
|
||||||
|
ToolTip(mouth_mask_size_slider, _("0 = use swapped mouth, 100 = expose original mouth to chin area"))
|
||||||
|
|
||||||
# Status and link at the bottom
|
# Status and link at the bottom
|
||||||
global status_label
|
global status_label
|
||||||
status_label = ctk.CTkLabel(root, text=None, justify="center")
|
status_label = ctk.CTkLabel(root, text=None, justify="center")
|
||||||
status_label.place(relx=0.1, rely=0.96, relwidth=0.8)
|
status_label.place(relx=0.1, rely=0.75, relwidth=0.8)
|
||||||
|
|
||||||
donate_label = ctk.CTkLabel(
|
donate_label = ctk.CTkLabel(
|
||||||
root, text="Deep Live Cam", justify="center", cursor="hand2"
|
root, text="Deep Live Cam", justify="center", cursor="hand2"
|
||||||
)
|
)
|
||||||
donate_label.place(relx=0.1, rely=0.98, relwidth=0.8)
|
donate_label.place(relx=0.1, rely=0.87, relwidth=0.8)
|
||||||
donate_label.configure(
|
donate_label.configure(
|
||||||
text_color=ctk.ThemeManager.theme.get("URL").get("text_color")
|
text_color=ctk.ThemeManager.theme.get("URL").get("text_color")
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user