File size: 10,258 Bytes
f1e1801
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image, ImageFilter
from transformers import SegformerImageProcessor, SegformerForSemanticSegmentation
from scipy.ndimage import gaussian_filter
import torch
import requests
from io import BytesIO
import cv2
import warnings
warnings.filterwarnings('ignore')
from transformers import DPTImageProcessor, DPTForDepthEstimation

# Global model cache
segformer_processor = None
segformer_model = None
dpt_processor = None
dpt_model = None

def load_models():
    global segformer_processor, segformer_model, dpt_processor, dpt_model
    
    if segformer_processor is None:
        print("Loading Segformer model...")
        segformer_processor = SegformerImageProcessor.from_pretrained(
            "nvidia/segformer-b0-finetuned-ade-512-512"
        )
        segformer_model = SegformerForSemanticSegmentation.from_pretrained(
            "nvidia/segformer-b0-finetuned-ade-512-512"
        )
    
    if dpt_processor is None:
        print("Loading DPT model...")
        dpt_processor = DPTImageProcessor.from_pretrained("Intel/dpt-large")
        dpt_model = DPTForDepthEstimation.from_pretrained("Intel/dpt-large")
    
    return segformer_processor, segformer_model, dpt_processor, dpt_model

def preprocess_image(image, target_size=512):
    if isinstance(image, np.ndarray):
        image = Image.fromarray(image)
    
    # Convert to RGB
    if image.mode != 'RGB':
        image = image.convert('RGB')
    
    # Resize to square
    image = image.resize((target_size, target_size), Image.Resampling.LANCZOS)
    return image

def segment_human(image, processor, model):
    inputs = processor(images=image, return_tensors="pt")
    
    with torch.no_grad():
        outputs = model(**inputs)
    
    logits = outputs.logits
    upsampled = torch.nn.functional.interpolate(
        logits,
        size=(512, 512),
        mode="bilinear",
        align_corners=False
    )
    
    pred_seg = upsampled.argmax(dim=1)[0].cpu().numpy()
    human_mask = (pred_seg == 12).astype(np.uint8) * 255
    return human_mask

def apply_background_blur(image, mask, sigma=15):
    img_array = np.array(image).astype(np.float32)
    mask_normalized = mask.astype(np.float32) / 255.0
    
    # Smooth edges
    mask_smooth = gaussian_filter(mask_normalized, sigma=2)
    mask_smooth = np.clip(mask_smooth, 0, 1)
    
    # Blur entire image
    blurred_array = np.zeros_like(img_array)
    for i in range(3):
        blurred_array[:, :, i] = gaussian_filter(img_array[:, :, i], sigma=sigma)
    
    # Blend
    mask_3d = np.stack([mask_smooth] * 3, axis=2)
    result = (img_array * mask_3d + blurred_array * (1 - mask_3d)).astype(np.uint8)
    return Image.fromarray(result)

def estimate_depth(image, processor, model):
    inputs = processor(images=image, return_tensors="pt")
    
    with torch.no_grad():
        outputs = model(**inputs)
        predicted_depth = outputs.predicted_depth
    
    prediction = torch.nn.functional.interpolate(
        predicted_depth.unsqueeze(1),
        size=(512, 512),
        mode="bicubic",
        align_corners=False,
    )
    
    depth_map = prediction.squeeze().cpu().numpy()
    
    # Normalize to 0-15 and INVERT (high=far for proper blur)
    depth_min = depth_map.min()
    depth_max = depth_map.max()
    normalized = (depth_map - depth_min) / (depth_max - depth_min)
    normalized = 1.0 - normalized  # INVERT: high = far
    depth_normalized = normalized * 15.0
    return depth_normalized

def apply_lens_blur(image, depth_map, max_sigma=15):
    img_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR).astype(np.float32)
    
    # Create blur pyramid
    num_levels = 10
    blur_pyramid = []
    
    for i in range(num_levels):
        sigma = (i / (num_levels - 1)) * max_sigma
        if sigma < 0.5:
            blur_pyramid.append(img_cv.copy())
        else:
            ksize = int(2 * np.ceil(3 * sigma) + 1)
            if ksize % 2 == 0:
                ksize += 1
            blurred = cv2.GaussianBlur(img_cv, (ksize, ksize), sigma)
            blur_pyramid.append(blurred)
    
    # Apply variable blur based on depth
    depth_norm = depth_map / 15.0
    output = np.zeros_like(img_cv)
    
    depth_scaled = depth_norm * (num_levels - 1)
    level_low = np.floor(depth_scaled).astype(np.int32)
    level_high = np.ceil(depth_scaled).astype(np.int32)
    level_low = np.clip(level_low, 0, num_levels - 1)
    level_high = np.clip(level_high, 0, num_levels - 1)
    
    weight = depth_scaled - level_low
    weight = np.expand_dims(weight, axis=2)
    
    for y in range(img_cv.shape[0]):
        for x in range(img_cv.shape[1]):
            ll = level_low[y, x]
            lh = level_high[y, x]
            w = weight[y, x, 0]
            
            if ll == lh:
                output[y, x] = blur_pyramid[ll][y, x]
            else:
                output[y, x] = (1 - w) * blur_pyramid[ll][y, x] + w * blur_pyramid[lh][y, x]
    
    output = np.clip(output, 0, 255).astype(np.uint8)
    output_rgb = cv2.cvtColor(output, cv2.COLOR_BGR2RGB)
    return Image.fromarray(output_rgb)

