Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -291,48 +291,46 @@ def apply_blur(image, x, y, w, h):
|
|
| 291 |
|
| 292 |
def draw_label(image, x, y, w, h, text, color):
|
| 293 |
"""
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
- Background box matches text size.
|
| 298 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 300 |
-
|
| 301 |
-
thickness = 2
|
| 302 |
-
(tw, th), _ = cv2.getTextSize(text, font, scale, thickness)
|
| 303 |
|
| 304 |
-
|
| 305 |
|
| 306 |
-
#
|
| 307 |
-
|
| 308 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
|
| 310 |
-
if space_above > 0:
|
| 311 |
-
# === DRAW ABOVE ===
|
| 312 |
-
text_y = y - margin
|
| 313 |
-
|
| 314 |
-
# Coordinates for the background rectangle
|
| 315 |
-
box_tl = (x, text_y - th - 5)
|
| 316 |
-
box_br = (x + tw + 10, text_y + 5)
|
| 317 |
-
|
| 318 |
-
cv2.rectangle(image, box_tl, box_br, color, -1)
|
| 319 |
-
cv2.putText(image, text, (x + 5, text_y), font, scale, (255, 255, 255), thickness)
|
| 320 |
-
else:
|
| 321 |
-
# === DRAW BELOW ===
|
| 322 |
-
# y + h is the bottom of the face
|
| 323 |
-
text_y = y + h + th + margin
|
| 324 |
-
|
| 325 |
-
# Coordinates for the background rectangle
|
| 326 |
-
box_tl = (x, text_y - th - 5)
|
| 327 |
-
box_br = (x + tw + 10, text_y + 5)
|
| 328 |
-
|
| 329 |
-
cv2.rectangle(image, box_tl, box_br, color, -1)
|
| 330 |
-
cv2.putText(image, text, (x + 5, text_y), font, scale, (255, 255, 255), thickness)
|
| 331 |
|
| 332 |
def process_frame(image, mode):
|
| 333 |
"""
|
| 334 |
-
|
| 335 |
-
Returns: (processed_image, log_string)
|
| 336 |
"""
|
| 337 |
if image is None: return None, "No Image"
|
| 338 |
|
|
@@ -340,59 +338,64 @@ def process_frame(image, mode):
|
|
| 340 |
faces = GLOBAL_DETECTOR.detect(image)
|
| 341 |
processed_img = image.copy()
|
| 342 |
log_entries = []
|
|
|
|
| 343 |
|
|
|
|
|
|
|
|
|
|
| 344 |
for i, face in enumerate(faces):
|
| 345 |
x, y, w, h = face['box']
|
| 346 |
|
| 347 |
-
#
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
color = (0, 255, 0)
|
| 352 |
|
| 353 |
if mode in ["data", "smart"]:
|
| 354 |
-
# Crop and Check DB
|
| 355 |
face_crop = image[y:y+h, x:x+w]
|
| 356 |
if face_crop.size > 0:
|
| 357 |
res = FACE_DB.recognize(face_crop)
|
| 358 |
|
| 359 |
if res['match']:
|
| 360 |
-
#
|
| 361 |
full_log_text = f"MATCH - {res['name']} (ID: {res['id']})"
|
| 362 |
log_entries.append(f"✅ Face #{i+1}: {full_log_text}")
|
| 363 |
-
|
| 364 |
-
# 2. IMAGE GETS ID ONLY
|
| 365 |
img_label_text = f"ID: {res['id']}"
|
|
|
|
| 366 |
else:
|
|
|
|
| 367 |
full_log_text = "UNKNOWN"
|
| 368 |
log_entries.append(f"⚠️ Face #{i+1}: {full_log_text}")
|
| 369 |
img_label_text = "Unknown"
|
| 370 |
-
|
| 371 |
-
color = res['color']
|
| 372 |
else:
|
| 373 |
log_entries.append(f"🔒 Face #{i+1}: Anonymized")
|
| 374 |
|
| 375 |
-
#
|
| 376 |
-
if mode == "privacy":
|
| 377 |
processed_img = apply_blur(processed_img, x, y, w, h)
|
| 378 |
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
|
|
|
|
|
|
|
|
|
| 390 |
if not log_entries:
|
| 391 |
final_log = "No faces detected."
|
| 392 |
else:
|
| 393 |
final_log = "--- Detection Report ---\n" + "\n".join(log_entries)
|
| 394 |
|
| 395 |
return processed_img, final_log
|
|
|
|
| 396 |
|
| 397 |
# ====================================================
|
| 398 |
# 5. VIDEO PROCESSING HELPERS
|
|
|
|
| 291 |
|
| 292 |
def draw_label(image, x, y, w, h, text, color):
|
| 293 |
"""
|
| 294 |
+
1. Always draw the border (The primary signal).
|
| 295 |
+
2. Attempt to draw text inside.
|
| 296 |
+
3. If text doesn't fit geometrically, hide it automatically.
|
|
|
|
| 297 |
"""
|
| 298 |
+
# 1. Dynamic Border Thickness
|
| 299 |
+
# Big face = 2px border, Tiny face = 1px border (so it doesn't look like a blob)
|
| 300 |
+
thickness = 2 if w > 40 else 1
|
| 301 |
+
cv2.rectangle(image, (x, y), (x+w, y+h), color, thickness)
|
| 302 |
+
|
| 303 |
+
# 2. Geometry Check: Can we fit text?
|
| 304 |
+
# We define a 'Minimum Readable Font' (Scale 0.4 is the smallest legible size)
|
| 305 |
+
min_font_scale = 0.4
|
| 306 |
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 307 |
+
font_thickness = 1
|
|
|
|
|
|
|
| 308 |
|
| 309 |
+
(tw, th), _ = cv2.getTextSize(text, font, min_font_scale, font_thickness)
|
| 310 |
|
| 311 |
+
# PADDING: We need at least 2 pixels on sides
|
| 312 |
+
required_width = tw + 4
|
| 313 |
+
|
| 314 |
+
# THE RULE: If the text is wider than the face, DO NOT DRAW IT.
|
| 315 |
+
if required_width > w:
|
| 316 |
+
return
|
| 317 |
+
|
| 318 |
+
# 3. If we passed the check, draw it centered
|
| 319 |
+
center_x = x + (w // 2) - (tw // 2)
|
| 320 |
+
center_y = y + (h // 2) + (th // 2)
|
| 321 |
+
|
| 322 |
+
# Background Strip (for readability against blur)
|
| 323 |
+
bg_tl = (center_x - 2, center_y - th - 2)
|
| 324 |
+
bg_br = (center_x + tw + 2, center_y + 2)
|
| 325 |
+
cv2.rectangle(image, bg_tl, bg_br, color, -1)
|
| 326 |
+
|
| 327 |
+
# Text
|
| 328 |
+
cv2.putText(image, text, (center_x, center_y), font, min_font_scale, (255, 255, 255), font_thickness, cv2.LINE_AA)
|
| 329 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
def process_frame(image, mode):
|
| 332 |
"""
|
| 333 |
+
MASTER FUNCTION (Standardized).
|
|
|
|
| 334 |
"""
|
| 335 |
if image is None: return None, "No Image"
|
| 336 |
|
|
|
|
| 338 |
faces = GLOBAL_DETECTOR.detect(image)
|
| 339 |
processed_img = image.copy()
|
| 340 |
log_entries = []
|
| 341 |
+
labels_to_draw = []
|
| 342 |
|
| 343 |
+
# =================================================
|
| 344 |
+
# PASS 1: BLUR & ANALYZE
|
| 345 |
+
# =================================================
|
| 346 |
for i, face in enumerate(faces):
|
| 347 |
x, y, w, h = face['box']
|
| 348 |
|
| 349 |
+
# Default: Unknown/Red
|
| 350 |
+
full_log_text = "Unknown"
|
| 351 |
+
img_label_text = "Unknown"
|
| 352 |
+
color = (255, 0, 0)
|
|
|
|
| 353 |
|
| 354 |
if mode in ["data", "smart"]:
|
|
|
|
| 355 |
face_crop = image[y:y+h, x:x+w]
|
| 356 |
if face_crop.size > 0:
|
| 357 |
res = FACE_DB.recognize(face_crop)
|
| 358 |
|
| 359 |
if res['match']:
|
| 360 |
+
# Match: Green
|
| 361 |
full_log_text = f"MATCH - {res['name']} (ID: {res['id']})"
|
| 362 |
log_entries.append(f"✅ Face #{i+1}: {full_log_text}")
|
|
|
|
|
|
|
| 363 |
img_label_text = f"ID: {res['id']}"
|
| 364 |
+
color = (0, 255, 0)
|
| 365 |
else:
|
| 366 |
+
# No Match: Red
|
| 367 |
full_log_text = "UNKNOWN"
|
| 368 |
log_entries.append(f"⚠️ Face #{i+1}: {full_log_text}")
|
| 369 |
img_label_text = "Unknown"
|
| 370 |
+
color = (255, 0, 0)
|
|
|
|
| 371 |
else:
|
| 372 |
log_entries.append(f"🔒 Face #{i+1}: Anonymized")
|
| 373 |
|
| 374 |
+
# Action: Blur
|
| 375 |
+
if mode == "privacy" or mode == "smart":
|
| 376 |
processed_img = apply_blur(processed_img, x, y, w, h)
|
| 377 |
|
| 378 |
+
# Action: Queue Badge
|
| 379 |
+
if mode == "data" or mode == "smart":
|
| 380 |
+
labels_to_draw.append({
|
| 381 |
+
"x": x, "y": y, "w": w, "h": h,
|
| 382 |
+
"text": img_label_text, "color": color
|
| 383 |
+
})
|
| 384 |
+
|
| 385 |
+
# =================================================
|
| 386 |
+
# PASS 2: DRAW BADGES (Top Layer)
|
| 387 |
+
# =================================================
|
| 388 |
+
for item in labels_to_draw:
|
| 389 |
+
draw_label(processed_img, item['x'], item['y'], item['w'], item['h'], item['text'], item['color'])
|
| 390 |
+
|
| 391 |
+
# Create Log
|
| 392 |
if not log_entries:
|
| 393 |
final_log = "No faces detected."
|
| 394 |
else:
|
| 395 |
final_log = "--- Detection Report ---\n" + "\n".join(log_entries)
|
| 396 |
|
| 397 |
return processed_img, final_log
|
| 398 |
+
|
| 399 |
|
| 400 |
# ====================================================
|
| 401 |
# 5. VIDEO PROCESSING HELPERS
|