Files
SnapOtter/packages/ai/python/detect_faces.py
T

115 lines
3.6 KiB
Python
Raw Normal View History

"""Face detection and blurring using OpenCV."""
import sys
import json
def emit_progress(percent, stage):
"""Emit structured progress to stderr for bridge.ts to capture."""
print(json.dumps({"progress": percent, "stage": stage}), file=sys.stderr, flush=True)
def main():
input_path = sys.argv[1]
output_path = sys.argv[2]
settings = json.loads(sys.argv[3]) if len(sys.argv) > 3 else {}
blur_radius = settings.get("blurRadius", 30)
sensitivity = settings.get("sensitivity", 0.5)
try:
emit_progress(10, "Preparing")
from PIL import Image, ImageFilter
img = Image.open(input_path).convert("RGB")
try:
import cv2
import numpy as np
emit_progress(20, "Ready")
# Load Haar cascade for face detection
haar_path = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
face_cascade = cv2.CascadeClassifier(haar_path)
# Convert to grayscale for detection
img_array = np.array(img)
gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
# Map sensitivity (0.1-0.9) to minNeighbors (8-2)
# Higher sensitivity = fewer required neighbors = more detections
min_neighbors = max(2, int(8 - sensitivity * 7))
emit_progress(25, "Scanning for faces")
faces_detected = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=min_neighbors,
minSize=(30, 30),
)
faces = []
num_faces = len(faces_detected)
emit_progress(50, f"Found {num_faces} face{'s' if num_faces != 1 else ''}")
if num_faces > 0:
for i, (x, y, w, h) in enumerate(faces_detected):
# Add padding around the face
pad = int(max(w, h) * 0.1)
x1 = max(0, x - pad)
y1 = max(0, y - pad)
x2 = min(img.width, x + w + pad)
y2 = min(img.height, y + h + pad)
face_region = img.crop((x1, y1, x2, y2))
blurred = face_region.filter(
ImageFilter.GaussianBlur(blur_radius)
)
img.paste(blurred, (x1, y1))
faces.append({"x": int(x), "y": int(y), "w": int(w), "h": int(h)})
emit_progress(
50 + int((i + 1) / num_faces * 40),
f"Blurring face {i + 1} of {num_faces}",
)
emit_progress(95, "Saving result")
img.save(output_path)
print(
json.dumps(
{
"success": True,
"facesDetected": len(faces),
"faces": faces,
}
)
)
except ImportError:
print(
json.dumps(
{
"success": False,
"error": "Face detection requires OpenCV. Install with: pip install opencv-python-headless",
}
)
)
sys.exit(1)
except ImportError:
print(
json.dumps(
{
"success": False,
"error": "Pillow is not installed. Install with: pip install Pillow",
}
)
)
sys.exit(1)
except Exception as e:
print(json.dumps({"success": False, "error": str(e)}))
sys.exit(1)
if __name__ == "__main__":
main()