Compare commits

..

No commits in common. "53c8aa25cb0bbcbcd37c5f7737062b672c038db7" and "e3d4b84803fce7e9f345592c797a8cdc62022dbb" have entirely different histories.

6 changed files with 39 additions and 122 deletions

View File

@ -1,31 +0,0 @@
---
id: bird_classification
title: Bird Classification
---
Bird classification identifies known birds using a quantized Tensorflow model. When a known bird is recognized, its common name will be added as a `sub_label`. This information is included in the UI, filters, as well as in notifications.
## Minimum System Requirements
Bird classification runs a lightweight tflite model on the CPU, there are no significantly different system requirements than running Frigate itself.
## Model
The classification model used is the MobileNet INat Bird Classification, [available identifiers can be found here.](https://raw.githubusercontent.com/google-coral/test_data/master/inat_bird_labels.txt)
## Configuration
Bird classification is disabled by default, it must be enabled in your config file before it can be used. Bird classification is a global configuration setting.
```yaml
classification:
bird:
enabled: true
```
## Advanced Configuration
Fine-tune bird classification with these optional parameters:
- `threshold`: Classification confidence score required to set the sub label on the object.
- Default: `0.9`.

View File

@ -33,12 +33,11 @@ const sidebars: SidebarsConfig = {
"configuration/object_detectors", "configuration/object_detectors",
"configuration/audio_detectors", "configuration/audio_detectors",
], ],
Enrichments: [ Classifiers: [
"configuration/semantic_search", "configuration/semantic_search",
"configuration/genai", "configuration/genai",
"configuration/face_recognition", "configuration/face_recognition",
"configuration/license_plate_recognition", "configuration/license_plate_recognition",
"configuration/bird_classification",
], ],
Cameras: [ Cameras: [
"configuration/cameras", "configuration/cameras",

View File

@ -198,16 +198,6 @@ async def register_face(request: Request, name: str, file: UploadFile):
context: EmbeddingsContext = request.app.embeddings context: EmbeddingsContext = request.app.embeddings
result = context.register_face(name, await file.read()) result = context.register_face(name, await file.read())
if not isinstance(result, dict):
return JSONResponse(
status_code=500,
content={
"success": False,
"message": "Could not process request. Try restarting Frigate.",
},
)
return JSONResponse( return JSONResponse(
status_code=200 if result.get("success", True) else 400, status_code=200 if result.get("success", True) else 400,
content=result, content=result,
@ -224,16 +214,6 @@ async def recognize_face(request: Request, file: UploadFile):
context: EmbeddingsContext = request.app.embeddings context: EmbeddingsContext = request.app.embeddings
result = context.recognize_face(await file.read()) result = context.recognize_face(await file.read())
if not isinstance(result, dict):
return JSONResponse(
status_code=500,
content={
"success": False,
"message": "Could not process request. Try restarting Frigate.",
},
)
return JSONResponse( return JSONResponse(
status_code=200 if result.get("success", True) else 400, status_code=200 if result.get("success", True) else 400,
content=result, content=result,

View File

@ -1,7 +1,5 @@
import logging import logging
import os import os
import queue
import threading
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import cv2 import cv2
@ -20,7 +18,10 @@ class FaceRecognizer(ABC):
def __init__(self, config: FrigateConfig) -> None: def __init__(self, config: FrigateConfig) -> None:
self.config = config self.config = config
self.init_landmark_detector() self.landmark_detector = cv2.face.createFacemarkLBF()
self.landmark_detector.loadModel(
os.path.join(MODEL_CACHE_DIR, "facedet/landmarkdet.yaml")
)
@abstractmethod @abstractmethod
def build(self) -> None: def build(self) -> None:
@ -36,13 +37,6 @@ class FaceRecognizer(ABC):
def classify(self, face_image: np.ndarray) -> tuple[str, float] | None: def classify(self, face_image: np.ndarray) -> tuple[str, float] | None:
pass pass
def init_landmark_detector(self) -> None:
landmark_model = os.path.join(MODEL_CACHE_DIR, "facedet/landmarkdet.yaml")
if os.path.exists(landmark_model):
self.landmark_detector = cv2.face.createFacemarkLBF()
self.landmark_detector.loadModel(landmark_model)
def align_face( def align_face(
self, self,
image: np.ndarray, image: np.ndarray,
@ -136,7 +130,6 @@ class LBPHRecognizer(FaceRecognizer):
def build(self): def build(self):
if not self.landmark_detector: if not self.landmark_detector:
self.init_landmark_detector()
return None return None
labels = [] labels = []
@ -208,69 +201,45 @@ class ArcFaceRecognizer(FaceRecognizer):
super().__init__(config) super().__init__(config)
self.mean_embs: dict[int, np.ndarray] = {} self.mean_embs: dict[int, np.ndarray] = {}
self.face_embedder: ArcfaceEmbedding = ArcfaceEmbedding() self.face_embedder: ArcfaceEmbedding = ArcfaceEmbedding()
self.model_builder_queue: queue.Queue | None = None
def clear(self) -> None: def clear(self) -> None:
self.mean_embs = {} self.mean_embs = {}
def run_build_task(self) -> None:
self.model_builder_queue = queue.Queue()
def build_model():
face_embeddings_map: dict[str, list[np.ndarray]] = {}
idx = 0
dir = "/media/frigate/clips/faces"
for name in os.listdir(dir):
if name == "train":
continue
face_folder = os.path.join(dir, name)
if not os.path.isdir(face_folder):
continue
face_embeddings_map[name] = []
for image in os.listdir(face_folder):
img = cv2.imread(os.path.join(face_folder, image))
if img is None:
continue
img = self.align_face(img, img.shape[1], img.shape[0])
emb = self.face_embedder([img])[0].squeeze()
face_embeddings_map[name].append(emb)
idx += 1
self.model_builder_queue.put(face_embeddings_map)
thread = threading.Thread(target=build_model, daemon=True)
thread.start()
def build(self): def build(self):
if not self.landmark_detector: if not self.landmark_detector:
self.init_landmark_detector()
return None return None
if self.model_builder_queue is not None: face_embeddings_map: dict[str, list[np.ndarray]] = {}
try: idx = 0
face_embeddings_map: dict[str, list[np.ndarray]] = (
self.model_builder_queue.get(timeout=0.1) dir = "/media/frigate/clips/faces"
) for name in os.listdir(dir):
self.model_builder_queue = None if name == "train":
except queue.Empty: continue
return
else: face_folder = os.path.join(dir, name)
self.run_build_task()
return if not os.path.isdir(face_folder):
continue
face_embeddings_map[name] = []
for image in os.listdir(face_folder):
img = cv2.imread(os.path.join(face_folder, image))
if img is None:
continue
img = self.align_face(img, img.shape[1], img.shape[0])
emb = self.face_embedder([img])[0].squeeze()
face_embeddings_map[name].append(emb)
idx += 1
if not face_embeddings_map: if not face_embeddings_map:
return return
for name, embs in face_embeddings_map.items(): for name, embs in face_embeddings_map.items():
if embs: self.mean_embs[name] = stats.trim_mean(embs, 0.15)
self.mean_embs[name] = stats.trim_mean(embs, 0.15)
logger.debug("Finished building ArcFace model") logger.debug("Finished building ArcFace model")

View File

@ -176,8 +176,8 @@ export default function ClassificationSettingsView({
}) })
.finally(() => { .finally(() => {
addMessage( addMessage(
"search_settings_restart", "search_settings",
`Restart required (Classification settings changed)`, `Restart Required (Classification settings changed)`,
undefined, undefined,
"search_settings", "search_settings",
); );

View File

@ -131,6 +131,12 @@ export default function FrigatePlusSettingsView({
position: "top-center", position: "top-center",
}); });
setChangedValue(false); setChangedValue(false);
addMessage(
"plus_restart",
"Restart required (Frigate+ model changed)",
undefined,
"plus_restart",
);
updateConfig(); updateConfig();
} else { } else {
toast.error( toast.error(
@ -154,12 +160,6 @@ export default function FrigatePlusSettingsView({
); );
}) })
.finally(() => { .finally(() => {
addMessage(
"plus_restart",
"Restart required (Frigate+ model changed)",
undefined,
"plus_restart",
);
setIsLoading(false); setIsLoading(false);
}); });
}, [updateConfig, addMessage, frigatePlusSettings, t]); }, [updateConfig, addMessage, frigatePlusSettings, t]);