| """ |
| face_live_search.py |
| ββββββββββββββββββββ |
| Search for a query face (from an uploaded image) across every active live camera |
| feed by comparing embedding similarity β without re-running detection on the live |
| frame. |
| |
| The live camera results stored in `vision_engine.face_results` include the |
| `embedding` field when produced by the local (non-cloud) vision engine. |
| In cloud/browser mode we fall back to comparing the uploaded query frame against |
| the latest raw frame for each camera via `match_frame`. |
| """ |
| from __future__ import annotations |
|
|
| import logging |
| import os |
| from typing import Any |
|
|
| import cv2 |
| import numpy as np |
|
|
| logger = logging.getLogger(__name__) |
|
|
| DEFAULT_THRESHOLD = float(os.environ.get("FACE_MATCH_THRESHOLD", "0.22")) |
|
|
|
|
| def _cosine(a: np.ndarray, b: np.ndarray) -> float: |
| na, nb = np.linalg.norm(a), np.linalg.norm(b) |
| if na == 0 or nb == 0: |
| return 0.0 |
| return float(np.dot(a, b) / (na * nb)) |
|
|
|
|
| def _is_known_identity(name: str | None) -> bool: |
| if not name: |
| return False |
| lowered = str(name).strip().lower() |
| return lowered not in ("unknown", "unidentified", "none", "") and not lowered.startswith("unknown_") |
|
|
|
|
| def search_query_in_live_feeds( |
| query_frame: np.ndarray, |
| face_engine, |
| vision_engine, |
| threshold: float | None = None, |
| ) -> dict[str, Any]: |
| """ |
| Compare the face in `query_frame` against every live camera feed. |
| |
| Strategy |
| -------- |
| 1. Extract the embedding from the query image (the uploaded photo). |
| 2. For each camera that has active face results: |
| a. If the stored face result includes an `embedding`, compare directly. |
| b. Otherwise fall back to `match_frame` on the latest raw frame (if |
| the engine exposes `latest_raw_frames`). |
| 3. Return the best matching camera, confidence, and which name was assigned |
| to the matched face in the live stream (so the UI can show the correct |
| unknown_N or known name). |
| """ |
| try: |
| from Face_Recognition.face_matcher import _cosine as _c, DEFAULT_THRESHOLD |
| except ImportError: |
| from face_matcher import _cosine as _c, DEFAULT_THRESHOLD |
|
|
| thresh = threshold if threshold is not None else DEFAULT_THRESHOLD |
|
|
| if face_engine is None or getattr(face_engine, "app", None) is None: |
| return { |
| "found": False, |
| "reason": "Face recognition engine unavailable.", |
| "cameras_searched": 0, |
| } |
|
|
| |
| query_face = None |
| with face_engine.lock: |
| faces = face_engine.app.get(query_frame) |
| if faces: |
| query_face = max(faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1])) |
|
|
| if query_face is None: |
| return {"found": False, "reason": "No face detected in the uploaded image."} |
|
|
| query_emb = query_face.embedding |
|
|
| |
| live_face_results: dict = {} |
| try: |
| live_face_results = vision_engine.face_results or {} |
| except Exception: |
| pass |
|
|
| best_cam = None |
| best_score = 0.0 |
| best_name = None |
| best_face_hit: dict[str, Any] | None = None |
| best_raw_frame = None |
|
|
| cameras_searched = 0 |
|
|
| for cam_id, face_list in live_face_results.items(): |
| if not face_list: |
| continue |
| cameras_searched += 1 |
| raw_frame = None |
| try: |
| raw_frame = getattr(vision_engine, "latest_raw_frames", {}).get(cam_id) |
| except Exception: |
| pass |
|
|
| for face_hit in face_list: |
| hit_name = face_hit.get("name", "Unknown") |
| emb = face_hit.get("embedding") |
|
|
| |
| if emb is not None: |
| if raw_frame is None: |
| |
| pass |
| emb = np.asarray(emb, dtype=np.float32) |
| score = _cosine(query_emb, emb) |
| if score > best_score: |
| best_score = score |
| best_cam = cam_id |
| best_name = hit_name |
| best_face_hit = face_hit |
| best_raw_frame = raw_frame |
| continue |
|
|
| |
| |
| |
| |
| if raw_frame is None: |
| continue |
|
|
| try: |
| with face_engine.lock: |
| live_faces = face_engine.app.get(raw_frame) |
|
|
| if not live_faces: |
| continue |
|
|
| |
| for lf in live_faces: |
| s = _cosine(query_emb, lf.embedding) |
| if s <= best_score: |
| continue |
|
|
| best_score = s |
| best_cam = cam_id |
| |
| |
| if _is_known_identity(hit_name): |
| best_name = hit_name |
| else: |
| best_name = lf.get('name') if isinstance(lf, dict) else (hit_name or "Unknown") |
| best_face_hit = face_hit |
| best_raw_frame = raw_frame |
|
|
| except Exception as exc: |
| logger.debug("fallback frame re-check failed for %s: %s", cam_id, exc) |
|
|
| |
| |
| evidence_image = None |
| if best_raw_frame is not None and best_face_hit: |
| try: |
| x1, y1, x2, y2 = [int(v) for v in (best_face_hit.get("bbox") or [0, 0, 0, 0])] |
| h, w = best_raw_frame.shape[:2] |
| x1, y1 = max(0, x1), max(0, y1) |
| x2, y2 = min(w, x2), min(h, y2) |
| |
| |
| frame_to_encode = best_raw_frame.copy() |
| if x2 > x1 and y2 > y1: |
| |
| cv2.rectangle(frame_to_encode, (x1, y1), (x2, y2), (0, 255, 0), 2) |
| |
| ok, buf = cv2.imencode(".jpg", frame_to_encode, [int(cv2.IMWRITE_JPEG_QUALITY), 70]) |
| if ok: |
| import base64 |
| evidence_image = f"data:image/jpeg;base64,{base64.b64encode(buf).decode()}" |
| except Exception as exc: |
| logger.debug("live evidence frame encoding failed for %s: %s", best_cam, exc) |
|
|
| |
| if evidence_image is None and best_face_hit: |
| evidence_image = best_face_hit.get("thumbnail") |
|
|
| if best_cam and best_score >= thresh: |
| return { |
| "found": True, |
| "name": best_name, |
| "cam_id": best_cam, |
| "confidence": round(best_score, 3), |
| "cameras_searched": cameras_searched, |
| "search_mode": "live", |
| "location": f"Live Camera: {best_cam}", |
| "details": f"Detected on active live feed {best_cam}", |
| "match_image": evidence_image, |
| "reason": f"Query face matched live camera feed '{best_cam}' with confidence {best_score:.1%}.", |
| } |
|
|
| return { |
| "found": False, |
| "best_score": round(best_score, 3), |
| "cameras_searched": cameras_searched, |
| "search_mode": "live", |
| "reason": ( |
| "The uploaded face was not detected on any active live camera feed." |
| if cameras_searched > 0 |
| else "No active camera feeds with recognised faces available." |
| ), |
| } |
|
|