Basic edge detection

This commit is contained in:
Vivida 2025-01-05 22:22:54 +01:00
parent e7ad38d827
commit e23bc15ec0
4 changed files with 94 additions and 4 deletions

View File

@ -20,6 +20,8 @@ class MediaLatestFrameQueryParams(BaseModel):
regions: Optional[int] = None regions: Optional[int] = None
quality: Optional[int] = 70 quality: Optional[int] = 70
height: Optional[int] = None height: Optional[int] = None
edges: Optional[int] = None
ethreshold: Optional[int] = None
class MediaEventsSnapshotQueryParams(BaseModel): class MediaEventsSnapshotQueryParams(BaseModel):

View File

@ -131,6 +131,8 @@ def latest_frame(
"mask": params.mask, "mask": params.mask,
"motion_boxes": params.motion, "motion_boxes": params.motion,
"regions": params.regions, "regions": params.regions,
"edges": params.edges,
"edges_threshold": params.ethreshold,
} }
quality = params.quality quality = params.quality

View File

@ -75,6 +75,15 @@ class CameraState:
frame_copy = cv2.cvtColor(frame_copy, cv2.COLOR_YUV2BGR_I420) frame_copy = cv2.cvtColor(frame_copy, cv2.COLOR_YUV2BGR_I420)
# draw on the frame # 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"): if draw_options.get("mask"):
mask_overlay = np.where(self.camera_config.motion.mask == [0]) mask_overlay = np.where(self.camera_config.motion.mask == [0])
frame_copy[mask_overlay] = [0, 0, 0] frame_copy[mask_overlay] = [0, 0, 0]

View File

@ -3,6 +3,7 @@ import ActivityIndicator from "@/components/indicators/activity-indicator";
import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage"; import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage";
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig"; import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import useSWR from "swr"; import useSWR from "swr";
import Heading from "@/components/ui/heading"; import Heading from "@/components/ui/heading";
@ -19,6 +20,7 @@ import {
import { ObjectType } from "@/types/ws"; import { ObjectType } from "@/types/ws";
import useDeepMemo from "@/hooks/use-deep-memo"; import useDeepMemo from "@/hooks/use-deep-memo";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { LuExternalLink, LuInfo } from "react-icons/lu"; import { LuExternalLink, LuInfo } from "react-icons/lu";
@ -28,7 +30,7 @@ type ObjectSettingsViewProps = {
selectedCamera?: string; selectedCamera?: string;
}; };
type Options = { [key: string]: boolean }; type Options = { [key: string]: boolean | number };
const emptyObject = Object.freeze({}); const emptyObject = Object.freeze({});
@ -37,6 +39,7 @@ export default function ObjectSettingsView({
}: ObjectSettingsViewProps) { }: ObjectSettingsViewProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const DEBUG_OPTIONS = [ const DEBUG_OPTIONS = [
{ {
param: "bbox", param: "bbox",
@ -123,7 +126,7 @@ export default function ObjectSettingsView({
); );
const handleSetOption = useCallback( const handleSetOption = useCallback(
(id: string, value: boolean) => { (id: string, value: boolean | number) => {
const newOptions = { ...options, [id]: value }; const newOptions = { ...options, [id]: value };
setOptions(newOptions); setOptions(newOptions);
}, },
@ -147,8 +150,19 @@ export default function ObjectSettingsView({
const params = new URLSearchParams( const params = new URLSearchParams(
Object.keys(options || {}).reduce((memo, key) => { Object.keys(options || {}).reduce((memo, key) => {
switch (typeof options[key]) {
case "boolean": {
//@ts-expect-error we know this is correct //@ts-expect-error we know this is correct
memo.push([key, options[key] === true ? "1" : "0"]); memo.push([key, options[key] === true ? "1" : "0"]);
break;
}
case "number": {
memo.push([key, options[key]]);
break;
}
}
return memo; return memo;
}, []), }, []),
); );
@ -255,6 +269,69 @@ export default function ObjectSettingsView({
/> />
</div> </div>
))} ))}
<Separator className="my-2 flex bg-secondary" />
<div
key="edges"
className="flex w-full flex-row items-center justify-between"
>
<div className="mb-2 flex flex-col">
<div className="flex items-center gap-2">
<Label
className="mb-0 cursor-pointer capitalize text-primary"
htmlFor="edges"
>
Edge Detection
</Label>
<Popover>
<PopoverTrigger asChild>
<div className="cursor-pointer p-0">
<LuInfo className="size-4" />
<span className="sr-only">Info</span>
</div>
</PopoverTrigger>
<PopoverContent className="w-80">
<p className="mb-2">
<strong>Edge Detection</strong>
</p>
<p>
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.
</p>
</PopoverContent>
</Popover>
</div>
<div className="mt-1 text-xs text-muted-foreground">
Highlights edges to help set the camera focus
</div>
</div>
<Switch
key={`$"edges"-${selectedCamera}`}
className="ml-1"
id="edges"
checked={options && options["edges"]}
onCheckedChange={(isChecked) => {
handleSetOption("edges", isChecked);
}}
/>
</div>
<div className="flex flex-row justify-between">
<Slider
id="focus-threshold"
className="w-full"
disabled={options["edges"] === false}
value={[options["ethreshold"] ?? 100]}
min={0}
max={255}
step={1}
onValueChange={(value) => {
handleSetOption("ethreshold", value[0]);
}}
/>
<div className="align-center ml-6 mr-2 flex text-lg">
{options["ethreshold"]}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>