File size: 5,865 Bytes
dbca1c6
c58b155
 
833b1ef
dbca1c6
833b1ef
dbca1c6
 
833b1ef
dbca1c6
 
833b1ef
 
dbca1c6
c58b155
 
 
 
 
 
 
 
 
 
dbca1c6
833b1ef
 
 
 
 
 
 
 
 
dbca1c6
833b1ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c58b155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833b1ef
 
 
 
 
7752d57
 
 
 
 
 
 
 
 
 
 
 
36fea75
 
833b1ef
 
 
 
36fea75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833b1ef
dbca1c6
 
 
833b1ef
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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)))