leandrodevai commited on
Commit
a5c72cb
·
verified ·
1 Parent(s): 475623e

Sync from GitHub via hub-sync

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ images/faceverification_demo.gif filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -8,14 +8,15 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
8
 
9
  WORKDIR /app
10
 
11
- RUN apt-get update && apt-get install -y --no-install-recommends \
 
12
  libgl1 \
13
  libglib2.0-0 \
14
  && rm -rf /var/lib/apt/lists/*
15
 
16
  COPY requirements.txt .
17
  RUN pip install --no-cache-dir --upgrade pip setuptools "wheel>=0.46.2"
18
- RUN pip install --no-cache-dir -r requirements.txt
19
 
20
  COPY pyproject.toml README.md ./
21
  COPY src ./src
 
8
 
9
  WORKDIR /app
10
 
11
+ RUN apt-get update && apt-get upgrade -y \
12
+ && apt-get install -y --no-install-recommends \
13
  libgl1 \
14
  libglib2.0-0 \
15
  && rm -rf /var/lib/apt/lists/*
16
 
17
  COPY requirements.txt .
18
  RUN pip install --no-cache-dir --upgrade pip setuptools "wheel>=0.46.2"
19
+ RUN pip install --no-cache-dir --extra-index-url https://download.pytorch.org/whl/cpu -r requirements.txt
20
 
21
  COPY pyproject.toml README.md ./
22
  COPY src ./src
README.md CHANGED
@@ -23,6 +23,18 @@ Try it on Hugging Face Spaces: https://huggingface.co/spaces/leandrodevai/faceve
23
 
24
  The app lets users add known people to a local embeddings database and verify whether a new face image matches one of the stored identities.
25
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  ## Features
27
 
28
  - Face detection and preprocessing from uploaded images
@@ -32,6 +44,65 @@ The app lets users add known people to a local embeddings database and verify wh
32
  - FastAPI interface for containerized API deployments
33
  - Docker-based Hugging Face Space deployment
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  ## Tech Stack
36
 
37
  - Python
 
23
 
24
  The app lets users add known people to a local embeddings database and verify whether a new face image matches one of the stored identities.
25
 
26
+ ![Face verification demo](images/faceverification_demo.gif)
27
+
28
+ This demo was built to show that FaceNet, despite being an older architecture,
29
+ is still a strong and practical baseline for face embedding workflows. Its
30
+ moderate runtime footprint also makes it an interesting candidate for constrained
31
+ deployments, including edge-style scenarios when hardware, latency, and accuracy
32
+ requirements are compatible.
33
+
34
+ The project is also intended as a reusable AI engineering template: the same
35
+ structure can be adapted to build CI/CD pipelines for other models, or extended
36
+ from image uploads into a video pipeline for near real-time face recognition.
37
+
38
  ## Features
39
 
40
  - Face detection and preprocessing from uploaded images
 
44
  - FastAPI interface for containerized API deployments
45
  - Docker-based Hugging Face Space deployment
46
 
47
+ ## Architecture
48
+
49
+ ```mermaid
50
+ flowchart LR
51
+ A[Upload] --> B[MTCNN]
52
+ B --> C[Crop]
53
+ C --> D[FaceNet]
54
+ D --> E[Normalize]
55
+ E --> F[ChromaDB]
56
+ F --> G[Threshold]
57
+ G --> H[Match]
58
+ ```
59
+
60
+ Images are first passed through MTCNN for face detection and cropping. FaceNet
61
+ then produces an embedding, which is L2-normalized before being stored or
62
+ queried in ChromaDB. Verification compares the nearest stored embedding against
63
+ the calibrated distance threshold.
64
+
65
+ ## Model Evaluation
66
+
67
+ The verification threshold was calibrated with a small study on the
68
+ `bitmind/lfw` dataset, a Hugging Face version of Labeled Faces in the Wild
69
+ (LFW). The study builds same-person and different-person image pairs, splits
70
+ them into calibration and held-out evaluation sets, and evaluates L2 distance
71
+ between L2-normalized FaceNet embeddings.
72
+
73
+ For this demo, the selected threshold is:
74
+
75
+ ```text
76
+ same person if L2 distance <= 1.0764
77
+ different person if L2 distance > 1.0764
78
+ ```
79
+
80
+ This value was chosen on the calibration split because it maximized balanced
81
+ accuracy while staying below the distance region where different-person pairs
82
+ start to dominate. The held-out evaluation split contains 720 same-person and
83
+ 720 different-person pairs.
84
+
85
+ ![Evaluation distance distributions](images/Densities.png)
86
+
87
+ Held-out evaluation results:
88
+
89
+ | Metric | Value |
90
+ | --- | ---: |
91
+ | AUC-ROC | 0.9838 |
92
+ | Accuracy | 0.9653 |
93
+ | FAR | 0.0097 |
94
+ | FRR | 0.0597 |
95
+ | TPR | 0.9403 |
96
+ | Calibrated threshold | 1.0764 |
97
+ | Calibration EER threshold | 1.1857 |
98
+
99
+ ![Held-out evaluation ROC curve](images/ROC.png)
100
+
101
+ ![Held-out evaluation confusion matrix](images/confusion_matrix.png)
102
+
103
+ These metrics are intended to justify the threshold for this demo dataset, not
104
+ as a production biometric benchmark.
105
+
106
  ## Tech Stack
107
 
108
  - Python
images/Densities.png ADDED
images/ROC.png ADDED
images/[Originals]/ROC.png ADDED
images/[Originals]/confusion_matrix.png ADDED
images/confusion_matrix.png ADDED
images/faceverification_demo.gif ADDED

Git LFS Details

  • SHA256: 656e8b4d2d98ee7150e76be63b548064dc0a36cd4346fd2ca1c8ea77a4f88094
  • Pointer size: 131 Bytes
  • Size of remote file: 162 kB
pyproject.toml CHANGED
@@ -14,13 +14,10 @@ dependencies = [
14
  "pydantic>=2.11.10,<=2.12.5",
15
  "pydantic-settings>=2.14.1",
16
  "chromadb>=1.5.9",
17
- "pytest>=9.0.3",
18
- "pytest-cov>=7.1.0",
19
  "fastapi>=0.136.1",
20
  "pyjwt>=2.10.1",
21
  "python-multipart>=0.0.28",
22
  "uvicorn[standard]>=0.46.0",
23
- "ruff>=0.15.13",
24
  "torch>=2.12.0",
25
  "torchvision>=0.27.0",
26
  ]
@@ -60,3 +57,10 @@ src = ["src", "test"]
60
 
61
  [tool.ruff.lint]
62
  select = ["E", "F", "I", "B", "UP", "SIM"]
 
 
 
 
 
 
 
 
14
  "pydantic>=2.11.10,<=2.12.5",
15
  "pydantic-settings>=2.14.1",
16
  "chromadb>=1.5.9",
 
 
17
  "fastapi>=0.136.1",
18
  "pyjwt>=2.10.1",
19
  "python-multipart>=0.0.28",
20
  "uvicorn[standard]>=0.46.0",
 
21
  "torch>=2.12.0",
22
  "torchvision>=0.27.0",
23
  ]
 
57
 
58
  [tool.ruff.lint]
59
  select = ["E", "F", "I", "B", "UP", "SIM"]
60
+
61
+ [dependency-groups]
62
+ dev = [
63
+ "pytest>=9.0.3",
64
+ "pytest-cov>=7.1.0",
65
+ "ruff>=0.15.13",
66
+ ]
requirements.txt CHANGED
@@ -1,7 +1,5 @@
1
  # This file was autogenerated by uv via the following command:
2
- # uv --cache-dir .uv-cache export --locked --no-hashes --no-emit-project --format requirements.txt --output-file requirements.txt
3
- --find-links https://download.pytorch.org/whl/cpu/torch/
4
- --find-links https://download.pytorch.org/whl/cpu/torchvision/
5
  aiohappyeyeballs==2.6.1
6
  # via aiohttp
7
  aiohttp==3.13.5
@@ -51,11 +49,8 @@ colorama==0.4.6 ; (os_name != 'nt' and sys_platform == 'win32') or (os_name == '
51
  # via
52
  # build
53
  # click
54
- # pytest
55
  # tqdm
56
  # uvicorn
57
- coverage==7.14.0
58
- # via pytest-cov
59
  datasets==4.8.5
60
  # via faceverification
61
  dill==0.4.1
@@ -138,8 +133,6 @@ importlib-metadata==8.7.1
138
  # via opentelemetry-api
139
  importlib-resources==7.1.0
140
  # via chromadb
141
- iniconfig==2.3.0
142
- # via pytest
143
  jinja2==3.1.6
144
  # via
145
  # gradio
@@ -218,7 +211,6 @@ packaging==26.2
218
  # gradio-client
219
  # huggingface-hub
220
  # onnxruntime
221
- # pytest
222
  pandas==3.0.2
223
  # via
224
  # datasets
@@ -228,10 +220,6 @@ pillow==12.2.0
228
  # facenet-pytorch
229
  # gradio
230
  # torchvision
231
- pluggy==1.6.0
232
- # via
233
- # pytest
234
- # pytest-cov
235
  propcache==0.5.2
236
  # via
237
  # aiohttp
@@ -261,21 +249,13 @@ pydantic-settings==2.14.1
261
  pydub==0.25.1
262
  # via gradio
263
  pygments==2.20.0
264
- # via
265
- # pytest
266
- # rich
267
  pyjwt==2.12.1
268
  # via faceverification
269
  pypika==0.51.1
270
  # via chromadb
271
  pyproject-hooks==1.2.0
272
  # via build
273
- pytest==9.0.3
274
- # via
275
- # faceverification
276
- # pytest-cov
277
- pytest-cov==7.1.0
278
- # via faceverification
279
  python-dateutil==2.9.0.post0
280
  # via
281
  # kubernetes
@@ -319,8 +299,6 @@ rpds-py==0.30.0
319
  # via
320
  # jsonschema
321
  # referencing
322
- ruff==0.15.13
323
- # via faceverification
324
  safehttpx==0.1.7
325
  # via gradio
326
  semantic-version==2.10.0
@@ -343,8 +321,6 @@ tenacity==9.1.4
343
  # via chromadb
344
  tokenizers==0.23.1
345
  # via chromadb
346
- tomli==2.4.1 ; python_full_version <= '3.11'
347
- # via coverage
348
  tomlkit==0.14.0
349
  # via gradio
350
  torch==2.12.0 ; sys_platform == 'darwin'
 
1
  # This file was autogenerated by uv via the following command:
2
+ # uv --cache-dir .uv-cache export --locked --no-dev --no-hashes --no-emit-project --format requirements.txt --index https://download.pytorch.org/whl/cpu --output-file requirements.txt
 
 
3
  aiohappyeyeballs==2.6.1
4
  # via aiohttp
5
  aiohttp==3.13.5
 
49
  # via
50
  # build
51
  # click
 
52
  # tqdm
53
  # uvicorn
 
 
54
  datasets==4.8.5
55
  # via faceverification
56
  dill==0.4.1
 
133
  # via opentelemetry-api
134
  importlib-resources==7.1.0
135
  # via chromadb
 
 
136
  jinja2==3.1.6
137
  # via
138
  # gradio
 
211
  # gradio-client
212
  # huggingface-hub
213
  # onnxruntime
 
214
  pandas==3.0.2
215
  # via
216
  # datasets
 
220
  # facenet-pytorch
221
  # gradio
222
  # torchvision
 
 
 
 
223
  propcache==0.5.2
224
  # via
225
  # aiohttp
 
249
  pydub==0.25.1
250
  # via gradio
251
  pygments==2.20.0
252
+ # via rich
 
 
253
  pyjwt==2.12.1
254
  # via faceverification
255
  pypika==0.51.1
256
  # via chromadb
257
  pyproject-hooks==1.2.0
258
  # via build
 
 
 
 
 
 
259
  python-dateutil==2.9.0.post0
260
  # via
261
  # kubernetes
 
299
  # via
300
  # jsonschema
301
  # referencing
 
 
302
  safehttpx==0.1.7
303
  # via gradio
304
  semantic-version==2.10.0
 
321
  # via chromadb
322
  tokenizers==0.23.1
323
  # via chromadb
 
 
324
  tomlkit==0.14.0
325
  # via gradio
326
  torch==2.12.0 ; sys_platform == 'darwin'
src/faceverification/config.py CHANGED
@@ -11,7 +11,7 @@ class Settings(BaseSettings):
11
  vector_db_collection: str = "face_embeddings"
12
  vector_db_persist_directory: str | None = None
13
  vector_db_n_results: int = 5
14
- face_match_threshold: float = 1.08
15
 
16
  device: Literal["auto", "cpu", "cuda"] = "auto"
17
  mtcnn_thresholds: tuple[float, float, float] = (0.6, 0.7, 0.95)
 
11
  vector_db_collection: str = "face_embeddings"
12
  vector_db_persist_directory: str | None = None
13
  vector_db_n_results: int = 5
14
+ face_match_threshold: float = 1.0764
15
 
16
  device: Literal["auto", "cpu", "cuda"] = "auto"
17
  mtcnn_thresholds: tuple[float, float, float] = (0.6, 0.7, 0.95)
uv.lock CHANGED
@@ -568,11 +568,8 @@ dependencies = [
568
  { name = "pydantic" },
569
  { name = "pydantic-settings" },
570
  { name = "pyjwt" },
571
- { name = "pytest" },
572
- { name = "pytest-cov" },
573
  { name = "python-dotenv" },
574
  { name = "python-multipart" },
575
- { name = "ruff" },
576
  { name = "torch", version = "2.12.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" },
577
  { name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" },
578
  { name = "torchvision", version = "0.27.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" },
@@ -580,6 +577,13 @@ dependencies = [
580
  { name = "uvicorn", extra = ["standard"] },
581
  ]
582
 
 
 
 
 
 
 
 
583
  [package.metadata]
584
  requires-dist = [
585
  { name = "chromadb", specifier = ">=1.5.9" },
@@ -592,16 +596,20 @@ requires-dist = [
592
  { name = "pydantic", specifier = ">=2.11.10,<=2.12.5" },
593
  { name = "pydantic-settings", specifier = ">=2.14.1" },
594
  { name = "pyjwt", specifier = ">=2.10.1" },
595
- { name = "pytest", specifier = ">=9.0.3" },
596
- { name = "pytest-cov", specifier = ">=7.1.0" },
597
  { name = "python-dotenv", specifier = ">=1.2.2" },
598
  { name = "python-multipart", specifier = ">=0.0.28" },
599
- { name = "ruff", specifier = ">=0.15.13" },
600
  { name = "torch", specifier = ">=2.12.0", index = "https://download.pytorch.org/whl/cpu" },
601
  { name = "torchvision", specifier = ">=0.27.0", index = "https://download.pytorch.org/whl/cpu" },
602
  { name = "uvicorn", extras = ["standard"], specifier = ">=0.46.0" },
603
  ]
604
 
 
 
 
 
 
 
 
605
  [[package]]
606
  name = "fastapi"
607
  version = "0.136.1"
 
568
  { name = "pydantic" },
569
  { name = "pydantic-settings" },
570
  { name = "pyjwt" },
 
 
571
  { name = "python-dotenv" },
572
  { name = "python-multipart" },
 
573
  { name = "torch", version = "2.12.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" },
574
  { name = "torch", version = "2.12.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" },
575
  { name = "torchvision", version = "0.27.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" },
 
577
  { name = "uvicorn", extra = ["standard"] },
578
  ]
579
 
580
+ [package.dev-dependencies]
581
+ dev = [
582
+ { name = "pytest" },
583
+ { name = "pytest-cov" },
584
+ { name = "ruff" },
585
+ ]
586
+
587
  [package.metadata]
588
  requires-dist = [
589
  { name = "chromadb", specifier = ">=1.5.9" },
 
596
  { name = "pydantic", specifier = ">=2.11.10,<=2.12.5" },
597
  { name = "pydantic-settings", specifier = ">=2.14.1" },
598
  { name = "pyjwt", specifier = ">=2.10.1" },
 
 
599
  { name = "python-dotenv", specifier = ">=1.2.2" },
600
  { name = "python-multipart", specifier = ">=0.0.28" },
 
601
  { name = "torch", specifier = ">=2.12.0", index = "https://download.pytorch.org/whl/cpu" },
602
  { name = "torchvision", specifier = ">=0.27.0", index = "https://download.pytorch.org/whl/cpu" },
603
  { name = "uvicorn", extras = ["standard"], specifier = ">=0.46.0" },
604
  ]
605
 
606
+ [package.metadata.requires-dev]
607
+ dev = [
608
+ { name = "pytest", specifier = ">=9.0.3" },
609
+ { name = "pytest-cov", specifier = ">=7.1.0" },
610
+ { name = "ruff", specifier = ">=0.15.13" },
611
+ ]
612
+
613
  [[package]]
614
  name = "fastapi"
615
  version = "0.136.1"