Files
frigate/frigate/data_processing/real_time/bird.py
T

168 lines
5.3 KiB
Python
Raw Normal View History

2025-01-13 08:09:04 -07:00
"""Handle processing images to classify birds."""
import logging
import os
2025-05-13 16:27:20 +02:00
from typing import Any
2025-01-13 08:09:04 -07:00
import cv2
import numpy as np
2025-03-10 16:29:29 -06:00
from frigate.comms.event_metadata_updater import (
EventMetadataPublisher,
EventMetadataTypeEnum,
)
2025-01-13 08:09:04 -07:00
from frigate.config import FrigateConfig
2025-03-10 16:29:29 -06:00
from frigate.const import MODEL_CACHE_DIR
2025-01-13 08:09:04 -07:00
from frigate.util.object import calculate_region
from ..types import DataProcessorMetrics
from .api import RealTimeProcessorApi
try:
from tflite_runtime.interpreter import Interpreter
except ModuleNotFoundError:
from tensorflow.lite.python.interpreter import Interpreter
logger = logging.getLogger(__name__)
class BirdRealTimeProcessor(RealTimeProcessorApi):
2025-03-10 16:29:29 -06:00
def __init__(
self,
config: FrigateConfig,
sub_label_publisher: EventMetadataPublisher,
metrics: DataProcessorMetrics,
):
2025-01-13 08:09:04 -07:00
super().__init__(config, metrics)
self.interpreter: Interpreter = None
2025-03-10 16:29:29 -06:00
self.sub_label_publisher = sub_label_publisher
2025-05-13 16:27:20 +02:00
self.tensor_input_details: dict[str, Any] = None
self.tensor_output_details: dict[str, Any] = None
2025-01-13 08:09:04 -07:00
self.detected_birds: dict[str, float] = {}
self.labelmap: dict[int, str] = {}
download_path = os.path.join(MODEL_CACHE_DIR, "bird")
self.model_files = {
"bird.tflite": "https://raw.githubusercontent.com/google-coral/test_data/master/mobilenet_v2_1.0_224_inat_bird_quant.tflite",
"birdmap.txt": "https://raw.githubusercontent.com/google-coral/test_data/master/inat_bird_labels.txt",
}
if not all(
os.path.exists(os.path.join(download_path, n))
for n in self.model_files.keys()
):
# conditionally import ModelDownloader
from frigate.util.downloader import ModelDownloader
self.downloader = ModelDownloader(
model_name="bird",
download_path=download_path,
file_names=self.model_files.keys(),
download_func=self.__download_models,
complete_func=self.__build_detector,
)
self.downloader.ensure_model_files()
else:
self.__build_detector()
def __download_models(self, path: str) -> None:
try:
file_name = os.path.basename(path)
# conditionally import ModelDownloader
from frigate.util.downloader import ModelDownloader
ModelDownloader.download_from_url(self.model_files[file_name], path)
except Exception as e:
logger.error(f"Failed to download {path}: {e}")
def __build_detector(self) -> None:
self.interpreter = Interpreter(
model_path=os.path.join(MODEL_CACHE_DIR, "bird/bird.tflite"),
num_threads=2,
)
self.interpreter.allocate_tensors()
self.tensor_input_details = self.interpreter.get_input_details()
self.tensor_output_details = self.interpreter.get_output_details()
i = 0
with open(os.path.join(MODEL_CACHE_DIR, "bird/birdmap.txt")) as f:
line = f.readline()
while line:
start = line.find("(")
end = line.find(")")
self.labelmap[i] = line[start + 1 : end]
i += 1
line = f.readline()
def process_frame(self, obj_data, frame):
if not self.interpreter:
return
2025-01-13 08:09:04 -07:00
if obj_data["label"] != "bird":
return
x, y, x2, y2 = calculate_region(
frame.shape,
obj_data["box"][0],
obj_data["box"][1],
obj_data["box"][2],
obj_data["box"][3],
2025-07-06 18:08:47 -06:00
int(
max(
obj_data["box"][1] - obj_data["box"][0],
obj_data["box"][3] - obj_data["box"][2],
)
* 1.1
),
2025-01-13 08:09:04 -07:00
1.0,
)
rgb = cv2.cvtColor(frame, cv2.COLOR_YUV2RGB_I420)
input = rgb[
y:y2,
x:x2,
]
2025-03-14 07:21:50 -06:00
if input.shape != (224, 224):
input = cv2.resize(input, (224, 224))
2025-01-13 08:09:04 -07:00
input = np.expand_dims(input, axis=0)
self.interpreter.set_tensor(self.tensor_input_details[0]["index"], input)
self.interpreter.invoke()
res: np.ndarray = self.interpreter.get_tensor(
self.tensor_output_details[0]["index"]
)[0]
probs = res / res.sum(axis=0)
best_id = np.argmax(probs)
if best_id == 964:
logger.debug("No bird classification was detected.")
return
score = round(probs[best_id], 2)
if score < self.config.classification.bird.threshold:
logger.debug(f"Score {score} is not above required threshold")
return
previous_score = self.detected_birds.get(obj_data["id"], 0.0)
if score <= previous_score:
logger.debug(f"Score {score} is worse than previous score {previous_score}")
return
2025-03-10 16:29:29 -06:00
self.sub_label_publisher.publish(
2025-03-16 05:01:15 -06:00
EventMetadataTypeEnum.sub_label,
(obj_data["id"], self.labelmap[best_id], score),
2025-01-13 08:09:04 -07:00
)
2025-03-10 16:29:29 -06:00
self.detected_birds[obj_data["id"]] = score
2025-01-13 08:09:04 -07:00
2025-01-18 10:52:01 -07:00
def handle_request(self, topic, request_data):
2025-01-13 08:09:04 -07:00
return None
2025-05-11 13:03:53 -05:00
def expire_object(self, object_id, camera):
2025-01-13 08:09:04 -07:00
if object_id in self.detected_birds:
self.detected_birds.pop(object_id)