Compare commits

..

2 Commits

Author SHA1 Message Date
google-labs-jules[bot] 5db23597e9 fix: More robust handling of feathered_mask normalization
This commit provides a more robust fix for the RuntimeWarning
(invalid value encountered in divide/cast) that could occur in
the `apply_mouth_area` function within
`modules/processors/frame/face_swapper.py`.

The previous check for `feathered_mask.max() == 0` was not
sufficient for all floating point edge cases.

The updated logic now:
- Checks if `feathered_mask.max()` is less than a small epsilon (1e-6).
- If true, it logs a warning and explicitly sets `feathered_mask`
  to an all-zero `uint8` array of the correct shape.
- Otherwise, it proceeds with the normalization and casting to `uint8`.

This ensures that division by zero or by extremely small numbers is
prevented, and the `feathered_mask` is always in a valid state for
subsequent blending operations.
2025-06-23 20:45:41 +00:00
google-labs-jules[bot] 84ae5810bf Fix issues 2025-06-23 20:38:28 +00:00
2 changed files with 37 additions and 11 deletions

View File

@ -2,6 +2,7 @@ import os
import shutil
from typing import Any
import insightface
import logging # Added logging import
import cv2
import numpy as np
@ -25,18 +26,27 @@ def get_face_analyser() -> Any:
def get_one_face(frame: Frame) -> Any:
face = get_face_analyser().get(frame)
faces = get_face_analyser().get(frame)
if not faces:
logging.debug("Face_analyser: get_one_face: No faces found by insightface.")
return None
try:
return min(face, key=lambda x: x.bbox[0])
return min(faces, key=lambda x: x.bbox[0])
except ValueError:
logging.debug("Face_analyser: get_one_face: ValueError, likely no faces after all.")
return None
def get_many_faces(frame: Frame) -> Any:
try:
return get_face_analyser().get(frame)
except IndexError:
return None
faces = get_face_analyser().get(frame)
if not faces: # Check if faces is None or an empty list
logging.debug("Face_analyser: get_many_faces: No faces found by insightface.")
# Depending on what insightface returns for no faces,
# you might return None or an empty list.
# If .get() returns an empty list for no faces, this check is sufficient.
# If .get() returns None, this is also fine.
return faces # Return original (None or empty list)
return faces
def has_valid_map() -> bool:
for map in modules.globals.source_target_map:

View File

@ -136,16 +136,26 @@ def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
many_faces = get_many_faces(temp_frame)
if many_faces:
for target_face in many_faces:
if source_face and target_face:
if source_face and target_face: # target_face from many_faces will always be valid here
temp_frame = swap_face(source_face, target_face, temp_frame)
else:
print("Face detection failed for target/source.")
elif not source_face: # Check source_face specifically
logging.error("Source face is not available or no face detected in source image. Skipping swap for this target face.")
# Optionally `continue` or `break` if source_face is essential for all
elif not source_face : # if many_faces is empty AND source_face is also an issue
logging.error("Source face is not available AND no faces detected in target frame.")
else: # many_faces is empty, but source_face is ok
logging.info(f"No faces detected in the current target frame for 'many_faces' mode.")
else:
target_face = get_one_face(temp_frame)
if target_face and source_face:
temp_frame = swap_face(source_face, target_face, temp_frame)
else:
logging.error("Face detection failed for target or source.")
if not source_face:
logging.error("Source face is not available or no face detected in source image.")
elif not target_face:
logging.error(f"No face detected in the current target frame.")
else: # Should not happen if logic is right, but as a fallback
logging.error("Face detection failed for an unknown reason concerning target or source.")
return temp_frame
@ -543,7 +553,13 @@ def apply_mouth_area(
feathered_mask = cv2.GaussianBlur(
polygon_mask.astype(float), (0, 0), feather_amount
)
feathered_mask = feathered_mask / feathered_mask.max()
mask_max_value = feathered_mask.max()
if mask_max_value < 1e-6: # Check if max is effectively zero
logging.warning("Mouth mask's feathered_mask is all zeros or near-zeros after blur. Resulting mask will be black.")
feathered_mask = np.zeros_like(polygon_mask, dtype=np.uint8)
else:
feathered_mask = (feathered_mask / mask_max_value * 255).astype(np.uint8)
face_mask_roi = face_mask[min_y:max_y, min_x:max_x]
combined_mask = feathered_mask * (face_mask_roi / 255.0)