Spaces:
Running
on
Zero
Running
on
Zero
| from transformers import pipeline | |
| import numpy as np | |
| import cv2 | |
| from PIL import Image, ImageDraw | |
| # Try to import insightface for face detection, fallback to OpenCV if not available | |
| try: | |
| from insightface.app import FaceAnalysis | |
| USE_INSIGHTFACE = True | |
| except ImportError: | |
| USE_INSIGHTFACE = False | |
| print("⚠️ InsightFace not available, using OpenCV for face detection") | |
| # Initialize face detection | |
| if USE_INSIGHTFACE: | |
| try: | |
| app = FaceAnalysis(providers=['CPUExecutionProvider']) # CPU only for HF Spaces | |
| app.prepare(ctx_id=-1, det_size=(640, 640)) # -1 for CPU | |
| except Exception as e: | |
| print(f"⚠️ InsightFace initialization failed: {e}, falling back to OpenCV") | |
| USE_INSIGHTFACE = False | |
| # Initialize segmentation pipeline | |
| segmenter = pipeline(model="mattmdjaga/segformer_b2_clothes", device=-1) # -1 for CPU | |
| def remove_face_opencv(img, mask): | |
| """Fallback face detection using OpenCV""" | |
| img_arr = np.asarray(img.convert('RGB')) | |
| gray = cv2.cvtColor(img_arr, cv2.COLOR_RGB2GRAY) | |
| # Load OpenCV's pre-trained face detector | |
| face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') | |
| # Detect faces | |
| faces = face_cascade.detectMultiScale(gray, 1.1, 4) | |
| if len(faces) > 0: | |
| # Get the first (largest) face | |
| x, y, w, h = faces[0] | |
| # Expand face region | |
| x = max(0, int(x - w * 0.5)) | |
| y = max(0, int(y - h * 0.5)) | |
| w = int(w * 2.0) | |
| h = int(h * 1.7) | |
| # Draw black rectangle on mask | |
| img1 = ImageDraw.Draw(mask) | |
| img1.rectangle([(x, y), (x + w, y + h)], fill=0) | |
| return mask | |
| def remove_face(img, mask): | |
| """Remove face from mask using InsightFace or OpenCV fallback""" | |
| if not USE_INSIGHTFACE: | |
| return remove_face_opencv(img, mask) | |
| try: | |
| # Convert image to numpy array | |
| img_arr = np.asarray(img) | |
| # Run face detection | |
| faces = app.get(img_arr) | |
| if len(faces) == 0: | |
| return mask | |
| # Get the first face | |
| bbox = faces[0]['bbox'] | |
| # Width and height of face | |
| w = bbox[2] - bbox[0] | |
| h = bbox[3] - bbox[1] | |
| # Make face locations bigger | |
| bbox[0] = bbox[0] - (w * 0.5) # x left | |
| bbox[2] = bbox[2] + (w * 0.5) # x right | |
| bbox[1] = bbox[1] - (h * 0.5) # y top | |
| bbox[3] = bbox[3] + (h * 0.2) # y bottom | |
| # Convert to [(x_left, y_top), (x_right, y_bottom)] | |
| face_locations = [(bbox[0], bbox[1]), (bbox[2], bbox[3])] | |
| # Draw black rect onto mask | |
| img1 = ImageDraw.Draw(mask) | |
| img1.rectangle(face_locations, fill=0) | |
| return mask | |
| except Exception as e: | |
| print(f"⚠️ InsightFace face removal failed: {e}, using OpenCV") | |
| return remove_face_opencv(img, mask) | |
| def segment_body(original_img, face=True): | |
| """ | |
| Segment body from image | |
| Args: | |
| original_img: PIL Image | |
| face: If True, includes face in mask. If False, excludes face. | |
| Returns: | |
| tuple: (segmented_image, mask_image) | |
| """ | |
| # Make a copy | |
| img = original_img.copy() | |
| # Segment image | |
| segments = segmenter(img) | |
| # Create list of masks | |
| segment_include = ["Hat", "Hair", "Sunglasses", "Upper-clothes", "Skirt", "Pants", | |
| "Dress", "Belt", "Left-shoe", "Right-shoe", "Face", "Left-leg", | |
| "Right-leg", "Left-arm", "Right-arm", "Bag", "Scarf"] | |
| mask_list = [] | |
| for s in segments: | |
| if s['label'] in segment_include: | |
| mask_list.append(s['mask']) | |
| if len(mask_list) == 0: | |
| # If no segments found, return full mask | |
| print("⚠️ No body segments detected, using full mask") | |
| final_mask = Image.new('L', img.size, 255) | |
| else: | |
| # Paste all masks on top of each other | |
| final_mask = np.array(mask_list[0]) | |
| for mask in mask_list[1:]: | |
| current_mask = np.array(mask) | |
| final_mask = final_mask + current_mask | |
| # Convert final mask from np array to PIL image | |
| final_mask = Image.fromarray(final_mask) | |
| # Remove face | |
| if face == False: | |
| final_mask = remove_face(img.convert('RGB'), final_mask) | |
| # Apply mask to original image | |
| img_copy = img.copy() | |
| img_copy.putalpha(final_mask) | |
| return img_copy, final_mask | |
| def segment_torso(original_img): | |
| """ | |
| Segment only torso/upper body from image | |
| Args: | |
| original_img: PIL Image | |
| Returns: | |
| tuple: (segmented_image, mask_image) | |
| """ | |
| # Make a copy | |
| img = original_img.copy() | |
| # Segment image | |
| segments = segmenter(img) | |
| # Create list of masks (torso only) | |
| segment_include = ["Upper-clothes", "Dress", "Belt", "Face", "Left-arm", "Right-arm"] | |
| mask_list = [] | |
| for s in segments: | |
| if s['label'] in segment_include: | |
| mask_list.append(s['mask']) | |
| if len(mask_list) == 0: | |
| # If no segments found, return full mask | |
| print("⚠️ No torso segments detected, using full mask") | |
| final_mask = Image.new('L', img.size, 255) | |
| else: | |
| # Paste all masks on top of each other | |
| final_mask = np.array(mask_list[0]) | |
| for mask in mask_list[1:]: | |
| current_mask = np.array(mask) | |
| final_mask = final_mask + current_mask | |
| # Convert final mask from np array to PIL image | |
| final_mask = Image.fromarray(final_mask) | |
| # Remove face | |
| final_mask = remove_face(img.convert('RGB'), final_mask) | |
| # Apply mask to original image | |
| img_copy = img.copy() | |
| img_copy.putalpha(final_mask) | |
| return img_copy, final_mask | |