SecureAttendAI / ARCHITECTURE.md
Nishant Katiyar
Deploy biometric node to HF Spaces
b561839
|
Raw
History Blame Contribute Delete
14.8 kB

FaceDetection System β€” Architecture Documentation

Last Updated: June 9, 2026
Stack: Python Β· FastAPI Β· OpenCV Β· ONNX Β· SQLite Β· React


Table of Contents

  1. Current Architecture
  2. Logic Flow (Step-by-Step)
  3. Core Models
  4. Liveness Detection
  5. Attendance Logging
  6. Proposed Architecture (Alternative Plan)
  7. Side-by-Side Comparison
  8. What's Already Done vs. What Can Be Improved
  9. Recommended Next Steps

1. Current Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         CAPTURE LAYER                                β”‚
β”‚   VideoCamera (Singleton)  Β·  640Γ—480  Β·  ~30 FPS cap               β”‚
β”‚   OpenCV VideoCapture  β†’  Horizontal Flip (mirror mode)             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       DETECTION LAYER                                β”‚
β”‚   Model : YuNet  (face_detection_yunet_2023mar.onnx)                β”‚
β”‚   Format: ONNX  Β·  Input: dynamic (set to frame size per call)      β”‚
β”‚   Output: bounding box [x, y, w, h] + 5-point facial landmarks      β”‚
β”‚           + detection confidence score                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       LIVENESS LAYER                                 β”‚
β”‚   Method : Landmark Standard-Deviation Analysis                      β”‚
β”‚   Buffer : Last 15 frames of 5-point landmark coordinates            β”‚
β”‚   Logic  : If avg. std-dev of all coordinates < 0.7 px β†’ SPOOF      β”‚
β”‚            (A real face has micro-tremors; a printed photo is rigid) β”‚
β”‚   Output : is_live = True / False + liveness label                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚  (only if is_live == True)
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      EMBEDDING LAYER                                 β”‚
β”‚   Model : SFace  (face_recognition_sface_2021dec.onnx)              β”‚
β”‚   Format: ONNX  Β·  ~38 MB on disk                                   β”‚
β”‚   Steps : 1. alignCrop(frame, face_landmarks)  β†’ aligned 112Γ—112    β”‚
β”‚            2. recognizer.feature(aligned_face) β†’ 128-dim vector     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     VERIFICATION LAYER                               β”‚
β”‚   Method    : Cosine Similarity  (cv2.FaceRecognizerSF_FR_COSINE)   β”‚
β”‚   Threshold : 0.363  (calibrated by OpenCV SFace research paper)    β”‚
β”‚   Strategy  : Best-of-N β€” takes highest score per employee across   β”‚
β”‚               all stored face templates                              β”‚
β”‚   Output    : (employee_id, employee_name, similarity_score) | None β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    ATTENDANCE LOGGER                                 β”‚
β”‚   Debounce  : 2-minute cooldown per employee_id (in-memory dict)    β”‚
β”‚   Logic     : check_in β†’ check_out β†’ check_in (toggle per day)     β”‚
β”‚   DB        : SQLite  (attendance.db)                               β”‚
β”‚   Alerts    : Thread-safe Queue β†’ SSE stream β†’ React frontend       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. Logic Flow (Step-by-Step)

