create tracked object class and save thumbnails

This commit is contained in:
Blake Blackshear 2020-11-08 16:05:15 -06:00
parent 24b703a875
commit 8874a55b0f
3 changed files with 237 additions and 226 deletions

View File

@ -88,7 +88,7 @@ class FrigateApp():
self.detectors[name] = EdgeTPUProcess(name, self.detection_queue, out_events=self.detection_out_events, tf_device=detector.device)
def start_detected_frames_processor(self):
self.detected_frames_processor = TrackedObjectProcessor(self.config.cameras, self.mqtt_client, self.config.mqtt.topic_prefix,
self.detected_frames_processor = TrackedObjectProcessor(self.config, self.mqtt_client, self.config.mqtt.topic_prefix,
self.detected_frames_queue, self.event_queue, self.stop_event)
self.detected_frames_processor.start()

View File

@ -91,16 +91,14 @@ OBJECTS_SCHEMA = vol.Schema(vol.All(filters_for_all_tracked_objects,
}
))
DEFAULT_CAMERA_MQTT = {
'crop_to_region': True
}
DEFAULT_CAMERA_SAVE_CLIPS = {
'enabled': False
}
DEFAULT_CAMERA_SNAPSHOTS = {
'show_timestamp': True,
'draw_zones': False,
'draw_bounding_boxes': True
'draw_bounding_boxes': True,
'crop_to_region': True
}
CAMERA_FFMPEG_SCHEMA = vol.Schema(
@ -122,10 +120,6 @@ CAMERAS_SCHEMA = vol.Schema(
'fps': int,
'mask': str,
vol.Optional('best_image_timeout', default=60): int,
vol.Optional('mqtt', default=DEFAULT_CAMERA_MQTT): {
vol.Optional('crop_to_region', default=True): bool,
'snapshot_height': int
},
vol.Optional('zones', default={}): {
str: {
vol.Required('coordinates'): vol.Any(str, [str]),
@ -140,7 +134,9 @@ CAMERAS_SCHEMA = vol.Schema(
vol.Optional('snapshots', default=DEFAULT_CAMERA_SNAPSHOTS): {
vol.Optional('show_timestamp', default=True): bool,
vol.Optional('draw_zones', default=False): bool,
vol.Optional('draw_bounding_boxes', default=True): bool
vol.Optional('draw_bounding_boxes', default=True): bool,
vol.Optional('crop_to_region', default=True): bool,
'height': int
},
'objects': OBJECTS_SCHEMA
}
@ -296,6 +292,8 @@ class CameraSnapshotsConfig():
self._show_timestamp = config['show_timestamp']
self._draw_zones = config['draw_zones']
self._draw_bounding_boxes = config['draw_bounding_boxes']
self._crop_to_region = config['crop_to_region']
self._height = config.get('height')
@property
def show_timestamp(self):
@ -309,6 +307,14 @@ class CameraSnapshotsConfig():
def draw_bounding_boxes(self):
return self._draw_bounding_boxes
@property
def crop_to_region(self):
return self._crop_to_region
@property
def height(self):
return self._height
class CameraSaveClipsConfig():
def __init__(self, config):
self._enabled = config['enabled']
@ -327,19 +333,6 @@ class CameraSaveClipsConfig():
def objects(self):
return self._objects
class CameraMqttConfig():
def __init__(self, config):
self._crop_to_region = config['crop_to_region']
self._snapshot_height = config.get('snapshot_height')
@property
def crop_to_region(self):
return self._crop_to_region
@property
def snapshot_height(self):
return self._snapshot_height
class ZoneConfig():
def __init__(self, name, config):
self._coordinates = config['coordinates']
@ -391,7 +384,6 @@ class CameraConfig():
self._fps = config.get('fps')
self._mask = self._create_mask(config.get('mask'))
self._best_image_timeout = config['best_image_timeout']
self._mqtt = CameraMqttConfig(config['mqtt'])
self._zones = { name: ZoneConfig(name, z) for name, z in config['zones'].items() }
self._save_clips = CameraSaveClipsConfig(config['save_clips'])
self._snapshots = CameraSnapshotsConfig(config['snapshots'])

View File

@ -4,6 +4,7 @@ import hashlib
import itertools
import json
import logging
import os
import queue
import threading
import time
@ -15,7 +16,7 @@ import cv2
import matplotlib.pyplot as plt
import numpy as np
from frigate.config import CameraConfig
from frigate.config import FrigateConfig, CameraConfig
from frigate.edgetpu import load_labels
from frigate.util import SharedMemoryFrameManager, draw_box_with_label
@ -30,28 +31,6 @@ COLOR_MAP = {}
for key, val in LABELS.items():
COLOR_MAP[val] = tuple(int(round(255 * c)) for c in cmap(key)[:3])
def zone_filtered(obj, object_config):
object_name = obj['label']
if object_name in object_config:
obj_settings = object_config[object_name]
# if the min area is larger than the
# detected object, don't add it to detected objects
if obj_settings.min_area > obj['area']:
return True
# if the detected object is larger than the
# max area, don't add it to detected objects
if obj_settings.max_area < obj['area']:
return True
# if the score is lower than the threshold, skip
if obj_settings.threshold > obj['computed_score']:
return True
return False
def on_edge(box, frame_shape):
if (
box[0] == 0 or
@ -80,19 +59,182 @@ def is_better_thumbnail(current_thumb, new_obj, frame_shape) -> bool:
return False
class TrackedObject():
def __init__(self, camera, camera_config: CameraConfig, thumbnail_frames, obj_data):
self.obj_data = obj_data
self.camera = camera
self.camera_config = camera_config
self.thumbnail_frames = thumbnail_frames
self.current_zones = []
self.entered_zones = set()
self._false_positive = True
self.top_score = self.computed_score = 0.0
self.thumbnail_data = {
'frame_time': obj_data['frame_time'],
'box': obj_data['box'],
'area': obj_data['area'],
'region': obj_data['region'],
'score': obj_data['score']
}
self.frame = None
self._snapshot_jpg_time = 0
self._snapshot_jpg = None
# start the score history
self.score_history = [self.obj_data['score']]
def false_positive(self):
# once a true positive, always a true positive
if not self._false_positive:
return False
threshold = self.camera_config.objects.filters[self.obj_data['label']].threshold
if self.computed_score < threshold:
return True
return False
def compute_score(self):
scores = self.score_history[:]
# pad with zeros if you dont have at least 3 scores
if len(scores) < 3:
scores += [0.0]*(3 - len(scores))
return median(scores)
def update(self, current_frame_time, obj_data):
self.obj_data.update(obj_data)
# if the object is not in the current frame, add a 0.0 to the score history
if self.obj_data['frame_time'] != current_frame_time:
self.score_history.append(0.0)
else:
self.score_history.append(self.obj_data['score'])
# only keep the last 10 scores
if len(self.score_history) > 10:
self.score_history = self.score_history[-10:]
# calculate if this is a false positive
self.computed_score = self.compute_score()
if self.computed_score > self.top_score:
self.top_score = self.computed_score
self._false_positive = self.false_positive()
# determine if this frame is a better thumbnail
if is_better_thumbnail(self.thumbnail_data, self.obj_data, self.camera_config.frame_shape):
self.thumbnail_data = {
'frame_time': self.obj_data['frame_time'],
'box': self.obj_data['box'],
'area': self.obj_data['area'],
'region': self.obj_data['region'],
'score': self.obj_data['score']
}
# check zones
current_zones = []
bottom_center = (self.obj_data['centroid'][0], self.obj_data['box'][3])
# check each zone
for name, zone in self.camera_config.zones.items():
contour = zone.contour
# check if the object is in the zone
if (cv2.pointPolygonTest(contour, bottom_center, False) >= 0):
# if the object passed the filters once, dont apply again
if name in self.current_zones or not zone_filtered(self, zone.filters):
current_zones.append(name)
self.entered_zones.add(name)
self.current_zones = current_zones
def to_dict(self):
return {
'id': self.obj_data['id'],
'camera': self.camera,
'frame_time': self.obj_data['frame_time'],
'label': self.obj_data['label'],
'top_score': self.top_score,
'false_positive': self._false_positive,
'start_time': self.obj_data['start_time'],
'end_time': self.obj_data.get('end_time', None),
'score': self.obj_data['score'],
'box': self.obj_data['box'],
'area': self.obj_data['area'],
'region': self.obj_data['region'],
'current_zones': self.current_zones.copy(),
'entered_zones': list(self.entered_zones).copy()
}
@property
def snapshot_jpg(self):
if self._snapshot_jpg_time == self.thumbnail_data['frame_time']:
return self._snapshot_jpg
# TODO: crop first to avoid converting the entire frame?
snapshot_config = self.camera_config.snapshots
best_frame = cv2.cvtColor(self.thumbnail_frames[self.thumbnail_data['frame_time']], cv2.COLOR_YUV2BGR_I420)
if snapshot_config.draw_bounding_boxes:
thickness = 2
color = COLOR_MAP[self.obj_data['label']]
box = self.thumbnail_data['box']
draw_box_with_label(best_frame, box[0], box[1], box[2], box[3], self.obj_data['label'],
f"{int(self.thumbnail_data['score']*100)}% {int(self.thumbnail_data['area'])}", thickness=thickness, color=color)
if snapshot_config.crop_to_region:
region = self.thumbnail_data['region']
best_frame = best_frame[region[1]:region[3], region[0]:region[2]]
if snapshot_config.height:
height = snapshot_config.height
width = int(height*best_frame.shape[1]/best_frame.shape[0])
best_frame = cv2.resize(best_frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
if snapshot_config.show_timestamp:
time_to_show = datetime.datetime.fromtimestamp(self.thumbnail_data['frame_time']).strftime("%m/%d/%Y %H:%M:%S")
size = cv2.getTextSize(time_to_show, cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, thickness=2)
text_width = size[0][0]
desired_size = max(200, 0.33*best_frame.shape[1])
font_scale = desired_size/text_width
cv2.putText(best_frame, time_to_show, (5, best_frame.shape[0]-7), cv2.FONT_HERSHEY_SIMPLEX,
fontScale=font_scale, color=(255, 255, 255), thickness=2)
ret, jpg = cv2.imencode('.jpg', best_frame)
if ret:
self._snapshot_jpg = jpg.tobytes()
return self._snapshot_jpg
def zone_filtered(obj: TrackedObject, object_config):
object_name = obj.obj_data['label']
if object_name in object_config:
obj_settings = object_config[object_name]
# if the min area is larger than the
# detected object, don't add it to detected objects
if obj_settings.min_area > obj.obj_data['area']:
return True
# if the detected object is larger than the
# max area, don't add it to detected objects
if obj_settings.max_area < obj.obj_data['area']:
return True
# if the score is lower than the threshold, skip
if obj_settings.threshold > obj.computed_score:
return True
return False
# Maintains the state of a camera
class CameraState():
def __init__(self, name, config, frame_manager):
self.name = name
self.config = config
self.camera_config = config.cameras[name]
self.frame_manager = frame_manager
self.best_objects = {}
self.object_status = defaultdict(lambda: 'OFF')
self.tracked_objects = {}
self.tracked_objects: Dict[str, TrackedObject] = {}
self.thumbnail_frames = {}
self.zone_objects = defaultdict(lambda: [])
self._current_frame = np.zeros(self.config.frame_shape_yuv, np.uint8)
self._current_frame = np.zeros(self.camera_config.frame_shape_yuv, np.uint8)
self.current_frame_lock = threading.Lock()
self.current_frame_time = 0.0
self.previous_frame_id = None
@ -102,7 +244,7 @@ class CameraState():
with self.current_frame_lock:
frame_copy = np.copy(self._current_frame)
frame_time = self.current_frame_time
tracked_objects = copy.deepcopy(self.tracked_objects)
tracked_objects = {k: v.to_dict() for k,v in self.tracked_objects.items()}
frame_copy = cv2.cvtColor(frame_copy, cv2.COLOR_YUV2BGR_I420)
# draw on the frame
@ -123,42 +265,25 @@ class CameraState():
region = obj['region']
cv2.rectangle(frame_copy, (region[0], region[1]), (region[2], region[3]), (0,255,0), 1)
if self.config.snapshots.show_timestamp:
if self.camera_config.snapshots.show_timestamp:
time_to_show = datetime.datetime.fromtimestamp(frame_time).strftime("%m/%d/%Y %H:%M:%S")
cv2.putText(frame_copy, time_to_show, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, fontScale=.8, color=(255, 255, 255), thickness=2)
if self.config.snapshots.draw_zones:
for name, zone in self.config.zones.items():
thickness = 8 if any([name in obj['zones'] for obj in tracked_objects.values()]) else 2
if self.camera_config.snapshots.draw_zones:
for name, zone in self.camera_config.zones.items():
thickness = 8 if any([name in obj['current_zones'] for obj in tracked_objects.values()]) else 2
cv2.drawContours(frame_copy, [zone.contour], -1, zone.color, thickness)
return frame_copy
def false_positive(self, obj):
# once a true positive, always a true positive
if not obj.get('false_positive', True):
return False
threshold = self.config.objects.filters[obj['label']].threshold
if obj['computed_score'] < threshold:
return True
return False
def compute_score(self, obj):
scores = obj['score_history'][:]
# pad with zeros if you dont have at least 3 scores
if len(scores) < 3:
scores += [0.0]*(3 - len(scores))
return median(scores)
def on(self, event_type: str, callback: Callable[[Dict], None]):
self.callbacks[event_type].append(callback)
def update(self, frame_time, tracked_objects):
self.current_frame_time = frame_time
# get the new frame and delete the old frame
# get the new frame
frame_id = f"{self.name}{frame_time}"
current_frame = self.frame_manager.get(frame_id, self.config.frame_shape_yuv)
current_frame = self.frame_manager.get(frame_id, self.camera_config.frame_shape_yuv)
current_ids = tracked_objects.keys()
previous_ids = self.tracked_objects.keys()
@ -167,59 +292,20 @@ class CameraState():
updated_ids = list(set(current_ids).intersection(previous_ids))
for id in new_ids:
new_obj = self.tracked_objects[id] = tracked_objects[id]
new_obj['zones'] = []
new_obj['entered_zones'] = set()
new_obj['thumbnail'] = {
'frame': new_obj['frame_time'],
'box': new_obj['box'],
'area': new_obj['area'],
'region': new_obj['region'],
'score': new_obj['score']
}
# start the score history
new_obj['score_history'] = [self.tracked_objects[id]['score']]
# calculate if this is a false positive
new_obj['computed_score'] = self.compute_score(self.tracked_objects[id])
new_obj['top_score'] = self.tracked_objects[id]['computed_score']
new_obj['false_positive'] = self.false_positive(self.tracked_objects[id])
new_obj = self.tracked_objects[id] = TrackedObject(self.name, self.camera_config, self.thumbnail_frames, tracked_objects[id])
# call event handlers
for c in self.callbacks['start']:
c(self.name, new_obj)
for id in updated_ids:
self.tracked_objects[id].update(tracked_objects[id])
updated_obj = self.tracked_objects[id]
updated_obj.update(frame_time, tracked_objects[id])
# if the object is not in the current frame, add a 0.0 to the score history
if updated_obj['frame_time'] != self.current_frame_time:
updated_obj['score_history'].append(0.0)
else:
updated_obj['score_history'].append(updated_obj['score'])
# only keep the last 10 scores
if len(updated_obj['score_history']) > 10:
updated_obj['score_history'] = updated_obj['score_history'][-10:]
# calculate if this is a false positive
computed_score = self.compute_score(updated_obj)
updated_obj['computed_score'] = computed_score
if computed_score > updated_obj['top_score']:
updated_obj['top_score'] = computed_score
updated_obj['false_positive'] = self.false_positive(updated_obj)
# determine if this frame is a better thumbnail
if is_better_thumbnail(updated_obj['thumbnail'], updated_obj, self.config.frame_shape):
updated_obj['thumbnail'] = {
'frame': updated_obj['frame_time'],
'box': updated_obj['box'],
'area': updated_obj['area'],
'region': updated_obj['region'],
'score': updated_obj['score']
}
if (not updated_obj._false_positive
and updated_obj.thumbnail_data['frame_time'] == frame_time
and frame_time not in self.thumbnail_frames):
self.thumbnail_frames[frame_time] = np.copy(current_frame)
# call event handlers
for c in self.callbacks['update']:
@ -227,53 +313,35 @@ class CameraState():
for id in removed_ids:
# publish events to mqtt
self.tracked_objects[id]['end_time'] = frame_time
removed_obj = self.tracked_objects[id]
removed_obj.obj_data['end_time'] = frame_time
for c in self.callbacks['end']:
c(self.name, self.tracked_objects[id])
c(self.name, removed_obj)
del self.tracked_objects[id]
# check to see if the objects are in any zones
for obj in self.tracked_objects.values():
current_zones = []
bottom_center = (obj['centroid'][0], obj['box'][3])
# check each zone
for name, zone in self.config.zones.items():
contour = zone.contour
# check if the object is in the zone
if (cv2.pointPolygonTest(contour, bottom_center, False) >= 0):
# if the object passed the filters once, dont apply again
if name in obj.get('zones', []) or not zone_filtered(obj, zone.filters):
current_zones.append(name)
obj['entered_zones'].add(name)
obj['zones'] = current_zones
# update frame storage for thumbnails based on thumbnails for all tracked objects
current_thumb_frames = set([obj['thumbnail']['frame'] for obj in self.tracked_objects.values()])
if self.current_frame_time in current_thumb_frames:
self.thumbnail_frames[self.current_frame_time] = np.copy(current_frame)
thumb_frames_to_delete = [t for t in self.thumbnail_frames.keys() if not t in current_thumb_frames]
for t in thumb_frames_to_delete: del self.thumbnail_frames[t]
# TODO: can i switch to looking this up and only changing when an event ends?
# maybe make an api endpoint that pulls the thumbnail from the file system?
# maintain best objects
for obj in self.tracked_objects.values():
object_type = obj['label']
# if the object wasn't seen on the current frame, skip it
if obj['frame_time'] != self.current_frame_time or obj['false_positive']:
object_type = obj.obj_data['label']
# if the object's thumbnail is not from the current frame
if obj.thumbnail_data['frame_time'] != self.current_frame_time or obj.false_positive:
continue
obj_copy = copy.deepcopy(obj)
if object_type in self.best_objects:
current_best = self.best_objects[object_type]
now = datetime.datetime.now().timestamp()
# if the object is a higher score than the current best score
# or the current object is older than desired, use the new object
if obj_copy['score'] > current_best['score'] or (now - current_best['frame_time']) > self.config.best_image_timeout:
if is_better_thumbnail(current_best['thumbnail'], obj.thumbnail, self.camera_config.frame_shape) or (now - current_best['frame_time']) > self.config.best_image_timeout:
obj_copy = copy.deepcopy(obj.obj_data)
obj_copy['thumbnail'] = copy.deepcopy(obj.thumbnail_data)
obj_copy['frame'] = np.copy(current_frame)
self.best_objects[object_type] = obj_copy
for c in self.callbacks['snapshot']:
c(self.name, self.best_objects[object_type])
else:
obj_copy = copy.deepcopy(obj)
obj_copy['thumbnail'] = copy.deepcopy(obj.thumbnail_data)
obj_copy['frame'] = np.copy(current_frame)
self.best_objects[object_type] = obj_copy
for c in self.callbacks['snapshot']:
@ -282,8 +350,8 @@ class CameraState():
# update overall camera state for each object type
obj_counter = Counter()
for obj in self.tracked_objects.values():
if not obj['false_positive']:
obj_counter[obj['label']] += 1
if not obj.false_positive:
obj_counter[obj.obj_data['label']] += 1
# report on detected objects
for obj_name, count in obj_counter.items():
@ -302,6 +370,12 @@ class CameraState():
for c in self.callbacks['snapshot']:
c(self.name, self.best_objects[obj_name])
# cleanup thumbnail frame cache
current_thumb_frames = set([obj.thumbnail_data['frame_time'] for obj in self.tracked_objects.values() if not obj._false_positive])
thumb_frames_to_delete = [t for t in self.thumbnail_frames.keys() if not t in current_thumb_frames]
for t in thumb_frames_to_delete:
del self.thumbnail_frames[t]
with self.current_frame_lock:
self._current_frame = current_frame
if not self.previous_frame_id is None:
@ -309,10 +383,10 @@ class CameraState():
self.previous_frame_id = frame_id
class TrackedObjectProcessor(threading.Thread):
def __init__(self, camera_config: Dict[str, CameraConfig], client, topic_prefix, tracked_objects_queue, event_queue, stop_event):
def __init__(self, config: FrigateConfig, client, topic_prefix, tracked_objects_queue, event_queue, stop_event):
threading.Thread.__init__(self)
self.name = "detected_frames_processor"
self.camera_config = camera_config
self.config = config
self.client = client
self.topic_prefix = topic_prefix
self.tracked_objects_queue = tracked_objects_queue
@ -321,76 +395,29 @@ class TrackedObjectProcessor(threading.Thread):
self.camera_states: Dict[str, CameraState] = {}
self.frame_manager = SharedMemoryFrameManager()
def start(camera, obj):
# publish events to mqtt
event_data = {
'id': obj['id'],
'label': obj['label'],
'camera': camera,
'start_time': obj['start_time'],
'top_score': obj['top_score'],
'false_positive': obj['false_positive'],
'zones': list(obj['entered_zones'])
}
self.client.publish(f"{self.topic_prefix}/{camera}/events/start", json.dumps(event_data), retain=False)
self.event_queue.put(('start', camera, obj))
def start(camera, obj: TrackedObject):
self.client.publish(f"{self.topic_prefix}/{camera}/events/start", json.dumps(obj.to_dict()), retain=False)
self.event_queue.put(('start', camera, obj.to_dict()))
def update(camera, obj):
def update(camera, obj: TrackedObject):
pass
def end(camera, obj):
event_data = {
'id': obj['id'],
'label': obj['label'],
'camera': camera,
'start_time': obj['start_time'],
'end_time': obj['end_time'],
'top_score': obj['top_score'],
'false_positive': obj['false_positive'],
'zones': list(obj['entered_zones'])
}
self.client.publish(f"{self.topic_prefix}/{camera}/events/end", json.dumps(event_data), retain=False)
self.event_queue.put(('end', camera, obj))
def end(camera, obj: TrackedObject):
self.client.publish(f"{self.topic_prefix}/{camera}/events/end", json.dumps(obj.to_dict()), retain=False)
if self.config.cameras[camera].save_clips.enabled:
thumbnail_file_name = f"{camera}-{obj.obj_data['id']}.jpg"
with open(os.path.join(self.config.save_clips.clips_dir, thumbnail_file_name), 'wb') as f:
f.write(obj.snapshot_jpg)
self.event_queue.put(('end', camera, obj.to_dict()))
def snapshot(camera, obj):
if not 'frame' in obj:
return
best_frame = cv2.cvtColor(obj['frame'], cv2.COLOR_YUV2BGR_I420)
if self.camera_config[camera].snapshots.draw_bounding_boxes:
thickness = 2
color = COLOR_MAP[obj['label']]
box = obj['box']
draw_box_with_label(best_frame, box[0], box[1], box[2], box[3], obj['label'], f"{int(obj['score']*100)}% {int(obj['area'])}", thickness=thickness, color=color)
mqtt_config = self.camera_config[camera].mqtt
if mqtt_config.crop_to_region:
region = obj['region']
best_frame = best_frame[region[1]:region[3], region[0]:region[2]]
if mqtt_config.snapshot_height:
height = mqtt_config.snapshot_height
width = int(height*best_frame.shape[1]/best_frame.shape[0])
best_frame = cv2.resize(best_frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
if self.camera_config[camera].snapshots.show_timestamp:
time_to_show = datetime.datetime.fromtimestamp(obj['frame_time']).strftime("%m/%d/%Y %H:%M:%S")
size = cv2.getTextSize(time_to_show, cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, thickness=2)
text_width = size[0][0]
text_height = size[0][1]
desired_size = max(200, 0.33*best_frame.shape[1])
font_scale = desired_size/text_width
cv2.putText(best_frame, time_to_show, (5, best_frame.shape[0]-7), cv2.FONT_HERSHEY_SIMPLEX, fontScale=font_scale, color=(255, 255, 255), thickness=2)
ret, jpg = cv2.imencode('.jpg', best_frame)
if ret:
jpg_bytes = jpg.tobytes()
self.client.publish(f"{self.topic_prefix}/{camera}/{obj['label']}/snapshot", jpg_bytes, retain=True)
def snapshot(camera, obj: TrackedObject):
self.client.publish(f"{self.topic_prefix}/{camera}/{obj['label']}/snapshot", obj.snapshot_jpg, retain=True)
def object_status(camera, object_name, status):
self.client.publish(f"{self.topic_prefix}/{camera}/{object_name}", status, retain=False)
for camera in self.camera_config.keys():
camera_state = CameraState(camera, self.camera_config[camera], self.frame_manager)
for camera in self.config.cameras.keys():
camera_state = CameraState(camera, self.config, self.frame_manager)
camera_state.on('start', start)
camera_state.on('update', update)
camera_state.on('end', end)
@ -398,14 +425,6 @@ class TrackedObjectProcessor(threading.Thread):
camera_state.on('object_status', object_status)
self.camera_states[camera] = camera_state
self.camera_data = defaultdict(lambda: {
'best_objects': {},
'object_status': defaultdict(lambda: defaultdict(lambda: 'OFF')),
'tracked_objects': {},
'current_frame': np.zeros((720,1280,3), np.uint8),
'current_frame_time': 0.0,
'object_id': None
})
# {
# 'zone_name': {
# 'person': ['camera_1', 'camera_2']
@ -439,9 +458,9 @@ class TrackedObjectProcessor(threading.Thread):
camera_state.update(frame_time, current_tracked_objects)
# update zone status for each label
for zone in camera_state.config.zones.keys():
for zone in self.config.cameras[camera].zones.keys():
# get labels for current camera and all labels in current zone
labels_for_camera = set([obj['label'] for obj in camera_state.tracked_objects.values() if zone in obj['zones'] and not obj['false_positive']])
labels_for_camera = set([obj.obj_data['label'] for obj in camera_state.tracked_objects.values() if zone in obj.current_zones and not obj._false_positive])
labels_to_check = labels_for_camera | set(self.zone_data[zone].keys())
# for each label in zone
for label in labels_to_check: