1- from typing import Any , List
1+ from typing import Any , List , Optional
22import cv2
33import insightface
44import threading
55import numpy as np
6+ import platform
67import modules .globals
78import modules .processors .frame .core
89from modules .core import update_status
1415 is_video ,
1516)
1617from modules .cluster_analysis import find_closest_centroid
17- # Removed modules.globals.face_swapper_enabled - assuming controlled elsewhere or implicitly true if used
18- # Removed modules.globals.opacity - accessed via getattr
1918import os
19+ from collections import deque
20+ import time
2021
2122FACE_SWAPPER = None
2223THREAD_LOCK = threading .Lock ()
2627PREVIOUS_FRAME_RESULT = None # Stores the final processed frame from the previous step
2728# --- END: Added for Interpolation ---
2829
30+ # --- START: Mac M1-M5 Optimizations ---
31+ IS_APPLE_SILICON = platform .system () == 'Darwin' and platform .machine () == 'arm64'
32+ FRAME_CACHE = deque (maxlen = 3 ) # Cache for frame reuse
33+ FACE_DETECTION_CACHE = {} # Cache face detections
34+ LAST_DETECTION_TIME = 0
35+ DETECTION_INTERVAL = 0.033 # ~30 FPS detection rate for live mode
36+ FRAME_SKIP_COUNTER = 0
37+ ADAPTIVE_QUALITY = True
38+ # --- END: Mac M1-M5 Optimizations ---
39+
2940abs_dir = os .path .dirname (os .path .abspath (__file__ ))
3041models_dir = os .path .join (
3142 os .path .dirname (os .path .dirname (os .path .dirname (abs_dir ))), "models"
@@ -69,34 +80,34 @@ def get_face_swapper() -> Any:
6980 model_path = os .path .join (models_dir , model_name )
7081 update_status (f"Loading face swapper model from: { model_path } " , NAME )
7182 try :
72- # Ensure the providers list is correctly passed
73- # Apply CoreML optimization for Mac systems
83+ # Optimized provider configuration for Apple Silicon
84+ providers_config = []
85+ for p in modules .globals .execution_providers :
86+ if p == "CoreMLExecutionProvider" and IS_APPLE_SILICON :
87+ # Enhanced CoreML configuration for M1-M5
88+ providers_config .append ((
89+ "CoreMLExecutionProvider" ,
90+ {
91+ "ModelFormat" : "MLProgram" ,
92+ "MLComputeUnits" : "ALL" , # Use Neural Engine + GPU + CPU
93+ "SpecializationStrategy" : "FastPrediction" ,
94+ "AllowLowPrecisionAccumulationOnGPU" : 1 ,
95+ "EnableOnSubgraphs" : 1 ,
96+ "RequireStaticShapes" : 0 ,
97+ "MaximumCacheSize" : 1024 * 1024 * 512 , # 512MB cache
98+ }
99+ ))
100+ else :
101+ providers_config .append (p )
102+
74103 FACE_SWAPPER = insightface .model_zoo .get_model (
75104 model_path ,
76- providers = [
77- (
78- (
79- "CoreMLExecutionProvider" ,
80- {
81- "ModelFormat" : "MLProgram" ,
82- "MLComputeUnits" : "CPUAndGPU" ,
83- "SpecializationStrategy" : "FastPrediction" ,
84- "AllowLowPrecisionAccumulationOnGPU" : 1 ,
85- },
86- )
87- if p == "CoreMLExecutionProvider"
88- else p
89- )
90- for p in modules .globals .execution_providers
91- ],
105+ providers = providers_config ,
92106 )
93107 update_status ("Face swapper model loaded successfully." , NAME )
94108 except Exception as e :
95109 update_status (f"Error loading face swapper model: { e } " , NAME )
96- # print traceback maybe?
97- # import traceback
98- # traceback.print_exc()
99- FACE_SWAPPER = None # Ensure it remains None on failure
110+ FACE_SWAPPER = None
100111 return None
101112 return FACE_SWAPPER
102113
@@ -105,19 +116,22 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
105116 face_swapper = get_face_swapper ()
106117 if face_swapper is None :
107118 update_status ("Face swapper model not loaded or failed to load. Skipping swap." , NAME )
108- return temp_frame # Return original frame if model failed or not loaded
119+ return temp_frame
109120
110121 # Store a copy of the original frame before swapping for opacity blending
111122 original_frame = temp_frame .copy ()
112123
113- # --- Pre-swap Input Check (Optional but good practice) ---
124+ # Pre-swap Input Check with optimization
114125 if temp_frame .dtype != np .uint8 :
115- # print(f"Warning: Input frame is {temp_frame.dtype}, converting to uint8 before swap.")
116126 temp_frame = np .clip (temp_frame , 0 , 255 ).astype (np .uint8 )
117- # --- End Input Check ---
118127
119- # Apply the face swap
128+ # Apply the face swap with optimized memory handling
120129 try :
130+ # For Apple Silicon, use optimized inference
131+ if IS_APPLE_SILICON :
132+ # Ensure contiguous memory layout for better performance
133+ temp_frame = np .ascontiguousarray (temp_frame )
134+
121135 swapped_frame_raw = face_swapper .get (
122136 temp_frame , target_face , source_face , paste_back = True
123137 )
@@ -195,14 +209,50 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
195209 return final_swapped_frame
196210
197211
212+ # --- START: Mac M1-M5 Optimized Face Detection ---
213+ def get_faces_optimized (frame : Frame , use_cache : bool = True ) -> Optional [List [Face ]]:
214+ """Optimized face detection for live mode on Apple Silicon"""
215+ global LAST_DETECTION_TIME , FACE_DETECTION_CACHE
216+
217+ if not use_cache or not IS_APPLE_SILICON :
218+ # Standard detection
219+ if modules .globals .many_faces :
220+ return get_many_faces (frame )
221+ else :
222+ face = get_one_face (frame )
223+ return [face ] if face else None
224+
225+ # Adaptive detection rate for live mode
226+ current_time = time .time ()
227+ time_since_last = current_time - LAST_DETECTION_TIME
228+
229+ # Skip detection if too soon (adaptive frame skipping)
230+ if time_since_last < DETECTION_INTERVAL and FACE_DETECTION_CACHE :
231+ return FACE_DETECTION_CACHE .get ('faces' )
232+
233+ # Perform detection
234+ LAST_DETECTION_TIME = current_time
235+ if modules .globals .many_faces :
236+ faces = get_many_faces (frame )
237+ else :
238+ face = get_one_face (frame )
239+ faces = [face ] if face else None
240+
241+ # Cache results
242+ FACE_DETECTION_CACHE ['faces' ] = faces
243+ FACE_DETECTION_CACHE ['timestamp' ] = current_time
244+
245+ return faces
246+ # --- END: Mac M1-M5 Optimized Face Detection ---
247+
198248# --- START: Helper function for interpolation and sharpening ---
199249def apply_post_processing (current_frame : Frame , swapped_face_bboxes : List [np .ndarray ]) -> Frame :
200- """Applies sharpening and interpolation."""
250+ """Applies sharpening and interpolation with Apple Silicon optimizations ."""
201251 global PREVIOUS_FRAME_RESULT
202252
203253 processed_frame = current_frame .copy ()
204254
205- # 1. Apply Sharpening (if enabled)
255+ # 1. Apply Sharpening (if enabled) with optimized kernel for Apple Silicon
206256 sharpness_value = getattr (modules .globals , "sharpness" , 0.0 )
207257 if sharpness_value > 0.0 and swapped_face_bboxes :
208258 height , width = processed_frame .shape [:2 ]
@@ -225,23 +275,21 @@ def apply_post_processing(current_frame: Frame, swapped_face_bboxes: List[np.nda
225275 continue
226276
227277 face_region = processed_frame [y1 :y2 , x1 :x2 ]
228- if face_region .size == 0 : continue # Skip empty regions
278+ if face_region .size == 0 : continue
229279
230- # Apply sharpening using addWeighted for smoother control
231- # Use try-except for GaussianBlur and addWeighted as they can fail on invalid inputs
280+ # Apply sharpening with optimized parameters for Apple Silicon
232281 try :
233- blurred = cv2 .GaussianBlur (face_region , (0 , 0 ), 3 ) # sigma=3, kernel size auto
234- sharpened_region = cv2 .addWeighted (
282+ # Use smaller sigma for faster processing on Apple Silicon
283+ sigma = 2 if IS_APPLE_SILICON else 3
284+ blurred = cv2 .GaussianBlur (face_region , (0 , 0 ), sigma )
285+ sharpened_region = cv2 .addWeighted (
235286 face_region , 1.0 + sharpness_value ,
236287 blurred , - sharpness_value ,
237288 0
238- )
239- # Ensure the sharpened region doesn't have invalid values
240- sharpened_region = np .clip (sharpened_region , 0 , 255 ).astype (np .uint8 )
241- processed_frame [y1 :y2 , x1 :x2 ] = sharpened_region
242- except cv2 .error as sharpen_e :
243- # print(f"Warning: OpenCV error during sharpening: {sharpen_e} for bbox {bbox}") # Debug
244- # Skip sharpening for this region if it fails
289+ )
290+ sharpened_region = np .clip (sharpened_region , 0 , 255 ).astype (np .uint8 )
291+ processed_frame [y1 :y2 , x1 :x2 ] = sharpened_region
292+ except cv2 .error :
245293 pass
246294
247295
0 commit comments