mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 09:04:28 +03:00
Implement best practices
This commit is contained in:
parent
70f46fae28
commit
aa5f7721b6
@ -24,6 +24,10 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
MIN_MATCHING_FACES = 2
|
||||
MIN_FACE_SCORE = 0.8
|
||||
NMS_THRESHOLD = 0.3
|
||||
FACE_INPUT_SIZE = (320, 320)
|
||||
FACE_QUALITY = 100
|
||||
|
||||
|
||||
class FaceProcessor(RealTimeProcessorApi):
|
||||
@ -358,49 +362,83 @@ class FaceProcessor(RealTimeProcessorApi):
|
||||
if topic == EmbeddingsRequestEnum.clear_face_classifier.value:
|
||||
self.__clear_classifier()
|
||||
elif topic == EmbeddingsRequestEnum.register_face.value:
|
||||
rand_id = "".join(
|
||||
random.choices(string.ascii_lowercase + string.digits, k=6)
|
||||
)
|
||||
label = request_data["face_name"]
|
||||
id = f"{label}-{rand_id}"
|
||||
face_name = request_data.get("face_name")
|
||||
if not self.__validate_face_name(face_name):
|
||||
return {
|
||||
"message": "Invalid face name",
|
||||
"success": False,
|
||||
}
|
||||
|
||||
if request_data.get("cropped"):
|
||||
thumbnail = request_data["image"]
|
||||
else:
|
||||
img = cv2.imdecode(
|
||||
np.frombuffer(
|
||||
base64.b64decode(request_data["image"]), dtype=np.uint8
|
||||
),
|
||||
cv2.IMREAD_COLOR,
|
||||
try:
|
||||
rand_id = "".join(
|
||||
random.choices(string.ascii_lowercase + string.digits, k=6)
|
||||
)
|
||||
face_box = self.__detect_face(img)
|
||||
id = f"{face_name}-{rand_id}"
|
||||
|
||||
if not face_box:
|
||||
return {
|
||||
"message": "No face was detected.",
|
||||
"success": False,
|
||||
}
|
||||
if request_data.get("cropped"):
|
||||
thumbnail = request_data["image"]
|
||||
else:
|
||||
img = cv2.imdecode(
|
||||
np.frombuffer(
|
||||
base64.b64decode(request_data["image"]), dtype=np.uint8
|
||||
),
|
||||
cv2.IMREAD_COLOR,
|
||||
)
|
||||
face_box = self.__detect_face(img)
|
||||
|
||||
face = img[face_box[1] : face_box[3], face_box[0] : face_box[2]]
|
||||
_, thumbnail = cv2.imencode(
|
||||
".webp", face, [int(cv2.IMWRITE_WEBP_QUALITY), 100]
|
||||
)
|
||||
if not face_box:
|
||||
return {
|
||||
"message": "No face was detected.",
|
||||
"success": False,
|
||||
}
|
||||
|
||||
# write face to library
|
||||
folder = os.path.join(FACE_DIR, label)
|
||||
file = os.path.join(folder, f"{id}.webp")
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
face = img[face_box[1] : face_box[3], face_box[0] : face_box[2]]
|
||||
_, thumbnail = cv2.imencode(
|
||||
".webp", face, [int(cv2.IMWRITE_WEBP_QUALITY), 100]
|
||||
)
|
||||
|
||||
# save face image
|
||||
with open(file, "wb") as output:
|
||||
output.write(thumbnail.tobytes())
|
||||
# write face to library
|
||||
folder = os.path.join(FACE_DIR, face_name)
|
||||
file = os.path.join(folder, f"{id}.webp")
|
||||
os.makedirs(folder, exist_ok=True)
|
||||
|
||||
self.__clear_classifier()
|
||||
return {
|
||||
"message": "Successfully registered face.",
|
||||
"success": True,
|
||||
}
|
||||
# save face image
|
||||
with open(file, "wb") as output:
|
||||
output.write(thumbnail.tobytes())
|
||||
|
||||
self.__clear_classifier()
|
||||
return {
|
||||
"message": "Successfully registered face.",
|
||||
"success": True,
|
||||
}
|
||||
except cv2.error as e:
|
||||
return {
|
||||
"message": f"Failed to process image: {str(e)}",
|
||||
"success": False,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error registering face: {str(e)}")
|
||||
return {
|
||||
"message": "Internal server error",
|
||||
"success": False,
|
||||
}
|
||||
|
||||
def expire_object(self, object_id: str):
|
||||
if object_id in self.detected_faces:
|
||||
self.detected_faces.pop(object_id)
|
||||
|
||||
def __validate_face_name(self, name: str) -> bool:
|
||||
"""Validate face name meets requirements."""
|
||||
if not name or not isinstance(name, str):
|
||||
return False
|
||||
# Add any other validation rules (e.g., no special chars)
|
||||
return True
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup resources when shutting down."""
|
||||
if self.face_detector:
|
||||
self.face_detector = None
|
||||
if self.landmark_detector:
|
||||
self.landmark_detector = None
|
||||
if self.face_recognizer:
|
||||
self.face_recognizer = None
|
||||
|
||||
@ -116,29 +116,38 @@ export default function FaceLibrary() {
|
||||
);
|
||||
|
||||
const [newFaceDialog, setNewFaceDialog] = useState(false);
|
||||
const [isCreatingFace, setIsCreatingFace] = useState(false);
|
||||
const [newFaceName, setNewFaceName] = useState("");
|
||||
|
||||
const createNewFace = useCallback(() => {
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
createNewFace();
|
||||
}
|
||||
};
|
||||
|
||||
const createNewFace = useCallback(async () => {
|
||||
if (!newFaceName.trim()) {
|
||||
toast.error("Face name cannot be empty", { position: "top-center" });
|
||||
return;
|
||||
}
|
||||
|
||||
axios
|
||||
.post(`/faces/${newFaceName}`)
|
||||
.then((resp) => {
|
||||
if (resp.status == 200) {
|
||||
setNewFaceDialog(false);
|
||||
setNewFaceName("");
|
||||
refreshFaces();
|
||||
toast.success("Successfully created new face", { position: "top-center" });
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast.error(`Failed to create face: ${error.response?.data?.message || error.message}`, {
|
||||
position: "top-center",
|
||||
});
|
||||
});
|
||||
setIsCreatingFace(true);
|
||||
try {
|
||||
const resp = await axios.post(`/faces/${newFaceName}`);
|
||||
if (resp.status === 200) {
|
||||
setNewFaceDialog(false);
|
||||
setNewFaceName("");
|
||||
refreshFaces();
|
||||
toast.success("Successfully created new face", { position: "top-center" });
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
`Failed to create face: ${error.response?.data?.message || error.message}`,
|
||||
{ position: "top-center" }
|
||||
);
|
||||
} finally {
|
||||
setIsCreatingFace(false);
|
||||
}
|
||||
}, [newFaceName, refreshFaces]);
|
||||
|
||||
if (!config) {
|
||||
@ -159,8 +168,12 @@ export default function FaceLibrary() {
|
||||
placeholder="Enter face name"
|
||||
value={newFaceName}
|
||||
onChange={(e) => setNewFaceName(e.target.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
disabled={isCreatingFace}
|
||||
/>
|
||||
<Button onClick={createNewFace}>Create</Button>
|
||||
<Button onClick={createNewFace} disabled={isCreatingFace}>
|
||||
{isCreatingFace ? "Creating..." : "Create"}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user