Camera Frame
    β”‚
    β”œβ”€β–Ί [YuNet Detector]
    β”‚        β”‚
    β”‚        β”œβ”€β”€ No faces found  ──► Clear liveness buffer, draw scan-line animation
    β”‚        β”‚
    β”‚        └── Faces found (1..N) ──► For each face:
    β”‚                β”‚
    β”‚                β”œβ”€β–Ί Parse bbox [x, y, w, h] + 5 landmarks + score
    β”‚                β”‚
    β”‚                β”œβ”€β–Ί Update landmark buffer (last 15 frames)
    β”‚                β”‚
    β”‚                β”œβ”€β–Ί [Liveness Check]
    β”‚                β”‚       β”œβ”€β”€ Buffer < 8 frames  ──► "PENDING..."
    β”‚                β”‚       β”œβ”€β”€ std_dev < 0.7 px   ──► "SPOOF: STATIC PHOTO" β†’ push SSE alert
    β”‚                β”‚       └── std_dev β‰₯ 0.7 px   ──► "LIVENESS: VERIFIED"
    β”‚                β”‚
    β”‚                β”œβ”€β–Ί [SFace Embedding]  (only if live)
    β”‚                β”‚       └── alignCrop β†’ feature() β†’ 128-d vector
    β”‚                β”‚
    β”‚                β”œβ”€β–Ί [Cosine Match]
    β”‚                β”‚       β”œβ”€β”€ score β‰₯ 0.363  ──► (emp_id, name, score)
    β”‚                β”‚       └── score < 0.363  ──► Unknown
    β”‚                β”‚
    β”‚                └─► [Attendance Trigger]  (only if emp_id found)
    β”‚                        β”œβ”€β”€ In cooldown?  ──► Skip
    β”‚                        β”œβ”€β”€ Determine event_type (check_in / check_out)
    β”‚                        β”œβ”€β”€ Log to SQLite
    β”‚                        └── Push notification to SSE Queue
    β”‚
    └─► [HUD Renderer]
            β”œβ”€β”€ Neon bounding box (color: green=match, red=unknown, crimson=spoof)
            β”œβ”€β”€ 5-point landmark dots
            β”œβ”€β”€ Label banner (name, score %, liveness status)
            └── Corner tick HUD overlay + scan-line animation

3. Core Models

Property YuNet (Detector) SFace (Recognizer)
File face_detection_yunet_2023mar.onnx face_recognition_sface_2021dec.onnx
Size ~232 KB ~38 MB
Format ONNX ONNX
Input Dynamic (frame resolution) 112Γ—112 aligned crop
Output bbox + 5 landmarks + score 128-dimensional embedding vector
Runtime OpenCV DNN (cv2.FaceDetectorYN) OpenCV DNN (cv2.FaceRecognizerSF)
Source HuggingFace: opencv/face_detection_yunet HuggingFace: opencv/face_recognition_sface

4. Liveness Detection

Goal: Prevent "proxy attendance" using a printed photo or screen-displayed image.

Method: Landmark Standard-Deviation Analysis

# Collect last 15 frames of 5-point landmarks
pts = np.array(landmark_buffer)        # shape: (15, 5, 2)
std_coords = np.std(pts, axis=0)       # shape: (5, 2)
avg_std = np.mean(std_coords)

if avg_std < 0.7:
    is_live = False   # Coordinates are rigid β†’ static photo / spoof
else:
    is_live = True    # Natural micro-tremors detected β†’ real face

Why it works: A real human face held in front of a camera has natural micro-tremors, breathing movements, and small head shifts. A photo or screen holds perfectly still. The 0.7-pixel threshold captures this difference reliably.

Current Limitation: This method only catches perfectly static spoofs. It won't catch a video of a face played on a phone, or a photo shaken manually.


5. Attendance Logging

  • Toggle Logic: The system alternates check_in β†’ check_out β†’ check_in based on the last event stored in the DB for that employee on the current day.
  • Debounce: A 2-minute in-memory cooldown per employee_id prevents duplicate logs from continuous recognition.
  • Real-Time Alerts: Every attendance event is pushed into a thread-safe queue.Queue which is streamed to the React frontend via Server-Sent Events (SSE).
  • Spoof Alerts: Spoof detection events also push to the SSE queue with a 10-second debounce to avoid flooding the UI.

6. Proposed Architecture (Alternative Plan)

The following is an alternative architecture plan that was evaluated for this system:

Capture Layer
    └─► BlazeFace (initial detection)
            └─► Correlation Tracker (tracks bounding box between detections)
                    └─► FaceNet (128-d or 512-d embeddings)
                            └─► Cosine Similarity (threshold: 0.7)
                                    └─► Attendance Logger