def process_gaussian_blur(image, sigma):
    if image is None:
        return None, "Please upload an image first!"
    
    try:
        # Load models
        seg_proc, seg_model, _, _ = load_models()
        
        # Preprocess
        img = preprocess_image(image, 512)
        
        # Segment human
        mask = segment_human(img, seg_proc, seg_model)
        
        # Apply blur
        result = apply_background_blur(img, mask, sigma)
        
        return result, f"Gaussian blur (σ={sigma}) applied successfully!"
    
    except Exception as e:
        return None, f"Error: {str(e)}"

def process_lens_blur(image, max_sigma):
    if image is None:
        return None, None, "Please upload an image first!"
    
    try:
        # Load models
        _, _, dpt_proc, dpt_model = load_models()
        
        # Preprocess
        img = preprocess_image(image, 512)
        
        # Estimate depth
        depth = estimate_depth(img, dpt_proc, dpt_model)
        
        # Apply lens blur
        result = apply_lens_blur(img, depth, max_sigma)
        
        # Create depth visualization
        depth_vis = ((depth / 15.0) * 255).astype(np.uint8)
        depth_colored = cv2.applyColorMap(depth_vis, cv2.COLORMAP_VIRIDIS)
        depth_colored = cv2.cvtColor(depth_colored, cv2.COLOR_BGR2RGB)
        depth_img = Image.fromarray(depth_colored)
        
        return result, depth_img, f"Lens blur (σ_max={max_sigma}) applied successfully!"
    
    except Exception as e:
        return None, None, f"Error: {str(e)}"

# Create Gradio interface
with gr.Blocks(title="GaussBlur App", theme=gr.themes.Soft()) as demo:
    gr.Markdown("""
    # GaussBlur App
    ### Professional Image Blur Effects using AI
    
    Two powerful blur techniques:
    1. **Gaussian Background Blur** - Zoom-style video conferencing effect
    2. **Depth-Based Lens Blur** - Realistic camera depth of field
    
    ---
    """)
    
    with gr.Tabs():
        # Tab 1: Gaussian Background Blur
        with gr.Tab("📹 Gaussian Background Blur"):
            gr.Markdown("""
            ### Zoom-Style Background Blur
            Segments the human and blurs only the background with uniform Gaussian blur.
            Perfect for video conferencing effects!
            """)
            
            with gr.Row():
                with gr.Column():
                    gaussian_input = gr.Image(label="Upload Image", type="numpy")
                    gaussian_sigma = gr.Slider(
                        minimum=0, 
                        maximum=30, 
                        value=15, 
                        step=1,
                        label="Blur Strength (σ)",
                        info="Higher values = more blur"
                    )
                    gaussian_btn = gr.Button("Apply Gaussian Blur", variant="primary")
                
                with gr.Column():
                    gaussian_output = gr.Image(label="Result")
                    gaussian_status = gr.Textbox(label="Status", lines=2)
            
            gr.Markdown("""
            **How it works:**
            1. AI segments human from background using Segformer
            2. Applies uniform Gaussian blur (σ=15) to background only
            3. Keeps human sharp with smooth edge transitions
            """)
        
        # Tab 2: Depth-Based Lens Blur
        with gr.Tab("📸 Depth-Based Lens Blur"):
            gr.Markdown("""
            ### Realistic Camera Depth of Field
            Estimates depth and applies variable blur based on distance.
            Close objects stay sharp, far objects get blurred - just like a real camera!
            """)
            
            with gr.Row():
                with gr.Column():
                    lens_input = gr.Image(label="Upload Image", type="numpy")
                    lens_sigma = gr.Slider(
                        minimum=0,
                        maximum=25,
                        value=15,
                        step=1,
                        label="Maximum Blur (σ_max)",
                        info="Blur for farthest objects"
                    )
                    lens_btn = gr.Button("Apply Lens Blur", variant="primary")
                
                with gr.Column():
                    lens_output = gr.Image(label="Blurred Result")
                    lens_depth = gr.Image(label="Depth Map (Purple=Close, Yellow=Far)")
                    lens_status = gr.Textbox(label="Status", lines=2)
    
    # Connect buttons to functions
    gaussian_btn.click(
        fn=process_gaussian_blur,
        inputs=[gaussian_input, gaussian_sigma],
        outputs=[gaussian_output, gaussian_status]
    )
    
    lens_btn.click(
        fn=process_lens_blur,
        inputs=[lens_input, lens_sigma],
        outputs=[lens_output, lens_depth, lens_status]
    )

# Launch app
if __name__ == "__main__":
    print("Starting Blur Effects Studio...")
    print("Loading AI models (this may take a moment)...")
    load_models()
    print("Models loaded! Launching app...")
    demo.launch(share=True)