ab2207 commited on
Commit
1ccc2d4
·
verified ·
1 Parent(s): 5750a64

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -0
app.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Face Privacy Tool (Gradio UI with YOLOv8 Segmentation & Video Support)
3
+ """
4
+
5
+ # --- Standard Libraries ---
6
+ import logging
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Dict, List, Tuple
10
+ import tempfile
11
+ import os
12
+
13
+ # --- Computer Vision & UI Libraries ---
14
+ import cv2
15
+ import numpy as np
16
+ import gradio as gr
17
+ from ultralytics import YOLO
18
+
19
+ # --- Configure Logging ---
20
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # ====================================================
24
+ # CONFIGURATION DATA CLASSES
25
+ # ====================================================
26
+ @dataclass
27
+ class BlurConfig:
28
+ type: str = "gaussian"
29
+ intensity: float = 1.5
30
+ pixel_size: int = 30
31
+ solid_color: Tuple[int, int, int] = (0, 0, 0)
32
+ adaptive_blur: bool = True
33
+ min_kernel: int = 15
34
+ max_kernel: int = 95
35
+
36
+ @dataclass
37
+ class DetectionConfig:
38
+ min_confidence: float = 0.4
39
+ model_path: str = "yolov8n-seg.pt" # YOLOv8 segmentation model
40
+
41
+ @dataclass
42
+ class AppConfig:
43
+ blur: BlurConfig = field(default_factory=BlurConfig)
44
+ detection: DetectionConfig = field(default_factory=DetectionConfig)
45
+ scaling_factor: float = 1.2
46
+ forehead_margin: int = 20
47
+ face_margin: int = 15
48
+
49
+ # ====================================================
50
+ # BLUR EFFECTS
51
+ # ====================================================
52
+ class BlurEffect(ABC):
53
+ def __init__(self, config: BlurConfig):
54
+ self.config = config
55
+
56
+ @abstractmethod
57
+ def apply(self, image: np.ndarray, roi: Tuple[int, int, int, int]) -> np.ndarray:
58
+ pass
59
+
60
+ class GaussianBlur(BlurEffect):
61
+ def apply(self, image: np.ndarray, roi: Tuple[int, int, int, int]) -> np.ndarray:
62
+ x, y, w, h = roi
63
+ face_roi = image[y:y+h, x:x+w]
64
+ if face_roi.size == 0:
65
+ return image
66
+ if self.config.adaptive_blur:
67
+ min_dim = min(w, h)
68
+ kernel_val = int(min_dim * 0.25 * self.config.intensity)
69
+ kernel_val = max(self.config.min_kernel, min(kernel_val, self.config.max_kernel))
70
+ else:
71
+ kernel_val = 45
72
+ kernel_val = kernel_val | 1
73
+ blurred_roi = cv2.GaussianBlur(face_roi, (kernel_val, kernel_val), 0)
74
+ image[y:y+h, x:x+w] = blurred_roi
75
+ return image
76
+
77
+ class PixelateBlur(BlurEffect):
78
+ def apply(self, image: np.ndarray, roi: Tuple[int, int, int, int]) -> np.ndarray:
79
+ x, y, w, h = roi
80
+ face_roi = image[y:y+h, x:x+w]
81
+ if face_roi.size == 0:
82
+ return image
83
+ h_roi, w_roi, _ = face_roi.shape
84
+ pixel_size = self.config.pixel_size
85
+ if pixel_size <= 0:
86
+ return image
87
+ small = cv2.resize(face_roi, (max(1, w_roi // pixel_size), max(1, h_roi // pixel_size)), interpolation=cv2.INTER_LINEAR)
88
+ pixelated = cv2.resize(small, (w_roi, h_roi), interpolation=cv2.INTER_NEAREST)
89
+ image[y:y+h, x:x+w] = pixelated
90
+ return image
91
+
92
+ class SolidColorBlur(BlurEffect):
93
+ def apply(self, image: np.ndarray, roi: Tuple[int, int, int, int]) -> np.ndarray:
94
+ x, y, w, h = roi
95
+ cv2.rectangle(image, (x, y), (x+w, y+h), self.config.solid_color, -1)
96
+ return image
97
+
98
+ def get_blur_effect(config: BlurConfig) -> BlurEffect:
99
+ if config.type == "gaussian":
100
+ return GaussianBlur(config)
101
+ if config.type == "pixelate":
102
+ return PixelateBlur(config)
103
+ if config.type == "solid":
104
+ return SolidColorBlur(config)
105
+ raise ValueError(f"Unknown blur type: {config.type}")
106
+
107
+ # ====================================================
108
+ # YOLOv8 DETECTOR
109
+ # ====================================================
110
+ class YOLOv8Detector:
111
+ def __init__(self, config: DetectionConfig):
112
+ self.model = YOLO(config.model_path)
113
+ self.min_conf = config.min_confidence
114
+
115
+ def detect_faces(self, image: np.ndarray) -> List[Dict[str, Any]]:
116
+ """Detect faces/objects with YOLOv8 segmentation"""
117
+ results = self.model(image, conf=self.min_conf)
118
+ faces = []
119
+ for r in results:
120
+ for box in r.boxes:
121
+ x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
122
+ faces.append({"x": x1, "y": y1, "width": x2 - x1, "height": y2 - y1})
123
+ return faces
124
+
125
+ # ====================================================
126
+ # MAIN APPLICATION
127
+ # ====================================================
128
+ class FacePrivacyApp:
129
+ def __init__(self, config: AppConfig):
130
+ self.config = config
131
+ self.blur_effect = get_blur_effect(config.blur)
132
+ self.detector = YOLOv8Detector(config.detection)
133
+
134
+ def _expand_bbox(self, bbox: Dict[str, Any], img_shape: Tuple[int, int]) -> Tuple[int, int, int, int]:
135
+ h_img, w_img = img_shape
136
+ new_w = int(bbox["width"] * self.config.scaling_factor)
137
+ new_h = int(bbox["height"] * self.config.scaling_factor)
138
+ x_offset = (new_w - bbox["width"]) // 2
139
+ y_offset = (new_h - bbox["height"]) // 2
140
+ x = max(0, bbox["x"] - x_offset - self.config.face_margin)
141
+ y = max(0, bbox["y"] - y_offset - self.config.forehead_margin)
142
+ w = min(w_img - x, new_w + 2 * self.config.face_margin)
143
+ h = min(h_img - y, new_h + self.config.forehead_margin)
144
+ return x, y, w, h
145
+
146
+ def process_image(self, image: np.ndarray) -> np.ndarray:
147
+ writable_image = image.copy()
148
+ faces = self.detector.detect_faces(writable_image)
149
+ for face in faces:
150
+ expanded_roi = self._expand_bbox(face, writable_image.shape[:2])
151
+ writable_image = self.blur_effect.apply(writable_image, expanded_roi)
152
+ return writable_image
153
+
154
+ # ====================================================
155
+ # VIDEO PROCESSING FUNCTION
156
+ # ====================================================
157
+ def process_video_fn(video_file, blur_type, blur_amount, blur_size):
158
+ if video_file is None:
159
+ return None
160
+
161
+ app_config = AppConfig(
162
+ scaling_factor=blur_size,
163
+ blur=BlurConfig(type=blur_type, intensity=blur_amount, pixel_size=int(blur_amount))
164
+ )
165
+ app = FacePrivacyApp(app_config)
166
+
167
+ cap = cv2.VideoCapture(video_file.name)
168
+ if not cap.isOpened():
169
+ logger.warning(f"Cannot open video: {video_file.name}")
170
+ return None
171
+
172
+ out_fd, out_path = tempfile.mkstemp(suffix=".mp4")
173
+ os.close(out_fd)
174
+
175
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
176
+ fps = cap.get(cv2.CAP_PROP_FPS)
177
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
178
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
179
+ out_vid = cv2.VideoWriter(out_path, fourcc, fps, (width, height))
180
+
181
+ while True:
182
+ ret, frame = cap.read()
183
+ if not ret:
184
+ break
185
+ processed_frame = app.process_image(frame)
186
+ out_vid.write(processed_frame)
187
+
188
+ cap.release()
189
+ out_vid.release()
190
+ return out_path
191
+
192
+ # ====================================================
193
+ # GRADIO INTERFACE FUNCTIONS
194
+ # ====================================================
195
+ def process_single_image_fn(media, blur_type, blur_amount, blur_size):
196
+ if media is None:
197
+ return None
198
+ app_config = AppConfig(
199
+ scaling_factor=blur_size,
200
+ blur=BlurConfig(type=blur_type, intensity=blur_amount, pixel_size=int(blur_amount))
201
+ )
202
+ app = FacePrivacyApp(app_config)
203
+ return app.process_image(media)
204
+
205
+ def update_amount_slider_visibility(blur_type):
206
+ return gr.update(visible=(blur_type != "solid"))
207
+
208
+ # ====================================================
209
+ # BUILD GRADIO APP
210
+ # ====================================================
211
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
212
+ gr.Markdown("# Face Privacy Tool (YOLOv8 Segmentation & Video)")
213
+
214
+ with gr.Row():
215
+ with gr.Column(scale=1):
216
+ gr.Markdown("### ⚙️ Settings")
217
+ blur_type = gr.Radio(["gaussian", "pixelate", "solid"], value="pixelate", label="Blur Type")
218
+ blur_amount = gr.Slider(1, 50, step=1, value=25, label="Blur Amount")
219
+ blur_size = gr.Slider(1.0, 2.0, step=0.05, value=1.2, label="Blur Size (Expansion)")
220
+
221
+ with gr.Column(scale=2):
222
+ with gr.Tabs():
223
+ with gr.TabItem("Single Image"):
224
+ image_input = gr.Image(sources=["upload"], type="numpy", label="Upload Image")
225
+ image_output = gr.Image(type="numpy", label="Blurred Image")
226
+ single_image_button = gr.Button("Apply Blur to Single Image", variant="primary")
227
+ single_image_button.click(
228
+ fn=process_single_image_fn,
229
+ inputs=[image_input, blur_type, blur_amount, blur_size],
230
+ outputs=image_output
231
+ )
232
+
233
+ with gr.TabItem("Video Upload"):
234
+ video_input = gr.File(file_types=[".mp4", ".mov", ".avi"], label="Upload Video")
235
+ video_output = gr.Video(label="Processed Video")
236
+ process_video_button = gr.Button("Process Video", variant="primary")
237
+ process_video_button.click(
238
+ fn=process_video_fn,
239
+ inputs=[video_input, blur_type, blur_amount, blur_size],
240
+ outputs=video_output
241
+ )
242
+
243
+ with gr.TabItem("Webcam"):
244
+ webcam_input = gr.Image(sources=["webcam"], type="numpy", streaming=True, label="Live Webcam")
245
+ webcam_output = gr.Image(type="numpy", label="Processed Feed")
246
+ webcam_input.stream(
247
+ fn=process_single_image_fn,
248
+ inputs=[webcam_input, blur_type, blur_amount, blur_size],
249
+ outputs=webcam_output
250
+ )
251
+
252
+ blur_type.change(fn=update_amount_slider_visibility, inputs=blur_type, outputs=blur_amount)
253
+
254
+ if __name__ == "__main__":
255
+ demo.launch()