mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02:00
Quick fixes (#17639)
* Use mobile drawer for face selection * Convert face selection to separate component * Cleanup dialogs * Add FAQ for record resolution * Update image name * Remove unused * Cleanup
This commit is contained in:
parent
64db518837
commit
30ac868757
@ -129,3 +129,10 @@ The Frigate considers the recognition scores across all recognition attempts for
|
||||
### Can I use other face recognition software like DoubleTake at the same time as the built in face recognition?
|
||||
|
||||
No, using another face recognition service will interfere with Frigate's built in face recognition. When using double-take the sub_label feature must be disabled if the built in face recognition is also desired.
|
||||
|
||||
### Does face recognition run on the recording stream?
|
||||
|
||||
Face recognition does not run on the recording stream, this would be suboptimal for many reasons:
|
||||
1. The latency of accessing the recordings means the notifications would not include the names of recognized people because recognition would not complete until after.
|
||||
2. The embedding models used run on a set image size, so larger images will be scaled down to match this anyway.
|
||||
3. Motion clarity is much more important than extra pixels, over-compression and motion blur are much more detrimental to results than resolution.
|
||||
|
@ -295,8 +295,7 @@ These instructions were originally based on the [Jellyfin documentation](https:/
|
||||
## NVIDIA Jetson (Orin AGX, Orin NX, Orin Nano\*, Xavier AGX, Xavier NX, TX2, TX1, Nano)
|
||||
|
||||
A separate set of docker images is available that is based on Jetpack/L4T. They come with an `ffmpeg` build
|
||||
with codecs that use the Jetson's dedicated media engine. If your Jetson host is running Jetpack 5.0+ use the `stable-tensorrt-jp5`
|
||||
tagged image, or if your Jetson host is running Jetpack 6.0+ use the `stable-tensorrt-jp6` tagged image. Note that the Orin Nano has no video encoder, so frigate will use software encoding on this platform, but the image will still allow hardware decoding and tensorrt object detection.
|
||||
with codecs that use the Jetson's dedicated media engine. If your Jetson host is running Jetpack 6.0+ use the `stable-tensorrt-jp6` tagged image. Note that the Orin Nano has no video encoder, so frigate will use software encoding on this platform, but the image will still allow hardware decoding and tensorrt object detection.
|
||||
|
||||
You will need to use the image with the nvidia container runtime:
|
||||
|
||||
@ -306,7 +305,7 @@ You will need to use the image with the nvidia container runtime:
|
||||
docker run -d \
|
||||
...
|
||||
--runtime nvidia
|
||||
ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp5
|
||||
ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp6
|
||||
```
|
||||
|
||||
### Docker Compose - Jetson
|
||||
@ -315,7 +314,7 @@ docker run -d \
|
||||
services:
|
||||
frigate:
|
||||
...
|
||||
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp5
|
||||
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp6
|
||||
runtime: nvidia # Add this
|
||||
```
|
||||
|
||||
|
@ -27,7 +27,7 @@ Frigate supports multiple different detectors that work on different types of ha
|
||||
**Nvidia**
|
||||
|
||||
- [TensortRT](#nvidia-tensorrt-detector): TensorRT can run on Nvidia GPUs and Jetson devices, using one of many default models.
|
||||
- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` or `-tensorrt-jp(4/5)` Frigate images when a supported ONNX model is configured.
|
||||
- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` or `-tensorrt-jp6` Frigate images when a supported ONNX model is configured.
|
||||
|
||||
**Rockchip**
|
||||
|
||||
|
117
web/src/components/overlay/FaceSelectionDialog.tsx
Normal file
117
web/src/components/overlay/FaceSelectionDialog.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { isDesktop, isMobile } from "react-device-detect";
|
||||
import { LuPlus, LuScanFace } from "react-icons/lu";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { ReactNode, useMemo, useState } from "react";
|
||||
import TextEntryDialog from "./dialog/TextEntryDialog";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
type FaceSelectionDialogProps = {
|
||||
className?: string;
|
||||
faceNames: string[];
|
||||
onTrainAttempt: (name: string) => void;
|
||||
children: ReactNode;
|
||||
};
|
||||
export default function FaceSelectionDialog({
|
||||
className,
|
||||
faceNames,
|
||||
onTrainAttempt,
|
||||
children,
|
||||
}: FaceSelectionDialogProps) {
|
||||
const { t } = useTranslation(["views/faceLibrary"]);
|
||||
|
||||
const isChildButton = useMemo(
|
||||
() => React.isValidElement(children) && children.type === Button,
|
||||
[children],
|
||||
);
|
||||
|
||||
// control
|
||||
const [newFace, setNewFace] = useState(false);
|
||||
|
||||
// components
|
||||
const Selector = isDesktop ? DropdownMenu : Drawer;
|
||||
const SelectorTrigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger;
|
||||
const SelectorContent = isDesktop ? DropdownMenuContent : DrawerContent;
|
||||
const SelectorItem = isDesktop ? DropdownMenuItem : DrawerClose;
|
||||
|
||||
return (
|
||||
<div className={className ?? ""}>
|
||||
{newFace && (
|
||||
<TextEntryDialog
|
||||
open={true}
|
||||
setOpen={setNewFace}
|
||||
title={t("createFaceLibrary.new")}
|
||||
onSave={(newName) => onTrainAttempt(newName)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Tooltip>
|
||||
<Selector>
|
||||
<SelectorTrigger asChild>
|
||||
<TooltipTrigger asChild={isChildButton}>{children}</TooltipTrigger>
|
||||
</SelectorTrigger>
|
||||
<SelectorContent
|
||||
className={cn(
|
||||
"max-h-[75dvh] overflow-hidden",
|
||||
isMobile && "mx-1 gap-2 rounded-t-2xl px-4",
|
||||
)}
|
||||
>
|
||||
{isMobile && (
|
||||
<DrawerHeader className="sr-only">
|
||||
<DrawerTitle>Log Details</DrawerTitle>
|
||||
<DrawerDescription>Log details</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
)}
|
||||
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col",
|
||||
isMobile && "gap-2 overflow-y-auto pb-4",
|
||||
)}
|
||||
>
|
||||
<SelectorItem
|
||||
className="flex cursor-pointer gap-2 capitalize"
|
||||
onClick={() => setNewFace(true)}
|
||||
>
|
||||
<LuPlus />
|
||||
{t("createFaceLibrary.new")}
|
||||
</SelectorItem>
|
||||
{faceNames.map((faceName) => (
|
||||
<SelectorItem
|
||||
key={faceName}
|
||||
className="flex cursor-pointer gap-2 capitalize"
|
||||
onClick={() => onTrainAttempt(faceName)}
|
||||
>
|
||||
<LuScanFace />
|
||||
{faceName}
|
||||
</SelectorItem>
|
||||
))}
|
||||
</div>
|
||||
</SelectorContent>
|
||||
</Selector>
|
||||
<TooltipContent>{t("trainFace")}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -57,7 +57,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
||||
@ -77,6 +76,7 @@ import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TbFaceId } from "react-icons/tb";
|
||||
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||
import FaceSelectionDialog from "../FaceSelectionDialog";
|
||||
|
||||
const SEARCH_TABS = [
|
||||
"details",
|
||||
@ -844,30 +844,18 @@ function ObjectDetailsTab({
|
||||
</Button>
|
||||
)}
|
||||
{hasFace && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="w-full">
|
||||
<div className="flex gap-1">
|
||||
<TbFaceId />
|
||||
{t("trainFace", { ns: "views/faceLibrary" })}
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>
|
||||
{t("trainFaceAs", { ns: "views/faceLibrary" })}
|
||||
</DropdownMenuLabel>
|
||||
{faceNames.map((faceName) => (
|
||||
<DropdownMenuItem
|
||||
key={faceName}
|
||||
className="cursor-pointer capitalize"
|
||||
onClick={() => onTrainFace(faceName)}
|
||||
>
|
||||
{faceName}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<FaceSelectionDialog
|
||||
className="w-full"
|
||||
faceNames={faceNames}
|
||||
onTrainAttempt={onTrainFace}
|
||||
>
|
||||
<Button className="w-full">
|
||||
<div className="flex gap-1">
|
||||
<TbFaceId />
|
||||
{t("trainFace", { ns: "views/faceLibrary" })}
|
||||
</div>
|
||||
</Button>
|
||||
</FaceSelectionDialog>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,6 +8,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type TextEntryDialogProps = {
|
||||
@ -43,7 +45,7 @@ export default function TextEntryDialog({
|
||||
allowEmpty={allowEmpty}
|
||||
onSave={onSave}
|
||||
>
|
||||
<DialogFooter className="pt-4">
|
||||
<DialogFooter className={cn("pt-4", isMobile && "gap-2")}>
|
||||
<Button type="button" onClick={() => setOpen(false)}>
|
||||
{t("button.cancel")}
|
||||
</Button>
|
||||
|
@ -3,8 +3,8 @@ import TimeAgo from "@/components/dynamic/TimeAgo";
|
||||
import AddFaceIcon from "@/components/icons/AddFaceIcon";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import CreateFaceWizardDialog from "@/components/overlay/detail/FaceCreateWizardDialog";
|
||||
import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog";
|
||||
import UploadImageDialog from "@/components/overlay/dialog/UploadImageDialog";
|
||||
import FaceSelectionDialog from "@/components/overlay/FaceSelectionDialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@ -17,7 +17,6 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
@ -42,7 +41,6 @@ import { isDesktop, isMobile } from "react-device-detect";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
LuImagePlus,
|
||||
LuPlus,
|
||||
LuRefreshCw,
|
||||
LuScanFace,
|
||||
LuSearch,
|
||||
@ -783,8 +781,6 @@ function FaceAttempt({
|
||||
|
||||
// interaction
|
||||
|
||||
const [newFace, setNewFace] = useState(false);
|
||||
|
||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||
|
||||
useContextMenu(imgRef, () => {
|
||||
@ -844,15 +840,6 @@ function FaceAttempt({
|
||||
|
||||
return (
|
||||
<>
|
||||
{newFace && (
|
||||
<TextEntryDialog
|
||||
open={true}
|
||||
setOpen={setNewFace}
|
||||
title={t("createFaceLibrary.new")}
|
||||
onSave={(newName) => onTrainAttempt(newName)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex cursor-pointer flex-col rounded-lg outline outline-[3px]",
|
||||
@ -895,36 +882,12 @@ function FaceAttempt({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-start justify-end gap-5 md:gap-4">
|
||||
<Tooltip>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<TooltipTrigger>
|
||||
<AddFaceIcon className="size-5 cursor-pointer text-primary-variant hover:text-primary" />
|
||||
</TooltipTrigger>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
className="flex cursor-pointer gap-2 capitalize"
|
||||
onClick={() => setNewFace(true)}
|
||||
>
|
||||
<LuPlus />
|
||||
{t("createFaceLibrary.new")}
|
||||
</DropdownMenuItem>
|
||||
{faceNames.map((faceName) => (
|
||||
<DropdownMenuItem
|
||||
key={faceName}
|
||||
className="flex cursor-pointer gap-2 capitalize"
|
||||
onClick={() => onTrainAttempt(faceName)}
|
||||
>
|
||||
<LuScanFace />
|
||||
{faceName}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<TooltipContent>{t("trainFace")}</TooltipContent>
|
||||
</Tooltip>
|
||||
<FaceSelectionDialog
|
||||
faceNames={faceNames}
|
||||
onTrainAttempt={onTrainAttempt}
|
||||
>
|
||||
<AddFaceIcon className="size-5 cursor-pointer text-primary-variant hover:text-primary" />
|
||||
</FaceSelectionDialog>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<LuRefreshCw
|
||||
|
Loading…
Reference in New Issue
Block a user