Face recognition improvements (#16034)

This commit is contained in:
Nicolas Mowen 2025-01-18 10:52:01 -07:00
parent 9a2de78fc9
commit 0ec536a4e5
7 changed files with 64 additions and 39 deletions

View File

@ -24,14 +24,18 @@ def get_faces():
face_dict: dict[str, list[str]] = {} face_dict: dict[str, list[str]] = {}
for name in os.listdir(FACE_DIR): for name in os.listdir(FACE_DIR):
face_dict[name] = []
face_dir = os.path.join(FACE_DIR, name) face_dir = os.path.join(FACE_DIR, name)
if not os.path.isdir(face_dir): if not os.path.isdir(face_dir):
continue continue
for file in os.listdir(face_dir): face_dict[name] = []
for file in sorted(
os.listdir(face_dir),
key=lambda f: os.path.getctime(os.path.join(face_dir, f)),
reverse=True,
):
face_dict[name].append(file) face_dict[name].append(file)
return JSONResponse(status_code=200, content=face_dict) return JSONResponse(status_code=200, content=face_dict)
@ -81,6 +85,10 @@ def train_face(request: Request, name: str, body: dict = None):
new_name = f"{name}-{rand_id}.webp" new_name = f"{name}-{rand_id}.webp"
new_file = os.path.join(FACE_DIR, f"{name}/{new_name}") new_file = os.path.join(FACE_DIR, f"{name}/{new_name}")
shutil.move(training_file, new_file) shutil.move(training_file, new_file)
context: EmbeddingsContext = request.app.embeddings
context.clear_face_classifier()
return JSONResponse( return JSONResponse(
content=( content=(
{ {

View File

@ -9,6 +9,7 @@ SOCKET_REP_REQ = "ipc:///tmp/cache/embeddings"
class EmbeddingsRequestEnum(Enum): class EmbeddingsRequestEnum(Enum):
clear_face_classifier = "clear_face_classifier"
embed_description = "embed_description" embed_description = "embed_description"
embed_thumbnail = "embed_thumbnail" embed_thumbnail = "embed_thumbnail"
generate_search = "generate_search" generate_search = "generate_search"

View File

@ -32,9 +32,12 @@ class RealTimeProcessorApi(ABC):
pass pass
@abstractmethod @abstractmethod
def handle_request(self, request_data: dict[str, any]) -> dict[str, any] | None: def handle_request(
self, topic: str, request_data: dict[str, any]
) -> dict[str, any] | None:
"""Handle metadata requests. """Handle metadata requests.
Args: Args:
topic (str): topic that dictates what work is requested.
request_data (dict): containing data about requested change to process. request_data (dict): containing data about requested change to process.
Returns: Returns:

View File

@ -146,7 +146,7 @@ class BirdProcessor(RealTimeProcessorApi):
if resp.status_code == 200: if resp.status_code == 200:
self.detected_birds[obj_data["id"]] = score self.detected_birds[obj_data["id"]] = score
def handle_request(self, request_data): def handle_request(self, topic, request_data):
return None return None
def expire_object(self, object_id): def expire_object(self, object_id):

View File

@ -12,6 +12,7 @@ import cv2
import numpy as np import numpy as np
import requests import requests
from frigate.comms.embeddings_updater import EmbeddingsRequestEnum
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.const import FACE_DIR, FRIGATE_LOCALHOST, MODEL_CACHE_DIR from frigate.const import FACE_DIR, FRIGATE_LOCALHOST, MODEL_CACHE_DIR
from frigate.util.image import area from frigate.util.image import area
@ -353,8 +354,13 @@ class FaceProcessor(RealTimeProcessorApi):
self.__update_metrics(datetime.datetime.now().timestamp() - start) self.__update_metrics(datetime.datetime.now().timestamp() - start)
def handle_request(self, request_data) -> dict[str, any] | None: def handle_request(self, topic, request_data) -> dict[str, any] | None:
rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) 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"] label = request_data["face_name"]
id = f"{label}-{rand_id}" id = f"{label}-{rand_id}"
@ -362,7 +368,9 @@ class FaceProcessor(RealTimeProcessorApi):
thumbnail = request_data["image"] thumbnail = request_data["image"]
else: else:
img = cv2.imdecode( img = cv2.imdecode(
np.frombuffer(base64.b64decode(request_data["image"]), dtype=np.uint8), np.frombuffer(
base64.b64decode(request_data["image"]), dtype=np.uint8
),
cv2.IMREAD_COLOR, cv2.IMREAD_COLOR,
) )
face_box = self.__detect_face(img) face_box = self.__detect_face(img)
@ -374,7 +382,7 @@ class FaceProcessor(RealTimeProcessorApi):
} }
face = img[face_box[1] : face_box[3], face_box[0] : face_box[2]] face = img[face_box[1] : face_box[3], face_box[0] : face_box[2]]
ret, thumbnail = cv2.imencode( _, thumbnail = cv2.imencode(
".webp", face, [int(cv2.IMWRITE_WEBP_QUALITY), 100] ".webp", face, [int(cv2.IMWRITE_WEBP_QUALITY), 100]
) )

View File

@ -211,6 +211,11 @@ class EmbeddingsContext:
return self.db.execute_sql(sql_query).fetchall() return self.db.execute_sql(sql_query).fetchall()
def clear_face_classifier(self) -> None:
self.requestor.send_data(
EmbeddingsRequestEnum.clear_face_classifier.value, None
)
def delete_face_ids(self, face: str, ids: list[str]) -> None: def delete_face_ids(self, face: str, ids: list[str]) -> None:
folder = os.path.join(FACE_DIR, face) folder = os.path.join(FACE_DIR, face)
for id in ids: for id in ids:

View File

@ -140,7 +140,7 @@ class EmbeddingMaintainer(threading.Thread):
) )
else: else:
for processor in self.processors: for processor in self.processors:
resp = processor.handle_request(data) resp = processor.handle_request(topic, data)
if resp is not None: if resp is not None:
return resp return resp