import cv2 import cv2.data import numpy as np def _torch_device(): try: import torch return torch.device("cuda" if torch.cuda.is_available() else "cpu") except Exception: return "cpu" # BiSeNet class indices for face parsing # 0:bg 1:skin 2:left brow 3:right brow 4:left eye 5:right eye # 6:glasses 7:left ear 8:right ear 9:earring 10:nose 11:mouth # 12:upper lip 13:lower lip 14:neck 15:necklace 16:clothing # 17:hair 18:hat _HAIR_CLASSES = [17] _FACE_CLASSES = [1, 2, 3, 4, 5, 10, 11, 12, 13] _NECK_CLASSES = [14] def segment_hair_neck_skin(image: np.ndarray) -> dict: """ Segment hair, skin (face), and neck regions. Returns dict with 'face_mask', 'hair_mask', 'neck_mask' (uint8 0/255). Uses facexlib BiSeNet (via head_swap._get_parser) when available; falls back to Haar-cascade heuristics otherwise. """ try: from .head_swap import _get_parser, _parse_region_mask import cv2 as _cv2 from .detector import detect_faces as _detect parser = _get_parser() if parser is None: return _segment_heuristic(image) faces = _detect(image) if not faces: return _segment_heuristic(image) bbox = faces[0] h, w = image.shape[:2] face_mask = (_parse_region_mask(image, bbox, {1, 2, 3, 4, 5, 10, 11, 12, 13}, up=0.3, down=0.3, side=0.4) * 255).astype(np.uint8) hair_mask = (_parse_region_mask(image, bbox, {17}, up=0.9, down=0.1, side=0.5) * 255).astype(np.uint8) neck_mask = (_parse_region_mask(image, bbox, {14}, up=0.1, down=1.0, side=0.4) * 255).astype(np.uint8) return {"face_mask": face_mask, "hair_mask": hair_mask, "neck_mask": neck_mask} except Exception: return _segment_heuristic(image) def _segment_heuristic(image: np.ndarray) -> dict: """ Heuristic segmentation when BiSeNet is unavailable. Uses GrabCut + skin-colour thresholding. """ h, w = image.shape[:2] gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) cascade = cv2.CascadeClassifier( cv2.data.haarcascades + "haarcascade_frontalface_default.xml" ) faces = cascade.detectMultiScale(gray, 1.1, 5, minSize=(64, 64)) face_mask = np.zeros((h, w), dtype=np.uint8) hair_mask = np.zeros((h, w), dtype=np.uint8) neck_mask = np.zeros((h, w), dtype=np.uint8) if len(faces) == 0: return {"face_mask": face_mask, "hair_mask": hair_mask, "neck_mask": neck_mask} fx, fy, fw, fh = faces[0] # Face region (inner face ellipse) cx, cy = fx + fw // 2, fy + fh // 2 cv2.ellipse(face_mask, (cx, cy), (fw // 2, int(fh * 0.55)), 0, 0, 360, 255, -1) # Hair region: above the face bounding box hair_y_top = max(0, fy - int(fh * 0.6)) hair_y_bot = fy + int(fh * 0.2) hair_x1 = max(0, fx - int(fw * 0.2)) hair_x2 = min(w, fx + fw + int(fw * 0.2)) hair_mask[hair_y_top:hair_y_bot, hair_x1:hair_x2] = 255 # Neck region: below the chin neck_y_top = fy + int(fh * 0.85) neck_y_bot = min(h, fy + fh + int(fh * 0.4)) neck_x1 = max(0, fx + int(fw * 0.2)) neck_x2 = min(w, fx + fw - int(fw * 0.2)) neck_mask[neck_y_top:neck_y_bot, neck_x1:neck_x2] = 255 # Smooth masks face_mask = cv2.GaussianBlur(face_mask, (11, 11), 5) _, face_mask = cv2.threshold(face_mask, 64, 255, cv2.THRESH_BINARY) return {"face_mask": face_mask, "hair_mask": hair_mask, "neck_mask": neck_mask}