Eye Mask
							parent
							
								
									59cd3be0f9
								
							
						
					
					
						commit
						5dfd1c0ced
					
				|  | @ -21,7 +21,7 @@ keep_audio = True | ||||||
| keep_frames = False | keep_frames = False | ||||||
| many_faces = False | many_faces = False | ||||||
| map_faces = False | map_faces = False | ||||||
| color_correction = False  # New global variable for color correction toggle | color_correction = False | ||||||
| nsfw_filter = False | nsfw_filter = False | ||||||
| video_encoder = None | video_encoder = None | ||||||
| video_quality = None | video_quality = None | ||||||
|  | @ -41,5 +41,7 @@ show_mouth_mask_box = False | ||||||
| mask_feather_ratio = 8 | mask_feather_ratio = 8 | ||||||
| mask_down_size = 0.50 | mask_down_size = 0.50 | ||||||
| mask_size = 1 | mask_size = 1 | ||||||
|  | eyes_mask = False | ||||||
|  | show_eyes_mask_box = False | ||||||
| use_fake_face = False | use_fake_face = False | ||||||
| fake_face_path = None | fake_face_path = None | ||||||
|  |  | ||||||
|  | @ -74,10 +74,10 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame: | ||||||
|         temp_frame, target_face, source_face, paste_back=True |         temp_frame, target_face, source_face, paste_back=True | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     if modules.globals.mouth_mask: |     # Create face mask for both mouth and eyes masking | ||||||
|         # Create a mask for the target face |     face_mask = create_face_mask(target_face, temp_frame) | ||||||
|         face_mask = create_face_mask(target_face, temp_frame) |  | ||||||
| 
 | 
 | ||||||
