danielquillanroxas commited on
Commit
83aae9f
·
1 Parent(s): 60d1e42
Files changed (3) hide show
  1. app.py +250 -0
  2. models/unified_detector(1).pt +3 -0
  3. utils/processing.py +290 -0
app.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import cv2
4
+ import numpy as np
5
+ import torch
6
+ from PIL import Image
7
+ from ultralytics import YOLO
8
+ from utils.processing import detect_and_blur, process_video
9
+ import tempfile
10
+ import time
11
+
12
+ # Style and theme configuration
13
+ PRIMARY_COLOR = "#4F46E5" # Indigo
14
+ SECONDARY_COLOR = "#6366F1" # Lighter indigo
15
+
16
+ # Setup paths and model
17
+ MODEL_DIR = os.path.join(os.path.dirname(__file__), "models")
18
+ MODEL_PATH = os.path.join(MODEL_DIR, "unified_detector.pt")
19
+ RESULTS_DIR = os.path.join(os.path.dirname(__file__), "results")
20
+ os.makedirs(RESULTS_DIR, exist_ok=True)
21
+
22
+ # Load model (with error handling and GPU support)
23
+ def load_model():
24
+ try:
25
+ print(f"CUDA Available: {torch.cuda.is_available()}")
26
+ device = "cuda" if torch.cuda.is_available() else "cpu"
27
+ print(f"Loading model from {MODEL_PATH} on {device}...")
28
+ model = YOLO(MODEL_PATH)
29
+ model.to(device)
30
+ print(f"Model loaded successfully")
31
+ return model
32
+ except Exception as e:
33
+ print(f"Error loading model: {e}")
34
+ return None
35
+
36
+ # Load model at startup
37
+ model = load_model()
38
+
39
+ # Image processing function
40
+ def process_image_interface(input_image, blur_strength=0.7):
41
+ if input_image is None:
42
+ return None, "Please upload an image to process"
43
+
44
+ start_time = time.time()
45
+
46
+ try:
47
+ # Convert from Gradio's PIL format to numpy
48
+ if isinstance(input_image, Image.Image):
49
+ img_array = np.array(input_image.convert('RGB'))
50
+ else:
51
+ img_array = input_image
52
+
53
+ # Adjust blur strength (scale from 0.3 to 1.0)
54
+ real_blur = 0.3 + (blur_strength * 0.7)
55
+
56
+ # Process the image
57
+ result_rgb, detections, _ = detect_and_blur(img_array, model)
58
+
59
+ # Create result message
60
+ elapsed_time = time.time() - start_time
61
+ message = (
62
+ f"✅ Processing complete in {elapsed_time:.2f}s\n"
63
+ f"👤 Detected and blurred {detections['faces']} faces\n"
64
+ f"🔢 Detected and blurred {detections['plates']} plates/text regions"
65
+ )
66
+
67
+ return result_rgb, message
68
+ except Exception as e:
69
+ return None, f"❌ Error processing image: {str(e)}"
70
+
71
+ # Video processing function
72
+ def process_video_interface(input_video, frame_skip=3, blur_strength=0.7):
73
+ if input_video is None:
74
+ return None, "Please upload a video to process"
75
+
76
+ try:
77
+ # Create a temporary file for the output
78
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_output:
79
+ output_path = temp_output.name
80
+
81
+ # Get original file
82
+ output_path = process_video(
83
+ input_path=input_video,
84
+ output_path=output_path,
85
+ model=model,
86
+ frame_skip=int(frame_skip)
87
+ )
88
+
89
+ if output_path and os.path.exists(output_path):
90
+ return output_path, f"✅ Video processed successfully. Skipped every {frame_skip} frames for efficiency."
91
+ else:
92
+ return None, "❌ Error processing video"
93
+ except Exception as e:
94
+ return None, f"❌ Error processing video: {str(e)}"
95
+
96
+ # Welcome message (Markdown)
97
+ welcome_md = """
98
+ # 🔒 Privacy Protector: AI-Powered Content Blurring
99
+
100
+ This application automatically detects and blurs sensitive content in your images and videos, including:
101
+
102
+ - 👤 **Faces**: Protects identity by blurring all human faces
103
+ - 🚗 **License Plates**: Ensures vehicle privacy
104
+ - 📝 **Text**: Blurs potentially sensitive text in images
105
+
106
+ ## How to Use
107
+
108
+ 1. Upload an image or video using the appropriate tab
109
+ 2. Adjust blurring settings if needed
110
+ 3. Click the "Process" button
111
+ 4. Download your privacy-protected result
112
+
113
+ *Powered by YOLOv8 deep learning technology*
114
+ """
115
+
116
+ # Create Gradio interface
117
+ with gr.Blocks(theme=gr.themes.Default(primary_hue="indigo", secondary_hue="indigo")) as demo:
118
+ gr.Markdown(welcome_md)
119
+
120
+ # Model status indicator
121
+ with gr.Row():
122
+ if model is not None:
123
+ gr.Markdown(
124
+ f"<div style='background-color: #dcfce7; padding: 10px; border-radius: 4px; margin-bottom: 15px'>"
125
+ f"✅ <b>Model Status:</b> Loaded and Ready"
126
+ f"</div>"
127
+ )
128
+ else:
129
+ gr.Markdown(
130
+ f"<div style='background-color: #fee2e2; padding: 10px; border-radius: 4px; margin-bottom: 15px'>"
131
+ f"❌ <b>Model Status:</b> Error Loading Model"
132
+ f"</div>"
133
+ )
134
+
135
+ # Tabs for different functions
136
+ with gr.Tabs():
137
+ # Image Processing Tab
138
+ with gr.TabItem("Image Processing"):
139
+ with gr.Row():
140
+ with gr.Column():
141
+ image_input = gr.Image(label="Upload Image", type="pil")
142
+ with gr.Row():
143
+ image_blur_strength = gr.Slider(
144
+ label="Blur Strength",
145
+ minimum=0,
146
+ maximum=1,
147
+ value=0.7,
148
+ step=0.1
149
+ )
150
+ image_process_btn = gr.Button("Process Image", variant="primary")
151
+
152
+ with gr.Column():
153
+ image_output = gr.Image(label="Result with Blurring")
154
+ image_output_text = gr.Textbox(label="Processing Results", lines=3)
155
+
156
+ # Set up examples for image processing
157
+ gr.Examples(
158
+ examples=[
159
+ os.path.join(os.path.dirname(__file__), "examples", "example1.jpg"),
160
+ os.path.join(os.path.dirname(__file__), "examples", "example2.jpg"),
161
+ ],
162
+ inputs=image_input,
163
+ outputs=[image_output, image_output_text],
164
+ fn=process_image_interface,
165
+ cache_examples=True,
166
+ )
167
+
168
+ # Video Processing Tab
169
+ with gr.TabItem("Video Processing"):
170
+ with gr.Row():
171
+ with gr.Column():
172
+ video_input = gr.Video(label="Upload Video")
173
+ with gr.Row():
174
+ video_frame_skip = gr.Slider(
175
+ label="Frame Skip Rate (higher = faster but less smooth)",
176
+ minimum=1,
177
+ maximum=10,
178
+ value=3,
179
+ step=1
180
+ )
181
+ video_blur_strength = gr.Slider(
182
+ label="Blur Strength",
183
+ minimum=0,
184
+ maximum=1,
185
+ value=0.7,
186
+ step=0.1
187
+ )
188
+ video_process_btn = gr.Button("Process Video", variant="primary")
189
+
190
+ with gr.Column():
191
+ video_output = gr.Video(label="Processed Video")
192
+ video_output_text = gr.Textbox(label="Processing Results", lines=3)
193
+
194
+ # About Tab
195
+ with gr.TabItem("About & Help"):
196
+ gr.Markdown("""
197
+ ## About Privacy Protector
198
+
199
+ This application uses a custom-trained YOLOv8 model to detect and blur sensitive content in images and videos.
200
+
201
+ ### Technical Details
202
+
203
+ - **Model Architecture**: YOLOv8 Medium
204
+ - **Training Dataset**: Custom dataset of faces, license plates, and text samples
205
+ - **Performance**: Fast inference suitable for real-time applications
206
+
207
+ ### Privacy Information
208
+
209
+ - All processing is done on the server - no data is stored
210
+ - Uploaded images and videos are automatically deleted after processing
211
+ - The application does not collect any personal information
212
+
213
+ ### Limitations
214
+
215
+ - May not detect all faces or text in low-quality images
216
+ - Very small text may be missed
217
+ - Processing large videos may take some time
218
+
219
+ ### Need Help?
220
+
221
+ If you're experiencing issues or have questions, please visit the repository or contact the developer.
222
+ """)
223
+
224
+ # Set up event handlers
225
+ image_process_btn.click(
226
+ fn=process_image_interface,
227
+ inputs=[image_input, image_blur_strength],
228
+ outputs=[image_output, image_output_text]
229
+ )
230
+
231
+ video_process_btn.click(
232
+ fn=process_video_interface,
233
+ inputs=[video_input, video_frame_skip, video_blur_strength],
234
+ outputs=[video_output, video_output_text]
235
+ )
236
+
237
+ # Footer
238
+ gr.Markdown("""
239
+ <div style="text-align: center; margin-top: 30px; padding-top: 10px; border-top: 1px solid #eee">
240
+ <p>Developed with ❤️ using YOLOv8 and Gradio | © 2025 Privacy Protector</p>
241
+ </div>
242
+ """)
243
+
244
+ # Launch the app
245
+ if __name__ == "__main__":
246
+ # Create examples directory if it doesn't exist
247
+ os.makedirs(os.path.join(os.path.dirname(__file__), "examples"), exist_ok=True)
248
+
249
+ # Launch Gradio app
250
+ demo.launch()
models/unified_detector(1).pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aff7271ad02c42539099973d8de79ea553b5cbb539c037840a5c777a702c7e10
3
+ size 52048876
utils/processing.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import time
4
+ import os
5
+ from ultralytics import YOLO
6
+ from PIL import Image
7
+ import matplotlib.pyplot as plt
8
+
9
+ def detect_and_blur(input_source, model=None, frame_skip=3):
10
+ """Detect and blur sensitive elements in images or video frames
11
+
12
+ Args:
13
+ input_source: Image path or video frame (numpy array)
14
+ model: YOLO model instance
15
+ frame_skip: Frame skip rate for video processing
16
+
17
+ Returns:
18
+ result_rgb: Processed image with blurred regions
19
+ detections: Dict with counts of detected objects
20
+ boxes: Dict with bounding boxes of detected objects
21
+ """
22
+ if isinstance(input_source, str): # Image path
23
+ frame = cv2.imread(input_source)
24
+ if frame is None:
25
+ raise ValueError(f"Could not read image from {input_source}")
26
+ else: # Video frame or numpy array
27
+ frame = input_source.copy()
28
+
29
+ # Handle RGB vs BGR input
30
+ if len(frame.shape) == 3 and frame.shape[2] == 3:
31
+ if isinstance(input_source, str) or input_source is not None:
32
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
33
+ else:
34
+ frame_rgb = frame # Assume RGB if directly passed
35
+ else:
36
+ raise ValueError("Input must be a color image with 3 channels")
37
+
38
+ result_img = frame.copy()
39
+ detections = {'faces': 0, 'plates': 0}
40
+ boxes = {'faces': [], 'plates': []}
41
+
42
+ if model:
43
+ try:
44
+ # Run YOLOv8 inference
45
+ results = model.predict(frame_rgb, conf=0.5)
46
+ for r in results:
47
+ for box in r.boxes:
48
+ x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
49
+ cls_id = int(box.cls[0])
50
+ conf = float(box.conf[0])
51
+
52
+ # Ensure coordinates are within image bounds
53
+ x1, y1 = max(0, x1), max(0, y1)
54
+ x2, y2 = min(frame.shape[1], x2), min(frame.shape[0], y2)
55
+
56
+ # Skip invalid boxes
57
+ if x2 <= x1 or y2 <= y1:
58
+ continue
59
+
60
+ # Apply Gaussian blur to the detected region
61
+ region = result_img[y1:y2, x1:x2]
62
+ # Adjust kernel size based on detection type and region size
63
+ kernel_size = 55 if cls_id == 1 else 71 # Different blur for faces vs plates/text
64
+ kernel_size = max(25, min(kernel_size, (x2-x1)//2*2+1, (y2-y1)//2*2+1))
65
+ kernel_size = kernel_size + 1 if kernel_size % 2 == 0 else kernel_size
66
+
67
+ # Apply blur only if kernel size is valid
68
+ if kernel_size >= 3:
69
+ blurred = cv2.GaussianBlur(region, (kernel_size, kernel_size), 15)
70
+ result_img[y1:y2, x1:x2] = blurred
71
+
72
+ # Update detection counts and boxes
73
+ if cls_id == 0: # Assuming 0 is plate/text
74
+ detections['plates'] += 1
75
+ boxes['plates'].append((x1, y1, x2, y2))
76
+ else: # Assuming 1 is face
77
+ detections['faces'] += 1
78
+ boxes['faces'].append((x1, y1, x2, y2))
79
+ except Exception as e:
80
+ print(f"Detection error: {e}")
81
+
82
+ # Ensure output is RGB for consistent interface
83
+ if isinstance(input_source, str) or input_source is not None:
84
+ result_rgb = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)
85
+ else:
86
+ result_rgb = result_img
87
+
88
+ return result_rgb, detections, boxes
89
+
90
+ def process_video(input_path, output_path=None, model=None, frame_skip=3, output_fps=30):
91
+ """Process video with optimized frame skipping
92
+
93
+ Args:
94
+ input_path: Path to input video
95
+ output_path: Path to save processed video (if None, auto-generated)
96
+ model: YOLO model instance
97
+ frame_skip: Process 1 in every N frames
98
+ output_fps: Output video frame rate
99
+
100
+ Returns:
101
+ output_path: Path to processed video file
102
+ """
103
+ try:
104
+ # Open video file
105
+ cap = cv2.VideoCapture(input_path)
106
+ if not cap.isOpened():
107
+ raise ValueError(f"Could not open video file {input_path}")
108
+
109
+ # Get video properties
110
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
111
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
112
+ original_fps = cap.get(cv2.CAP_PROP_FPS)
113
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
114
+
115
+ # Set output parameters
116
+ fps = original_fps if original_fps > 0 else output_fps
117
+ if output_path is None:
118
+ # Create results directory if it doesn't exist
119
+ results_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "results")
120
+ os.makedirs(results_dir, exist_ok=True)
121
+ output_path = os.path.join(results_dir, f"processed_{os.path.basename(input_path)}")
122
+
123
+ # Create video writer
124
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
125
+ out = cv2.VideoWriter(output_path, fourcc, fps//frame_skip, (frame_width, frame_height))
126
+
127
+ # Display processing information
128
+ print(f"Processing video: {os.path.basename(input_path)}")
129
+ print(f"Original: {frame_width}x{frame_height} @ {original_fps:.1f}fps")
130
+ print(f"Processing: 1 every {frame_skip} frames")
131
+ print(f"Output: {fps//frame_skip:.1f}fps | Estimated time: {total_frames/(fps*frame_skip):.1f}s")
132
+
133
+ # Process video frames
134
+ frame_count = 0
135
+ processed_frames = 0
136
+ start_time = time.time()
137
+
138
+ while True:
139
+ ret, frame = cap.read()
140
+ if not ret:
141
+ break
142
+
143
+ # Skip frames according to frame_skip
144
+ if frame_count % frame_skip != 0:
145
+ frame_count += 1
146
+ continue
147
+
148
+ try:
149
+ # Process frame
150
+ result_rgb, _, _ = detect_and_blur(frame, model)
151
+ result_bgr = cv2.cvtColor(result_rgb, cv2.COLOR_RGB2BGR)
152
+ out.write(result_bgr)
153
+ processed_frames += 1
154
+
155
+ # Print progress periodically
156
+ if time.time() - start_time >= 5:
157
+ elapsed = time.time() - start_time
158
+ fps = processed_frames / elapsed
159
+ print(f"Progress: {frame_count}/{total_frames} | "
160
+ f"Processed: {processed_frames} | "
161
+ f"Current FPS: {fps:.1f}")
162
+ start_time = time.time()
163
+ processed_frames = 0
164
+
165
+ except Exception as e:
166
+ print(f"Error processing frame {frame_count}: {e}")
167
+
168
+ frame_count += 1
169
+
170
+ # Clean up
171
+ cap.release()
172
+ out.release()
173
+ print(f"\nVideo processing complete! Saved to {output_path}")
174
+ return output_path
175
+
176
+ except Exception as e:
177
+ print(f"Video processing failed: {e}")
178
+ return None
179
+
180
+ def process_image(image_path, output_path=None, model=None, visualize=False):
181
+ """Process single image with optional visualization
182
+
183
+ Args:
184
+ image_path: Path to input image or numpy array
185
+ output_path: Path to save processed image (if None, auto-generated)
186
+ model: YOLO model instance
187
+ visualize: Whether to create visualization with original, detections, and result
188
+
189
+ Returns:
190
+ result_path: Path to processed image file
191
+ or
192
+ result_rgb: Processed image as numpy array (if output_path is None)
193
+ """
194
+ try:
195
+ # Process image
196
+ result_rgb, detections, boxes = detect_and_blur(image_path, model)
197
+
198
+ # Handle input as numpy array
199
+ if not isinstance(image_path, str):
200
+ if output_path is None:
201
+ return result_rgb
202
+ image_filename = "processed_image.jpg"
203
+ else:
204
+ image_filename = os.path.basename(image_path)
205
+
206
+ # Create visualization if requested
207
+ if visualize:
208
+ # Load original image
209
+ if isinstance(image_path, str):
210
+ original = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)
211
+ else:
212
+ original = image_path
213
+
214
+ # Create image with detection boxes
215
+ detection_img = original.copy()
216
+
217
+ # Draw face boxes
218
+ for box in boxes['faces']:
219
+ x1, y1, x2, y2 = box
220
+ cv2.rectangle(detection_img, (x1, y1), (x2, y2), (255, 0, 0), 2)
221
+ cv2.putText(detection_img, "Face", (x1, y1-10),
222
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)
223
+
224
+ # Draw plate/text boxes
225
+ for box in boxes['plates']:
226
+ x1, y1, x2, y2 = box
227
+ cv2.rectangle(detection_img, (x1, y1), (x2, y2), (0, 0, 255), 2)
228
+ cv2.putText(detection_img, "Plate/Text", (x1, y1-10),
229
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
230
+
231
+ # Create comparison visualization (only for local debugging)
232
+ fig, axes = plt.subplots(1, 3, figsize=(20, 7))
233
+ titles = ["Original", "Detections", "Blurred Result"]
234
+ images = [original, detection_img, result_rgb]
235
+
236
+ for ax, title, img in zip(axes, titles, images):
237
+ ax.imshow(img)
238
+ ax.set_title(title)
239
+ ax.axis("off")
240
+
241
+ plt.tight_layout()
242
+ plt.show()
243
+
244
+ # Save result if output path provided
245
+ if output_path is not None:
246
+ # Save the processed image
247
+ cv2.imwrite(output_path, cv2.cvtColor(result_rgb, cv2.COLOR_RGB2BGR))
248
+ print(f"Saved result to {output_path}")
249
+ else:
250
+ # Auto-generate output path if not provided
251
+ results_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "results")
252
+ os.makedirs(results_dir, exist_ok=True)
253
+ result_path = os.path.join(results_dir, f"processed_{image_filename}")
254
+ cv2.imwrite(result_path, cv2.cvtColor(result_rgb, cv2.COLOR_RGB2BGR))
255
+ print(f"Saved result to {result_path}")
256
+ output_path = result_path
257
+
258
+ print(f"Detections: {detections['faces']} faces, {detections['plates']} plates/text regions")
259
+ return output_path if isinstance(image_path, str) else result_rgb
260
+
261
+ except Exception as e:
262
+ print(f"Image processing error: {e}")
263
+ return None
264
+
265
+ def process_pil_image(pil_image, model=None):
266
+ """Process PIL Image for Gradio interface
267
+
268
+ Args:
269
+ pil_image: PIL Image
270
+ model: YOLO model instance
271
+
272
+ Returns:
273
+ result_pil: Processed PIL Image
274
+ detections: Dict with counts of detected objects
275
+ """
276
+ try:
277
+ # Convert PIL to numpy array
278
+ img_array = np.array(pil_image)
279
+
280
+ # Process the image
281
+ result_rgb, detections, _ = detect_and_blur(img_array, model)
282
+
283
+ # Convert back to PIL if needed
284
+ result_pil = Image.fromarray(result_rgb)
285
+
286
+ return result_pil, detections
287
+
288
+ except Exception as e:
289
+ print(f"PIL image processing error: {e}")
290
+ return pil_image, {'faces': 0, 'plates': 0}