Spaces:
Running
Running
import gradio as gr | |
import os | |
import cv2 | |
import numpy as np | |
import asyncio | |
from utils import detect_faces_frame, apply_blur, load_caffe_models | |
from ultralight import UltraLightDetector | |
import tempfile | |
import json | |
# Create output directories | |
os.makedirs("output/image", exist_ok=True) | |
os.makedirs("output/video", exist_ok=True) | |
os.makedirs("temp", exist_ok=True) | |
# Initialize detector once | |
detector = UltraLightDetector() | |
# Age and gender options for filters | |
AGE_OPTIONS = ['0-2', '4-6', '8-12', '15-20', '25-32', '38-43', '48-53', '60+'] | |
GENDER_OPTIONS = ['Male', 'Female'] | |
# Operation options | |
OPERATION_OPTIONS = { | |
"Gaussian Blur": 0, | |
"Black Patch": 1, | |
"Pixelation": 2 | |
} | |
def convert_for_json(obj): | |
"""Convert NumPy arrays to lists for JSON serialization""" | |
if isinstance(obj, np.ndarray): | |
return obj.tolist() | |
elif isinstance(obj, np.float32) or isinstance(obj, np.float64): | |
return float(obj) | |
elif isinstance(obj, np.int32) or isinstance(obj, np.int64): | |
return int(obj) | |
elif isinstance(obj, dict): | |
return {k: convert_for_json(v) for k, v in obj.items()} | |
elif isinstance(obj, list): | |
return [convert_for_json(item) for item in obj] | |
else: | |
return obj | |
def process_image(image, operation_name, age_filters=[], gender_filters=[], selected_face_indices=[]): | |
"""Process an image with face blurring""" | |
# Convert from PIL to cv2 format | |
if image is None: | |
return None, "Please upload an image" | |
# Convert from RGB (gradio) to BGR (OpenCV) | |
if isinstance(image, str): # If it's a path | |
image_cv = cv2.imread(image) | |
else: # If it's a numpy array | |
image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) | |
# Get operation code | |
operation = OPERATION_OPTIONS.get(operation_name, 0) | |
# Detect faces | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
predictions = loop.run_until_complete(detect_faces_frame(detector=detector, frame=image_cv)) | |
loop.close() | |
# Create a temporary copy for drawing face boxes | |
image_with_boxes = image_cv.copy() | |
face_thumbnails = [] | |
# Draw boxes around all detected faces with indices | |
for i, pred in enumerate(predictions): | |
box = np.array(pred['box']) | |
x1, y1, x2, y2 = box.astype(int) | |
# Draw box | |
cv2.rectangle(image_with_boxes, (x1, y1), (x2, y2), (0, 255, 0), 1) | |
face_img = image_cv[y1:y2, x1:x2] | |
face_rgb = cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB) | |
caption = f"Face #{i} | {pred['gender']} | {pred['age']}" | |
face_thumbnails.append((face_rgb, caption)) | |
# Draw index | |
# cv2.putText(image_with_boxes, f"#{i}: {pred['gender']}, {pred['age']}", | |
# (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) | |
# Convert to RGB for display | |
image_with_boxes_rgb = cv2.cvtColor(image_with_boxes, cv2.COLOR_BGR2RGB) | |
# Create filters dictionary | |
filters = { | |
"gender": gender_filters, | |
"age": age_filters | |
} | |
# Create selected_faces list based on indices | |
selected_faces = [] | |
if selected_face_indices: | |
indices = [int(idx.strip()) for idx in selected_face_indices.split(",") if idx.strip().isdigit()] | |
for i in indices: | |
if i < len(predictions): | |
selected_faces.append({"box": predictions[i]["box"]}) | |
# Apply blur | |
loop = asyncio.new_event_loop() | |
asyncio.set_event_loop(loop) | |
processed_image = loop.run_until_complete( | |
apply_blur( | |
detected_faces=predictions, | |
frame=image_cv.copy(), | |
filters=filters, | |
selected_faces=selected_faces, | |
operation=operation | |
) | |
) | |
loop.close() | |
# Convert back to RGB for Gradio | |
processed_image_rgb = cv2.cvtColor(processed_image, cv2.COLOR_BGR2RGB) | |
# Save results as JSON | |
results_data = { | |
"faces_detected": len(predictions), | |
"predictions": convert_for_json(predictions), | |
"operation": operation_name, | |
"filters": { | |
"gender": gender_filters, | |
"age": age_filters | |
}, | |
"selected_faces": [int(idx.strip()) for idx in selected_face_indices.split(",") if idx.strip().isdigit()] if selected_face_indices else [] | |
} | |
return [image_with_boxes_rgb, processed_image_rgb, json.dumps(results_data, indent=2), face_thumbnails] | |
# def process_video(video_path, operation_name, age_filters=[], gender_filters=[], progress=gr.Progress()): | |
# """Process a video with face blurring""" | |
# if video_path is None: | |
# return None, "Please upload a video" | |
# # Get operation code | |
# operation = OPERATION_OPTIONS.get(operation_name, 0) | |
# # Create a temporary file for the output | |
# output_path = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False).name | |
# # Open the video | |
# cap = cv2.VideoCapture(video_path) | |
# if not cap.isOpened(): | |
# return None, "Could not open video file" | |
# # Get video properties | |
# width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) | |
# height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) | |
# fps = cap.get(cv2.CAP_PROP_FPS) | |
# total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
# # Determine frame skipping (process every nth frame for speed) | |
# frame_skip = max(1, round(fps / 15)) # Process at most 15 fps | |
# # Create VideoWriter object | |
# fourcc = cv2.VideoWriter_fourcc(*'mp4v') | |
# out = cv2.VideoWriter(output_path, fourcc, fps, (width, height)) | |
# # Create filters dictionary | |
# filters = { | |
# "gender": gender_filters, | |
# "age": age_filters | |
# } | |
# # Process frames | |
# frame_count = 0 | |
# face_count = 0 | |
# # Process limited frames to prevent timeout (Gradio has a 60s limit by default) | |
# max_frames_to_process = min(300, total_frames) # Limit to 300 frames | |
# for _ in progress.tqdm(range(max_frames_to_process)): | |
# ret, frame = cap.read() | |
# if not ret: | |
# break | |
# # Process every nth frame (for efficiency) | |
# if frame_count % frame_skip == 0: | |
# # Detect faces | |
# loop = asyncio.new_event_loop() | |
# asyncio.set_event_loop(loop) | |
# predictions = loop.run_until_complete(detect_faces_frame(detector=detector, frame=frame)) | |
# loop.close() | |
# face_count += len(predictions) | |
# # Apply blur | |
# loop = asyncio.new_event_loop() | |
# asyncio.set_event_loop(loop) | |
# processed_frame = loop.run_until_complete( | |
# apply_blur( | |
# detected_faces=predictions, | |
# frame=frame, | |
# filters=filters, | |
# operation=operation | |
# ) | |
# ) | |
# loop.close() | |
# # Write processed frame | |
# out.write(processed_frame) | |
# else: | |
# # Write original frame for skipped frames | |
# out.write(frame) | |
# frame_count += 1 | |
# # Release resources | |
# cap.release() | |
# out.release() | |
# # Summary message | |
# summary = f"Processed {frame_count} frames, detected {face_count} faces" | |
# if frame_count < total_frames: | |
# summary += f" (limited to first {frame_count} frames out of {total_frames})" | |
# return output_path, summary | |
# Create Gradio interface | |
with gr.Blocks(title="Face Privacy Protection Tool") as demo: | |
gr.Markdown("# Face Privacy Protection Tool") | |
gr.Markdown("Upload an image or video to detect faces and apply privacy filters") | |
with gr.Tabs(): | |
with gr.TabItem("Image Processing"): | |
with gr.Row(): | |
with gr.Column(): | |
image_input = gr.Image(label="Upload Image", type="pil") | |
operation_dropdown = gr.Dropdown( | |
choices=list(OPERATION_OPTIONS.keys()), | |
value="Gaussian Blur", | |
label="Blur Operation" | |
) | |
with gr.Accordion("Advanced Filtering", open=False): | |
age_filter = gr.CheckboxGroup( | |
choices=AGE_OPTIONS, | |
label="Filter by Age (select to blur)" | |
) | |
gender_filter = gr.CheckboxGroup( | |
choices=GENDER_OPTIONS, | |
label="Filter by Gender (select to blur)" | |
) | |
selected_faces = gr.Textbox( | |
label="Select Specific Faces to Blur (comma-separated indices, e.g., 0,1,3)", | |
placeholder="Enter face indices separated by commas" | |
) | |
image_button = gr.Button("Process Image") | |
with gr.Column(): | |
output_tabs = gr.Tabs() | |
with output_tabs: | |
with gr.TabItem("Face Detection"): | |
image_with_boxes = gr.Image(label="Detected Faces") | |
with gr.TabItem("Processed Image"): | |
image_output = gr.Image(label="Processed Image") | |
with gr.TabItem("JSON Results"): | |
json_output = gr.JSON(label="Detection Results") | |
with gr.TabItem("Detected Faces (Metadata)"): | |
face_gallery = gr.Gallery( | |
label="Detected Faces", | |
show_label=True, | |
columns=4, | |
height="auto", | |
object_fit="contain" | |
) | |
image_button.click( | |
process_image, | |
inputs=[image_input, operation_dropdown, age_filter, gender_filter, selected_faces], | |
outputs=[image_with_boxes, image_output, json_output, face_gallery] | |
) | |
# with gr.TabItem("Video Processing"): | |
# with gr.Row(): | |
# with gr.Column(): | |
# video_input = gr.Video(label="Upload Video") | |
# video_operation = gr.Dropdown( | |
# choices=list(OPERATION_OPTIONS.keys()), | |
# value="Gaussian Blur", | |
# label="Blur Operation" | |
# ) | |
# with gr.Accordion("Advanced Filtering", open=False): | |
# video_age_filter = gr.CheckboxGroup( | |
# choices=AGE_OPTIONS, | |
# label="Filter by Age (select to blur)" | |
# ) | |
# video_gender_filter = gr.CheckboxGroup( | |
# choices=GENDER_OPTIONS, | |
# label="Filter by Gender (select to blur)" | |
# ) | |
# video_button = gr.Button("Process Video") | |
# with gr.Column(): | |
# video_output = gr.Video(label="Processed Video") | |
# video_summary = gr.Textbox(label="Processing Summary") | |
# video_button.click( | |
# process_video, | |
# inputs=[video_input, video_operation, video_age_filter, video_gender_filter], | |
# outputs=[video_output, video_summary] | |
# ) | |
gr.Markdown(""" | |
## How to Use | |
1. **Upload** an image or video using the respective tab | |
2. **Choose** your preferred blur operation: | |
- **Gaussian Blur**: Blurs facial features while maintaining face shape | |
- **Black Patch**: Completely covers faces with black rectangles | |
- **Pixelation**: Creates a mosaic effect over faces | |
3. **Advanced Filtering**: | |
- Filter by age group (select which age groups to blur) | |
- Filter by gender (select which genders to blur) | |
- For images, you can select specific face indices to blur | |
4. **Process** the media and view the results | |
Note: Video processing may take some time depending on the file size. | |
""") | |
if __name__ == "__main__": | |
demo.launch() |