""" PRODUCTION-READY: Professional AI Facial Editor Combines real-time manual preview with GPU-powered high-quality rendering Similar to Facetune/PicsArt with professional filters and effects """ import gradio as gr import cv2 import numpy as np from typing import Tuple, Optional, Dict import logging import os from PIL import Image, ImageEnhance, ImageFilter import time # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # ============================================================================ # FACE DETECTION # ============================================================================ try: import insightface app = insightface.app.FaceAnalysis(name='buffalo_l', providers=['CPUProvider']) app.prepare(ctx_id=-1, det_size=(640, 640)) logger.info("✓ InsightFace loaded") except Exception as e: logger.error(f"InsightFace error: {e}") app = None def detect_face_landmarks(image: np.ndarray) -> Optional[np.ndarray]: """Detect 106 facial landmarks using InsightFace.""" try: if app is None: return None rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) faces = app.get(rgb_image) if len(faces) == 0: return None face = faces[0] landmarks_106 = face.landmark_2d_106 # Expand to 468-point format landmarks_468 = np.zeros((468, 2), dtype=np.float32) landmarks_468[:106] = landmarks_106.astype(np.float32) for i in range(106, 468): idx = i % 106 landmarks_468[i] = landmarks_106[idx] return landmarks_468 except Exception as e: logger.error(f"Landmark detection error: {e}") return None # ============================================================================ # PRECISE REGION MASKS # ============================================================================ def create_region_masks(landmarks: np.ndarray, h: int, w: int) -> Dict[str, np.ndarray]: """Create accurate facial region masks for blending.""" masks = {} # LIP MASK lip_points = landmarks[55:71].astype(np.int32) if len(lip_points) >= 4: lip_mask = np.zeros((h, w), dtype=np.uint8) cv2.fillPoly(lip_mask, [lip_points], 255) lip_mask = cv2.dilate(lip_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=1) lip_mask = cv2.GaussianBlur(lip_mask.astype(np.float32), (11, 11), 0) masks['lips'] = np.clip(lip_mask / 255.0, 0, 1) # NOSE MASK nose_points = landmarks[51:57].astype(np.int32) if len(nose_points) >= 3: nose_mask = np.zeros((h, w), dtype=np.uint8) cv2.fillPoly(nose_mask, [nose_points], 255) nose_mask = cv2.dilate(nose_mask, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)), iterations=2) nose_mask = cv2.GaussianBlur(nose_mask.astype(np.float32), (13, 13), 0) masks['nose'] = np.clip(nose_mask / 255.0, 0, 1) # EYEBROW MASK (TIGHT - NO EYE REGION) left_brow = landmarks[33:38].astype(np.int32) right_brow = landmarks[38:43].astype(np.int32) if len(left_brow) >= 3 and len(right_brow) >= 3: brow_mask = np.zeros((h, w), dtype=np.uint8) cv2.fillPoly(brow_mask, [left_brow], 255) cv2.fillPoly(brow_mask, [right_brow], 255) brow_mask = cv2.GaussianBlur(brow_mask.astype(np.float32), (7, 7), 0) masks['eyebrows'] = np.clip(brow_mask / 255.0, 0, 1) # FACE MASK (FOR FILTERS) face_points = landmarks[:104].astype(np.int32) if len(face_points) >= 4: face_mask = np.zeros((h, w), dtype=np.uint8) hull = cv2.convexHull(face_points) cv2.fillPoly(face_mask, [hull], 255) face_mask = cv2.GaussianBlur(face_mask.astype(np.float32), (25, 25), 0) masks['face'] = np.clip(face_mask / 255.0, 0, 1) return masks # ============================================================================ # FAST MANUAL EDITING (FOR REAL-TIME PREVIEW) # ============================================================================ def enlarge_lips(image: np.ndarray, landmarks: np.ndarray, scale: float) -> np.ndarray: """Fast lip enlargement for real-time preview.""" if scale == 1.0: return image h, w = image.shape[:2] masks = create_region_masks(landmarks, h, w) if 'lips' not in masks: return image lip_mask = masks['lips'] mouth_points = landmarks[55:71].astype(np.float32) mouth_center = np.mean(mouth_points, axis=0) scale_factor = 1.0 + (scale - 1.0) * 0.15 y_coords, x_coords = np.meshgrid(np.arange(h), np.arange(w), indexing='ij') dx = x_coords.astype(np.float32) - mouth_center[0] dy = y_coords.astype(np.float32) - mouth_center[1] map_x = (mouth_center[0] + dx / scale_factor).astype(np.float32) map_y = (mouth_center[1] + dy / scale_factor).astype(np.float32) warped = cv2.remap(image.astype(np.uint8), map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT) lip_mask_blurred = cv2.GaussianBlur(lip_mask, (15, 15), 0) result = image.astype(np.float32) * (1 - lip_mask_blurred[:, :, np.newaxis]) + \ warped.astype(np.float32) * lip_mask_blurred[:, :, np.newaxis] result_uint8 = np.clip(result, 0, 255).astype(np.uint8) result_uint8 = cv2.bilateralFilter(result_uint8, 5, 50, 50) return result_uint8 def adjust_nose_width(image: np.ndarray, landmarks: np.ndarray, scale: float) -> np.ndarray: """Fast nose adjustment for real-time preview.""" if scale == 1.0: return image h, w = image.shape[:2] masks = create_region_masks(landmarks, h, w) if 'nose' not in masks: return image nose_mask = masks['nose'] nose_points = landmarks[51:57].astype(np.float32) nose_center = np.mean(nose_points, axis=0) compression = 1.0 + (scale - 1.0) * 0.25 y_coords, x_coords = np.meshgrid(np.arange(h), np.arange(w), indexing='ij') dx = x_coords.astype(np.float32) - nose_center[0] map_x = (nose_center[0] + dx / compression).astype(np.float32) map_y = y_coords.astype(np.float32) warped = cv2.remap(image.astype(np.uint8), map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT) nose_mask_blurred = cv2.GaussianBlur(nose_mask, (15, 15), 0) result = image.astype(np.float32) * (1 - nose_mask_blurred[:, :, np.newaxis]) + \ warped.astype(np.float32) * nose_mask_blurred[:, :, np.newaxis] result_uint8 = np.clip(result, 0, 255).astype(np.uint8) result_uint8 = cv2.bilateralFilter(result_uint8, 5, 50, 50) return result_uint8 def raise_eyebrows(image: np.ndarray, landmarks: np.ndarray, scale: float) -> np.ndarray: """Fast eyebrow raising for real-time preview.""" if scale == 1.0: return image h, w = image.shape[:2] masks = create_region_masks(landmarks, h, w) if 'eyebrows' not in masks: return image brow_mask = masks['eyebrows'] shift_pixels = (scale - 1.0) * 10 y_coords, x_coords = np.meshgrid(np.arange(h), np.arange(w), indexing='ij') map_y = (y_coords.astype(np.float32) - shift_pixels * brow_mask).astype(np.float32) map_y = np.clip(map_y, 0, h - 1) map_x = x_coords.astype(np.float32) warped = cv2.remap(image.astype(np.uint8), map_x, map_y, cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT) brow_mask_blurred = cv2.GaussianBlur(brow_mask, (9, 9), 0) result = image.astype(np.float32) * (1 - brow_mask_blurred[:, :, np.newaxis]) + \ warped.astype(np.float32) * brow_mask_blurred[:, :, np.newaxis] if scale > 1.0: result_uint8 = np.clip(result, 0, 255).astype(np.uint8) hsv = cv2.cvtColor(result_uint8, cv2.COLOR_BGR2HSV).astype(np.float32) hsv[:, :, 2] = np.clip(hsv[:, :, 2] * (1 - brow_mask * (scale - 1.0) * 0.08), 0, 255) result = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR).astype(np.float32) result_uint8 = np.clip(result, 0, 255).astype(np.uint8) result_uint8 = cv2.bilateralFilter(result_uint8, 5, 50, 50) return result_uint8 # ============================================================================ # FILTERS & EFFECTS # ============================================================================ def apply_filter(image: np.ndarray, filter_type: str) -> np.ndarray: """Apply professional filters: cinematic, B&W, 4K, rainy, original.""" if filter_type == "original": return image img_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) if filter_type == "cinematic": # Warm tones + increased contrast + slight vignette enhancer = ImageEnhance.Color(img_pil) img_pil = enhancer.enhance(1.1) # +10% saturation enhancer = ImageEnhance.Contrast(img_pil) img_pil = enhancer.enhance(1.2) # +20% contrast # Add warm tone (reduce blue slightly) img_array = np.array(img_pil) img_array[:, :, 2] = np.clip(img_array[:, :, 2] * 0.95, 0, 255) img_pil = Image.fromarray(img_array.astype(np.uint8)) elif filter_type == "black_white": img_pil = img_pil.convert('L') # Increase contrast for B&W enhancer = ImageEnhance.Contrast(img_pil) img_pil = enhancer.enhance(1.3) # Convert back to RGB (grayscale) img_pil = Image.new('RGB', img_pil.size) img_pil.paste(img_pil.convert('L')) elif filter_type == "4k": # Increase brightness + saturation + sharpness enhancer = ImageEnhance.Brightness(img_pil) img_pil = enhancer.enhance(1.1) enhancer = ImageEnhance.Color(img_pil) img_pil = enhancer.enhance(1.3) # +30% saturation enhancer = ImageEnhance.Sharpness(img_pil) img_pil = enhancer.enhance(2.0) # 2x sharpness elif filter_type == "rainy": # Cool tones + blue overlay + reduced brightness img_array = np.array(img_pil) # Add blue tint (increase blue channel) img_array[:, :, 2] = np.clip(img_array[:, :, 2] * 1.2, 0, 255) # Reduce red and green slightly img_array[:, :, 0] = np.clip(img_array[:, :, 0] * 0.9, 0, 255) img_array[:, :, 1] = np.clip(img_array[:, :, 1] * 0.9, 0, 255) img_pil = Image.fromarray(img_array.astype(np.uint8)) # Reduce brightness enhancer = ImageEnhance.Brightness(img_pil) img_pil = enhancer.enhance(0.85) return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR) def adjust_brightness(image: np.ndarray, brightness: float) -> np.ndarray: """Adjust brightness.""" if brightness == 1.0: return image result = image.astype(np.float32) * brightness return np.clip(result, 0, 255).astype(np.uint8) def smooth_skin(image: np.ndarray, intensity: int, landmarks: np.ndarray) -> np.ndarray: """Apply skin smoothing.""" if intensity == 0: return image h, w = image.shape[:2] masks = create_region_masks(landmarks, h, w) face_mask = masks.get('face', np.ones((h, w))) diameter = 5 + intensity sigma_color = 50 + intensity * 3 sigma_space = 50 + intensity * 3 smoothed = cv2.bilateralFilter(image, diameter, sigma_color, sigma_space) blend_factor = intensity / 10.0 result = image.astype(np.float32) * (1 - face_mask[:, :, np.newaxis] * blend_factor) + \ smoothed.astype(np.float32) * face_mask[:, :, np.newaxis] * blend_factor return np.clip(result, 0, 255).astype(np.uint8) # ============================================================================ # MAIN EDITING PIPELINE # ============================================================================ def edit_face( image: np.ndarray, lips: float = 1.0, nose: float = 1.0, eyebrows: float = 1.0, brightness: float = 1.0, smooth: int = 0, filter_type: str = "original" ) -> Tuple[np.ndarray, str]: """Real-time editing pipeline.""" try: if image is None: return None, "❌ Please upload an image first" if len(image.shape) == 3 and image.shape[2] == 3: working_image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) else: working_image = image start_time = time.time() landmarks = detect_face_landmarks(working_image) if landmarks is None: return image, "⚠️ No face detected" result = working_image.copy() # Feature edits if lips != 1.0: result = enlarge_lips(result, landmarks, lips) if nose != 1.0: result = adjust_nose_width(result, landmarks, nose) if eyebrows != 1.0: result = raise_eyebrows(result, landmarks, eyebrows) # Filters (before brightness to apply to base) if filter_type != "original": result = apply_filter(result, filter_type) # Brightness and smoothing if brightness != 1.0: result = adjust_brightness(result, brightness) if smooth > 0: result = smooth_skin(result, smooth, landmarks) # Final global smoothing result = cv2.bilateralFilter(result, 5, 80, 80) elapsed = time.time() - start_time result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB) return result_rgb, f"✅ Real-time preview ({elapsed:.2f}s)" except Exception as e: logger.error(f"Error: {e}", exc_info=True) return image, f"❌ Error: {str(e)}" # ============================================================================ # GRADIO INTERFACE # ============================================================================ def create_interface(): """Professional Gradio UI with real-time effects.""" with gr.Blocks(title="AI Facial Editor Pro") as demo: gr.Markdown(""" # 🎨 Professional AI Facial Editor **Real-time effects** — Move sliders to see instant changes! """) # BEFORE & AFTER gr.Markdown("## 📸 Before & After") with gr.Row(): with gr.Column(): gr.Markdown("### Original") input_image = gr.Image( label="Upload Face Image", type="numpy", sources=["upload", "webcam"] ) with gr.Column(): gr.Markdown("### Live Preview") output_image = gr.Image( label="Real-time Edit", type="numpy" ) # CONTROLS gr.Markdown("## 🎛️ Adjust Features (Real-Time)") with gr.Group(): gr.Markdown("### Facial Features") lips_slider = gr.Slider( label="💋 Lips Size", minimum=0.5, maximum=2.0, value=1.0, step=0.05 ) nose_slider = gr.Slider( label="👃 Nose Width", minimum=0.5, maximum=2.0, value=1.0, step=0.05 ) eyebrows_slider = gr.Slider( label="🤨 Eyebrow Height", minimum=0.5, maximum=2.0, value=1.0, step=0.05 ) with gr.Group(): gr.Markdown("### Filters & Adjustment") filter_dropdown = gr.Dropdown( label="✨ Filter", choices=["original", "cinematic", "black_white", "4k", "rainy"], value="original" ) brightness_slider = gr.Slider( label="☀️ Brightness", minimum=0.5, maximum=2.0, value=1.0, step=0.05 ) smooth_slider = gr.Slider( label="🧴 Skin Smoothing", minimum=0, maximum=10, value=0, step=1 ) status_text = gr.Textbox( label="Status", interactive=False, value="📸 Upload image — effects update in real-time!", lines=1 ) reset_btn = gr.Button("🔄 Reset All Sliders", size="lg") # ===== REAL-TIME EVENT HANDLERS ===== def update_preview(image, lips, nose, eyebrows, brightness, smooth, filter_type): """Real-time preview.""" if image is None: return None, "📸 Please upload an image first" return edit_face(image, lips, nose, eyebrows, brightness, smooth, filter_type) # Connect all controls to real-time update for control in [lips_slider, nose_slider, eyebrows_slider, brightness_slider, smooth_slider, filter_dropdown]: control.change( fn=update_preview, inputs=[input_image, lips_slider, nose_slider, eyebrows_slider, brightness_slider, smooth_slider, filter_dropdown], outputs=[output_image, status_text] ) input_image.change( fn=update_preview, inputs=[input_image, lips_slider, nose_slider, eyebrows_slider, brightness_slider, smooth_slider, filter_dropdown], outputs=[output_image, status_text] ) def reset_all(): return 1.0, 1.0, 1.0, 1.0, 0, "original", "✨ Reset!" reset_btn.click( fn=reset_all, outputs=[lips_slider, nose_slider, eyebrows_slider, brightness_slider, smooth_slider, filter_dropdown, status_text] ) return demo # ============================================================================ # MAIN # ============================================================================ if __name__ == "__main__": demo = create_interface() demo.launch(share=False, theme=gr.themes.Soft())