Spaces:
Running
Running
File size: 4,870 Bytes
a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 a02f1ff 7fd00f2 | 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 time
from typing import Optional, Tuple
import cv2
import gradio as gr
import numpy as np
FACE_CASCADE = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
CUSTOM_CSS = """
:root { --radius-xl: 22px; }
.gradio-container {
max-width: 1500px !important;
margin: auto !important;
background: radial-gradient(circle at 15% 15%, rgba(38, 166, 191, .22), transparent 30%),
linear-gradient(135deg, #101827 0%, #273142 48%, #10232b 100%) !important;
color: #eef6ff !important;
}
#component-0, .contain, .block, .panel {
border-radius: var(--radius-xl) !important;
}
.prose h1, h1 {
font-size: clamp(2.2rem, 5vw, 4.4rem) !important;
text-align: center !important;
letter-spacing: -0.04em !important;
}
.prose p, .prose li { color: #c9d6e8 !important; }
label, .label-wrap span { font-weight: 800 !important; }
button {
border-radius: 999px !important;
font-weight: 800 !important;
}
.image-container, .wrap, .block {
overflow: hidden !important;
}
#status_box textarea, #status_box .prose {
font-size: 1.05rem !important;
}
@media (max-width: 760px) {
.gradio-container { padding: 8px !important; }
h1 { font-size: 2.35rem !important; }
.image-container img, .image-container video { max-height: 62vh !important; object-fit: contain !important; }
}
"""
HEADER = """
# FaceSense Live
Real-time face boxes for desktop and mobile webcam testing.
Click **Record** on the camera panel to start live analysis. On phones, allow camera permission; your browser may offer front/rear camera selection.
"""
def draw_corner_box(img: np.ndarray, x: int, y: int, w: int, h: int) -> None:
color = (0, 238, 255)
shadow = (6, 18, 28)
thickness = 4
line = max(18, int(min(w, h) * 0.22))
# shadow first for readability
cv2.rectangle(img, (x, y), (x + w, y + h), shadow, 7)
# corner style box
cv2.line(img, (x, y), (x + line, y), color, thickness)
cv2.line(img, (x, y), (x, y + line), color, thickness)
cv2.line(img, (x + w, y), (x + w - line, y), color, thickness)
cv2.line(img, (x + w, y), (x + w, y + line), color, thickness)
cv2.line(img, (x, y + h), (x + line, y + h), color, thickness)
cv2.line(img, (x, y + h), (x, y + h - line), color, thickness)
cv2.line(img, (x + w, y + h), (x + w - line, y + h), color, thickness)
cv2.line(img, (x + w, y + h), (x + w, y + h - line), color, thickness)
label = "Face detected"
cv2.rectangle(img, (x, max(0, y - 38)), (x + 185, y), (0, 145, 175), -1)
cv2.putText(img, label, (x + 10, max(22, y - 12)), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
def process_frame(frame: Optional[np.ndarray]) -> Tuple[Optional[np.ndarray], str]:
start = time.perf_counter()
if frame is None:
return None, "### Waiting for camera\nClick **Record** on the webcam panel to start live analysis."
output = frame.copy()
if output.ndim == 2:
output = cv2.cvtColor(output, cv2.COLOR_GRAY2RGB)
if output.shape[-1] == 4:
output = cv2.cvtColor(output, cv2.COLOR_RGBA2RGB)
gray = cv2.cvtColor(output, cv2.COLOR_RGB2GRAY)
gray = cv2.equalizeHist(gray)
faces = FACE_CASCADE.detectMultiScale(
gray,
scaleFactor=1.08,
minNeighbors=5,
minSize=(55, 55),
flags=cv2.CASCADE_SCALE_IMAGE,
)
for (x, y, w, h) in faces:
draw_corner_box(output, int(x), int(y), int(w), int(h))
elapsed_ms = (time.perf_counter() - start) * 1000
fps = 1000 / elapsed_ms if elapsed_ms > 0 else 0
if len(faces) == 0:
status = (
"### Live status\n"
"No frontal face detected yet. Move your face toward the center, improve lighting, and keep the camera steady.\n\n"
f"**Processing:** {elapsed_ms:.1f} ms \n"
f"**Approx FPS:** {fps:.1f}"
)
else:
status = (
"### Live status\n"
f"**Faces detected:** {len(faces)} \n"
"**Phase 1:** bounding boxes only \n"
"**Next phase:** facial expression + apparent age range + optional presentation estimate \n\n"
f"**Processing:** {elapsed_ms:.1f} ms \n"
f"**Approx FPS:** {fps:.1f}"
)
return output, status
demo = gr.Interface(
fn=process_frame,
inputs=gr.Image(
label="Camera input",
sources=["webcam"],
type="numpy",
streaming=True,
mirror_webcam=True,
height=520,
),
outputs=[
gr.Image(label="Annotated output", type="numpy", height=520),
gr.Markdown(label="Status"),
],
title="FaceSense Live",
description=HEADER,
live=True,
css=CUSTOM_CSS,
allow_flagging="never",
api_name="predict",
)
if __name__ == "__main__":
demo.queue(default_concurrency_limit=4).launch()
|