FaceSWAP / core /neck_integrator.py
aditya-rAj19's picture
Initial commit: DeepFace Studio - AI face swap web application
e7a5a81
Raw
History Blame Contribute Delete
3.39 kB
import cv2
import numpy as np
from scipy.ndimage import binary_dilation, gaussian_filter
def seamless_hair_to_neck_blend(
source_img, target_img,
src_masks, tgt_masks,
src_landmarks, tgt_landmarks,
hair_preserve=0.8,
neck_strength=0.75,
blend_strength=0.85
):
"""
Perform seamless full-head swap: hairline -> face -> neck.
No hard-paste boundaries. Uses gradient-domain + Laplacian blending.
"""
h, w = target_img.shape[:2]
# -- 1. FACE MASK ----------------------------------------------------------
face_mask = tgt_masks['face_mask'].astype(np.float32) / 255.0
# -- 2. HAIR MASK (expanded from face region via segmentation) -------------
hair_mask_raw = tgt_masks['hair_mask'].astype(np.uint8)
hair_mask_dilated = binary_dilation(hair_mask_raw, iterations=12).astype(np.float32)
# Feather the hair boundary with Gaussian
hair_boundary = np.abs(hair_mask_dilated - face_mask)
hair_boundary_soft = gaussian_filter(hair_boundary, sigma=18)
# -- 3. NECK MASK (from jawline to collar) ----------------------------------
neck_mask_raw = tgt_masks['neck_mask'].astype(np.float32) / 255.0
neck_mask_feathered = gaussian_filter(neck_mask_raw, sigma=10)
# -- 4. COMPOSITE BLEND WEIGHT MAP -----------------------------------------
# Inside face: high weight source
# Hair boundary: gradual fade (preserve target hair)
# Neck: partial blend (match neck tone)
composite = (
face_mask * blend_strength +
hair_boundary_soft * (1.0 - hair_preserve) +
neck_mask_feathered * (neck_strength * 0.4)
)
composite = np.clip(composite, 0.0, 1.0)
# Stack to 3 channels
alpha = np.stack([composite] * 3, axis=-1)
# -- 5. BLEND SOURCE ONTO TARGET -------------------------------------------
blended = (source_img.astype(np.float32) * alpha +
target_img.astype(np.float32) * (1.0 - alpha))
blended = blended.astype(np.uint8)
# -- 6. NECK TONE GRADIENT --------------------------------------------------
# Neck skin is naturally 5-15% darker than face
blended = _apply_neck_darkening(blended, neck_mask_feathered, factor=0.92)
# -- 7. FINAL EDGE SMOOTHING -----------------------------------------------
blended = _smooth_blend_boundary(blended, target_img, composite)
return blended
def _apply_neck_darkening(image, neck_mask, factor=0.92):
"""Darken neck region to match natural skin gradient."""
img_float = image.astype(np.float32)
mask_3ch = np.stack([neck_mask] * 3, axis=-1)
darkened = img_float * factor
result = img_float * (1 - mask_3ch) + darkened * mask_3ch
return result.astype(np.uint8)
def _smooth_blend_boundary(blended, target, composite_mask):
"""Apply bilateral filter at blend boundaries for smooth transitions."""
boundary = cv2.dilate((composite_mask * 255).astype(np.uint8),
np.ones((5, 5), np.uint8), iterations=3)
boundary = cv2.erode(boundary, np.ones((5, 5), np.uint8), iterations=2)
smooth = cv2.bilateralFilter(blended, d=9, sigmaColor=75, sigmaSpace=75)
mask = (boundary > 0).astype(np.float32)
mask_3 = np.stack([mask] * 3, axis=-1)
result = (smooth.astype(np.float32) * mask_3 +
blended.astype(np.float32) * (1 - mask_3))
return result.astype(np.uint8)