Compare commits

...

16 Commits

Author SHA1 Message Date
Kenneth Estanislao b3c4ed9250 optimization with mac
Hoping this would solve the mac issues, if you're a mac user, please report if there is an improvement
2025-11-16 20:09:12 +08:00
Kenneth Estanislao 2411f1e9b1 Update Quick Start section to v2.3c 2025-11-10 15:13:04 +08:00
Kenneth Estanislao 96224efe07 Update version in Quick Start section of README 2025-11-09 23:19:40 +08:00
Kenneth Estanislao 8e05142cda Merge pull request #1573 from phieudu241/main
fix: fix typos which caused "No faces found in target" issue
2025-11-09 19:18:00 +08:00
Dung Le a007db2ffa fix: fix typos which cause "No faces found in target" issue 2025-11-09 15:51:14 +07:00
Kenneth Estanislao 475740b22b Update IShowSpeed quote in README.md 2025-11-08 05:21:19 +08:00
Kenneth Estanislao 600ce34c8d Add new quote from IShowSpeed to README 2025-11-08 05:17:54 +08:00
Kenneth Estanislao 865ab3ca02 Add Henry as a major contributor in credits 2025-11-08 05:08:55 +08:00
Kenneth Estanislao 178578b034 Merge pull request #1565 from aic1x/patch-1
Fix typo in source_target_map variable name
2025-11-06 00:08:41 +08:00
AiC b53132f3a4 Fix typo in source_target_map variable name 2025-11-04 21:16:26 +01:00
Kenneth Estanislao 00da11b491 Merge pull request #1529 from laurensius/main
Add Indonesian localization file
2025-11-04 17:46:27 +08:00
Kenneth Estanislao b82fdc3f31 Update face_swapper.py
Optimization based on @SanderGi (experimental) to improve mac FPS
2025-10-28 19:16:40 +08:00
Kenneth Estanislao 3ffa9f38b0 Add pygrabber to requirements 2025-10-16 01:32:43 +08:00
Kenneth Estanislao 3f98d4c826 Update torch and torchvision versions in requirements 2025-10-13 00:50:26 +08:00
Kenneth Estanislao 9b6ca286b9 Update Quick Start section to version 2.3
Updated the Quickstart version to 2.3
2025-10-12 23:44:21 +08:00
Laurensius Dede Suhardiman 0999c0447e Add Indonesian localization file
Create new JSON file for id locale
2025-10-11 23:29:41 +07:00
6 changed files with 160 additions and 46 deletions
+3 -1
View File
@@ -30,7 +30,7 @@ By using this software, you agree to these terms and commit to using it in a man
Users are expected to use this software responsibly and legally. If using a real person's face, obtain their consent and clearly label any output as a deepfake when sharing online. We are not responsible for end-user actions. Users are expected to use this software responsibly and legally. If using a real person's face, obtain their consent and clearly label any output as a deepfake when sharing online. We are not responsible for end-user actions.
## Exclusive v2.2 Quick Start - Pre-built (Windows/Mac Silicon) ## Exclusive v2.3c Quick Start - Pre-built (Windows/Mac Silicon)
<a href="https://deeplivecam.net/index.php/quickstart"> <img src="media/Download.png" width="285" height="77" /> <a href="https://deeplivecam.net/index.php/quickstart"> <img src="media/Download.png" width="285" height="77" />
@@ -354,11 +354,13 @@ Looking for a CLI mode? Using the -s/--source argument will make the run program
- [*"Alright look look look, now look chat, we can do any face we want to look like chat"*](https://www.youtube.com/live/mFsCe7AIxq8?feature=shared&t=2686) - IShowSpeed - [*"Alright look look look, now look chat, we can do any face we want to look like chat"*](https://www.youtube.com/live/mFsCe7AIxq8?feature=shared&t=2686) - IShowSpeed
- [*"They do a pretty good job matching poses, expression and even the lighting"*](https://www.youtube.com/watch?v=wnCghLjqv3s&t=551s) - TechLinked (LTT) - [*"They do a pretty good job matching poses, expression and even the lighting"*](https://www.youtube.com/watch?v=wnCghLjqv3s&t=551s) - TechLinked (LTT)
- [*"Als Sean Connery an der Redaktionskonferenz teilnahm"*](https://www.golem.de/news/deepfakes-als-sean-connery-an-der-redaktionskonferenz-teilnahm-2408-188172.html) - Golem.de (German) - [*"Als Sean Connery an der Redaktionskonferenz teilnahm"*](https://www.golem.de/news/deepfakes-als-sean-connery-an-der-redaktionskonferenz-teilnahm-2408-188172.html) - Golem.de (German)
- [*"What the F***! Why do I look like Vinny Jr? I look exactly like Vinny Jr!? No, this shit is crazy! Bro This is F*** Crazy! "*](https://youtu.be/JbUPRmXRUtE?t=3964) - IShowSpeed
## Credits ## Credits
- [ffmpeg](https://ffmpeg.org/): for making video-related operations easy - [ffmpeg](https://ffmpeg.org/): for making video-related operations easy
- [Henry](https://github.com/henryruhs): One of the major contributor in this repo
- [deepinsight](https://github.com/deepinsight): for their [insightface](https://github.com/deepinsight/insightface) project which provided a well-made library and models. Please be reminded that the [use of the model is for non-commercial research purposes only](https://github.com/deepinsight/insightface?tab=readme-ov-file#license). - [deepinsight](https://github.com/deepinsight): for their [insightface](https://github.com/deepinsight/insightface) project which provided a well-made library and models. Please be reminded that the [use of the model is for non-commercial research purposes only](https://github.com/deepinsight/insightface?tab=readme-ov-file#license).
- [havok2-htwo](https://github.com/havok2-htwo): for sharing the code for webcam - [havok2-htwo](https://github.com/havok2-htwo): for sharing the code for webcam
- [GosuDRM](https://github.com/GosuDRM): for the open version of roop - [GosuDRM](https://github.com/GosuDRM): for the open version of roop
+45
View File
@@ -0,0 +1,45 @@
{
"Source x Target Mapper": "Pemetaan Sumber x Target",
"select a source image": "Pilih gambar sumber",
"Preview": "Pratinjau",
"select a target image or video": "Pilih gambar atau video target",
"save image output file": "Simpan file keluaran gambar",
"save video output file": "Simpan file keluaran video",
"select a target image": "Pilih gambar target",
"source": "Sumber",
"Select a target": "Pilih target",
"Select a face": "Pilih wajah",
"Keep audio": "Pertahankan audio",
"Face Enhancer": "Peningkat wajah",
"Many faces": "Banyak wajah",
"Show FPS": "Tampilkan FPS",
"Keep fps": "Pertahankan FPS",
"Keep frames": "Pertahankan frame",
"Fix Blueish Cam": "Perbaiki kamera kebiruan",
"Mouth Mask": "Masker mulut",
"Show Mouth Mask Box": "Tampilkan kotak masker mulut",
"Start": "Mulai",
"Live": "Langsung",
"Destroy": "Hentikan",
"Map faces": "Petakan wajah",
"Processing...": "Sedang memproses...",
"Processing succeed!": "Pemrosesan berhasil!",
"Processing ignored!": "Pemrosesan diabaikan!",
"Failed to start camera": "Gagal memulai kamera",
"Please complete pop-up or close it.": "Harap selesaikan atau tutup pop-up.",
"Getting unique faces": "Mengambil wajah unik",
"Please select a source image first": "Silakan pilih gambar sumber terlebih dahulu",
"No faces found in target": "Tidak ada wajah ditemukan pada target",
"Add": "Tambah",
"Clear": "Bersihkan",
"Submit": "Kirim",
"Select source image": "Pilih gambar sumber",
"Select target image": "Pilih gambar target",
"Please provide mapping!": "Harap tentukan pemetaan!",
"At least 1 source with target is required!": "Minimal 1 sumber dengan target diperlukan!",
"Face could not be detected in last upload!": "Wajah tidak dapat terdeteksi pada unggahan terakhir!",
"Select Camera:": "Pilih Kamera:",
"All mappings cleared!": "Semua pemetaan telah dibersihkan!",
"Mappings successfully submitted!": "Pemetaan berhasil dikirim!",
"Source x Target Mapper is already open.": "Pemetaan Sumber x Target sudah terbuka."
}
+3 -3
View File
@@ -12,7 +12,7 @@ file_types = [
] ]
# Face Mapping Data # Face Mapping Data
souce_target_map: List[Dict[str, Any]] = [] # Stores detailed map for image/video processing source_target_map: List[Dict[str, Any]] = [] # Stores detailed map for image/video processing
simple_map: Dict[str, Any] = {} # Stores simplified map (embeddings/faces) for live/simple mode simple_map: Dict[str, Any] = {} # Stores simplified map (embeddings/faces) for live/simple mode
# Paths # Paths
@@ -26,7 +26,7 @@ keep_fps: bool = True
keep_audio: bool = True keep_audio: bool = True
keep_frames: bool = False keep_frames: bool = False
many_faces: bool = False # Process all detected faces with default source many_faces: bool = False # Process all detected faces with default source
map_faces: bool = False # Use souce_target_map or simple_map for specific swaps map_faces: bool = False # Use source_target_map or simple_map for specific swaps
color_correction: bool = False # Enable color correction (implementation specific) color_correction: bool = False # Enable color correction (implementation specific)
nsfw_filter: bool = False nsfw_filter: bool = False
@@ -68,4 +68,4 @@ enable_interpolation: bool = True # Toggle temporal smoothing
interpolation_weight: float = 0 # Blend weight for current frame (0.0-1.0). Lower=smoother. interpolation_weight: float = 0 # Blend weight for current frame (0.0-1.0). Lower=smoother.
# --- END: Added for Frame Interpolation --- # --- END: Added for Frame Interpolation ---
# --- END OF FILE globals.py --- # --- END OF FILE globals.py ---
+101 -35
View File
@@ -1,8 +1,9 @@
from typing import Any, List from typing import Any, List, Optional
import cv2 import cv2
import insightface import insightface
import threading import threading
import numpy as np import numpy as np
import platform
import modules.globals import modules.globals
import modules.processors.frame.core import modules.processors.frame.core
from modules.core import update_status from modules.core import update_status
@@ -14,9 +15,9 @@ from modules.utilities import (
is_video, is_video,
) )
from modules.cluster_analysis import find_closest_centroid from modules.cluster_analysis import find_closest_centroid
# Removed modules.globals.face_swapper_enabled - assuming controlled elsewhere or implicitly true if used
# Removed modules.globals.opacity - accessed via getattr
import os import os
from collections import deque
import time
FACE_SWAPPER = None FACE_SWAPPER = None
THREAD_LOCK = threading.Lock() THREAD_LOCK = threading.Lock()
@@ -26,6 +27,16 @@ NAME = "DLC.FACE-SWAPPER"
PREVIOUS_FRAME_RESULT = None # Stores the final processed frame from the previous step PREVIOUS_FRAME_RESULT = None # Stores the final processed frame from the previous step
# --- END: Added for Interpolation --- # --- END: Added for Interpolation ---
# --- START: Mac M1-M5 Optimizations ---
IS_APPLE_SILICON = platform.system() == 'Darwin' and platform.machine() == 'arm64'
FRAME_CACHE = deque(maxlen=3) # Cache for frame reuse
FACE_DETECTION_CACHE = {} # Cache face detections
LAST_DETECTION_TIME = 0
DETECTION_INTERVAL = 0.033 # ~30 FPS detection rate for live mode
FRAME_SKIP_COUNTER = 0
ADAPTIVE_QUALITY = True
# --- END: Mac M1-M5 Optimizations ---
abs_dir = os.path.dirname(os.path.abspath(__file__)) abs_dir = os.path.dirname(os.path.abspath(__file__))
models_dir = os.path.join( models_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models" os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models"
@@ -63,22 +74,40 @@ def get_face_swapper() -> Any:
with THREAD_LOCK: with THREAD_LOCK:
if FACE_SWAPPER is None: if FACE_SWAPPER is None:
model_path = os.path.join(models_dir, "inswapper_128_fp16.onnx") model_name = "inswapper_128.onnx"
if "CUDAExecutionProvider" in modules.globals.execution_providers:
model_name = "inswapper_128_fp16.onnx"
model_path = os.path.join(models_dir, model_name)
update_status(f"Loading face swapper model from: {model_path}", NAME) update_status(f"Loading face swapper model from: {model_path}", NAME)
try: try:
# Ensure the providers list is correctly passed # Optimized provider configuration for Apple Silicon
providers = modules.globals.execution_providers providers_config = []
# print(f"Attempting to load model with providers: {providers}") # Debug print for p in modules.globals.execution_providers:
if p == "CoreMLExecutionProvider" and IS_APPLE_SILICON:
# Enhanced CoreML configuration for M1-M5
providers_config.append((
"CoreMLExecutionProvider",
{
"ModelFormat": "MLProgram",
"MLComputeUnits": "ALL", # Use Neural Engine + GPU + CPU
"SpecializationStrategy": "FastPrediction",
"AllowLowPrecisionAccumulationOnGPU": 1,
"EnableOnSubgraphs": 1,
"RequireStaticShapes": 0,
"MaximumCacheSize": 1024 * 1024 * 512, # 512MB cache
}
))
else:
providers_config.append(p)
FACE_SWAPPER = insightface.model_zoo.get_model( FACE_SWAPPER = insightface.model_zoo.get_model(
model_path, providers=providers model_path,
providers=providers_config,
) )
update_status("Face swapper model loaded successfully.", NAME) update_status("Face swapper model loaded successfully.", NAME)
except Exception as e: except Exception as e:
update_status(f"Error loading face swapper model: {e}", NAME) update_status(f"Error loading face swapper model: {e}", NAME)
# print traceback maybe? FACE_SWAPPER = None
# import traceback
# traceback.print_exc()
FACE_SWAPPER = None # Ensure it remains None on failure
return None return None
return FACE_SWAPPER return FACE_SWAPPER
@@ -87,19 +116,22 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
face_swapper = get_face_swapper() face_swapper = get_face_swapper()
if face_swapper is None: if face_swapper is None:
update_status("Face swapper model not loaded or failed to load. Skipping swap.", NAME) update_status("Face swapper model not loaded or failed to load. Skipping swap.", NAME)
return temp_frame # Return original frame if model failed or not loaded 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
original_frame = temp_frame.copy() original_frame = temp_frame.copy()
# --- Pre-swap Input Check (Optional but good practice) --- # Pre-swap Input Check with optimization
if temp_frame.dtype != np.uint8: if temp_frame.dtype != np.uint8:
# print(f"Warning: Input frame is {temp_frame.dtype}, converting to uint8 before swap.")
temp_frame = np.clip(temp_frame, 0, 255).astype(np.uint8) temp_frame = np.clip(temp_frame, 0, 255).astype(np.uint8)
# --- End Input Check ---
# Apply the face swap # Apply the face swap with optimized memory handling
try: try:
# For Apple Silicon, use optimized inference
if IS_APPLE_SILICON:
# Ensure contiguous memory layout for better performance
temp_frame = np.ascontiguousarray(temp_frame)
swapped_frame_raw = face_swapper.get( swapped_frame_raw = face_swapper.get(
temp_frame, target_face, source_face, paste_back=True temp_frame, target_face, source_face, paste_back=True
) )
@@ -177,14 +209,50 @@ def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
return final_swapped_frame return final_swapped_frame
# --- START: Mac M1-M5 Optimized Face Detection ---
def get_faces_optimized(frame: Frame, use_cache: bool = True) -> Optional[List[Face]]:
"""Optimized face detection for live mode on Apple Silicon"""
global LAST_DETECTION_TIME, FACE_DETECTION_CACHE
if not use_cache or not IS_APPLE_SILICON:
# Standard detection
if modules.globals.many_faces:
return get_many_faces(frame)
else:
face = get_one_face(frame)
return [face] if face else None
# Adaptive detection rate for live mode
current_time = time.time()
time_since_last = current_time - LAST_DETECTION_TIME
# Skip detection if too soon (adaptive frame skipping)
if time_since_last < DETECTION_INTERVAL and FACE_DETECTION_CACHE:
return FACE_DETECTION_CACHE.get('faces')
# Perform detection
LAST_DETECTION_TIME = current_time
if modules.globals.many_faces:
faces = get_many_faces(frame)
else:
face = get_one_face(frame)
faces = [face] if face else None
# Cache results
FACE_DETECTION_CACHE['faces'] = faces
FACE_DETECTION_CACHE['timestamp'] = current_time
return faces
# --- END: Mac M1-M5 Optimized Face Detection ---
# --- START: Helper function for interpolation and sharpening --- # --- START: Helper function for interpolation and sharpening ---
def apply_post_processing(current_frame: Frame, swapped_face_bboxes: List[np.ndarray]) -> Frame: def apply_post_processing(current_frame: Frame, swapped_face_bboxes: List[np.ndarray]) -> Frame:
"""Applies sharpening and interpolation.""" """Applies sharpening and interpolation with Apple Silicon optimizations."""
global PREVIOUS_FRAME_RESULT global PREVIOUS_FRAME_RESULT
processed_frame = current_frame.copy() processed_frame = current_frame.copy()
# 1. Apply Sharpening (if enabled) # 1. Apply Sharpening (if enabled) with optimized kernel for Apple Silicon
sharpness_value = getattr(modules.globals, "sharpness", 0.0) sharpness_value = getattr(modules.globals, "sharpness", 0.0)
if sharpness_value > 0.0 and swapped_face_bboxes: if sharpness_value > 0.0 and swapped_face_bboxes:
height, width = processed_frame.shape[:2] height, width = processed_frame.shape[:2]
@@ -207,23 +275,21 @@ def apply_post_processing(current_frame: Frame, swapped_face_bboxes: List[np.nda
continue continue
face_region = processed_frame[y1:y2, x1:x2] face_region = processed_frame[y1:y2, x1:x2]
if face_region.size == 0: continue # Skip empty regions if face_region.size == 0: continue
# Apply sharpening using addWeighted for smoother control # Apply sharpening with optimized parameters for Apple Silicon
# Use try-except for GaussianBlur and addWeighted as they can fail on invalid inputs
try: try:
blurred = cv2.GaussianBlur(face_region, (0, 0), 3) # sigma=3, kernel size auto # Use smaller sigma for faster processing on Apple Silicon
sharpened_region = cv2.addWeighted( sigma = 2 if IS_APPLE_SILICON else 3
blurred = cv2.GaussianBlur(face_region, (0, 0), sigma)
sharpened_region = cv2.addWeighted(
face_region, 1.0 + sharpness_value, face_region, 1.0 + sharpness_value,
blurred, -sharpness_value, blurred, -sharpness_value,
0 0
) )
# Ensure the sharpened region doesn't have invalid values sharpened_region = np.clip(sharpened_region, 0, 255).astype(np.uint8)
sharpened_region = np.clip(sharpened_region, 0, 255).astype(np.uint8) processed_frame[y1:y2, x1:x2] = sharpened_region
processed_frame[y1:y2, x1:x2] = sharpened_region except cv2.error:
except cv2.error as sharpen_e:
# print(f"Warning: OpenCV error during sharpening: {sharpen_e} for bbox {bbox}") # Debug
# Skip sharpening for this region if it fails
pass pass
@@ -323,7 +389,7 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
source_target_pairs = [] source_target_pairs = []
# Ensure maps exist before accessing them # Ensure maps exist before accessing them
souce_target_map = getattr(modules.globals, "souce_target_map", None) source_target_map = getattr(modules.globals, "source_target_map", None)
simple_map = getattr(modules.globals, "simple_map", None) simple_map = getattr(modules.globals, "simple_map", None)
# Check if target is a file path (image or video) or live stream # Check if target is a file path (image or video) or live stream
@@ -331,11 +397,11 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
if is_file_target: if is_file_target:
# Processing specific image or video file with pre-analyzed maps # Processing specific image or video file with pre-analyzed maps
if souce_target_map: if source_target_map:
if modules.globals.many_faces: if modules.globals.many_faces:
source_face = default_source_face() # Use default source for all targets source_face = default_source_face() # Use default source for all targets
if source_face: if source_face:
for map_data in souce_target_map: for map_data in source_target_map:
if is_image(modules.globals.target_path): if is_image(modules.globals.target_path):
target_info = map_data.get("target", {}) target_info = map_data.get("target", {})
if target_info: # Check if target info exists if target_info: # Check if target info exists
@@ -353,7 +419,7 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
for target_face in faces_in_frame: for target_face in faces_in_frame:
source_target_pairs.append((source_face, target_face)) source_target_pairs.append((source_face, target_face))
else: # Single face or specific mapping else: # Single face or specific mapping
for map_data in souce_target_map: for map_data in source_target_map:
source_info = map_data.get("source", {}) source_info = map_data.get("source", {})
if not source_info: continue # Skip if no source info if not source_info: continue # Skip if no source info
source_face = source_info.get("face") source_face = source_info.get("face")
+5 -5
View File
@@ -465,7 +465,7 @@ def analyze_target(start: Callable[[], None], root: ctk.CTk):
return return
if modules.globals.map_faces: if modules.globals.map_faces:
modules.globals.souce_target_map = [] modules.globals.source_target_map = []
if is_image(modules.globals.target_path): if is_image(modules.globals.target_path):
update_status("Getting unique faces") update_status("Getting unique faces")
@@ -474,8 +474,8 @@ def analyze_target(start: Callable[[], None], root: ctk.CTk):
update_status("Getting unique faces") update_status("Getting unique faces")
get_unique_faces_from_target_video() get_unique_faces_from_target_video()
if len(modules.globals.souce_target_map) > 0: if len(modules.globals.source_target_map) > 0:
create_source_target_popup(start, root, modules.globals.souce_target_map) create_source_target_popup(start, root, modules.globals.source_target_map)
else: else:
update_status("No faces found in target") update_status("No faces found in target")
else: else:
@@ -855,9 +855,9 @@ def webcam_preview(root: ctk.CTk, camera_index: int):
return return
create_webcam_preview(camera_index) create_webcam_preview(camera_index)
else: else:
modules.globals.souce_target_map = [] modules.globals.source_target_map = []
create_source_target_popup_for_webcam( create_source_target_popup_for_webcam(
root, modules.globals.souce_target_map, camera_index root, modules.globals.source_target_map, camera_index
) )
+3 -2
View File
@@ -11,7 +11,7 @@ tk==0.1.0
customtkinter==5.2.2 customtkinter==5.2.2
pillow==11.1.0 pillow==11.1.0
torch; sys_platform != 'darwin' torch; sys_platform != 'darwin'
torch==2.7.1+cu128; sys_platform == 'darwin' torch==2.8.0+cu128; sys_platform == 'darwin'
torchvision; sys_platform != 'darwin' torchvision; sys_platform != 'darwin'
torchvision==0.20.1; sys_platform == 'darwin' torchvision==0.20.1; sys_platform == 'darwin'
onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64' onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64'
@@ -20,4 +20,5 @@ tensorflow; sys_platform != 'darwin'
opennsfw2==0.10.2 opennsfw2==0.10.2
protobuf==4.25.1 protobuf==4.25.1
git+https://github.com/xinntao/BasicSR.git@master git+https://github.com/xinntao/BasicSR.git@master
git+https://github.com/TencentARC/GFPGAN.git@master git+https://github.com/TencentARC/GFPGAN.git@master
pygrabber