Fix Occlusion Handling - Make it Optional
FIXES: - Occlusion detection now DISABLED by default - Face swap works normally without interference - Added toggle: enable_occlusion_detection = False - Much more conservative occlusion detection when enabled - Face swap continues working even with hands/objects BEHAVIOR: - Default: Normal face swap behavior (no blocking) - Optional: Enable occlusion detection for subtle hand protection - Face swap always stays active and visible - Only very obvious occlusions are handled (>15% coverage) SETTINGS: - modules.globals.enable_occlusion_detection = False (default) - modules.globals.occlusion_sensitivity = 0.3 (adjustable) USAGE: - Face swap now works exactly like before by default - To enable occlusion protection: set enable_occlusion_detection = True - Face swap will never be completely blocked anymorepull/1411/head
parent
feae2657c9
commit
81c1a817cc
|
@ -50,3 +50,7 @@ quality_level = 1.0
|
||||||
face_detection_interval = 0.1
|
face_detection_interval = 0.1
|
||||||
enable_frame_caching = True
|
enable_frame_caching = True
|
||||||
enable_gpu_acceleration = True
|
enable_gpu_acceleration = True
|
||||||
|
|
||||||
|
# Occlusion handling settings
|
||||||
|
enable_occlusion_detection = False # Disable by default to keep normal face swap behavior
|
||||||
|
occlusion_sensitivity = 0.3 # Lower = less sensitive, higher = more sensitive
|
||||||
|
|
|
@ -218,35 +218,21 @@ def apply_edge_smoothing(face: np.ndarray, reference: np.ndarray) -> np.ndarray:
|
||||||
|
|
||||||
|
|
||||||
def swap_face_enhanced_with_occlusion(source_face: Face, target_face: Face, temp_frame: Frame, original_frame: Frame) -> Frame:
|
def swap_face_enhanced_with_occlusion(source_face: Face, target_face: Face, temp_frame: Frame, original_frame: Frame) -> Frame:
|
||||||
"""Enhanced face swapping with occlusion handling and stabilization"""
|
"""Enhanced face swapping with optional occlusion handling"""
|
||||||
face_swapper = get_face_swapper()
|
face_swapper = get_face_swapper()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get face bounding box
|
# First, apply the normal face swap (this should always work)
|
||||||
bbox = target_face.bbox.astype(int)
|
|
||||||
x1, y1, x2, y2 = bbox
|
|
||||||
|
|
||||||
# Ensure coordinates are within frame bounds
|
|
||||||
h, w = temp_frame.shape[:2]
|
|
||||||
x1, y1 = max(0, x1), max(0, y1)
|
|
||||||
x2, y2 = min(w, x2), min(h, y2)
|
|
||||||
|
|
||||||
if x2 <= x1 or y2 <= y1:
|
|
||||||
return temp_frame
|
|
||||||
|
|
||||||
# Create face mask to handle occlusion
|
|
||||||
face_mask = create_enhanced_face_mask(target_face, temp_frame)
|
|
||||||
|
|
||||||
# Apply face swap
|
|
||||||
swapped_frame = face_swapper.get(temp_frame, target_face, source_face, paste_back=True)
|
swapped_frame = face_swapper.get(temp_frame, target_face, source_face, paste_back=True)
|
||||||
|
|
||||||
# Apply occlusion-aware blending
|
|
||||||
final_frame = apply_occlusion_aware_blending(
|
|
||||||
swapped_frame, temp_frame, face_mask, bbox
|
|
||||||
)
|
|
||||||
|
|
||||||
# Enhanced post-processing for better quality
|
# Enhanced post-processing for better quality
|
||||||
final_frame = enhance_face_swap_quality(final_frame, source_face, target_face, original_frame)
|
swapped_frame = enhance_face_swap_quality(swapped_frame, source_face, target_face, original_frame)
|
||||||
|
|
||||||
|
# Only apply occlusion handling if explicitly enabled
|
||||||
|
if modules.globals.enable_occlusion_detection:
|
||||||
|
final_frame = apply_subtle_occlusion_protection(swapped_frame, temp_frame, target_face)
|
||||||
|
else:
|
||||||
|
final_frame = swapped_frame
|
||||||
|
|
||||||
# Apply mouth mask if enabled
|
# Apply mouth mask if enabled
|
||||||
if modules.globals.mouth_mask:
|
if modules.globals.mouth_mask:
|
||||||
|
@ -267,7 +253,7 @@ def swap_face_enhanced_with_occlusion(source_face: Face, target_face: Face, temp
|
||||||
return final_frame
|
return final_frame
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in occlusion-aware face swap: {e}")
|
print(f"Error in enhanced face swap: {e}")
|
||||||
# Fallback to regular enhanced swap
|
# Fallback to regular enhanced swap
|
||||||
return swap_face_enhanced(source_face, target_face, temp_frame)
|
return swap_face_enhanced(source_face, target_face, temp_frame)
|
||||||
|
|
||||||
|
@ -422,6 +408,102 @@ def detect_occlusion(original_region: np.ndarray, swapped_region: np.ndarray) ->
|
||||||
return np.zeros(original_region.shape[:2], dtype=np.uint8)
|
return np.zeros(original_region.shape[:2], dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_subtle_occlusion_protection(swapped_frame: Frame, original_frame: Frame, target_face: Face) -> Frame:
|
||||||
|
"""Apply very subtle occlusion protection - only affects obvious hand/object areas"""
|
||||||
|
try:
|
||||||
|
# Get face bounding box
|
||||||
|
bbox = target_face.bbox.astype(int)
|
||||||
|
x1, y1, x2, y2 = bbox
|
||||||
|
|
||||||
|
# Ensure coordinates are within frame bounds
|
||||||
|
h, w = swapped_frame.shape[:2]
|
||||||
|
x1, y1 = max(0, x1), max(0, y1)
|
||||||
|
x2, y2 = min(w, x2), min(h, y2)
|
||||||
|
|
||||||
|
if x2 <= x1 or y2 <= y1:
|
||||||
|
return swapped_frame
|
||||||
|
|
||||||
|
# Extract face regions
|
||||||
|
swapped_region = swapped_frame[y1:y2, x1:x2]
|
||||||
|
original_region = original_frame[y1:y2, x1:x2]
|
||||||
|
|
||||||
|
# Very conservative occlusion detection - only detect obvious hands/objects
|
||||||
|
occlusion_mask = detect_obvious_occlusion(original_region)
|
||||||
|
|
||||||
|
# Only apply protection if significant occlusion is detected
|
||||||
|
occlusion_percentage = np.sum(occlusion_mask > 128) / (occlusion_mask.shape[0] * occlusion_mask.shape[1])
|
||||||
|
|
||||||
|
if occlusion_percentage > 0.15: # Only if more than 15% of face is occluded
|
||||||
|
# Create a very soft blend mask
|
||||||
|
blend_mask = (255 - occlusion_mask).astype(np.float32) / 255.0
|
||||||
|
blend_mask = cv2.GaussianBlur(blend_mask, (21, 21), 7) # Very soft edges
|
||||||
|
blend_mask = blend_mask[:, :, np.newaxis]
|
||||||
|
|
||||||
|
# Very subtle blending - mostly keep the swapped face
|
||||||
|
protected_region = (swapped_region * (0.7 + 0.3 * blend_mask) +
|
||||||
|
original_region * (0.3 * (1 - blend_mask))).astype(np.uint8)
|
||||||
|
|
||||||
|
# Copy back to full frame
|
||||||
|
result_frame = swapped_frame.copy()
|
||||||
|
result_frame[y1:y2, x1:x2] = protected_region
|
||||||
|
return result_frame
|
||||||
|
|
||||||
|
# If no significant occlusion, return original swapped frame
|
||||||
|
return swapped_frame
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# If anything fails, just return the swapped frame
|
||||||
|
return swapped_frame
|
||||||
|
|
||||||
|
|
||||||
|
def detect_obvious_occlusion(region: np.ndarray) -> np.ndarray:
|
||||||
|
"""Detect only very obvious occlusion (hands, large objects) - much more conservative"""
|
||||||
|
try:
|
||||||
|
# Convert to HSV for better skin detection
|
||||||
|
hsv = cv2.cvtColor(region, cv2.COLOR_BGR2HSV)
|
||||||
|
|
||||||
|
# More restrictive skin detection for hands
|
||||||
|
lower_skin = np.array([0, 30, 80], dtype=np.uint8) # More restrictive
|
||||||
|
upper_skin = np.array([15, 255, 255], dtype=np.uint8)
|
||||||
|
skin_mask1 = cv2.inRange(hsv, lower_skin, upper_skin)
|
||||||
|
|
||||||
|
lower_skin2 = np.array([165, 30, 80], dtype=np.uint8)
|
||||||
|
upper_skin2 = np.array([180, 255, 255], dtype=np.uint8)
|
||||||
|
skin_mask2 = cv2.inRange(hsv, lower_skin2, upper_skin2)
|
||||||
|
|
||||||
|
skin_mask = cv2.bitwise_or(skin_mask1, skin_mask2)
|
||||||
|
|
||||||
|
# Very conservative edge detection
|
||||||
|
gray = cv2.cvtColor(region, cv2.COLOR_BGR2GRAY)
|
||||||
|
edges = cv2.Canny(gray, 80, 160) # Higher thresholds for obvious edges only
|
||||||
|
|
||||||
|
# Combine but be very conservative
|
||||||
|
occlusion_mask = cv2.bitwise_and(skin_mask, edges) # Must be both skin-like AND have edges
|
||||||
|
|
||||||
|
# Clean up with morphological operations
|
||||||
|
kernel = np.ones((7, 7), np.uint8)
|
||||||
|
occlusion_mask = cv2.morphologyEx(occlusion_mask, cv2.MORPH_CLOSE, kernel)
|
||||||
|
occlusion_mask = cv2.morphologyEx(occlusion_mask, cv2.MORPH_OPEN, kernel)
|
||||||
|
|
||||||
|
# Only keep significant connected components
|
||||||
|
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(occlusion_mask)
|
||||||
|
filtered_mask = np.zeros_like(occlusion_mask)
|
||||||
|
|
||||||
|
for i in range(1, num_labels):
|
||||||
|
area = stats[i, cv2.CC_STAT_AREA]
|
||||||
|
if area > 200: # Only keep larger occlusions
|
||||||
|
filtered_mask[labels == i] = 255
|
||||||
|
|
||||||
|
# Apply very light Gaussian blur
|
||||||
|
filtered_mask = cv2.GaussianBlur(filtered_mask, (5, 5), 1)
|
||||||
|
|
||||||
|
return filtered_mask
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# Return empty mask if detection fails
|
||||||
|
return np.zeros(region.shape[:2], dtype=np.uint8)
|
||||||
|
|
||||||
|
|
||||||
def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
|
def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
|
||||||
from modules.performance_optimizer import performance_optimizer
|
from modules.performance_optimizer import performance_optimizer
|
||||||
from modules.face_tracker import face_tracker
|
from modules.face_tracker import face_tracker
|
||||||
|
|
Loading…
Reference in New Issue