VanNguyen1214 commited on
Commit
e9eaca6
·
verified ·
1 Parent(s): 8b68d0a

Upload 23 files

Browse files
apply_color_transfer.py CHANGED
@@ -88,8 +88,7 @@ def histogram_matching_with_pixels(source, reference_pixels):
88
 
89
  def apply_color_transfer_to_output(swapped_img, original_bg, opacity=0.7, color_region=None):
90
  """
91
- Apply color transfer to make the swapped face match the original background lighting.
92
- Automatically uses face+forehead region (excluding hair) as color reference by default.
93
 
94
  Args:
95
  swapped_img: PIL Image - The image after face swapping
@@ -98,9 +97,11 @@ def apply_color_transfer_to_output(swapped_img, original_bg, opacity=0.7, color_
98
  color_region: tuple or None - (x, y, width, height) for specific color sampling region
99
 
100
  Returns:
101
- PIL Image - The color-corrected result
102
  """
103
  try:
 
 
104
  # Clamp opacity to valid range
105
  opacity = max(0.0, min(1.0, opacity))
106
 
@@ -108,32 +109,50 @@ def apply_color_transfer_to_output(swapped_img, original_bg, opacity=0.7, color_
108
  swapped_np = np.array(swapped_img.convert('RGB'))
109
  original_np = np.array(original_bg.convert('RGB'))
110
 
111
- # Try advanced face+forehead detection first (excluding hair)
 
 
 
112
  face_pixels = get_face_forehead_region_no_hair(original_bg)
113
 
114
- if face_pixels is not None:
115
- print(f"✅ Using advanced face+forehead detection: {len(face_pixels)} pixels")
116
- # Apply color transfer using extracted face+forehead pixels
117
- color_corrected = histogram_matching_with_pixels(swapped_np, face_pixels)
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  elif color_region is not None:
120
  # Fallback to manual region if provided
121
  x, y, w, h = color_region
122
  reference_region = original_np[y:y+h, x:x+w]
123
- if reference_region.size > 0:
124
- print(f"⚙️ Using manual region: {color_region}")
125
- color_corrected = histogram_matching_with_region(swapped_np, original_np, reference_region)
 
 
 
 
 
 
 
126
  else:
127
- print("⚠️ Invalid manual region, using full image")
128
- color_corrected = histogram_matching(swapped_np, original_np)
129
 
130
  else:
131
- # Final fallback to full image
132
- print("⚠️ No face detection, using full image color transfer")
133
- color_corrected = histogram_matching(swapped_np, original_np)
134
-
135
- # Blend original and color-corrected image based on opacity
136
- result = blend_images(swapped_np, color_corrected, opacity)
137
 
138
  return Image.fromarray(result)
139
 
 
88
 
89
  def apply_color_transfer_to_output(swapped_img, original_bg, opacity=0.7, color_region=None):
90
  """
91
+ Apply color transfer ONLY to face regions, using face+forehead (excluding hair) as color reference.
 
92
 
93
  Args:
94
  swapped_img: PIL Image - The image after face swapping
 
97
  color_region: tuple or None - (x, y, width, height) for specific color sampling region
98
 
99
  Returns:
100
+ PIL Image - The color-corrected result with face-only color transfer
101
  """
102
  try:
103
+ from segmentation import detect_face_and_forehead_no_hair
104
+
105
  # Clamp opacity to valid range
106
  opacity = max(0.0, min(1.0, opacity))
107
 
 
109
  swapped_np = np.array(swapped_img.convert('RGB'))
110
  original_np = np.array(original_bg.convert('RGB'))
111
 
112
+ # Get face mask for TARGET (where to apply color transfer)
113
+ target_face_mask = detect_face_and_forehead_no_hair(swapped_img)
114
+
115
+ # Try advanced face+forehead detection for REFERENCE color
116
  face_pixels = get_face_forehead_region_no_hair(original_bg)
117
 
118
+ if face_pixels is not None and target_face_mask.sum() > 0:
119
+ print(f"✅ Face-only color transfer: {len(face_pixels)} reference pixels → {target_face_mask.sum()} target pixels")
120
+
121
+ # Apply color transfer using extracted face+forehead pixels
122
+ color_corrected_full = histogram_matching_with_pixels(swapped_np, face_pixels)
123
+
124
+ # Create result by blending ONLY face regions
125
+ result = swapped_np.copy()
126
+ face_indices = target_face_mask == 1
127
+
128
+ # Blend only face pixels
129
+ result[face_indices] = (
130
+ swapped_np[face_indices].astype(np.float32) * (1 - opacity) +
131
+ color_corrected_full[face_indices].astype(np.float32) * opacity
132
+ ).astype(np.uint8)
133
 
134
  elif color_region is not None:
135
  # Fallback to manual region if provided
136
  x, y, w, h = color_region
137
  reference_region = original_np[y:y+h, x:x+w]
138
+ if reference_region.size > 0 and target_face_mask.sum() > 0:
139
+ print(f"⚙️ Face-only with manual region: {color_region}")
140
+ color_corrected_full = histogram_matching_with_region(swapped_np, original_np, reference_region)
141
+
142
+ result = swapped_np.copy()
143
+ face_indices = target_face_mask == 1
144
+ result[face_indices] = (
145
+ swapped_np[face_indices].astype(np.float32) * (1 - opacity) +
146
+ color_corrected_full[face_indices].astype(np.float32) * opacity
147
+ ).astype(np.uint8)
148
  else:
149
+ print("⚠️ Invalid manual region or no face detected")
150
+ result = swapped_np
151
 
152
  else:
153
+ # No face detection - return original
154
+ print("⚠️ No face detection, no color transfer applied")
155
+ result = swapped_np
 
 
 
156
 
157
  return Image.fromarray(result)
158
 
roop/face_analyser.py CHANGED
@@ -1,6 +1,8 @@
1
  import threading
2
  from typing import Any
3
  import insightface
 
 
4
 
5
  import roop.globals
6
  from roop.typing import Frame
@@ -19,7 +21,69 @@ def get_face_analyser() -> Any:
19
  return FACE_ANALYSER
20
 
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  def get_one_face(frame: Frame) -> Any:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  face = get_face_analyser().get(frame)
24
  try:
25
  selected_face = min(face, key=lambda x: x.bbox[0])
@@ -27,8 +91,34 @@ def get_one_face(frame: Frame) -> Any:
27
  except ValueError:
28
  return None
29
 
 
30
  def get_many_faces(frame: Frame) -> Any:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  try:
32
  return get_face_analyser().get(frame)
33
  except IndexError:
34
  return None
 
 
 
 
 
 
 
 
1
  import threading
2
  from typing import Any
3
  import insightface
4
+ import numpy as np
5
+ from PIL import Image
6
 
7
  import roop.globals
8
  from roop.typing import Frame
 
21
  return FACE_ANALYSER
22
 
23
 
24
+ def get_precise_face_mask(frame: Frame) -> Any:
25
+ """
26
+ Get precise face mask using advanced segmentation (same as detect_face_and_forehead_no_hair).
27
+ Returns both InsightFace detection and precise mask.
28
+ """
29
+ try:
30
+ # Import the precise detection function
31
+ import sys
32
+ import os
33
+ sys.path.append(os.path.dirname(os.path.dirname(__file__)))
34
+ from segmentation import detect_face_and_forehead_no_hair
35
+
36
+ # Convert frame to PIL Image
37
+ if isinstance(frame, np.ndarray):
38
+ pil_image = Image.fromarray(frame)
39
+ else:
40
+ pil_image = frame
41
+
42
+ # Get precise face mask (clean skin only)
43
+ precise_mask = detect_face_and_forehead_no_hair(pil_image)
44
+
45
+ # Also get InsightFace detection for face swapping compatibility
46
+ insightface_faces = get_face_analyser().get(frame)
47
+
48
+ return {
49
+ 'precise_mask': precise_mask,
50
+ 'insightface_faces': insightface_faces,
51
+ 'has_face': precise_mask.sum() > 0 and len(insightface_faces) > 0
52
+ }
53
+
54
+ except Exception as e:
55
+ print(f"Precise face detection failed: {e}")
56
+ # Fallback to regular InsightFace
57
+ insightface_faces = get_face_analyser().get(frame)
58
+ return {
59
+ 'precise_mask': None,
60
+ 'insightface_faces': insightface_faces,
61
+ 'has_face': len(insightface_faces) > 0
62
+ }
63
+
64
+
65
  def get_one_face(frame: Frame) -> Any:
66
+ """
67
+ Get one face with enhanced precision detection.
68
+ """
69
+ # Get precise detection info
70
+ face_info = get_precise_face_mask(frame)
71
+
72
+ if face_info['has_face'] and face_info['insightface_faces']:
73
+ try:
74
+ # Select face (leftmost) for compatibility
75
+ selected_face = min(face_info['insightface_faces'], key=lambda x: x.bbox[0])
76
+
77
+ # Add precise mask info to face object
78
+ if face_info['precise_mask'] is not None:
79
+ selected_face.precise_mask = face_info['precise_mask']
80
+ print(f"✅ Enhanced face detection: {face_info['precise_mask'].sum()} precise pixels")
81
+
82
+ return selected_face
83
+ except (ValueError, IndexError):
84
+ return None
85
+
86
+ # Fallback to original method
87
  face = get_face_analyser().get(frame)
88
  try:
89
  selected_face = min(face, key=lambda x: x.bbox[0])
 
91
  except ValueError:
92
  return None
93
 
94
+
95
  def get_many_faces(frame: Frame) -> Any:
96
+ """
97
+ Get many faces with enhanced precision detection.
98
+ """
99
+ # Get precise detection info
100
+ face_info = get_precise_face_mask(frame)
101
+
102
+ if face_info['has_face'] and face_info['insightface_faces']:
103
+ faces = face_info['insightface_faces']
104
+
105
+ # Add precise mask info to all face objects
106
+ if face_info['precise_mask'] is not None:
107
+ for face in faces:
108
+ face.precise_mask = face_info['precise_mask']
109
+
110
+ print(f"✅ Enhanced multi-face detection: {len(faces)} faces with precise masks")
111
+ return faces
112
+
113
+ # Fallback to original method
114
  try:
115
  return get_face_analyser().get(frame)
116
  except IndexError:
117
  return None
118
+
119
+
120
+ def has_precise_face_mask(face_obj) -> bool:
121
+ """
122
+ Check if face object has precise mask attached.
123
+ """
124
+ return hasattr(face_obj, 'precise_mask') and face_obj.precise_mask is not None
roop/processors/frame/face_enhancer.py CHANGED
@@ -37,7 +37,7 @@ def get_face_enhancer() -> Any:
37
  def pre_check() -> bool:
38
  download_directory_path = resolve_relative_path('../models')
39
  # conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/GFPGANv1.4.pth'])
40
- conditional_download(download_directory_path, ['https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth'])
41
  return True
42
 
43
 
 
37
  def pre_check() -> bool:
38
  download_directory_path = resolve_relative_path('../models')
39
  # conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/GFPGANv1.4.pth'])
40
+ conditional_download(download_directory_path, ['https://huggingface.co/VanNguyen1214/detect_face/resolve/main/GFPGANv1.4.pth'])
41
  return True
42
 
43
 
roop/processors/frame/face_swapper.py CHANGED
@@ -28,7 +28,7 @@ def get_face_swapper() -> Any:
28
  def pre_check() -> bool:
29
  download_directory_path = resolve_relative_path('../models')
30
  # conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/inswapper_128.onnx'])
31
- conditional_download(download_directory_path, ['https://huggingface.co/thebiglaskowski/inswapper_128.onnx/resolve/main/inswapper_128.onnx'])
32
  return True
33
 
34
 
 
28
  def pre_check() -> bool:
29
  download_directory_path = resolve_relative_path('../models')
30
  # conditional_download(download_directory_path, ['https://huggingface.co/henryruhs/roop/resolve/main/inswapper_128.onnx'])
31
+ conditional_download(download_directory_path, ['https://huggingface.co/VanNguyen1214/detect_face/resolve/main/inswapper_128.onnx'])
32
  return True
33
 
34
 
segmentation.py CHANGED
@@ -5,6 +5,16 @@ import torch.nn.functional as F
5
  import numpy as np
6
  import mediapipe as mp
7
  import cv2
 
 
 
 
 
 
 
 
 
 
8
 
9
  # Load model
10
  processor = SegformerImageProcessor.from_pretrained("VanNguyen1214/get_face_and_hair")
 
5
  import numpy as np
6
  import mediapipe as mp
7
  import cv2
8
+ import os
9
+ import warnings
10
+
11
+ # Suppress MediaPipe warnings
12
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
13
+ warnings.filterwarnings('ignore')
14
+
15
+ # Suppress MediaPipe logs
16
+ import logging
17
+ logging.getLogger('mediapipe').setLevel(logging.ERROR)
18
 
19
  # Load model
20
  processor = SegformerImageProcessor.from_pretrained("VanNguyen1214/get_face_and_hair")