Skip to content
This repository has been archived by the owner on Dec 8, 2024. It is now read-only.

Commit

Permalink
DeepFace -> face_recognition (dlib) migration (#70)
Browse files Browse the repository at this point in the history
* feat,ref: swapped register faces to register embeddings

* feat: added func for iterating over embeddings

* feat: added register face embeddings func and face match for dlib face-rec

* fix: robustness fixes for faces folder and get_facematch

* build: added face-recognition to poetry config

* ref,doc: Cleaned up recognition after dlib changes

* ref,doc: added documentation for iter_face_embeddings

* fix: put rpi-gpio back

* feat: face_unlock.py migrated and dlib working on laptop

* feat: Made face_unlock.py work for laptop or rpi

* feat: made statuses more specific and added Enum

* fix: restored take_snapshot

Opencv only takes next frame in buffer not most recent so we need to reset device each snapshot

* ref: deleted old test scripts as these are captured by face_unlock.py now

* ref: removed explore-deepface script
  • Loading branch information
MitchellJC authored Oct 3, 2024
1 parent 4f46079 commit 77cbc27
Show file tree
Hide file tree
Showing 8 changed files with 730 additions and 667 deletions.
39 changes: 28 additions & 11 deletions client/data/routines.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
import sqlite3

from datetime import datetime
from typing import Any, NamedTuple, Optional
from typing import Any, NamedTuple, Optional, Iterator
from importlib import resources

import cv2
import numpy as np
from pydbml import PyDBML

Expand Down Expand Up @@ -186,20 +185,30 @@ def get_user_postures(
return [Posture(*record) for record in result.fetchall()]


def register_faces(user_id: int, faces: list[np.ndarray]) -> None:
"""Register faces for a user.
def register_face_embeddings(user_id: int, face_embeddings: list[np.ndarray]) -> None:
"""Register face embeddings for a user.
Args:
user_id: The user to register faces for.
faces: List of face arrays in the format HxWxC where channels are RGB
faces: List of face embedding arrays.
"""
stacked_faces = np.vstack(face_embeddings)
with resources.as_file(FACES_FOLDER) as faces_folder:
faces_folder.mkdir(exist_ok=True)
user_folder = faces_folder / str(user_id)
user_folder.mkdir()
for i, image in enumerate(faces):
image_path = user_folder / f"{i}.png"
cv2.imwrite(str(image_path), image)
embedding_path = faces_folder / f"{user_id}.npy"
np.save(embedding_path, stacked_faces)


def iter_face_embeddings() -> Iterator[tuple[int, list[np.ndarray]]]:
"""
Returns:
Generator which yields (user_id, face_embeddings) each iteration. Each iteration will give
a different user. face_embeddings is a list of numpy arrays which each represent an
embedded face for the user.
"""
with resources.as_file(FACES_FOLDER) as faces_folder:
for user_embeddings_path in faces_folder.iterdir():
user_id = int(user_embeddings_path.stem)
yield user_id, list(np.load(user_embeddings_path))


def get_schema_info() -> list[list[tuple[Any]]]:
Expand Down Expand Up @@ -228,3 +237,11 @@ def _connect() -> sqlite3.Connection:
return sqlite3.connect(
database_file, detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES
)


def _init_faces_folder() -> None:
with resources.as_file(FACES_FOLDER) as faces_folder:
faces_folder.mkdir(exist_ok=True)


_init_faces_folder()
71 changes: 56 additions & 15 deletions client/models/face_recognition/recognition.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from importlib import resources
from enum import Enum

import numpy as np
from data.routines import FACES_FOLDER
from deepface import DeepFace
import face_recognition

MODEL_NAME = "GhostFaceNet"
from data.routines import register_face_embeddings, iter_face_embeddings

MODEL_NAME = "small"

def _path_to_user_id(path: str) -> str:
return path.split("/")[-2]

class Status(Enum):
NO_FACES = -3
TOO_MANY_FACES = -2
NO_MATCH = -1
OK = 0


def get_face_match(login_face: np.ndarray) -> int:
Expand All @@ -19,13 +23,50 @@ def get_face_match(login_face: np.ndarray) -> int:
login_face: Image of user's face as an array
Returns:
Matching user id, or -1 if no users matched
Matching user id, or one of Status values.
"""
with resources.as_file(FACES_FOLDER) as faces_folder:
try:
dfs = DeepFace.find(login_face, str(faces_folder), model_name=MODEL_NAME)
except ValueError:
return -1
df = dfs[0]
user_id = int(_path_to_user_id(df.iloc[0]["identity"]))
return user_id
login_embeddings = face_recognition.face_encodings(login_face, model=MODEL_NAME)

# Should only detect exactly one face
if len(login_embeddings) == 0:
return Status.NO_FACES.value
if len(login_embeddings) > 1:
return Status.TOO_MANY_FACES.value

login_embedding = login_embeddings[0]

for user_id, user_embeddings in iter_face_embeddings():
matches = face_recognition.compare_faces(user_embeddings, login_embedding)

if any(matches):
return user_id

return Status.NO_MATCH.value


def register_faces(user_id: int, faces: list[np.ndarray]) -> int:
"""Compute and store face embeddings in the database.
Args:
user_id: Id of the user who belongs to the faces
faces: List of face images in the shape HxWxC where (C)hannels are in RGB
Returns:
Registration status
"""
face_embeddings = []
for face in faces:
all_faces_embed = face_recognition.face_encodings(face, model=MODEL_NAME)

# Should only detect exactly one face
if len(all_faces_embed) == 0:
return Status.NO_FACES.value
if len(all_faces_embed) > 1:
return Status.TOO_MANY_FACES.value

face_embedding = all_faces_embed[0]
face_embeddings.append(face_embedding)

register_face_embeddings(user_id, face_embeddings)

return Status.OK.value
Loading

0 comments on commit 77cbc27

Please sign in to comment.