mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-08-01 00:22:19 +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:
@@ -25,6 +25,7 @@ import { baseUrl } from "@/api/baseUrl";
|
||||
import { cn } from "@/lib/utils";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import {
|
||||
FaArrowRight,
|
||||
FaCheckCircle,
|
||||
FaChevronDown,
|
||||
FaDownload,
|
||||
@@ -329,6 +330,30 @@ 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 velocityAngle = useMemo(() => {
|
||||
if (!search || !search.data?.velocity_angle) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (search.data?.velocity_angle != 0) {
|
||||
return search.data?.velocity_angle.toFixed(1);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}, [search]);
|
||||
|
||||
const updateDescription = useCallback(() => {
|
||||
if (!search) {
|
||||
return;
|
||||
@@ -440,6 +465,29 @@ function ObjectDetailsTab({
|
||||
{score}%{subLabelScore && ` (${subLabelScore}%)`}
|
||||
</div>
|
||||
</div>
|
||||
{averageEstimatedSpeed && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">Estimated Speed</div>
|
||||
<div className="flex flex-col space-y-0.5 text-sm">
|
||||
{averageEstimatedSpeed && (
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{averageEstimatedSpeed}{" "}
|
||||
{config?.ui.unit_system == "imperial" ? "mph" : "kph"}{" "}
|
||||
{velocityAngle != undefined && (
|
||||
<span className="text-primary/40">
|
||||
<FaArrowRight
|
||||
size={10}
|
||||
style={{
|
||||
transform: `rotate(${(360 - Number(velocityAngle)) % 360}deg)`,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">Camera</div>
|
||||
<div className="text-sm capitalize">
|
||||
|
||||
@@ -71,9 +71,11 @@ export default function SearchFilterDialog({
|
||||
currentFilter &&
|
||||
(currentFilter.time_range ||
|
||||
(currentFilter.min_score ?? 0) > 0.5 ||
|
||||
(currentFilter.min_speed ?? 1) > 1 ||
|
||||
(currentFilter.has_snapshot ?? 0) === 1 ||
|
||||
(currentFilter.has_clip ?? 0) === 1 ||
|
||||
(currentFilter.max_score ?? 1) < 1 ||
|
||||
(currentFilter.max_speed ?? 150) < 150 ||
|
||||
(currentFilter.zones?.length ?? 0) > 0 ||
|
||||
(currentFilter.sub_labels?.length ?? 0) > 0),
|
||||
[currentFilter],
|
||||
@@ -124,6 +126,14 @@ export default function SearchFilterDialog({
|
||||
setCurrentFilter({ ...currentFilter, min_score: min, max_score: max })
|
||||
}
|
||||
/>
|
||||
<SpeedFilterContent
|
||||
config={config}
|
||||
minSpeed={currentFilter.min_speed}
|
||||
maxSpeed={currentFilter.max_speed}
|
||||
setSpeedRange={(min, max) =>
|
||||
setCurrentFilter({ ...currentFilter, min_speed: min, max_speed: max })
|
||||
}
|
||||
/>
|
||||
<SnapshotClipFilterContent
|
||||
config={config}
|
||||
hasSnapshot={
|
||||
@@ -178,6 +188,8 @@ export default function SearchFilterDialog({
|
||||
search_type: undefined,
|
||||
min_score: undefined,
|
||||
max_score: undefined,
|
||||
min_speed: undefined,
|
||||
max_speed: undefined,
|
||||
has_snapshot: undefined,
|
||||
has_clip: undefined,
|
||||
}));
|
||||
@@ -521,6 +533,62 @@ export function ScoreFilterContent({
|
||||
);
|
||||
}
|
||||
|
||||
type SpeedFilterContentProps = {
|
||||
config?: FrigateConfig;
|
||||
minSpeed: number | undefined;
|
||||
maxSpeed: number | undefined;
|
||||
setSpeedRange: (min: number | undefined, max: number | undefined) => void;
|
||||
};
|
||||
export function SpeedFilterContent({
|
||||
config,
|
||||
minSpeed,
|
||||
maxSpeed,
|
||||
setSpeedRange,
|
||||
}: SpeedFilterContentProps) {
|
||||
return (
|
||||
<div className="overflow-x-hidden">
|
||||
<DropdownMenuSeparator className="mb-3" />
|
||||
<div className="mb-3 text-lg">
|
||||
Estimated Speed ({config?.ui.unit_system == "metric" ? "kph" : "mph"})
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Input
|
||||
className="w-14 text-center"
|
||||
inputMode="numeric"
|
||||
value={minSpeed ?? 1}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
if (value) {
|
||||
setSpeedRange(parseInt(value), maxSpeed ?? 1.0);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<DualThumbSlider
|
||||
className="mx-2 w-full"
|
||||
min={1}
|
||||
max={150}
|
||||
step={1}
|
||||
value={[minSpeed ?? 1, maxSpeed ?? 150]}
|
||||
onValueChange={([min, max]) => setSpeedRange(min, max)}
|
||||
/>
|
||||
<Input
|
||||
className="w-14 text-center"
|
||||
inputMode="numeric"
|
||||
value={maxSpeed ?? 150}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
|
||||
if (value) {
|
||||
setSpeedRange(minSpeed ?? 1, parseInt(value));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type SnapshotClipContentProps = {
|
||||
config?: FrigateConfig;
|
||||
hasSnapshot: boolean | undefined;
|
||||
|
||||
Reference in New Issue
Block a user