zom696's picture
Update app.py
7752d57 verified
Raw
History Blame Contribute Delete
5.87 kB
import io
import os
import datetime
from typing import Tuple, List
import cv2
import numpy as np
from PIL import Image
import gradio as gr
CASCADE_PATH = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
face_cascade = cv2.CascadeClassifier(CASCADE_PATH)
# Optional Supabase client (configured via environment variables in Spaces)
supabase = None
try:
from supabase import create_client
SUPABASE_URL = os.environ.get('SUPABASE_URL')
SUPABASE_KEY = os.environ.get('SUPABASE_SERVICE_ROLE_KEY')
if SUPABASE_URL and SUPABASE_KEY:
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
except Exception:
supabase = None
def detect_faces_np(img: np.ndarray, scaleFactor: float = 1.1, minNeighbors: int = 5, minSize: Tuple[int, int] = (30, 30)) -> Tuple[Image.Image, List[Tuple[int, int, int, int]]]:
"""Detect faces in a numpy RGB image, return annotated PIL image and list of boxes (x,y,w,h)."""
# Ensure image is RGB numpy array
if img is None:
raise ValueError("No image provided")
# Convert to OpenCV BGR for drawing
if img.ndim == 2:
rgb = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
else:
# assume RGB
bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, scaleFactor=scaleFactor, minNeighbors=minNeighbors, minSize=minSize)
# Draw rectangles
out_bgr = bgr.copy()
boxes = []
for (x, y, w, h) in faces:
cv2.rectangle(out_bgr, (x, y), (x + w, y + h), (0, 255, 0), 2)
boxes.append((int(x), int(y), int(w), int(h)))
# Convert back to RGB PIL Image
out_rgb = cv2.cvtColor(out_bgr, cv2.COLOR_BGR2RGB)
pil = Image.fromarray(out_rgb)
return pil, boxes
def detect_faces_gradio(image: Image.Image, scaleFactor: float = 1.1, minNeighbors: int = 5, minSize: int = 30):
"""Gradio wrapper: accepts PIL image and returns annotated image and textual summary."""
if image is None:
return None, "No image provided"
img_np = np.array(image.convert('RGB'))
pil_out, boxes = detect_faces_np(img_np, scaleFactor=scaleFactor, minNeighbors=int(minNeighbors), minSize=(int(minSize), int(minSize)))
if len(boxes) == 0:
return pil_out, "No faces detected"
summary_lines = [f"faces: {len(boxes)}"]
for i, (x, y, w, h) in enumerate(boxes, start=1):
summary_lines.append(f"{i}: x={x}, y={y}, w={w}, h={h}")
return pil_out, "\n".join(summary_lines)
def upload_image_to_supabase(pil_image: Image.Image, folder: str = 'captures'):
"""Upload a PIL Image to Supabase Storage and return public URL or None."""
if supabase is None:
return None
buf = io.BytesIO()
pil_image.save(buf, format='JPEG', quality=85)
buf.seek(0)
filename = f"{datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S_%f')}.jpg"
try:
supabase.storage.from_(folder).upload(path=filename, file=buf.getvalue(), file_options={"content-type": "image/jpeg"})
res = supabase.storage.from_(folder).get_public_url(filename)
if isinstance(res, dict):
return res.get('publicUrl') or res.get('public_url')
return str(res)
except Exception as e:
print('Supabase upload error:', e)
return None
def upload_annotated(image: Image.Image):
if image is None:
return "No image to upload"
url = upload_image_to_supabase(image)
if url:
return url
if supabase is None:
return "Supabase not configured (set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY)"
return "Upload failed"
def build_interface():
with gr.Blocks() as demo:
gr.Markdown("# Face Detection (OpenCV Haar Cascade)")
with gr.Row():
with gr.Column():
# Create Image input with webcam support when available; provide
# fallbacks for older Gradio versions that don't accept `source`.
try:
inp = gr.Image(source="webcam", type="pil", label="Webcam")
except TypeError:
try:
inp = gr.Image(type="pil", label="Webcam")
except Exception:
try:
inp = gr.inputs.Image(image_mode="RGB", label="Webcam")
except Exception:
inp = gr.Image(label="Webcam")
# Only provide a single Capture button for simplicity
run = gr.Button("Capture")
with gr.Column():
out_img = gr.Image(type="pil", label="Annotated image")
out_text = gr.Textbox(label="Detections")
# Camera returns either a numpy array or a PIL Image depending on Gradio version
def detect_wrapper(cam_image):
# Convert numpy array (BGR or RGB) to PIL Image consistently
img = cam_image
if isinstance(cam_image, (list, tuple)):
# Received as list (rare) -> convert to array
img = np.array(cam_image)
if isinstance(img, np.ndarray):
# If the array has 3 channels assume RGB from browser; convert to PIL
try:
pil = Image.fromarray(img.astype('uint8'), 'RGB')
except Exception:
# fallback: convert BGR->RGB
pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
else:
pil = img
return detect_faces_gradio(pil)
run.click(detect_wrapper, inputs=[inp], outputs=[out_img, out_text])
return demo
if __name__ == '__main__':
demo = build_interface()
demo.launch(server_name='0.0.0.0', server_port=int(__import__('os').environ.get('PORT', 7860)))