Skip to content

Commit

Permalink
Face recognition improvements (blakeblackshear#16034)
Browse files Browse the repository at this point in the history
  • Loading branch information
NickM-27 committed Jan 19, 2025
1 parent 5585da7 commit 8036b58
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 41 deletions.
14 changes: 11 additions & 3 deletions frigate/api/classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ def get_faces():
face_dict: dict[str, list[str]] = {}

for name in os.listdir(FACE_DIR):
face_dict[name] = []

face_dir = os.path.join(FACE_DIR, name)

if not os.path.isdir(face_dir):
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)

return JSONResponse(status_code=200, content=face_dict)
Expand Down Expand Up @@ -81,6 +85,10 @@ def train_face(request: Request, name: str, body: dict = None):
new_name = f"{name}-{rand_id}.webp"
new_file = os.path.join(FACE_DIR, f"{name}/{new_name}")
shutil.move(training_file, new_file)

context: EmbeddingsContext = request.app.embeddings
context.clear_face_classifier()

return JSONResponse(
content=(
{
Expand Down
1 change: 1 addition & 0 deletions frigate/comms/embeddings_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@


class EmbeddingsRequestEnum(Enum):
clear_face_classifier = "clear_face_classifier"
embed_description = "embed_description"
embed_thumbnail = "embed_thumbnail"
generate_search = "generate_search"
Expand Down
5 changes: 4 additions & 1 deletion frigate/data_processing/real_time/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ def process_frame(self, obj_data: dict[str, any], frame: np.ndarray) -> None:
pass

@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.
Args:
topic (str): topic that dictates what work is requested.
request_data (dict): containing data about requested change to process.
Returns:
Expand Down
2 changes: 1 addition & 1 deletion frigate/data_processing/real_time/bird_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def process_frame(self, obj_data, frame):
if resp.status_code == 200:
self.detected_birds[obj_data["id"]] = score

def handle_request(self, request_data):
def handle_request(self, topic, request_data):
return None

def expire_object(self, object_id):
Expand Down
78 changes: 43 additions & 35 deletions frigate/data_processing/real_time/face_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import numpy as np
import requests

from frigate.comms.embeddings_updater import EmbeddingsRequestEnum
from frigate.config import FrigateConfig
from frigate.const import FACE_DIR, FRIGATE_LOCALHOST, MODEL_CACHE_DIR
from frigate.util.image import area
Expand Down Expand Up @@ -353,45 +354,52 @@ def process_frame(self, obj_data: dict[str, any], frame: np.ndarray):

self.__update_metrics(datetime.datetime.now().timestamp() - start)

def handle_request(self, request_data) -> dict[str, any] | None:
rand_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
label = request_data["face_name"]
id = f"{label}-{rand_id}"

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)

if not face_box:
return {
"message": "No face was detected.",
"success": False,
}

face = img[face_box[1] : face_box[3], face_box[0] : face_box[2]]
ret, thumbnail = cv2.imencode(
".webp", face, [int(cv2.IMWRITE_WEBP_QUALITY), 100]
def handle_request(self, topic, request_data) -> dict[str, any] | None:
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}"

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)

if not face_box:
return {
"message": "No face was detected.",
"success": False,
}

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]
)

# 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)
# 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)

# save face image
with open(file, "wb") as output:
output.write(thumbnail.tobytes())
# save face image
with open(file, "wb") as output:
output.write(thumbnail.tobytes())

self.__clear_classifier()
return {
"message": "Successfully registered face.",
"success": True,
}
self.__clear_classifier()
return {
"message": "Successfully registered face.",
"success": True,
}

def expire_object(self, object_id: str):
if object_id in self.detected_faces:
Expand Down
5 changes: 5 additions & 0 deletions frigate/embeddings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ def get_face_ids(self, name: str) -> list[str]:

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:
folder = os.path.join(FACE_DIR, face)
for id in ids:
Expand Down
2 changes: 1 addition & 1 deletion frigate/embeddings/maintainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def _handle_request(topic: str, data: dict[str, any]) -> str:
)
else:
for processor in self.processors:
resp = processor.handle_request(data)
resp = processor.handle_request(topic, data)

if resp is not None:
return resp
Expand Down

0 comments on commit 8036b58

Please sign in to comment.