Updates to object processing

Lock updates to tracked objects, current frame time, motion boxes, and
regions on `update()`.

Directly create Counters using counted values.

Don't convert removed_ids, new_ids, or updated_ids sets to lists.

Update defaultdict's to remove un-necessary lambdas when possible.

When possible, drop un-necessay list comprehensions, such as when
calling `any`.

Use set comprehension, rather than passing a list comprehension into
`set()`.

Do the slightly more pythonic `x not in y` rather than `not x in y` to
check list inclusion.
This commit is contained in:
Sean Vig 2021-05-23 10:29:39 -04:00 committed by Blake Blackshear
parent 9634ec8e31
commit abbc608ee4

View File

@ -91,9 +91,7 @@ class TrackedObject:
return False return False
threshold = self.camera_config.objects.filters[self.obj_data["label"]].threshold threshold = self.camera_config.objects.filters[self.obj_data["label"]].threshold
if self.computed_score < threshold: return self.computed_score < threshold
return True
return False
def compute_score(self): def compute_score(self):
scores = self.score_history[:] scores = self.score_history[:]
@ -156,31 +154,31 @@ class TrackedObject:
def to_dict(self, include_thumbnail: bool = False): def to_dict(self, include_thumbnail: bool = False):
event = { event = {
'id': self.obj_data['id'], "id": self.obj_data["id"],
'camera': self.camera, "camera": self.camera,
'frame_time': self.obj_data['frame_time'], "frame_time": self.obj_data["frame_time"],
'label': self.obj_data['label'], "label": self.obj_data["label"],
'top_score': self.top_score, "top_score": self.top_score,
'false_positive': self.false_positive, "false_positive": self.false_positive,
'start_time': self.obj_data['start_time'], "start_time": self.obj_data["start_time"],
'end_time': self.obj_data.get('end_time', None), "end_time": self.obj_data.get("end_time", None),
'score': self.obj_data['score'], "score": self.obj_data["score"],
'box': self.obj_data['box'], "box": self.obj_data["box"],
'area': self.obj_data['area'], "area": self.obj_data["area"],
'region': self.obj_data['region'], "region": self.obj_data["region"],
'current_zones': self.current_zones.copy(), "current_zones": self.current_zones.copy(),
'entered_zones': list(self.entered_zones).copy(), "entered_zones": list(self.entered_zones).copy(),
} }
if include_thumbnail: if include_thumbnail:
event['thumbnail'] = base64.b64encode(self.get_thumbnail()).decode('utf-8') event["thumbnail"] = base64.b64encode(self.get_thumbnail()).decode("utf-8")
return event return event
def get_thumbnail(self): def get_thumbnail(self):
if ( if (
self.thumbnail_data is None self.thumbnail_data is None
or not self.thumbnail_data["frame_time"] in self.frame_cache or self.thumbnail_data["frame_time"] not in self.frame_cache
): ):
ret, jpg = cv2.imencode(".jpg", np.zeros((175, 175, 3), np.uint8)) ret, jpg = cv2.imencode(".jpg", np.zeros((175, 175, 3), np.uint8))
@ -300,17 +298,17 @@ class CameraState:
self.camera_config = config.cameras[name] self.camera_config = config.cameras[name]
self.frame_manager = frame_manager self.frame_manager = frame_manager
self.best_objects: Dict[str, TrackedObject] = {} self.best_objects: Dict[str, TrackedObject] = {}
self.object_counts = defaultdict(lambda: 0) self.object_counts = defaultdict(int)
self.tracked_objects: Dict[str, TrackedObject] = {} self.tracked_objects: Dict[str, TrackedObject] = {}
self.frame_cache = {} self.frame_cache = {}
self.zone_objects = defaultdict(lambda: []) self.zone_objects = defaultdict(list)
self._current_frame = np.zeros(self.camera_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_lock = threading.Lock()
self.current_frame_time = 0.0 self.current_frame_time = 0.0
self.motion_boxes = [] self.motion_boxes = []
self.regions = [] self.regions = []
self.previous_frame_id = None self.previous_frame_id = None
self.callbacks = defaultdict(lambda: []) self.callbacks = defaultdict(list)
def get_current_frame(self, draw_options={}): def get_current_frame(self, draw_options={}):
with self.current_frame_lock: with self.current_frame_lock:
@ -325,10 +323,10 @@ class CameraState:
if draw_options.get("bounding_boxes"): if draw_options.get("bounding_boxes"):
# draw the bounding boxes on the frame # draw the bounding boxes on the frame
for obj in tracked_objects.values(): for obj in tracked_objects.values():
thickness = 2 if obj["frame_time"] == frame_time:
color = COLOR_MAP[obj["label"]] thickness = 2
color = COLOR_MAP[obj["label"]]
if obj["frame_time"] != frame_time: else:
thickness = 1 thickness = 1
color = (255, 0, 0) color = (255, 0, 0)
@ -341,7 +339,7 @@ class CameraState:
box[2], box[2],
box[3], box[3],
obj["label"], obj["label"],
f"{int(obj['score']*100)}% {int(obj['area'])}", f"{obj['score']:.0%} {int(obj['area'])}",
thickness=thickness, thickness=thickness,
color=color, color=color,
) )
@ -361,10 +359,7 @@ class CameraState:
thickness = ( thickness = (
8 8
if any( if any(
[ name in obj["current_zones"] for obj in tracked_objects.values()
name in obj["current_zones"]
for obj in tracked_objects.values()
]
) )
else 2 else 2
) )
@ -407,23 +402,21 @@ class CameraState:
self.callbacks[event_type].append(callback) self.callbacks[event_type].append(callback)
def update(self, frame_time, current_detections, motion_boxes, regions): def update(self, frame_time, current_detections, motion_boxes, regions):
self.current_frame_time = frame_time
self.motion_boxes = motion_boxes
self.regions = regions
# get the new frame # get the new frame
frame_id = f"{self.name}{frame_time}" frame_id = f"{self.name}{frame_time}"
current_frame = self.frame_manager.get( current_frame = self.frame_manager.get(
frame_id, self.camera_config.frame_shape_yuv frame_id, self.camera_config.frame_shape_yuv
) )
current_ids = current_detections.keys() tracked_objects = self.tracked_objects.copy()
previous_ids = self.tracked_objects.keys() current_ids = set(current_detections.keys())
removed_ids = list(set(previous_ids).difference(current_ids)) previous_ids = set(tracked_objects.keys())
new_ids = list(set(current_ids).difference(previous_ids)) removed_ids = previous_ids.difference(current_ids)
updated_ids = list(set(current_ids).intersection(previous_ids)) new_ids = current_ids.difference(previous_ids)
updated_ids = current_ids.intersection(previous_ids)
for id in new_ids: for id in new_ids:
new_obj = self.tracked_objects[id] = TrackedObject( new_obj = tracked_objects[id] = TrackedObject(
self.name, self.camera_config, self.frame_cache, current_detections[id] self.name, self.camera_config, self.frame_cache, current_detections[id]
) )
@ -432,7 +425,7 @@ class CameraState:
c(self.name, new_obj, frame_time) c(self.name, new_obj, frame_time)
for id in updated_ids: for id in updated_ids:
updated_obj = self.tracked_objects[id] updated_obj = tracked_objects[id]
significant_update = updated_obj.update(frame_time, current_detections[id]) significant_update = updated_obj.update(frame_time, current_detections[id])
if significant_update: if significant_update:
@ -458,7 +451,7 @@ class CameraState:
for id in removed_ids: for id in removed_ids:
# publish events to mqtt # publish events to mqtt
removed_obj = self.tracked_objects[id] removed_obj = tracked_objects[id]
if not "end_time" in removed_obj.obj_data: if not "end_time" in removed_obj.obj_data:
removed_obj.obj_data["end_time"] = frame_time removed_obj.obj_data["end_time"] = frame_time
for c in self.callbacks["end"]: for c in self.callbacks["end"]:
@ -466,13 +459,10 @@ class CameraState:
# TODO: can i switch to looking this up and only changing when an event ends? # TODO: can i switch to looking this up and only changing when an event ends?
# maintain best objects # maintain best objects
for obj in self.tracked_objects.values(): for obj in tracked_objects.values():
object_type = obj.obj_data["label"] object_type = obj.obj_data["label"]
# if the object's thumbnail is not from the current frame # if the object's thumbnail is not from the current frame
if ( if obj.false_positive or obj.thumbnail_data["frame_time"] != frame_time:
obj.false_positive
or obj.thumbnail_data["frame_time"] != self.current_frame_time
):
continue continue
if object_type in self.best_objects: if object_type in self.best_objects:
current_best = self.best_objects[object_type] current_best = self.best_objects[object_type]
@ -497,10 +487,11 @@ class CameraState:
c(self.name, self.best_objects[object_type], frame_time) c(self.name, self.best_objects[object_type], frame_time)
# update overall camera state for each object type # update overall camera state for each object type
obj_counter = Counter() obj_counter = Counter(
for obj in self.tracked_objects.values(): obj.obj_data["label"]
if not obj.false_positive: for obj in tracked_objects.values()
obj_counter[obj.obj_data["label"]] += 1 if not obj.false_positive
)
# report on detected objects # report on detected objects
for obj_name, count in obj_counter.items(): for obj_name, count in obj_counter.items():
@ -513,7 +504,7 @@ class CameraState:
expired_objects = [ expired_objects = [
obj_name obj_name
for obj_name, count in self.object_counts.items() for obj_name, count in self.object_counts.items()
if count > 0 and not obj_name in obj_counter if count > 0 and obj_name not in obj_counter
] ]
for obj_name in expired_objects: for obj_name in expired_objects:
self.object_counts[obj_name] = 0 self.object_counts[obj_name] = 0
@ -523,27 +514,29 @@ class CameraState:
c(self.name, self.best_objects[obj_name], frame_time) c(self.name, self.best_objects[obj_name], frame_time)
# cleanup thumbnail frame cache # cleanup thumbnail frame cache
current_thumb_frames = set( current_thumb_frames = {
[ obj.thumbnail_data["frame_time"]
obj.thumbnail_data["frame_time"] for obj in tracked_objects.values()
for obj in self.tracked_objects.values() if not obj.false_positive
if not obj.false_positive }
] current_best_frames = {
) obj.thumbnail_data["frame_time"] for obj in self.best_objects.values()
current_best_frames = set( }
[obj.thumbnail_data["frame_time"] for obj in self.best_objects.values()]
)
thumb_frames_to_delete = [ thumb_frames_to_delete = [
t t
for t in self.frame_cache.keys() for t in self.frame_cache.keys()
if not t in current_thumb_frames and not t in current_best_frames if t not in current_thumb_frames and t not in current_best_frames
] ]
for t in thumb_frames_to_delete: for t in thumb_frames_to_delete:
del self.frame_cache[t] del self.frame_cache[t]
with self.current_frame_lock: with self.current_frame_lock:
self.tracked_objects = tracked_objects
self.current_frame_time = frame_time
self.motion_boxes = motion_boxes
self.regions = regions
self._current_frame = current_frame self._current_frame = current_frame
if not self.previous_frame_id is None: if self.previous_frame_id is not None:
self.frame_manager.delete(self.previous_frame_id) self.frame_manager.delete(self.previous_frame_id)
self.previous_frame_id = frame_id self.previous_frame_id = frame_id
@ -665,7 +658,7 @@ class TrackedObjectProcessor(threading.Thread):
# } # }
# } # }
# } # }
self.zone_data = defaultdict(lambda: defaultdict(lambda: {})) self.zone_data = defaultdict(lambda: defaultdict(dict))
def should_save_snapshot(self, camera, obj: TrackedObject): def should_save_snapshot(self, camera, obj: TrackedObject):
# if there are required zones and there is no overlap # if there are required zones and there is no overlap
@ -728,15 +721,14 @@ class TrackedObjectProcessor(threading.Thread):
# for each zone in the current camera # for each zone in the current camera
for zone in self.config.cameras[camera].zones.keys(): for zone in self.config.cameras[camera].zones.keys():
# count labels for the camera in the zone # count labels for the camera in the zone
obj_counter = Counter() obj_counter = Counter(
for obj in camera_state.tracked_objects.values(): obj.obj_data["label"]
if zone in obj.current_zones and not obj.false_positive: for obj in camera_state.tracked_objects.values()
obj_counter[obj.obj_data["label"]] += 1 if zone in obj.current_zones and not obj.false_positive
)
# update counts and publish status # update counts and publish status
for label in set( for label in set(self.zone_data[zone].keys()) | set(obj_counter.keys()):
list(self.zone_data[zone].keys()) + list(obj_counter.keys())
):
# if we have previously published a count for this zone/label # if we have previously published a count for this zone/label
zone_label = self.zone_data[zone][label] zone_label = self.zone_data[zone][label]
if camera in zone_label: if camera in zone_label: