mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-03 01:22:17 +03:00
Estimated object speed for zones (#16452)
* utility functions * backend config * backend object speed tracking * draw speed on debug view * basic frontend zone editor * remove line sorting * fix types * highlight line on canvas when entering value in zone edit pane * rename vars and add validation * ensure speed estimation is disabled when user adds more than 4 points * pixel velocity in debug * unit_system in config * ability to define unit system in config * save max speed to db * frontend * docs * clarify docs * utility functions * backend config * backend object speed tracking * draw speed on debug view * basic frontend zone editor * remove line sorting * fix types * highlight line on canvas when entering value in zone edit pane * rename vars and add validation * ensure speed estimation is disabled when user adds more than 4 points * pixel velocity in debug * unit_system in config * ability to define unit system in config * save max speed to db * frontend * docs * clarify docs * fix duplicates from merge * include max_estimated_speed in api responses * add units to zone edit pane * catch undefined * add average speed * clarify docs * only track average speed when object is active * rename vars * ensure points and distances are ordered clockwise * only store the last 10 speeds like score history * remove max estimated speed * update docs * update docs * fix point ordering * improve readability * docs inertia recommendation * fix point ordering * check object frame time * add velocity angle to frontend * docs clarity * add frontend speed filter * fix mqtt docs * fix mqtt docs * don't try to remove distances if they weren't already defined * don't display estimates on debug view/snapshots if object is not in a speed tracking zone * docs * implement speed_threshold for zone presence * docs for threshold * better ground plane image * improve image zone size * add inertia to speed threshold example
This commit is contained in:
@@ -12,6 +12,7 @@ import numpy as np
|
||||
from frigate.config import (
|
||||
CameraConfig,
|
||||
ModelConfig,
|
||||
UIConfig,
|
||||
)
|
||||
from frigate.review.types import SeverityEnum
|
||||
from frigate.util.image import (
|
||||
@@ -22,6 +23,7 @@ from frigate.util.image import (
|
||||
is_better_thumbnail,
|
||||
)
|
||||
from frigate.util.object import box_inside
|
||||
from frigate.util.velocity import calculate_real_world_speed
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,6 +33,7 @@ class TrackedObject:
|
||||
self,
|
||||
model_config: ModelConfig,
|
||||
camera_config: CameraConfig,
|
||||
ui_config: UIConfig,
|
||||
frame_cache,
|
||||
obj_data: dict[str, any],
|
||||
):
|
||||
@@ -42,6 +45,7 @@ class TrackedObject:
|
||||
self.colormap = model_config.colormap
|
||||
self.logos = model_config.all_attribute_logos
|
||||
self.camera_config = camera_config
|
||||
self.ui_config = ui_config
|
||||
self.frame_cache = frame_cache
|
||||
self.zone_presence: dict[str, int] = {}
|
||||
self.zone_loitering: dict[str, int] = {}
|
||||
@@ -58,6 +62,10 @@ class TrackedObject:
|
||||
self.frame = None
|
||||
self.active = True
|
||||
self.pending_loitering = False
|
||||
self.speed_history = []
|
||||
self.current_estimated_speed = 0
|
||||
self.average_estimated_speed = 0
|
||||
self.velocity_angle = 0
|
||||
self.previous = self.to_dict()
|
||||
|
||||
@property
|
||||
@@ -129,6 +137,8 @@ class TrackedObject:
|
||||
"region": obj_data["region"],
|
||||
"score": obj_data["score"],
|
||||
"attributes": obj_data["attributes"],
|
||||
"current_estimated_speed": self.current_estimated_speed,
|
||||
"velocity_angle": self.velocity_angle,
|
||||
}
|
||||
thumb_update = True
|
||||
|
||||
@@ -136,6 +146,7 @@ class TrackedObject:
|
||||
current_zones = []
|
||||
bottom_center = (obj_data["centroid"][0], obj_data["box"][3])
|
||||
in_loitering_zone = False
|
||||
in_speed_zone = False
|
||||
|
||||
# check each zone
|
||||
for name, zone in self.camera_config.zones.items():
|
||||
@@ -144,12 +155,66 @@ class TrackedObject:
|
||||
continue
|
||||
contour = zone.contour
|
||||
zone_score = self.zone_presence.get(name, 0) + 1
|
||||
|
||||
# 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):
|
||||
# an object is only considered present in a zone if it has a zone inertia of 3+
|
||||
# Calculate speed first if this is a speed zone
|
||||
if (
|
||||
zone.distances
|
||||
and obj_data["frame_time"] == current_frame_time
|
||||
and self.active
|
||||
):
|
||||
speed_magnitude, self.velocity_angle = (
|
||||
calculate_real_world_speed(
|
||||
zone.contour,
|
||||
zone.distances,
|
||||
self.obj_data["estimate_velocity"],
|
||||
bottom_center,
|
||||
self.camera_config.detect.fps,
|
||||
)
|
||||
)
|
||||
|
||||
if self.ui_config.unit_system == "metric":
|
||||
self.current_estimated_speed = (
|
||||
speed_magnitude * 3.6
|
||||
) # m/s to km/h
|
||||
else:
|
||||
self.current_estimated_speed = (
|
||||
speed_magnitude * 0.681818
|
||||
) # ft/s to mph
|
||||
|
||||
self.speed_history.append(self.current_estimated_speed)
|
||||
if len(self.speed_history) > 10:
|
||||
self.speed_history = self.speed_history[-10:]
|
||||
|
||||
self.average_estimated_speed = sum(self.speed_history) / len(
|
||||
self.speed_history
|
||||
)
|
||||
|
||||
# we've exceeded the speed threshold on the zone
|
||||
# or we don't have a speed threshold set
|
||||
if (
|
||||
zone.speed_threshold is None
|
||||
or self.average_estimated_speed > zone.speed_threshold
|
||||
):
|
||||
in_speed_zone = True
|
||||
|
||||
logger.debug(
|
||||
f"Camera: {self.camera_config.name}, tracked object ID: {self.obj_data['id']}, "
|
||||
f"zone: {name}, pixel velocity: {str(tuple(np.round(self.obj_data['estimate_velocity']).flatten().astype(int)))}, "
|
||||
f"speed magnitude: {speed_magnitude}, velocity angle: {self.velocity_angle}, "
|
||||
f"estimated speed: {self.current_estimated_speed:.1f}, "
|
||||
f"average speed: {self.average_estimated_speed:.1f}, "
|
||||
f"length: {len(self.speed_history)}"
|
||||
)
|
||||
|
||||
# Check zone entry conditions - for speed zones, require both inertia and speed
|
||||
if zone_score >= zone.inertia:
|
||||
if zone.distances and not in_speed_zone:
|
||||
continue # Skip zone entry for speed zones until speed threshold met
|
||||
|
||||
# if the zone has loitering time, update loitering status
|
||||
if zone.loitering_time > 0:
|
||||
in_loitering_zone = True
|
||||
@@ -174,6 +239,10 @@ class TrackedObject:
|
||||
if 0 < zone_score < zone.inertia:
|
||||
self.zone_presence[name] = zone_score - 1
|
||||
|
||||
# Reset speed if not in speed zone
|
||||
if zone.distances and name not in current_zones:
|
||||
self.current_estimated_speed = 0
|
||||
|
||||
# update loitering status
|
||||
self.pending_loitering = in_loitering_zone
|
||||
|
||||
@@ -255,6 +324,9 @@ class TrackedObject:
|
||||
"current_attributes": self.obj_data["attributes"],
|
||||
"pending_loitering": self.pending_loitering,
|
||||
"max_severity": self.max_severity,
|
||||
"current_estimated_speed": self.current_estimated_speed,
|
||||
"average_estimated_speed": self.average_estimated_speed,
|
||||
"velocity_angle": self.velocity_angle,
|
||||
}
|
||||
|
||||
if include_thumbnail:
|
||||
@@ -339,7 +411,12 @@ class TrackedObject:
|
||||
box[2],
|
||||
box[3],
|
||||
self.obj_data["label"],
|
||||
f"{int(self.thumbnail_data['score'] * 100)}% {int(self.thumbnail_data['area'])}",
|
||||
f"{int(self.thumbnail_data['score'] * 100)}% {int(self.thumbnail_data['area'])}"
|
||||
+ (
|
||||
f" {self.thumbnail_data['current_estimated_speed']:.1f}"
|
||||
if self.thumbnail_data["current_estimated_speed"] != 0
|
||||
else ""
|
||||
),
|
||||
thickness=thickness,
|
||||
color=color,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user