mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-08-04 13:47:37 +02:00
Small Tweaks (#17652)
* Ensure that hailo uses correct labelmap * Make whole button clickable * Add weblate to readme * Update docs for HEIC * Fix explore chip icon logic * Sort regardless of case * Don't allow selection * Fix image uploading
This commit is contained in:
parent
664889d487
commit
e9787c5a88
12
README.md
12
README.md
@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
# Frigate - NVR With Realtime Object Detection for IP Cameras
|
# Frigate - NVR With Realtime Object Detection for IP Cameras
|
||||||
|
|
||||||
|
<a href="https://hosted.weblate.org/engage/frigate-nvr/">
|
||||||
|
<img src="https://hosted.weblate.org/widget/frigate-nvr/language-badge.svg" alt="Translation status" />
|
||||||
|
</a>
|
||||||
|
|
||||||
\[English\] | [简体中文](https://github.com/blakeblackshear/frigate/blob/dev/README_CN.md)
|
\[English\] | [简体中文](https://github.com/blakeblackshear/frigate/blob/dev/README_CN.md)
|
||||||
|
|
||||||
A complete and local NVR designed for [Home Assistant](https://www.home-assistant.io) with AI object detection. Uses OpenCV and Tensorflow to perform realtime object detection locally for IP cameras.
|
A complete and local NVR designed for [Home Assistant](https://www.home-assistant.io) with AI object detection. Uses OpenCV and Tensorflow to perform realtime object detection locally for IP cameras.
|
||||||
@ -32,21 +36,25 @@ If you would like to make a donation to support development, please use [Github
|
|||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
### Live dashboard
|
### Live dashboard
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<img width="800" alt="Live dashboard" src="https://github.com/blakeblackshear/frigate/assets/569905/5e713cb9-9db5-41dc-947a-6937c3bc376e">
|
<img width="800" alt="Live dashboard" src="https://github.com/blakeblackshear/frigate/assets/569905/5e713cb9-9db5-41dc-947a-6937c3bc376e">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Streamlined review workflow
|
### Streamlined review workflow
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<img width="800" alt="Streamlined review workflow" src="https://github.com/blakeblackshear/frigate/assets/569905/6fed96e8-3b18-40e5-9ddc-31e6f3c9f2ff">
|
<img width="800" alt="Streamlined review workflow" src="https://github.com/blakeblackshear/frigate/assets/569905/6fed96e8-3b18-40e5-9ddc-31e6f3c9f2ff">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Multi-camera scrubbing
|
### Multi-camera scrubbing
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<img width="800" alt="Multi-camera scrubbing" src="https://github.com/blakeblackshear/frigate/assets/569905/d6788a15-0eeb-4427-a8d4-80b93cae3d74">
|
<img width="800" alt="Multi-camera scrubbing" src="https://github.com/blakeblackshear/frigate/assets/569905/d6788a15-0eeb-4427-a8d4-80b93cae3d74">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Built-in mask and zone editor
|
### Built-in mask and zone editor
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<img width="800" alt="Multi-camera scrubbing" src="https://github.com/blakeblackshear/frigate/assets/569905/d7885fc3-bfe6-452f-b7d0-d957cb3e31f5">
|
<img width="800" alt="Multi-camera scrubbing" src="https://github.com/blakeblackshear/frigate/assets/569905/d7885fc3-bfe6-452f-b7d0-d957cb3e31f5">
|
||||||
</div>
|
</div>
|
||||||
@ -54,3 +62,7 @@ If you would like to make a donation to support development, please use [Github
|
|||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
We use [Weblate](https://hosted.weblate.org/projects/frigate-nvr/) to support language translations. Contributions are always welcome.
|
We use [Weblate](https://hosted.weblate.org/projects/frigate-nvr/) to support language translations. Contributions are always welcome.
|
||||||
|
|
||||||
|
<a href="https://hosted.weblate.org/engage/frigate-nvr/">
|
||||||
|
<img src="https://hosted.weblate.org/widget/frigate-nvr/multi-auto.svg" alt="Translation status" />
|
||||||
|
</a>
|
||||||
|
@ -136,3 +136,7 @@ Face recognition does not run on the recording stream, this would be suboptimal
|
|||||||
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.
|
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.
|
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.
|
3. Motion clarity is much more important than extra pixels, over-compression and motion blur are much more detrimental to results than resolution.
|
||||||
|
|
||||||
|
### I get an unknown error when taking a photo directly with my iPhone
|
||||||
|
|
||||||
|
By default iOS devices will use HEIC (High Efficiency Image Container) for images, but this format is not supported for uploads. Choosing `large` as the format instead of `original` will use JPG which will work correctly.
|
||||||
|
@ -163,6 +163,7 @@ model:
|
|||||||
input_pixel_format: rgb
|
input_pixel_format: rgb
|
||||||
input_dtype: int
|
input_dtype: int
|
||||||
model_type: yolo-generic
|
model_type: yolo-generic
|
||||||
|
labelmap_path: /labelmap/coco-80.txt
|
||||||
|
|
||||||
# The detector automatically selects the default model based on your hardware:
|
# The detector automatically selects the default model based on your hardware:
|
||||||
# - For Hailo-8 hardware: YOLOv6n (default: yolov6n.hef)
|
# - For Hailo-8 hardware: YOLOv6n (default: yolov6n.hef)
|
||||||
@ -219,6 +220,7 @@ model:
|
|||||||
input_pixel_format: rgb
|
input_pixel_format: rgb
|
||||||
input_dtype: int
|
input_dtype: int
|
||||||
model_type: yolo-generic
|
model_type: yolo-generic
|
||||||
|
labelmap_path: /labelmap/coco-80.txt
|
||||||
# Optional: Specify a local model path.
|
# Optional: Specify a local model path.
|
||||||
# path: /config/model_cache/hailo/custom_model.hef
|
# path: /config/model_cache/hailo/custom_model.hef
|
||||||
#
|
#
|
||||||
|
@ -44,23 +44,31 @@ export default function SearchThumbnail({
|
|||||||
[searchResult, onClick],
|
[searchResult, onClick],
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectLabel = useMemo(() => {
|
|
||||||
if (
|
|
||||||
!config ||
|
|
||||||
!searchResult.sub_label ||
|
|
||||||
!config.model.attributes_map[searchResult.label]
|
|
||||||
) {
|
|
||||||
return searchResult.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${searchResult.label}-verified`;
|
|
||||||
}, [config, searchResult]);
|
|
||||||
|
|
||||||
const hasRecognizedPlate = useMemo(
|
const hasRecognizedPlate = useMemo(
|
||||||
() => (searchResult.data.recognized_license_plate?.length || 0) > 0,
|
() => (searchResult.data.recognized_license_plate?.length || 0) > 0,
|
||||||
[searchResult],
|
[searchResult],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const objectLabel = useMemo(() => {
|
||||||
|
if (!config) {
|
||||||
|
return searchResult.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!searchResult.sub_label) {
|
||||||
|
return `${searchResult.label}${hasRecognizedPlate ? "-plate" : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.model.attributes_map[searchResult.label]?.includes(
|
||||||
|
searchResult.sub_label,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return searchResult.sub_label;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${searchResult.label}-verified`;
|
||||||
|
}, [config, hasRecognizedPlate, searchResult]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative size-full cursor-pointer"
|
className="relative size-full cursor-pointer"
|
||||||
@ -102,10 +110,7 @@ export default function SearchThumbnail({
|
|||||||
className={`z-0 flex items-center justify-between gap-1 space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs`}
|
className={`z-0 flex items-center justify-between gap-1 space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs`}
|
||||||
onClick={() => onClick(searchResult, false, true)}
|
onClick={() => onClick(searchResult, false, true)}
|
||||||
>
|
>
|
||||||
{getIconForLabel(
|
{getIconForLabel(objectLabel, "size-3 text-white")}
|
||||||
`${objectLabel}${hasRecognizedPlate ? "-plate" : ""}`,
|
|
||||||
"size-3 text-white",
|
|
||||||
)}
|
|
||||||
{Math.round(
|
{Math.round(
|
||||||
(searchResult.data.score ??
|
(searchResult.data.score ??
|
||||||
searchResult.data.top_score ??
|
searchResult.data.top_score ??
|
||||||
|
@ -32,7 +32,11 @@ export default function ImageEntry({
|
|||||||
const [preview, setPreview] = useState<string | null>(null);
|
const [preview, setPreview] = useState<string | null>(null);
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
file: z.instanceof(File, { message: "Please select an image file." }),
|
file: z
|
||||||
|
.instanceof(File, { message: t("imageEntry.validation.selectImage") })
|
||||||
|
.refine((file) =>
|
||||||
|
accept["image/*"].includes(`.${file.type.split("/")[1]}`),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
@ -462,6 +462,13 @@ export function SubFilterContent({
|
|||||||
setSubLabels,
|
setSubLabels,
|
||||||
}: SubFilterContentProps) {
|
}: SubFilterContentProps) {
|
||||||
const { t } = useTranslation(["components/filter"]);
|
const { t } = useTranslation(["components/filter"]);
|
||||||
|
const sortedSubLabels = useMemo(
|
||||||
|
() =>
|
||||||
|
[...allSubLabels].sort((a, b) =>
|
||||||
|
a.toLowerCase().localeCompare(b.toLowerCase()),
|
||||||
|
),
|
||||||
|
[allSubLabels],
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className="overflow-x-hidden">
|
<div className="overflow-x-hidden">
|
||||||
<DropdownMenuSeparator className="mb-3" />
|
<DropdownMenuSeparator className="mb-3" />
|
||||||
@ -482,7 +489,7 @@ export function SubFilterContent({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2.5 flex flex-col gap-2.5">
|
<div className="mt-2.5 flex flex-col gap-2.5">
|
||||||
{allSubLabels.map((item) => (
|
{sortedSubLabels.map((item) => (
|
||||||
<FilterSwitch
|
<FilterSwitch
|
||||||
key={item}
|
key={item}
|
||||||
label={item.replaceAll("_", " ")}
|
label={item.replaceAll("_", " ")}
|
||||||
|
@ -690,7 +690,7 @@ function FaceAttemptGroup({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<div className="capitalize">
|
<div className="select-none capitalize">
|
||||||
Person
|
Person
|
||||||
{event?.sub_label
|
{event?.sub_label
|
||||||
? `: ${event.sub_label} (${Math.round((event.data.sub_label_score || 0) * 100)}%)`
|
? `: ${event.sub_label} (${Math.round((event.data.sub_label_score || 0) * 100)}%)`
|
||||||
@ -848,7 +848,7 @@ function FaceAttempt({
|
|||||||
: "outline-transparent duration-500",
|
: "outline-transparent duration-500",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative w-full overflow-hidden rounded-lg *:text-card-foreground">
|
<div className="relative w-full select-none overflow-hidden rounded-lg *:text-card-foreground">
|
||||||
<img
|
<img
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
className={cn("size-44", isMobile && "w-full")}
|
className={cn("size-44", isMobile && "w-full")}
|
||||||
@ -866,7 +866,7 @@ function FaceAttempt({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2">
|
<div className="select-none p-2">
|
||||||
<div className="flex w-full flex-row items-center justify-between gap-2">
|
<div className="flex w-full flex-row items-center justify-between gap-2">
|
||||||
<div className="flex flex-col items-start text-xs text-primary-variant">
|
<div className="flex flex-col items-start text-xs text-primary-variant">
|
||||||
<div className="capitalize">{data.name}</div>
|
<div className="capitalize">{data.name}</div>
|
||||||
|
@ -1479,17 +1479,17 @@ function FrigateCameraFeatures({
|
|||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div
|
||||||
|
className="flex cursor-pointer flex-col gap-1"
|
||||||
|
onClick={() =>
|
||||||
|
navigate(`/settings?page=debug&camera=${camera.name}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between text-sm font-medium leading-none">
|
<div className="flex items-center justify-between text-sm font-medium leading-none">
|
||||||
{t("streaming.debugView", {
|
{t("streaming.debugView", {
|
||||||
ns: "components/dialog",
|
ns: "components/dialog",
|
||||||
})}
|
})}
|
||||||
<LuExternalLink
|
<LuExternalLink className="ml-2 inline-flex size-5" />
|
||||||
onClick={() =>
|
|
||||||
navigate(`/settings?page=debug&camera=${camera.name}`)
|
|
||||||
}
|
|
||||||
className="ml-2 inline-flex size-5 cursor-pointer"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user