Lilann/face-access-control
Face Access Control
即時人臉辨識的辦公室門禁與考勤系統。Webcam 串流進 WebSocket, 三道防線過濾後寫進 Postgres + pgvector,所有 audit 都留紀錄。
🌐 Live Deployment
健康檢查:`/healthz`
為什麼做這個
中小企業 / 共享空間需要「無接觸進出 + 自動考勤」的場景越來越多 (疫情後、 人臉支付興起)。市面上現成方案要嘛綁定特定硬體 (海康、商湯)、要嘛收人頭費 (Kisi、Verkada)。
這個專案展示一條 「全開源、可自架、用免費雲端 tier 就能跑」 的可行路線:
- 後端 InsightFace + FastAPI + Supabase pgvector,跑在 HuggingFace Space (Docker, CPU)
- 前端 Next.js 14 + TypeScript,跑在 Vercel free
- 真實生產級別考量:品質檢查 / 活體檢測 / cooldown / 多 embedding / threshold ROC 校準
- 個資法 / GDPR 合規:硬刪、不存原圖、audit log
Note (2026-05-08):兩個已知限制計劃在後續迭代修: 1. Supabase servicerole 仍用 legacy HS256 JWT(supabase-py 2.9.1 不支援 `sbsecret*` opaque keys)。等 supabase-py 升級後改用新 secret + disable legacy。 2. CORS 仍是 `ALLOWEDORIGINS=*`:實測 HF Space reverse proxy 會把 request Origin echo 回 ACAO header,即使後端 starlette CORSMiddleware 設了 specific origins 也擋不住外部 origin。等改部署到 Render / Fly 才能真正鎖 origin。
系統架構
┌────────────────────┐ WebSocket ┌─────────────────────────┐ RPC ┌───────────────────────┐
│ Browser (Vercel) │ ───────────► │ FastAPI (HF Docker) │ ─────► │ Supabase Postgres │
│ │ {frame: b64}│ │ │ + pgvector + HNSW │
│ Next.js 14 + TS │ │ _do_recognize(): │ │ │
│ webcam getUserMedia│ ◄─────────── │ A. 品質評估 │ │ users │
│ canvas resize 640 │ RecognizeRes │ B. Liveness │ │ face_embeddings │
│ JPEG 0.85 / 500ms │ │ C. 1:N match │ │ attendance_logs │
│ Top-K bar / bbox │ │ cooldown 30s │ │ recognition_events │
│ backpressure │ │ InsightFace buffalo_l │ │ match_face() RPC │
└────────────────────┘ └─────────────────────────┘ └───────────────────────┘詳細圖在 /about 頁。
關鍵設計決策
1. SQLite → pgvector 為什麼換
上一版 (face_recognition_system) 用 SQLite + 應用層線性掃 cosine。N=1000 時 50-150ms,prod 不可接受。pgvector + HNSW index 在 N=10k 時 top-K <1ms。 schema/migration/auth 跟業務 DB 同一條,部署簡單。
為什麼不是 Pinecone/Qdrant?專業向量 DB 要付費或自架, portfolio + 中小企業 demo 階段 pgvector + Supabase free tier 已綽綽有餘。
2. 多 embedding 註冊為什麼魯棒
單一張 embedding 對「不同光照 / 戴眼鏡 / 微側臉」很脆弱。每個 user 註冊 3-5 張 不同角度照片 → 5 個獨立 embedding 進 HNSW,比對時取「最近的 K 個 embedding」 而不是「最近的 K 個 user」,自然提升命中率與 confidence。
3. Threshold 怎麼用 ROC 決定的(不是拍腦袋)
scripts/evaluate_threshold.py 在自家 dataset 上跑:
- 算 genuine pairs (同人不同張) vs impostor pairs (跨人) 的 cosine similarity
- 出 ROC + DET curve + 分布圖
- 表列 FAR=1e-1 / 1e-2 / 1e-3 / 1e-4 對應的 threshold 與 FRR
- 生產級門禁建議 FAR=1e-3 (千分之一冒名),腳本印推薦值
預設 RECOGNITION_THRESHOLD=0.42 是 LFW-style 經驗起點,請自家照片重跑校準。
4. 三道防線 (defense in depth)
每張幀依序:
⚠️ B 目前用啟發式,生產請替換 [Silent-Face-Anti-Spoofing](https://github.com/minivision-ai/Silent-Face-Anti-Spoofing) 的 MiniFASNet。
5. Cooldown 機制
實機 webcam 500ms 一幀 → 同個人在門口站 5 秒會打卡 10 次。in-memory last_check_in[user_id] dict 30 秒內不重複 insert attendance_logs,但仍 回傳「比對成功」讓前端顯示綠色狀態。
隱私與合規
- 不存原始照片:註冊時抽完 embedding 立刻丟掉 binary,DB 只留 vector(512)
- DELETE /users/{id} 為硬刪(CASCADE 連帶 faceembeddings + attendancelogs),符合個資法可被遺忘權
- 所有 frame 經 WSS / HTTPS,server in-memory 處理完即釋放
- audit log (recognition_events) 不含 image,只留 metadata
效能指標 (實測待填)
技術棧
前端:Next.js 14 App Router · TypeScript · Tailwind CSS · recharts 後端:FastAPI · WebSocket (asyncio) · InsightFace buffalo_l · ONNX Runtime · OpenCV · Pillow · Pydantic v2 · Loguru 資料庫:Supabase Postgres + pgvector + HNSW 部署:Vercel (前端) · Render / Fly.io (後端 Docker)
本地啟動
# 1. Clone + 進專案
git clone <this-repo> && cd face-access-control
# 2. Supabase: 在 SQL Editor 整段貼 backend/schema.sql 跑
# Settings → API 拿 URL + service_role key
# 3. Backend
cd backend
python -m venv venv
venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # 填 SUPABASE_URL / SUPABASE_SERVICE_KEY
uvicorn main:app --reload # http://localhost:8000
# 4. Frontend (另開 terminal)
cd ../frontend
npm install
cp .env.local.example .env.local # NEXT_PUBLIC_API_URL=http://localhost:8000
npm run dev # http://localhost:3000第一次跑後端時 InsightFace 會下載 buffalo_l (~280MB)。Dockerfile 會在 build 時 預載,所以 docker build 後容器啟動即可用。
部署到雲端
詳見最末「下一步行動清單」。簡述:
- Supabase:開新 project (free tier 500MB),跑 schema.sql
- Render:連 GitHub repo,選 backend/Dockerfile,設環境變數 SUPABASEURL / SUPABASESERVICEKEY (+ ALLOWEDORIGINS=https://your.vercel.app)
- Vercel:連同一個 repo,root 設
frontend,環境變數NEXT_PUBLIC_API_URL=https://your-backend.onrender.com
跑 threshold 評估
mkdir -p data
# 把每個人的照片放進對應子資料夾:
# data/Alice/img1.jpg img2.jpg img3.jpg
# data/Bob/img1.jpg ...
# 至少 2 個人,每人至少 2 張
python scripts/evaluate_threshold.py --data ./data --output ./reports
# 終端會印推薦的 threshold 值,貼回 backend/.env產出 reports/:
roc_curve.png— ROC (FAR x-log scale)det_curve.png— DET (兩軸 log)score_distribution.png— genuine vs impostor 分布threshold_table.csv— FAR=1e-1..1e-4 對應 threshold + FRR
後續路線
- [ ] Liveness 升級:替換成 Silent-Face-Anti-Spoofing MiniFASNet(成熟、輕量、開源)
- [ ] 戴口罩支援:替換成 InsightFace
antelopev2模型(注意 license) - [ ] 多攝影機:每攝影機獨立 WS room,server 維護 stream → user_id 對應
- [ ] Edge 部署:Jetson Nano + ONNX Runtime ARM build,門口本機跑不依賴雲端
- [ ] Redis Cooldown:取代 in-memory dict,server 重啟也保留狀態
- [ ] GDPR 自服務:用戶自己上 portal 申請刪除,無需找 admin
License
MIT — 程式碼。 InsightFace buffalo_l 權重為 non-commercial research only(modelzoo 明文)。 商用部署請:(a) 跟 InsightFace 商業授權、(b) 用 VGGFace2 retrain、或 (c) 換 facerecognition (dlib)。
