mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-08-04 13:47:37 +02:00
Face recognition logic improvements (#15679)
* Always initialize face model on startup * Add ability to save face images for debugging * Implement better face recognition reasonability
This commit is contained in:
parent
5a2113a62d
commit
5c6f169975
@ -32,6 +32,9 @@ class FaceRecognitionConfig(FrigateBaseModel):
|
|||||||
min_area: int = Field(
|
min_area: int = Field(
|
||||||
default=500, title="Min area of face box to consider running face recognition."
|
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):
|
class LicensePlateRecognitionConfig(FrigateBaseModel):
|
||||||
|
@ -505,13 +505,26 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
|
|
||||||
sub_label, score = res
|
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(
|
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(
|
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
|
return
|
||||||
|
|
||||||
@ -525,7 +538,7 @@ class EmbeddingMaintainer(threading.Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if resp.status_code == 200:
|
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]:
|
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]."""
|
"""Return the dimensions of the input image as [x, y, width, height]."""
|
||||||
|
@ -170,6 +170,7 @@ class FaceClassificationModel:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.label_map: dict[int, str] = {}
|
self.label_map: dict[int, str] = {}
|
||||||
|
self.__build_classifier()
|
||||||
|
|
||||||
def __build_classifier(self) -> None:
|
def __build_classifier(self) -> None:
|
||||||
labels = []
|
labels = []
|
||||||
@ -177,6 +178,9 @@ class FaceClassificationModel:
|
|||||||
|
|
||||||
dir = "/media/frigate/clips/faces"
|
dir = "/media/frigate/clips/faces"
|
||||||
for idx, name in enumerate(os.listdir(dir)):
|
for idx, name in enumerate(os.listdir(dir)):
|
||||||
|
if name == "debug":
|
||||||
|
continue
|
||||||
|
|
||||||
self.label_map[idx] = name
|
self.label_map[idx] = name
|
||||||
face_folder = os.path.join(dir, name)
|
face_folder = os.path.join(dir, name)
|
||||||
for image in os.listdir(face_folder):
|
for image in os.listdir(face_folder):
|
||||||
@ -248,6 +252,7 @@ class FaceClassificationModel:
|
|||||||
def clear_classifier(self) -> None:
|
def clear_classifier(self) -> None:
|
||||||
self.classifier = None
|
self.classifier = None
|
||||||
self.labeler = None
|
self.labeler = None
|
||||||
|
self.label_map = {}
|
||||||
|
|
||||||
def classify_face(self, face_image: np.ndarray) -> Optional[tuple[str, float]]:
|
def classify_face(self, face_image: np.ndarray) -> Optional[tuple[str, float]]:
|
||||||
if not self.label_map:
|
if not self.label_map:
|
||||||
|
Loading…
Reference in New Issue
Block a user