diff --git a/docs/docs/configuration/zones.md b/docs/docs/configuration/zones.md index 0edfea299a..804c323adb 100644 --- a/docs/docs/configuration/zones.md +++ b/docs/docs/configuration/zones.md @@ -153,7 +153,7 @@ ui: unit_system: metric ``` -The maximum speed during the object's lifetime is saved in Frigate's database and can be seen in the UI in the Tracked Object Details pane in Explore. Current estimated speed can also be seen on the debug view as the third value in the object label. Current estimated speed, max estimated speed, and velocity angle (the angle of the direction the object is moving relative to the frame) of tracked objects is also sent through the `events` MQTT topic. See the [MQTT docs](../integrations/mqtt.md#frigateevents). +The average and maximum speed during the object's lifetime is saved in Frigate's database and can be seen in the UI in the Tracked Object Details pane in Explore. Current estimated speed can also be seen on the debug view as the third value in the object label. Current estimated speed, average estimated speed, max estimated speed, and velocity angle (the angle of the direction the object is moving relative to the frame) of tracked objects is also sent through the `events` MQTT topic. See the [MQTT docs](../integrations/mqtt.md#frigateevents). These speed values are output as a number in miles per hour (mph) or kilometers per hour (kph), depending on how `unit_system` is configured in your `ui` config. #### Best practices and caveats diff --git a/frigate/api/event.py b/frigate/api/event.py index 3ba4ae4260..6c77d20fd8 100644 --- a/frigate/api/event.py +++ b/frigate/api/event.py @@ -316,7 +316,15 @@ def process_events(): k: v for k, v in event.data.items() if k - in ["type", "score", "top_score", "description", "sub_label_score"] + in [ + "type", + "score", + "top_score", + "description", + "sub_label_score", + "average_estimated_speed", + "max_estimated_speed", + ] }, "event_count": label_counts[event.label], } @@ -581,7 +589,16 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends()) processed_event["data"] = { k: v for k, v in event["data"].items() - if k in ["type", "score", "top_score", "description"] + if k + in [ + "type", + "score", + "top_score", + "description", + "sub_label_score", + "average_estimated_speed", + "max_estimated_speed", + ] } if event["id"] in search_results: diff --git a/frigate/events/maintainer.py b/frigate/events/maintainer.py index 5f0716a6ee..2a401cfbf4 100644 --- a/frigate/events/maintainer.py +++ b/frigate/events/maintainer.py @@ -25,6 +25,8 @@ def should_update_db(prev_event: Event, current_event: Event) -> bool: or prev_event["entered_zones"] != current_event["entered_zones"] or prev_event["thumbnail"] != current_event["thumbnail"] or prev_event["end_time"] != current_event["end_time"] + or prev_event["average_estimated_speed"] + != current_event["average_estimated_speed"] or prev_event["max_estimated_speed"] != current_event["max_estimated_speed"] ): return True @@ -211,6 +213,7 @@ def handle_object_detection( "score": score, "top_score": event_data["top_score"], "attributes": attributes, + "average_estimated_speed": event_data["average_estimated_speed"], "max_estimated_speed": event_data["max_estimated_speed"], "type": "object", "max_severity": event_data.get("max_severity"), diff --git a/frigate/track/tracked_object.py b/frigate/track/tracked_object.py index e3bc62c5be..62f1873806 100644 --- a/frigate/track/tracked_object.py +++ b/frigate/track/tracked_object.py @@ -62,7 +62,9 @@ def __init__( self.frame = None self.active = True self.pending_loitering = False - self.estimated_speed = 0 + self.speed_history = [] + self.current_estimated_speed = 0 + self.average_estimated_speed = 0 self.max_estimated_speed = 0 self.velocity_angle = 0 self.previous = self.to_dict() @@ -193,20 +195,27 @@ def update(self, current_frame_time: float, obj_data, has_valid_frame: bool): self.camera_config.detect.fps, ) if self.active - else 0 + else (0, 0) ) if self.ui_config.unit_system == "metric": # Convert m/s to km/h - self.estimated_speed = speed_magnitude * 3.6 + self.current_estimated_speed = speed_magnitude * 3.6 elif self.ui_config.unit_system == "imperial": # Convert ft/s to mph - self.estimated_speed = speed_magnitude * 0.681818 + self.current_estimated_speed = speed_magnitude * 0.681818 + logger.debug( - f"Camera: {self.camera_config.name}, zone: {name}, tracked object ID: {self.obj_data['id']}, pixel velocity: {str(tuple(np.round(self.obj_data['estimate_velocity']).flatten().astype(int)))} estimated speed: {self.estimated_speed:.1f}" + f"Camera: {self.camera_config.name}, zone: {name}, tracked object ID: {self.obj_data['id']}, pixel velocity: {str(tuple(np.round(self.obj_data['estimate_velocity']).flatten().astype(int)))} estimated speed: {self.current_estimated_speed:.1f}" ) - if self.estimated_speed > self.max_estimated_speed: - self.max_estimated_speed = self.estimated_speed + if self.active: + self.speed_history.append(self.current_estimated_speed) + self.average_estimated_speed = sum(self.speed_history) / len( + self.speed_history + ) + + if self.current_estimated_speed > self.max_estimated_speed: + self.max_estimated_speed = self.current_estimated_speed # update loitering status self.pending_loitering = in_loitering_zone @@ -289,7 +298,8 @@ def to_dict(self, include_thumbnail: bool = False): "current_attributes": self.obj_data["attributes"], "pending_loitering": self.pending_loitering, "max_severity": self.max_severity, - "estimated_speed": self.estimated_speed, + "current_estimated_speed": self.current_estimated_speed, + "average_estimated_speed": self.average_estimated_speed, "max_estimated_speed": self.max_estimated_speed, "velocity_angle": self.velocity_angle, } diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index e8b4fec384..450274b140 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -316,6 +316,18 @@ function ObjectDetailsTab({ } }, [search]); + const averageEstimatedSpeed = useMemo(() => { + if (!search || !search.data?.average_estimated_speed) { + return undefined; + } + + if (search.data?.average_estimated_speed != 0) { + return search.data?.average_estimated_speed.toFixed(1); + } else { + return undefined; + } + }, [search]); + const maxEstimatedSpeed = useMemo(() => { if (!search || !search.data?.max_estimated_speed) { return undefined; @@ -439,12 +451,24 @@ function ObjectDetailsTab({ {score}%{subLabelScore && ` (${subLabelScore}%)`} - {maxEstimatedSpeed && ( + {(averageEstimatedSpeed || maxEstimatedSpeed) && (
-
Max Estimated Speed
-
- {maxEstimatedSpeed}{" "} - {config?.ui.unit_system == "imperial" ? "mph" : "kph"} +
Estimated Speeds
+
+ {averageEstimatedSpeed && ( +
+ {averageEstimatedSpeed}{" "} + {config?.ui.unit_system == "imperial" ? "mph" : "kph"}{" "} + (average) +
+ )} + {maxEstimatedSpeed && ( +
+ {maxEstimatedSpeed}{" "} + {config?.ui.unit_system == "imperial" ? "mph" : "kph"}{" "} + (maximum) +
+ )}
)} diff --git a/web/src/components/settings/ZoneEditPane.tsx b/web/src/components/settings/ZoneEditPane.tsx index ec60086c58..59a5462907 100644 --- a/web/src/components/settings/ZoneEditPane.tsx +++ b/web/src/components/settings/ZoneEditPane.tsx @@ -596,7 +596,13 @@ export default function ZoneEditPane({ name="lineA" render={({ field }) => ( - Line A distance + + Line A distance ( + {config?.ui.unit_system == "imperial" + ? "feet" + : "meters"} + ) + ( - Line B distance + + Line B distance ( + {config?.ui.unit_system == "imperial" + ? "feet" + : "meters"} + ) + ( - Line C distance + + Line C distance ( + {config?.ui.unit_system == "imperial" + ? "feet" + : "meters"} + ) + ( - Line D distance + + Line D distance ( + {config?.ui.unit_system == "imperial" + ? "feet" + : "meters"} + ) + parseFloat(distance)), + distances: + zoneData.distances?.map((distance) => parseFloat(distance)) ?? [], isFinished: true, color: zoneData.color, }),