|  |     if modules.globals.mouth_mask: | ||||||
|         # Create the mouth mask |         # Create the mouth mask | ||||||
|         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) |             create_lower_mouth_mask(target_face, temp_frame) | ||||||
|  | @ -94,6 +94,23 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame: | ||||||
|                 swapped_frame, target_face, mouth_mask_data |                 swapped_frame, target_face, mouth_mask_data | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  |     if modules.globals.eyes_mask: | ||||||
|  |         # Create the eyes mask | ||||||
|  |         eyes_mask, eyes_cutout, eyes_box, eyes_polygon = ( | ||||||
|  |             create_eyes_mask(target_face, temp_frame) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # Apply the eyes area | ||||||
|  |         swapped_frame = apply_eyes_area( | ||||||
|  |             swapped_frame, eyes_cutout, eyes_box, face_mask, eyes_polygon | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         if modules.globals.show_eyes_mask_box: | ||||||
|  |             eyes_mask_data = (eyes_mask, eyes_cutout, eyes_box, eyes_polygon) | ||||||
|  |             swapped_frame = draw_eyes_mask_visualization( | ||||||
|  |                 swapped_frame, target_face, eyes_mask_data | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|     return swapped_frame |     return swapped_frame | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -613,3 +630,232 @@ def apply_color_transfer(source, target): | ||||||
|     source = (source - source_mean) * (target_std / source_std) + target_mean |     source = (source - source_mean) * (target_std / source_std) + target_mean | ||||||
| 
 | 
 | ||||||
|     return cv2.cvtColor(np.clip(source, 0, 255).astype("uint8"), cv2.COLOR_LAB2BGR) |     return cv2.cvtColor(np.clip(source, 0, 255).astype("uint8"), cv2.COLOR_LAB2BGR) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def create_eyes_mask(face: Face, frame: Frame) -> (np.ndarray, np.ndarray, tuple, np.ndarray): | ||||||
|  |     mask = np.zeros(frame.shape[:2], dtype=np.uint8) | ||||||
|  |     eyes_cutout = None | ||||||
|  |     landmarks = face.landmark_2d_106 | ||||||
|  |     if landmarks is not None: | ||||||
|  |         # Left eye landmarks (87-96) and right eye landmarks (33-42) | ||||||
|  |         left_eye = landmarks[87:96] | ||||||
|  |         right_eye = landmarks[33:42] | ||||||
|  |          | ||||||
|  |         # Calculate centers and dimensions for each eye | ||||||
|  |         left_eye_center = np.mean(left_eye, axis=0).astype(np.int32) | ||||||
|  |         right_eye_center = np.mean(right_eye, axis=0).astype(np.int32) | ||||||
|  |          | ||||||
|  |         # Calculate eye dimensions | ||||||
|  |         def get_eye_dimensions(eye_points): | ||||||
|  |             x_coords = eye_points[:, 0] | ||||||
|  |             y_coords = eye_points[:, 1] | ||||||
|  |             width = int((np.max(x_coords) - np.min(x_coords)) * (1 + modules.globals.mask_down_size)) | ||||||
|  |             height = int((np.max(y_coords) - np.min(y_coords)) * (1 + modules.globals.mask_down_size)) | ||||||
|  |             return width, height | ||||||
|  |          | ||||||
|  |         left_width, left_height = get_eye_dimensions(left_eye) | ||||||
|  |         right_width, right_height = get_eye_dimensions(right_eye) | ||||||
|  |          | ||||||
|  |         # Add extra padding | ||||||
|  |         padding = int(max(left_width, right_width) * 0.2) | ||||||
|  |          | ||||||
|  |         # Calculate bounding box for both eyes | ||||||
|  |         min_x = min(left_eye_center[0] - left_width//2, right_eye_center[0] - right_width//2) - padding | ||||||
|  |         max_x = max(left_eye_center[0] + left_width//2, right_eye_center[0] + right_width//2) + padding | ||||||
|  |         min_y = min(left_eye_center[1] - left_height//2, right_eye_center[1] - right_height//2) - padding | ||||||
|  |         max_y = max(left_eye_center[1] + left_height//2, right_eye_center[1] + right_height//2) + padding | ||||||
|  |          | ||||||
|  |         # Ensure coordinates are within frame bounds | ||||||
|  |         min_x = max(0, min_x) | ||||||
|  |         min_y = max(0, min_y) | ||||||
|  |         max_x = min(frame.shape[1], max_x) | ||||||
|  |         max_y = min(frame.shape[0], max_y) | ||||||
|  |          | ||||||
|  |         # Create mask for the eyes region | ||||||
|  |         mask_roi = np.zeros((max_y - min_y, max_x - min_x), dtype=np.uint8) | ||||||
|  |          | ||||||
|  |         # Draw ellipses for both eyes | ||||||
|  |         left_center = (left_eye_center[0] - min_x, left_eye_center[1] - min_y) | ||||||
|  |         right_center = (right_eye_center[0] - min_x, right_eye_center[1] - min_y) | ||||||
|  |          | ||||||
|  |         # Calculate axes lengths (half of width and height) | ||||||
|  |         left_axes = (left_width//2, left_height//2) | ||||||
|  |         right_axes = (right_width//2, right_height//2) | ||||||
|  |          | ||||||
|  |         # Draw filled ellipses | ||||||
|  |         cv2.ellipse(mask_roi, left_center, left_axes, 0, 0, 360, 255, -1) | ||||||
|  |         cv2.ellipse(mask_roi, right_center, right_axes, 0, 0, 360, 255, -1) | ||||||
|  |          | ||||||
|  |         # Apply Gaussian blur to soften mask edges | ||||||
|  |         mask_roi = cv2.GaussianBlur(mask_roi, (15, 15), 5) | ||||||
|  |          | ||||||
|  |         # Place the mask ROI in the full-sized mask | ||||||
|  |         mask[min_y:max_y, min_x:max_x] = mask_roi | ||||||
|  |          | ||||||
|  |         # Extract the masked area from the frame | ||||||
|  |         eyes_cutout = frame[min_y:max_y, min_x:max_x].copy() | ||||||
|  |          | ||||||
|  |         # Create polygon points for visualization | ||||||
|  |         def create_ellipse_points(center, axes): | ||||||
|  |             t = np.linspace(0, 2*np.pi, 32) | ||||||
|  |             x = center[0] + axes[0] * np.cos(t) | ||||||
|  |             y = center[1] + axes[1] * np.sin(t) | ||||||
|  |             return np.column_stack((x, y)).astype(np.int32) | ||||||
|  |          | ||||||
|  |         # Generate points for both ellipses | ||||||
|  |         left_points = create_ellipse_points((left_eye_center[0], left_eye_center[1]), (left_width//2, left_height//2)) | ||||||
|  |         right_points = create_ellipse_points((right_eye_center[0], right_eye_center[1]), (right_width//2, right_height//2)) | ||||||
|  |          | ||||||
|  |         # Combine points for both eyes | ||||||
|  |         eyes_polygon = np.vstack([left_points, right_points]) | ||||||
|  |          | ||||||
|  |     return mask, eyes_cutout, (min_x, min_y, max_x, max_y), eyes_polygon | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def apply_eyes_area( | ||||||
|  |     frame: np.ndarray, | ||||||
|  |     eyes_cutout: np.ndarray, | ||||||
|  |     eyes_box: tuple, | ||||||
|  |     face_mask: np.ndarray, | ||||||
|  |     eyes_polygon: np.ndarray, | ||||||
|  | ) -> np.ndarray: | ||||||
|  |     min_x, min_y, max_x, max_y = eyes_box | ||||||
|  |     box_width = max_x - min_x | ||||||
|  |     box_height = max_y - min_y | ||||||
|  | 
 | ||||||
|  |     if ( | ||||||
|  |         eyes_cutout is None | ||||||
|  |         or box_width is None | ||||||
|  |         or box_height is None | ||||||
|  |         or face_mask is None | ||||||
|  |         or eyes_polygon is None | ||||||
|  |     ): | ||||||
|  |         return frame | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         resized_eyes_cutout = cv2.resize(eyes_cutout, (box_width, box_height)) | ||||||
|  |         roi = frame[min_y:max_y, min_x:max_x] | ||||||
|  | 
 | ||||||
|  |         if roi.shape != resized_eyes_cutout.shape: | ||||||
|  |             resized_eyes_cutout = cv2.resize( | ||||||
|  |                 resized_eyes_cutout, (roi.shape[1], roi.shape[0]) | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         color_corrected_eyes = apply_color_transfer(resized_eyes_cutout, roi) | ||||||
|  | 
 | ||||||
|  |         # Create mask for both eyes | ||||||
|  |         polygon_mask = np.zeros(roi.shape[:2], dtype=np.uint8) | ||||||
|  |          | ||||||
|  |         # Split points for left and right eyes | ||||||
|  |         mid_point = len(eyes_polygon) // 2 | ||||||
|  |         left_eye_points = eyes_polygon[:mid_point] - [min_x, min_y] | ||||||
|  |         right_eye_points = eyes_polygon[mid_point:] - [min_x, min_y] | ||||||
|  |          | ||||||
|  |         # Draw filled ellipses using points | ||||||
|  |         left_rect = cv2.minAreaRect(left_eye_points) | ||||||
|  |         right_rect = cv2.minAreaRect(right_eye_points) | ||||||
|  |          | ||||||
|  |         # Convert rect to ellipse parameters | ||||||
|  |         def rect_to_ellipse_params(rect): | ||||||
|  |             center = rect[0] | ||||||
|  |             size = rect[1] | ||||||
|  |             angle = rect[2] | ||||||
|  |             return (int(center[0]), int(center[1])), (int(size[0]/2), int(size[1]/2)), angle | ||||||
|  |          | ||||||
|  |         # Draw filled ellipses | ||||||
|  |         left_params = rect_to_ellipse_params(left_rect) | ||||||
|  |         right_params = rect_to_ellipse_params(right_rect) | ||||||
|  |         cv2.ellipse(polygon_mask, left_params[0], left_params[1], left_params[2], 0, 360, 255, -1) | ||||||
|  |         cv2.ellipse(polygon_mask, right_params[0], right_params[1], right_params[2], 0, 360, 255, -1) | ||||||
|  | 
 | ||||||
|  |         # Apply feathering | ||||||
|  |         feather_amount = min( | ||||||
|  |             30, | ||||||
|  |             box_width // modules.globals.mask_feather_ratio, | ||||||
|  |             box_height // modules.globals.mask_feather_ratio, | ||||||
|  |         ) | ||||||
|  |         feathered_mask = cv2.GaussianBlur( | ||||||
|  |             polygon_mask.astype(float), (0, 0), feather_amount | ||||||
|  |         ) | ||||||
|  |         feathered_mask = feathered_mask / feathered_mask.max() | ||||||
|  | 
 | ||||||
|  |         face_mask_roi = face_mask[min_y:max_y, min_x:max_x] | ||||||
|  |         combined_mask = feathered_mask * (face_mask_roi / 255.0) | ||||||
|  | 
 | ||||||
|  |         combined_mask = combined_mask[:, :, np.newaxis] | ||||||
|  |         blended = ( | ||||||
|  |             color_corrected_eyes * combined_mask + roi * (1 - combined_mask) | ||||||
|  |         ).astype(np.uint8) | ||||||
|  | 
 | ||||||
|  |         # Apply face mask to blended result | ||||||
|  |         face_mask_3channel = ( | ||||||
|  |             np.repeat(face_mask_roi[:, :, np.newaxis], 3, axis=2) / 255.0 | ||||||
|  |         ) | ||||||
|  |         final_blend = blended * face_mask_3channel + roi * (1 - face_mask_3channel) | ||||||
|  | 
 | ||||||
|  |         frame[min_y:max_y, min_x:max_x] = final_blend.astype(np.uint8) | ||||||
|  |     except Exception as e: | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     return frame | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def draw_eyes_mask_visualization( | ||||||
|  |     frame: Frame, face: Face, eyes_mask_data: tuple | ||||||
|  | ) -> Frame: | ||||||
|  |     landmarks = face.landmark_2d_106 | ||||||
|  |     if landmarks is not None and eyes_mask_data is not None: | ||||||
|  |         mask, eyes_cutout, (min_x, min_y, max_x, max_y), eyes_polygon = eyes_mask_data | ||||||
|  | 
 | ||||||
|  |         vis_frame = frame.copy() | ||||||
|  | 
 | ||||||
|  |         # Ensure coordinates are within frame bounds | ||||||
|  |         height, width = vis_frame.shape[:2] | ||||||
|  |         min_x, min_y = max(0, min_x), max(0, min_y) | ||||||
|  |         max_x, max_y = min(width, max_x), min(height, max_y) | ||||||
|  | 
 | ||||||
|  |         # Draw the eyes ellipses | ||||||
|  |         mid_point = len(eyes_polygon) // 2 | ||||||
|  |         left_points = eyes_polygon[:mid_point] | ||||||
|  |         right_points = eyes_polygon[mid_point:] | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Fit ellipses to points - need at least 5 points | ||||||
|  |             if len(left_points) >= 5 and len(right_points) >= 5: | ||||||
|  |                 # Convert points to the correct format for ellipse fitting | ||||||
|  |                 left_points = left_points.astype(np.float32) | ||||||
|  |                 right_points = right_points.astype(np.float32) | ||||||
|  |                  | ||||||
|  |                 # Fit ellipses | ||||||
|  |                 left_ellipse = cv2.fitEllipse(left_points) | ||||||
|  |                 right_ellipse = cv2.fitEllipse(right_points) | ||||||
|  |                  | ||||||
|  |                 # Draw the ellipses | ||||||
|  |                 cv2.ellipse(vis_frame, left_ellipse, (0, 255, 0), 2) | ||||||
|  |                 cv2.ellipse(vis_frame, right_ellipse, (0, 255, 0), 2) | ||||||
|  |         except Exception as e: | ||||||
|  |             # If ellipse fitting fails, draw simple rectangles as fallback | ||||||
|  |             left_rect = cv2.boundingRect(left_points) | ||||||
|  |             right_rect = cv2.boundingRect(right_points) | ||||||
|  |             cv2.rectangle(vis_frame,  | ||||||
|  |                         (left_rect[0], left_rect[1]),  | ||||||
|  |                         (left_rect[0] + left_rect[2], left_rect[1] + left_rect[3]),  | ||||||
|  |                         (0, 255, 0), 2) | ||||||
|  |             cv2.rectangle(vis_frame, | ||||||
|  |                         (right_rect[0], right_rect[1]), | ||||||
|  |                         (right_rect[0] + right_rect[2], right_rect[1] + right_rect[3]), | ||||||
|  |                         (0, 255, 0), 2) | ||||||
|  | 
 | ||||||
|  |         # Add label | ||||||
|  |         cv2.putText( | ||||||
|  |             vis_frame, | ||||||
|  |             "Eyes Mask", | ||||||
|  |             (min_x, min_y - 10), | ||||||
|  |             cv2.FONT_HERSHEY_SIMPLEX, | ||||||
|  |             0.5, | ||||||
|  |             (255, 255, 255), | ||||||
|  |             1, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         return vis_frame | ||||||
|  |     return frame | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import webbrowser | ||||||
| import customtkinter as ctk | import customtkinter as ctk | ||||||
| from typing import Callable, Tuple | from typing import Callable, Tuple | ||||||
| import cv2 | import cv2 | ||||||
| from cv2_enumerate_cameras import enumerate_cameras  # Add this import | from cv2_enumerate_cameras import enumerate_cameras | ||||||
| from PIL import Image, ImageOps | from PIL import Image, ImageOps | ||||||
| import time | import time | ||||||
| import json | import json | ||||||
|  | @ -252,6 +252,19 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|     ) |     ) | ||||||
|     enhancer_switch.place(relx=0.1, rely=0.70) |     enhancer_switch.place(relx=0.1, rely=0.70) | ||||||
| 
 | 
 | ||||||
|  |     keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio) | ||||||
|  |     keep_audio_switch = ctk.CTkSwitch( | ||||||
|  |         root, | ||||||
|  |         text=_("Keep audio"), | ||||||
|  |         variable=keep_audio_value, | ||||||
|  |         cursor="hand2", | ||||||
|  |         command=lambda: ( | ||||||
|  |             setattr(modules.globals, "keep_audio", keep_audio_value.get()), | ||||||
|  |             save_switch_states(), | ||||||
|  |         ), | ||||||
|  |     ) | ||||||
|  |     keep_audio_switch.place(relx=0.1, rely=0.75) | ||||||
|  | 
 | ||||||
|     # Additional Options (Middle Right) |     # Additional Options (Middle Right) | ||||||
|     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( |     mouth_mask_switch = ctk.CTkSwitch( | ||||||
|  | @ -273,20 +286,31 @@ def create_root(start: Callable[[], None], destroy: Callable[[], None]) -> ctk.C | ||||||
|             modules.globals, "show_mouth_mask_box", show_mouth_mask_box_var.get() |             modules.globals, "show_mouth_mask_box", show_mouth_mask_box_var.get() | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     show_mouth_mask_box_switch.place(relx=0.6, rely=0.6) |     show_mouth_mask_box_switch.place(relx=0.6, rely=0.60) | ||||||
| 
 | 
 | ||||||
|     keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio) |     # Add eyes mask switch | ||||||
|     keep_audio_switch = ctk.CTkSwitch( |     eyes_mask_var = ctk.BooleanVar(value=modules.globals.eyes_mask) | ||||||
|  |     eyes_mask_switch = ctk.CTkSwitch( | ||||||
|         root, |         root, | ||||||
|         text=_("Keep audio"), |         text=_("Eyes Mask"), | ||||||
|         variable=keep_audio_value, |         variable=eyes_mask_var, | ||||||
|         cursor="hand2", |         cursor="hand2", | ||||||
|         command=lambda: ( |         command=lambda: setattr(modules.globals, "eyes_mask", eyes_mask_var.get()), | ||||||
|             setattr(modules.globals, "keep_audio", keep_audio_value.get()), |     ) | ||||||
|             save_switch_states(), |     eyes_mask_switch.place(relx=0.6, rely=0.65) | ||||||
|  | 
 | ||||||
|  |     # Add show eyes mask box switch | ||||||
|  |     show_eyes_mask_box_var = ctk.BooleanVar(value=modules.globals.show_eyes_mask_box) | ||||||
|  |     show_eyes_mask_box_switch = ctk.CTkSwitch( | ||||||
|  |         root, | ||||||
|  |         text=_("Show Eyes Mask Box"), | ||||||
|  |         variable=show_eyes_mask_box_var, | ||||||
|  |         cursor="hand2", | ||||||
|  |         command=lambda: setattr( | ||||||
|  |             modules.globals, "show_eyes_mask_box", show_eyes_mask_box_var.get() | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     keep_audio_switch.place(relx=0.6, rely=0.65) |     show_eyes_mask_box_switch.place(relx=0.6, rely=0.70) | ||||||
| 
 | 
 | ||||||
|     # Add show FPS switch |     # Add show FPS switch | ||||||
|     show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps) |     show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps) | ||||||
|  | @ -300,7 +324,7 @@ 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.70) |     show_fps_switch.place(relx=0.6, rely=0.75) | ||||||
| 
 | 
 | ||||||
|     # Main Control Buttons (Bottom) |     # Main Control Buttons (Bottom) | ||||||
|     start_button = ctk.CTkButton( |     start_button = ctk.CTkButton( | ||||||
|  | @ -767,8 +791,7 @@ def update_preview(frame_number: int = 0) -> None: | ||||||
|                 modules.globals.frame_processors |                 modules.globals.frame_processors | ||||||
|         ): |         ): | ||||||
|             temp_frame = frame_processor.process_frame( |             temp_frame = frame_processor.process_frame( | ||||||
|                 get_one_face(cv2.imread(modules.globals.source_path)), temp_frame |                 get_one_face(cv2.imread(modules.globals.source_path)), temp_frame) | ||||||
|             ) |  | ||||||
|         image = Image.fromarray(cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB)) |         image = Image.fromarray(cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB)) | ||||||
|         image = ImageOps.contain( |         image = ImageOps.contain( | ||||||
|             image, (PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT), Image.LANCZOS |             image, (PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT), Image.LANCZOS | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue