mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01: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