Skip to content

Commit

Permalink
Face recognition logic improvements (#15679)
Browse files Browse the repository at this point in the history
* Always initialize face model on startup

* Add ability to save face images for debugging

* Implement better face recognition reasonability
  • Loading branch information
NickM-27 committed Dec 26, 2024
1 parent fb8394b commit f847134
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 4 deletions.
3 changes: 3 additions & 0 deletions frigate/config/semantic_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class FaceRecognitionConfig(FrigateBaseModel):
min_area: int = Field(
default=500, title="Min area of face box to consider running face recognition."
)
debug_save_images: bool = Field(
default=False, title="Save images of face detections for debugging."
)


class LicensePlateRecognitionConfig(FrigateBaseModel):
Expand Down
21 changes: 17 additions & 4 deletions frigate/embeddings/maintainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,13 +505,26 @@ def _process_face(self, obj_data: dict[str, any], frame: np.ndarray) -> None:

sub_label, score = res

# calculate the overall face score as the probability * area of face
# this will help to reduce false positives from small side-angle faces
# if a large front-on face image may have scored slightly lower but
# is more likely to be accurate due to the larger face area
face_score = round(score * face_frame.shape[0] * face_frame.shape[1], 2)

logger.debug(
f"Detected best face for person as: {sub_label} with score {score}"
f"Detected best face for person as: {sub_label} with probability {score} and overall face score {face_score}"
)

if id in self.detected_faces and score <= self.detected_faces[id]:
if self.config.face_recognition.debug_save_images:
# write face to library
folder = os.path.join(FACE_DIR, "debug")
file = os.path.join(folder, f"{id}-{sub_label}-{score}-{face_score}.webp")
os.makedirs(folder, exist_ok=True)
cv2.imwrite(file, face_frame)

if id in self.detected_faces and face_score <= self.detected_faces[id]:
logger.debug(
f"Recognized face distance {score} is less than previous face distance ({self.detected_faces.get(id)})."
f"Recognized face distance {score} and overall score {face_score} is less than previous overall face score ({self.detected_faces.get(id)})."
)
return

Expand All @@ -525,7 +538,7 @@ def _process_face(self, obj_data: dict[str, any], frame: np.ndarray) -> None:
)

if resp.status_code == 200:
self.detected_faces[id] = score
self.detected_faces[id] = face_score

def _detect_license_plate(self, input: np.ndarray) -> tuple[int, int, int, int]:
"""Return the dimensions of the input image as [x, y, width, height]."""
Expand Down
5 changes: 5 additions & 0 deletions frigate/util/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,17 @@ def __init__(self, config: FaceRecognitionConfig, db: SqliteQueueDatabase):
)
)
self.label_map: dict[int, str] = {}
self.__build_classifier()

def __build_classifier(self) -> None:
labels = []
faces = []

dir = "/media/frigate/clips/faces"
for idx, name in enumerate(os.listdir(dir)):
if name == "debug":
continue

self.label_map[idx] = name
face_folder = os.path.join(dir, name)
for image in os.listdir(face_folder):
Expand Down Expand Up @@ -248,6 +252,7 @@ def __align_face(
def clear_classifier(self) -> None:
self.classifier = None
self.labeler = None
self.label_map = {}

def classify_face(self, face_image: np.ndarray) -> Optional[tuple[str, float]]:
if not self.label_map:
Expand Down

0 comments on commit f847134

Please sign in to comment.