mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-18 09:04:28 +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
|
||||
quality: Optional[int] = 70
|
||||
height: Optional[int] = None
|
||||
edges: Optional[int] = None
|
||||
ethreshold: Optional[int] = None
|
||||
|
||||
|
||||
class MediaEventsSnapshotQueryParams(BaseModel):
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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<FrigateConfig>("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({
|
||||
/>
|
||||
</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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user