Additional tips proposed:

  • Model Quantization (ONNX / TFLite)
  • Frame Skipping (process every other frame)
  • Lower detection resolution (320Γ—320)
  • Liveness via blink detection / MediaPipe landmarks

7. Side-by-Side Comparison

Dimension Current Implementation Proposed Plan
Detector YuNet (ONNX, ~232 KB) BlazeFace
Tracking Per-frame YuNet detection BlazeFace β†’ Correlation Tracker handoff
Recognizer SFace (ONNX, 128-d) via OpenCV FaceNet (PyTorch / TFLite, 128-d or 512-d)
Similarity Cosine via cv2.FaceRecognizerSF Manual cosine similarity
Threshold 0.363 (SFace-calibrated) 0.7 (incompatible with SFace scale)
Model Format βœ… Already ONNX Needs conversion to ONNX/TFLite
Liveness βœ… Landmark std-dev (static photo guard) Blink detection (EAR via MediaPipe)
Frame Rate ~30 FPS cap (sleep 0.033s) 15–20 FPS target
Complexity Low β€” single pipeline Higher β€” 2-stage detect+track pipeline
Dependencies OpenCV only (no heavy ML framework) Requires PyTorch or TFLite runtime
Status βœ… Production-ready πŸ”§ Would require significant re-engineering

Key Conflicts to Note

⚠️ Threshold Incompatibility: The proposed threshold of 0.7 is designed for FaceNet's cosine similarity scale. SFace cosine scores use a different calibration where 0.363 is the verified boundary. Applying 0.7 to the current SFace engine would reject almost all legitimate matches.

ℹ️ Detector Redundancy: YuNet already operates at a low-resolution input size and is fast enough for real-time CPU use. Adding a Correlation Tracker on top introduces state complexity with marginal speed gain for an attendance use case (stationary camera, controlled environment).

βœ… ONNX is already done: Both YuNet and SFace are .onnx format. The quantization recommendation is already fulfilled.


8. What's Already Done vs. What Can Be Improved

βœ… Already Implemented

  • ONNX model inference (YuNet + SFace)
  • Real-time face detection at 640Γ—480
  • 128-d embedding extraction with face alignment
  • Cosine similarity matching with calibrated threshold
  • In-memory embedding cache (reload on new employee registration)
  • Liveness detection (static photo / spoof guard)
  • Attendance toggle logic (check-in / check-out)
  • 2-minute debounce cooldown per employee
  • Real-time SSE alerts to frontend
  • Spoof alerts with 10-second debounce
  • Premium HUD overlay (neon bounding boxes, corner ticks, scan-line animation)
  • Thread-safe singleton camera instance

πŸ”§ Improvements Worth Adding

Priority Improvement Effort Impact
⚑ High Frame Skipping β€” process every 2nd frame Low 30–50% CPU reduction
⚑ High Downscale for detection β€” run YuNet on 320Γ—240 copy, scale landmarks back to 640Γ—480 for display Low Faster detection pass
πŸ‘οΈ Medium Blink Detection (EAR) β€” upgrade to MediaPipe 468-point face mesh for eye-aspect-ratio liveness Medium Catches video/moving-photo spoofs
πŸ“ Medium Multi-angle enrollment β€” store 3–5 embeddings per employee from different angles Low Improves recognition rate
πŸš€ Low Async embedding extraction β€” move embedding+matching to a worker thread, keep capture loop purely for frames Medium Eliminates blocking in capture loop

9. Recommended Next Steps

Priority 1 (Quick Wins β€” 1-2 hours):
  β”œβ”€β”€ Add frame_count % 2 skip in _capture_loop()
  └── Add downscale copy for YuNet detect call

Priority 2 (Accuracy β€” 1 day):
  └── Multi-angle face enrollment UI (capture 3-5 samples per person)

Priority 3 (Liveness Upgrade β€” 2-3 days):
  └── Integrate MediaPipe FaceMesh for blink-based EAR liveness

Generated by Antigravity AI Β· FaceDetection Project Β· June 2026