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 anymore
pull/1411/head
asateesh99 2025-07-15 23:40:43 +05:30
parent feae2657c9
commit 81c1a817cc
2 changed files with 110 additions and 24 deletions

View File

@ -50,3 +50,7 @@ quality_level = 1.0
face_detection_interval = 0.1
enable_frame_caching = 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

View File

@ -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:
"""Enhanced face swapping with occlusion handling and stabilization"""
"""Enhanced face swapping with optional occlusion handling"""
face_swapper = get_face_swapper()
try:
# Get face bounding box
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
# First, apply the normal face swap (this should always work)
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
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
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
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
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)
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:
from modules.performance_optimizer import performance_optimizer
from modules.face_tracker import face_tracker