diff --git a/frigate/api/defs/query/media_query_parameters.py b/frigate/api/defs/query/media_query_parameters.py index b7df85d30..c28205e3f 100644 --- a/frigate/api/defs/query/media_query_parameters.py +++ b/frigate/api/defs/query/media_query_parameters.py @@ -20,6 +20,8 @@ class MediaLatestFrameQueryParams(BaseModel): regions: Optional[int] = None quality: Optional[int] = 70 height: Optional[int] = None + edges: Optional[int] = None + ethreshold: Optional[int] = None class MediaEventsSnapshotQueryParams(BaseModel): diff --git a/frigate/api/media.py b/frigate/api/media.py index e19fe547f..bac1cbdc6 100644 --- a/frigate/api/media.py +++ b/frigate/api/media.py @@ -131,6 +131,8 @@ def latest_frame( "mask": params.mask, "motion_boxes": params.motion, "regions": params.regions, + "edges": params.edges, + "edges_threshold": params.ethreshold, } quality = params.quality diff --git a/frigate/object_processing.py b/frigate/object_processing.py index b5196e686..5aeb68f49 100644 --- a/frigate/object_processing.py +++ b/frigate/object_processing.py @@ -75,6 +75,15 @@ class CameraState: frame_copy = cv2.cvtColor(frame_copy, cv2.COLOR_YUV2BGR_I420) # draw on the frame + + if draw_options.get("edges") and draw_options.get("edges_threshold"): + # Higher Threshold = 3 * Lower Threshold (following Canny's recommendation) + high_threshold = np.clip(draw_options.get("edges_threshold"), 0, 255) + low_threshold = np.floor_divide(high_threshold, 3) + + edges = cv2.Canny(frame_copy, low_threshold, high_threshold) + frame_copy[edges==255] = (0,0,255) + if draw_options.get("mask"): mask_overlay = np.where(self.camera_config.motion.mask == [0]) frame_copy[mask_overlay] = [0, 0, 0] diff --git a/web/src/views/settings/ObjectSettingsView.tsx b/web/src/views/settings/ObjectSettingsView.tsx index 7b8a08d2e..f14947e24 100644 --- a/web/src/views/settings/ObjectSettingsView.tsx +++ b/web/src/views/settings/ObjectSettingsView.tsx @@ -3,6 +3,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator"; import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage"; import { CameraConfig, FrigateConfig } from "@/types/frigateConfig"; import { Toaster } from "@/components/ui/sonner"; +import { Slider } from "@/components/ui/slider"; import { Label } from "@/components/ui/label"; import useSWR from "swr"; import Heading from "@/components/ui/heading"; @@ -19,6 +20,7 @@ import { import { ObjectType } from "@/types/ws"; import useDeepMemo from "@/hooks/use-deep-memo"; import { Card } from "@/components/ui/card"; +import { Separator } from "@/components/ui/separator"; import { getIconForLabel } from "@/utils/iconUtil"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { LuExternalLink, LuInfo } from "react-icons/lu"; @@ -28,7 +30,7 @@ type ObjectSettingsViewProps = { selectedCamera?: string; }; -type Options = { [key: string]: boolean }; +type Options = { [key: string]: boolean | number }; const emptyObject = Object.freeze({}); @@ -37,6 +39,7 @@ export default function ObjectSettingsView({ }: ObjectSettingsViewProps) { const { data: config } = useSWR("config"); + const DEBUG_OPTIONS = [ { param: "bbox", @@ -123,7 +126,7 @@ export default function ObjectSettingsView({ ); const handleSetOption = useCallback( - (id: string, value: boolean) => { + (id: string, value: boolean | number) => { const newOptions = { ...options, [id]: value }; setOptions(newOptions); }, @@ -147,8 +150,19 @@ export default function ObjectSettingsView({ const params = new URLSearchParams( Object.keys(options || {}).reduce((memo, key) => { - //@ts-expect-error we know this is correct - memo.push([key, options[key] === true ? "1" : "0"]); + + switch (typeof options[key]) { + case "boolean": { + //@ts-expect-error we know this is correct + memo.push([key, options[key] === true ? "1" : "0"]); + break; + } + case "number": { + memo.push([key, options[key]]); + break; + } + } + return memo; }, []), ); @@ -255,6 +269,69 @@ export default function ObjectSettingsView({ /> ))} + + +
+
+
+ + + +
+ + Info +
+
+ +

+ Edge Detection +

+

+ Bright red lines will be overlaid on areas of focus. + Use the slider below to adjust the threshold if too many or too little lines are visible. +

+
+
+
+
+ Highlights edges to help set the camera focus +
+
+ { + handleSetOption("edges", isChecked); + }} + /> +
+
+ { + handleSetOption("ethreshold", value[0]); + }} + /> +
+ {options["ethreshold"]} +
+