State-of-the-art unbiased facial recognition system.
Face AI is a comprehensive facial recognition pipeline designed with a focus on fairness and bias mitigation. It integrates state-of-the-art detection and recognition models with advanced fairness-aware techniques to ensure equitable performance across different demographic groups.
- Unified Pipeline: Seamless integration of face detection, alignment, embedding extraction, and recognition.
- Fairness-First Design: Built-in bias detection and mitigation strategies.
- Demographic Calibration: Tools to calibrate thresholds per demographic group to ensure equal error rates.
- Production Ready: Designed for deployment with optimized Docker builds and robust error handling.
- Python 3.9+
- Docker (optional, for containerized deployment)
-
Clone the repository:
git clone https://github.com/NickEinstein1/PROSOPO.git cd PROSOPO -
Create and activate a virtual environment:
python -m venv venv source venv/bin/activate -
Install dependencies:
pip install -e .For development dependencies:
pip install -e ".[dev]"
from face_ai.core.pipeline import FaceAIPipeline
# Initialize the pipeline
pipeline = FaceAIPipeline()
# Detect faces
faces = pipeline.detect_faces("path/to/image.jpg")
print(f"Detected {len(faces)} faces")
# Verify identity
is_same, score = pipeline.verify("photo1.jpg", "photo2.jpg")
print(f"Match: {is_same}, Score: {score}")from face_ai.core.pipeline import FaceAIPipeline
pipeline = FaceAIPipeline(enable_fairness=True)
# Register identity with metadata
pipeline.register_identity(
name="user_123",
images=["user_photo.jpg"],
metadata={"demographic": "group_a"}
)
# Generate fairness report
report = pipeline.get_fairness_report()
print(report)Runtime settings are loaded from environment variables (edge, API, Docker):
| Variable | Default | Description |
|---|---|---|
FACE_AI_THRESHOLD |
0.5 |
Cosine similarity match threshold |
FACE_AI_MODEL_PACK |
buffalo_l |
buffalo_l, buffalo_sc (edge), antelopev2 (stronger), custom |
FACE_AI_RECOGNITION_ONNX |
— | Custom ArcFace ONNX when pack is custom |
FACE_AI_DETECTION_PACK |
buffalo_l |
Detector pack used with custom recognition |
FACE_AI_INSIGHTFACE_ROOT |
— | InsightFace model zoo cache directory |
FACE_AI_DEVICE |
cpu |
cpu or cuda |
FACE_AI_GALLERY_FUSION |
max |
max, mean, or quality_weighted |
FACE_AI_STORAGE_BACKEND |
memory |
memory or qdrant |
FACE_AI_MAX_FACES |
10 |
Max faces per image |
FACE_AI_MIN_QUALITY_SCORE |
0.35 |
Min face quality for enrollment |
FACE_AI_TARGET_FAR |
0.001 |
Target false positive rate for calibration |
FACE_AI_CALIBRATION_PATH |
auto |
Per-pack threshold file, or explicit JSON path |
FACE_AI_THRESHOLDS_DIR |
data/thresholds |
Directory of {pack}.json threshold files |
FACE_AI_INTERNAL_BENCHMARK_DIR |
data/eval/internal |
Production capture + pairs root |
FACE_AI_CAPTURE_BENCHMARK |
false |
Save enroll/verify images for internal benchmark |
FACE_AI_CAPTURE_BENCHMARK_SECRET |
— | Required (min 16 chars) when capture is enabled; send as X-Face-AI-Capture-Token |
FACE_AI_FAIRNESS_DIR |
— | Directory with demographic pairs.csv for /fairness/calibrate |
FACE_AI_SECURITY_MODE |
standard |
standard or high (fail-closed liveness on enroll/verify) |
FACE_AI_PROFILE |
server |
server, edge, or high_security |
FACE_AI_ENABLE_AUDIT |
true |
Log enroll/recognize/verify to audit file |
FACE_AI_AUDIT_LOG |
logs/audit.log |
Audit JSONL path |
FACE_AI_CALIBRATION_DIR |
— | Server directory with pairs.csv for API /calibrate |
FACE_AI_REQUIRE_LIVENESS_VERIFY |
false |
Require liveness on /verify by default |
FACE_AI_LIVENESS_FAIL_CLOSED |
true |
Reject when anti-spoof model unavailable |
FACE_AI_LIVENESS_MODEL |
liveness/minifasnet.onnx |
ONNX path under FACE_AI_MODEL_PATH |
FACE_AI_JOBS_DIR |
data/jobs |
Async video job storage |
FACE_AI_VIDEO_MAX_WORKERS |
1 |
Parallel video job workers |
FACE_AI_VIDEO_FRAME_STRIDE |
5 |
Default process-every-Nth-frame |
FACE_AI_VIDEO_MAX_UPLOAD_MB |
512 |
Max upload size per video |
from face_ai.core.pipeline import FaceAIPipeline
from face_ai.config import get_settings
pipeline = FaceAIPipeline.from_settings(get_settings())| Method | Path | Description |
|---|---|---|
| POST | /liveness |
Anti-spoof check per face |
| POST | /verify?require_liveness=true |
1:1 verify with liveness gate |
| POST | /calibrate |
Threshold calibration from FACE_AI_CALIBRATION_DIR |
| GET | /calibration |
Current calibrated threshold |
| POST | /video/jobs |
Enqueue one or more videos (async) |
| GET | /video/jobs/{id} |
Job status / progress |
| GET | /video/jobs/{id}/result |
Full batch summary when finished |
| POST | /video/analyze |
Short clip, synchronous |
| GET | /models/packs |
List recognition packs and active model |
Place MiniFASNet ONNX at models/liveness/minifasnet.onnx for silent anti-spoof.
face-ai download-models liveness
# or
python scripts/download_liveness_model.py
python scripts/download_liveness_model.py --check # verify SHA256See models/liveness/README.md for manual download and Docker setup.
python benchmarks/calibrate_threshold.py --data-dir data/calibration --output data/calibration.jsonFor pack ranking on LFW / CFP-FP / an internal set, see Benchmarks below.
From a local run with --write-thresholds (partial LFW + bootstrapped internal set; re-run on full LFW/CFP-FP for publication):
| Dataset | Pairs | EER | TAR@FAR=1e-3 | Notes |
|---|---|---|---|---|
| LFW (partial) | 152 | 1.37% | 97.3% | Full View-2 = 6000 pairs |
| Internal | 458 | 3.71% | 95.9% | Replace with production cameras |
| Mean | — | 2.54% | 96.6% | Pack recommendation: antelopev2 |
Threshold written to data/thresholds/antelopev2.json (FAR=1e-3 operating point ≈ 0.168 on that internal set).
CI runs parser/unit tests on every PR (.github/workflows/ci.yml). Full LFW/CFP-FP/IJB-C is not downloaded in CI (too large); run locally:
# Public LFW
face-ai benchmark --download lfw --lfw-dir data/eval/lfw
# Compare packs + write per-pack thresholds
face-ai benchmark \
--datasets lfw,cfp_fp,internal \
--packs buffalo_l,buffalo_sc,antelopev2 \
--write-thresholds \
-o data/eval/reports/pack_comparison.jsonRFW-style subgroup tables: face_ai.eval.subgroup.subgroup_table (needs demographic-labeled pairs).
Demographic thresholds from BiasMitigation are applied in FaceRecognizer.match():
- Probe
predicted_ethnicityis filled when attributes are on (appearance proxy + gender). - Enrollment
metadata.demographicis used when the probe label is missing. - Calibrated score/threshold via
get_fair_match_decision().
export FACE_AI_FAIRNESS_DIR=data/fairness # pairs.csv: path1,path2,demographic,label
curl -X POST "http://localhost:8000/fairness/calibrate?target_fpr=0.001"Optional fairlearn / aif360 enrich /fairness summaries when installed (pip install -e ".[fairness]").
| Mode | Env | Behavior |
|---|---|---|
| standard | FACE_AI_SECURITY_MODE=standard |
Liveness optional on verify |
| high | FACE_AI_SECURITY_MODE=high or FACE_AI_PROFILE=high_security |
Liveness required on enroll + verify; fail-closed if ONNX missing; min 2 enrollment images |
docker compose --profile secure up face-ai-api-secure| Profile | Env | Effect |
|---|---|---|
| server | FACE_AI_PROFILE=server |
Default; Docker uses antelopev2 |
| edge | FACE_AI_PROFILE=edge |
buffalo_sc, det_size 320, CPU |
| high_security | FACE_AI_PROFILE=high_security |
Same as high security mode |
Audit logging: FACE_AI_ENABLE_AUDIT=true (default) writes to FACE_AI_AUDIT_LOG (API enroll/recognize/verify).
Qdrant gallery: docker compose --profile with-qdrant up then FACE_AI_STORAGE_BACKEND=qdrant.
Jobs note: video jobs run in-process. Restarting the API loses in-flight work; for production use an external queue (Redis/RQ/Celery) later.
Ship the stronger pack with per-pack thresholds:
# InsightFace ResNet100 (recommended for accuracy)
face-ai download-models pack antelopev2
export FACE_AI_MODEL_PACK=antelopev2
export FACE_AI_CALIBRATION_PATH=auto # loads data/thresholds/antelopev2.json
# Custom ArcFace ONNX + buffalo_l detector
face-ai download-models recognition w600k_r50
export FACE_AI_MODEL_PACK=custom
export FACE_AI_RECOGNITION_ONNX=models/recognition/w600k_r50.onnx
export FACE_AI_DETECTION_PACK=buffalo_l
# threshold file: data/thresholds/custom_w600k_r50.json
# Calibrate / refresh thresholds
face-ai calibrate-pack --pack antelopev2 --data-dir data/calibration
face-ai benchmark --datasets lfw --packs antelopev2 --write-thresholds
face-ai thresholdsDocker:
docker compose up --build # antelopev2 (default)
docker compose --profile strong up face-ai-api-strong # antelopev2 alias
docker compose --profile secure up face-ai-api-secure # high-security
docker compose --profile custom up face-ai-api-custom # custom ONNX
docker compose --profile with-qdrant up # + Qdrant| Pack | Role |
|---|---|
buffalo_l |
Balanced SCRFD + ResNet50 ArcFace (512-d). |
buffalo_sc |
Edge/CPU. Smaller detector + MobileFaceNet. |
antelopev2 |
Default in Docker. ResNet100. Higher accuracy. |
custom |
Your ArcFace/MagFace/AdaFace ONNX + InsightFace detector. |
face-ai packs
face-ai download-models pack buffalo_sc
face-ai download-models pack antelopev2
# Edge
export FACE_AI_PROFILE=edge
# or
export FACE_AI_MODEL_PACK=buffalo_sc
# Stronger official
export FACE_AI_MODEL_PACK=antelopev2
# Custom recognizer (keep buffalo_l for detection)
export FACE_AI_MODEL_PACK=custom
export FACE_AI_RECOGNITION_ONNX=models/recognition/w600k_r50.onnx
export FACE_AI_DETECTION_PACK=buffalo_lPlace custom graphs under models/recognition/ (see that folder’s README). /health and GET /models/packs report the active pack.
Run the test suite using pytest:
pytestSubmit long or multiple videos without blocking the API:
# Local batch (writes JSON)
face-ai process-videos clip1.mp4 clip2.mp4 --stride 5 -o data/video_results
# API: enqueue, then poll
curl -X POST http://localhost:8000/video/jobs \
-F "files=@clip1.mp4" -F "files=@clip2.mp4" \
-F "frame_stride=5" -F "enable_liveness=false"
curl http://localhost:8000/video/jobs/{job_id}
curl http://localhost:8000/video/jobs/{job_id}/result| Method | Path | Description |
|---|---|---|
| POST | /video/jobs |
Enqueue one or more videos |
| GET | /video/jobs |
List recent jobs |
| GET | /video/jobs/{id} |
Job status / progress |
| GET | /video/jobs/{id}/result |
Full summary when finished |
| POST | /video/jobs/{id}/cancel |
Cooperative cancel |
| POST | /video/analyze |
Small clips, synchronous |
Jobs persist under FACE_AI_JOBS_DIR (default data/jobs). Inference runs in a background worker (FACE_AI_VIDEO_MAX_WORKERS, default 1).
Build and run the API server (MiniFASNet liveness model is downloaded at build time):
docker compose up --buildThe image includes:
- Full Python dependencies (InsightFace, ONNX Runtime, FastAPI)
- MiniFASNet ONNX at
/app/models/liveness/minifasnet.onnx - Entrypoint that re-downloads liveness weights if the models volume is empty
Check deployment health:
curl http://localhost:8000/healthDevelopment with hot reload:
docker compose --profile dev up face-ai-dev