CoolFace
Apppublic

nermadie/2.5D_Depth_Studio

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes
app.py329 linesDownload Raw Back to root
1from fastapi import FastAPI, File, UploadFile2from fastapi.middleware.cors import CORSMiddleware3from fastapi.responses import JSONResponse4import torch5import cv26import numpy as np7from PIL import Image8import io9import base6410from transformers import DPTImageProcessor, DPTForDepthEstimation11from scipy.ndimage import binary_dilation, binary_erosion, gaussian_filter12 13app = FastAPI()14 15app.add_middleware(16    CORSMiddleware,17    allow_origins=["*"],18    allow_credentials=True,19    allow_methods=["*"],20    allow_headers=["*"],21)22 23device = "cuda" if torch.cuda.is_available() else "cpu"24print(f"Using device: {device}")25 26# Load depth model (prefer the larger model when available)27try:28    processor = DPTImageProcessor.from_pretrained("Intel/dpt-large")29    model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large")30except:31    processor = DPTImageProcessor.from_pretrained("Intel/dpt-hybrid-midas")32    model = DPTForDepthEstimation.from_pretrained("Intel/dpt-hybrid-midas")33 34model.to(device)35model.eval()36 37 38def predict_depth(image: Image.Image):39    """Generate high-quality depth map with enhanced background detail"""40    inputs = processor(images=image, return_tensors="pt").to(device)41 42    with torch.no_grad():43        outputs = model(**inputs)44        depth = outputs.predicted_depth45 46    depth = depth.squeeze().cpu().numpy()47 48    # Resize to original size49    depth = cv2.resize(50        depth, (image.width, image.height), interpolation=cv2.INTER_CUBIC51    )52 53    # Normalize while preserving details54    depth = (depth - depth.min()) / (depth.max() - depth.min())55 56    # Apply CLAHE with a higher clipLimit to increase contrast/detail in the background57    depth = (depth * 255).astype(np.uint8)58    clahe = cv2.createCLAHE(clipLimit=5.0, tileGridSize=(8, 8))59    depth = clahe.apply(depth)60 61    # Apply bilateral filter to preserve edges while smoothing noise62    depth = cv2.bilateralFilter(depth, 9, 75, 75)63 64    # Light normalization to increase variation in the background65    depth = cv2.normalize(depth, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)66 67    return depth68 69 70def edge_aware_inpaint(image, mask, edge_mask=None):71    """72    Smarter inpainting that better respects edges.73    """74    # Dilate mask to cover the full inpaint region75    kernel = np.ones((11, 11), np.uint8)76    mask_dilated = cv2.dilate(mask, kernel, iterations=3)77 78    # Use TELEA instead of NS for more natural results79    inpainted = cv2.inpaint(80        image, mask_dilated, inpaintRadius=5, flags=cv2.INPAINT_TELEA81    )82 83    # Blend with surrounding pixels84    mask_blur = cv2.GaussianBlur(mask_dilated.astype(float) / 255.0, (21, 21), 0)85    mask_blur = np.stack([mask_blur] * 3, axis=-1)86 87    result = inpainted * mask_blur + image * (1 - mask_blur)88 89    return result.astype(np.uint8)90 91 92def create_soft_mask(hard_mask, blur_amount=15):93    """Create a soft mask with more natural edges."""94    # Morphological operations to smooth95    kernel = np.ones((5, 5), np.uint8)96    mask = cv2.morphologyEx(hard_mask, cv2.MORPH_CLOSE, kernel, iterations=2)97    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)98 99    # Gaussian blur for soft edges100    mask_float = mask.astype(np.float32) / 255.0101    mask_soft = cv2.GaussianBlur(mask_float, (0, 0), sigmaX=blur_amount)102 103    # Adjust mask contrast104    mask_soft = np.power(mask_soft, 0.8)  # Softer falloff105    mask_soft = np.clip(mask_soft, 0, 1)106 107    return mask_soft108 109 110def advanced_layer_separation(image_np, depth):111    """112    Automatically split the image into multiple layers based on the depth map.113    Facebook-style with a configurable number of layers.114    """115    NUM_LAYERS = 128  # Increase to 128 layers for richer background detail116 117    h, w = image_np.shape[:2]118 119    # Ensure depth matches image120    if depth.shape[:2] != (h, w):121        depth = cv2.resize(depth, (w, h), interpolation=cv2.INTER_CUBIC)122 123    # Create percentile-based thresholds, focusing more density in mid-range124    depth_flat = depth.flatten()125    # Use non-uniform percentiles: 70% background, 25% midground, 5% foreground126    bg_layers = int(NUM_LAYERS * 0.7)  # 70% for background127    mg_layers = int(NUM_LAYERS * 0.25)  # 25% for midground128    fg_layers = NUM_LAYERS - bg_layers - mg_layers  # 5% for foreground129 130    percentiles = np.concatenate(131        [132            np.linspace(0, 35, bg_layers),  # Background: 70% layers, high detail133            np.linspace(35, 80, mg_layers),  # Midground: 25% layers134            np.linspace(80, 100, fg_layers + 1),  # Foreground: 5% layers, fewer layers135        ]136    )137    thresholds = [np.percentile(depth_flat, p) for p in percentiles]138 139    def create_soft_mask_adaptive(mask, blur_amount, is_foreground=False):140        """Create soft alpha edges (foreground needs more blur)."""141        kernel_size = 9 if is_foreground else 7142        kernel = np.ones((kernel_size, kernel_size), np.uint8)143        mask_clean = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)144        mask_clean = cv2.dilate(mask_clean, kernel, iterations=1)145        mask_float = mask_clean.astype(np.float32) / 255.0146        # Foreground uses more blur for smoother edges147        blur_sigma = (blur_amount / 10.0) * (2.0 if is_foreground else 1.0)148        mask_blur = cv2.GaussianBlur(mask_float, (0, 0), blur_sigma)149        return np.clip(mask_blur, 0.0, 1.0)150 151    layers = []152 153    # Layer 0: Backplate (full opaque background)154    all_fg_mask = (depth >= thresholds[1]).astype(np.uint8) * 255155    inpaint_mask = cv2.dilate(all_fg_mask, np.ones((7, 7), np.uint8), iterations=2)156    backplate = edge_aware_inpaint(image_np, inpaint_mask)157    backplate = cv2.GaussianBlur(backplate, (7, 7), 1.5)158    bp_alpha = np.ones((h, w), dtype=np.uint8) * 255159    layers.append(np.dstack([backplate, bp_alpha]))160 161    # Create layers from far to near162    for i in range(NUM_LAYERS):163        # Create mask for this layer164        if i == NUM_LAYERS - 1:165            mask = (depth >= thresholds[i]).astype(np.uint8) * 255166        else:167            mask = ((depth >= thresholds[i]) & (depth < thresholds[i + 1])).astype(168                np.uint8169            ) * 255170 171        # Determine if this layer is foreground (top 5%)172        is_foreground = i >= (NUM_LAYERS - fg_layers)173 174        # Smooth mask (foreground uses more blur)175        blur_amount = max(8, 16 - i // 4)176        mask_soft = create_soft_mask_adaptive(mask, blur_amount, is_foreground)177 178        # Only inpaint the first 8 layers (background) to optimize speed179        if i < 8:180            # Compute combined mask of all layers in front181            combined_fg = np.zeros_like(depth, dtype=np.uint8)182            for j in range(i + 1, NUM_LAYERS):183                if j == NUM_LAYERS - 1:184                    fg = (depth >= thresholds[j]).astype(np.uint8)185                else:186                    fg = (187                        (depth >= thresholds[j]) & (depth < thresholds[j + 1])188                    ).astype(np.uint8)189                combined_fg = np.clip(combined_fg + fg, 0, 1)190 191            # Inpaint background192            if combined_fg.sum() > 100:193                inpaint_mask_layer = cv2.dilate(194                    combined_fg * 255, np.ones((5, 5), np.uint8), iterations=1195                )196                layer_img = edge_aware_inpaint(image_np, inpaint_mask_layer)197                # Reduce blur as depth increases198                blur_radius = max(1, 5 - i // 2)199                if blur_radius > 1:200                    layer_img = cv2.GaussianBlur(201                        layer_img,202                        (blur_radius * 2 + 1, blur_radius * 2 + 1),203                        blur_radius / 2.0,204                    )205            else:206                layer_img = image_np.copy()207        else:208            # Near layers don't need inpainting209            layer_img = image_np.copy()210 211        # Build alpha channel212        alpha = (mask_soft * 255).astype(np.uint8)213        layer = np.dstack([layer_img, alpha])214        layers.append(layer)215 216    return layers, depth217 218 219def image_to_base64(img_array, has_alpha=False):220    """Convert numpy array to base64"""221    if has_alpha:222        img = Image.fromarray(img_array, mode="RGBA")223    else:224        img = Image.fromarray(img_array)225 226    buffered = io.BytesIO()227    img.save(buffered, format="PNG", optimize=True)228    return base64.b64encode(buffered.getvalue()).decode()229 230 231@app.post("/api/process")232async def process_image(file: UploadFile = File(...)):233    """234    Process image with Facebook-quality 3D effect235    """236    try:237        # Read image238        contents = await file.read()239        image = Image.open(io.BytesIO(contents)).convert("RGB")240 241        # Resize to optimize performance while keeping quality242        max_size = 1024  # Increased from 800 to 1024243        if max(image.size) > max_size:244            ratio = max_size / max(image.size)245            new_size = tuple(int(dim * ratio) for dim in image.size)246            image = image.resize(new_size, Image.Resampling.LANCZOS)247 248        image_np = np.array(image)249 250        # Generate depth map251        print("Generating depth map...")252        depth = predict_depth(image)253 254        # Create advanced layers255        print("Creating layers...")256        layers, depth_processed = advanced_layer_separation(image_np, depth)257 258        # Convert to base64 and generate metadata for N layers259        layers_b64 = []260        num_layers = len(layers)261 262        for i, layer in enumerate(layers):263            layer_b64 = image_to_base64(layer, has_alpha=True)264 265            # Automatically compute depth and name for each layer266            if i == 0:267                layer_name = "backplate"268                layer_depth = 0.0269            else:270                # Linear depth from 0 to 1271                layer_depth = (i - 1) / (num_layers - 2) if num_layers > 2 else 0.5272                layer_name = f"layer_{i-1}"273 274            layers_b64.append(275                {276                    "index": i,277                    "name": layer_name,278                    "depth": layer_depth,279                    "data": f"data:image/png;base64,{layer_b64}",280                }281            )282 283        # Original image284        image_b64 = image_to_base64(image_np)285 286        # Depth visualization287        depth_colored = cv2.applyColorMap(depth_processed, cv2.COLORMAP_MAGMA)288        depth_colored = cv2.cvtColor(depth_colored, cv2.COLOR_BGR2RGB)289        depth_b64 = image_to_base64(depth_colored)290 291        # Depth data for mesh rendering (normalized 0..1)292        depth_normalized = (depth_processed.astype(np.float32) / 255.0).tolist()293 294        print("Processing complete!")295 296        return JSONResponse(297            {298                "success": True,299                "image": f"data:image/png;base64,{image_b64}",300                "depth": f"data:image/png;base64,{depth_b64}",301                "depth_data": depth_normalized,302                "layers": layers_b64,303                "width": image.width,304                "height": image.height,305                "use_mesh": True,  # Flag for the frontend to use mesh rendering306            }307        )308 309    except Exception as e:310        import traceback311 312        traceback.print_exc()313        return JSONResponse({"success": False, "error": str(e)}, status_code=500)314 315 316@app.get("/")317def root():318    return {319        "message": "Facebook 3D Photo Backend - Improved Version",320        "status": "running",321        "device": device,322    }323 324 325if __name__ == "__main__":326    import uvicorn327 328    uvicorn.run(app, host="0.0.0.0", port=8000)329