mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-05-03 06:50:58 +00:00
Compare commits
3 Commits
e3d4b84803
...
53c8aa25cb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53c8aa25cb | ||
|
|
e3f34d6f11 | ||
|
|
e6936c177b |
31
docs/docs/configuration/bird_classification.md
Normal file
31
docs/docs/configuration/bird_classification.md
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
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`.
|
||||
@ -33,11 +33,12 @@ const sidebars: SidebarsConfig = {
|
||||
"configuration/object_detectors",
|
||||
"configuration/audio_detectors",
|
||||
],
|
||||
Classifiers: [
|
||||
Enrichments: [
|
||||
"configuration/semantic_search",
|
||||
"configuration/genai",
|
||||
"configuration/face_recognition",
|
||||
"configuration/license_plate_recognition",
|
||||
"configuration/bird_classification",
|
||||
],
|
||||
Cameras: [
|
||||
"configuration/cameras",
|
||||
|
||||
@ -198,6 +198,16 @@ async def register_face(request: Request, name: str, file: UploadFile):
|
||||
|
||||
context: EmbeddingsContext = request.app.embeddings
|
||||
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(
|
||||
status_code=200 if result.get("success", True) else 400,
|
||||
content=result,
|
||||
@ -214,6 +224,16 @@ async def recognize_face(request: Request, file: UploadFile):
|
||||
|
||||
context: EmbeddingsContext = request.app.embeddings
|
||||
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(
|
||||
status_code=200 if result.get("success", True) else 400,
|
||||
content=result,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import logging
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import cv2
|
||||
@ -18,10 +20,7 @@ class FaceRecognizer(ABC):
|
||||
|
||||
def __init__(self, config: FrigateConfig) -> None:
|
||||
self.config = config
|
||||
self.landmark_detector = cv2.face.createFacemarkLBF()
|
||||
self.landmark_detector.loadModel(
|
||||
os.path.join(MODEL_CACHE_DIR, "facedet/landmarkdet.yaml")
|
||||
)
|
||||
self.init_landmark_detector()
|
||||
|
||||
@abstractmethod
|
||||
def build(self) -> None:
|
||||
@ -37,6 +36,13 @@ class FaceRecognizer(ABC):
|
||||
def classify(self, face_image: np.ndarray) -> tuple[str, float] | None:
|
||||
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(
|
||||
self,
|
||||
image: np.ndarray,
|
||||
@ -130,6 +136,7 @@ class LBPHRecognizer(FaceRecognizer):
|
||||
|
||||
def build(self):
|
||||
if not self.landmark_detector:
|
||||
self.init_landmark_detector()
|
||||
return None
|
||||
|
||||
labels = []
|
||||
@ -201,45 +208,69 @@ class ArcFaceRecognizer(FaceRecognizer):
|
||||
super().__init__(config)
|
||||
self.mean_embs: dict[int, np.ndarray] = {}
|
||||
self.face_embedder: ArcfaceEmbedding = ArcfaceEmbedding()
|
||||
self.model_builder_queue: queue.Queue | None = None
|
||||
|
||||
def clear(self) -> None:
|
||||
self.mean_embs = {}
|
||||
|
||||
def build(self):
|
||||
if not self.landmark_detector:
|
||||
return None
|
||||
def run_build_task(self) -> None:
|
||||
self.model_builder_queue = queue.Queue()
|
||||
|
||||
face_embeddings_map: dict[str, list[np.ndarray]] = {}
|
||||
idx = 0
|
||||
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:
|
||||
dir = "/media/frigate/clips/faces"
|
||||
for name in os.listdir(dir):
|
||||
if name == "train":
|
||||
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)
|
||||
face_folder = os.path.join(dir, name)
|
||||
|
||||
idx += 1
|
||||
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):
|
||||
if not self.landmark_detector:
|
||||
self.init_landmark_detector()
|
||||
return None
|
||||
|
||||
if self.model_builder_queue is not None:
|
||||
try:
|
||||
face_embeddings_map: dict[str, list[np.ndarray]] = (
|
||||
self.model_builder_queue.get(timeout=0.1)
|
||||
)
|
||||
self.model_builder_queue = None
|
||||
except queue.Empty:
|
||||
return
|
||||
else:
|
||||
self.run_build_task()
|
||||
return
|
||||
|
||||
if not face_embeddings_map:
|
||||
return
|
||||
|
||||
for name, embs in face_embeddings_map.items():
|
||||
self.mean_embs[name] = stats.trim_mean(embs, 0.15)
|
||||
if embs:
|
||||
self.mean_embs[name] = stats.trim_mean(embs, 0.15)
|
||||
|
||||
logger.debug("Finished building ArcFace model")
|
||||
|
||||
|
||||
@ -176,8 +176,8 @@ export default function ClassificationSettingsView({
|
||||
})
|
||||
.finally(() => {
|
||||
addMessage(
|
||||
"search_settings",
|
||||
`Restart Required (Classification settings changed)`,
|
||||
"search_settings_restart",
|
||||
`Restart required (Classification settings changed)`,
|
||||
undefined,
|
||||
"search_settings",
|
||||
);
|
||||
|
||||
@ -131,12 +131,6 @@ export default function FrigatePlusSettingsView({
|
||||
position: "top-center",
|
||||
});
|
||||
setChangedValue(false);
|
||||
addMessage(
|
||||
"plus_restart",
|
||||
"Restart required (Frigate+ model changed)",
|
||||
undefined,
|
||||
"plus_restart",
|
||||
);
|
||||
updateConfig();
|
||||
} else {
|
||||
toast.error(
|
||||
@ -160,6 +154,12 @@ export default function FrigatePlusSettingsView({
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
addMessage(
|
||||
"plus_restart",
|
||||
"Restart required (Frigate+ model changed)",
|
||||
undefined,
|
||||
"plus_restart",
|
||||
);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [updateConfig, addMessage, frigatePlusSettings, t]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user