From 6216e84b64e343097ab2fb2d290779c18a36e09a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 1 Apr 2026 12:55:56 -0300 Subject: [PATCH] fix: save face crops as unknown when no model is trained yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit classify() returns None when no faces have been trained (mean_embs is empty). The early return that followed skipped write_face_attempt(), leaving the train queue permanently empty. This made it impossible to bootstrap face recognition after a fresh install or after deleting all known faces — there was no way to get crops into the UI for labelling without restarting Frigate or manually calling the register API. When classify() returns None, call write_face_attempt() with sub_label="unknown" and score=0.0 before returning. write_face_attempt already respects the save_attempts config flag, so no new config option is needed. Co-Authored-By: Claude Sonnet 4.6 --- frigate/data_processing/real_time/face.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frigate/data_processing/real_time/face.py b/frigate/data_processing/real_time/face.py index c6b6346b54..d546d8d43f 100644 --- a/frigate/data_processing/real_time/face.py +++ b/frigate/data_processing/real_time/face.py @@ -295,7 +295,15 @@ class FaceRealTimeProcessor(RealTimeProcessorApi): res = self.recognizer.classify(face_frame) if not res: - logger.debug(f"Face recognizer returned no result for {id}") + # classify() returns None when no faces have been trained yet (empty + # embeddings). Save the crop as "unknown" so the user can assign it + # from the UI and bootstrap face recognition after a fresh start or + # after deleting all known faces — otherwise the train queue stays + # empty forever and there is no way to add new training data. + self.write_face_attempt( + face_frame, id, datetime.datetime.now().timestamp(), "unknown", 0.0 + ) + logger.debug(f"Face recognizer returned no result for {id}, saved crop as unknown") self.__update_metrics(datetime.datetime.now().timestamp() - start) return