mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 17:14:26 +03:00
Basic edge detection
This commit is contained in:
parent
e7ad38d827
commit
e23bc15ec0
@ -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):
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -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) => {
|
||||||
//@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;
|
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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user