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:
Nicolas Mowen 2025-04-11 08:21:01 -06:00 committed by GitHub
parent 664889d487
commit e9787c5a88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 62 additions and 28 deletions

View File

@ -4,6 +4,10 @@
# 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)
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
### Live dashboard
<div>
<img width="800" alt="Live dashboard" src="https://github.com/blakeblackshear/frigate/assets/569905/5e713cb9-9db5-41dc-947a-6937c3bc376e">
</div>
### Streamlined review workflow
<div>
<img width="800" alt="Streamlined review workflow" src="https://github.com/blakeblackshear/frigate/assets/569905/6fed96e8-3b18-40e5-9ddc-31e6f3c9f2ff">
</div>
### Multi-camera scrubbing
<div>
<img width="800" alt="Multi-camera scrubbing" src="https://github.com/blakeblackshear/frigate/assets/569905/d6788a15-0eeb-4427-a8d4-80b93cae3d74">
</div>
### Built-in mask and zone editor
<div>
<img width="800" alt="Multi-camera scrubbing" src="https://github.com/blakeblackshear/frigate/assets/569905/d7885fc3-bfe6-452f-b7d0-d957cb3e31f5">
</div>
@ -54,3 +62,7 @@ If you would like to make a donation to support development, please use [Github
## Translations
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>

View File

@ -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.
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.
### 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.

View File

@ -163,6 +163,7 @@ model:
input_pixel_format: rgb
input_dtype: int
model_type: yolo-generic
labelmap_path: /labelmap/coco-80.txt
# The detector automatically selects the default model based on your hardware:
# - For Hailo-8 hardware: YOLOv6n (default: yolov6n.hef)
@ -219,6 +220,7 @@ model:
input_pixel_format: rgb
input_dtype: int
model_type: yolo-generic
labelmap_path: /labelmap/coco-80.txt
# Optional: Specify a local model path.
# path: /config/model_cache/hailo/custom_model.hef
#

View File

@ -44,23 +44,31 @@ export default function SearchThumbnail({
[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(
() => (searchResult.data.recognized_license_plate?.length || 0) > 0,
[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 (
<div
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`}
onClick={() => onClick(searchResult, false, true)}
>
{getIconForLabel(
`${objectLabel}${hasRecognizedPlate ? "-plate" : ""}`,
"size-3 text-white",
)}
{getIconForLabel(objectLabel, "size-3 text-white")}
{Math.round(
(searchResult.data.score ??
searchResult.data.top_score ??

View File

@ -32,7 +32,11 @@ export default function ImageEntry({
const [preview, setPreview] = useState<string | null>(null);
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>>({

View File

@ -462,6 +462,13 @@ export function SubFilterContent({
setSubLabels,
}: SubFilterContentProps) {
const { t } = useTranslation(["components/filter"]);
const sortedSubLabels = useMemo(
() =>
[...allSubLabels].sort((a, b) =>
a.toLowerCase().localeCompare(b.toLowerCase()),
),
[allSubLabels],
);
return (
<div className="overflow-x-hidden">
<DropdownMenuSeparator className="mb-3" />
@ -482,7 +489,7 @@ export function SubFilterContent({
/>
</div>
<div className="mt-2.5 flex flex-col gap-2.5">
{allSubLabels.map((item) => (
{sortedSubLabels.map((item) => (
<FilterSwitch
key={item}
label={item.replaceAll("_", " ")}

View File

@ -690,7 +690,7 @@ function FaceAttemptGroup({
}}
>
<div className="flex flex-row justify-between">
<div className="capitalize">
<div className="select-none capitalize">
Person
{event?.sub_label
? `: ${event.sub_label} (${Math.round((event.data.sub_label_score || 0) * 100)}%)`
@ -848,7 +848,7 @@ function FaceAttempt({
: "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
ref={imgRef}
className={cn("size-44", isMobile && "w-full")}
@ -866,7 +866,7 @@ function FaceAttempt({
/>
</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 flex-col items-start text-xs text-primary-variant">
<div className="capitalize">{data.name}</div>

View File

@ -1479,17 +1479,17 @@ function FrigateCameraFeatures({
})}
</p>
</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">
{t("streaming.debugView", {
ns: "components/dialog",
})}
<LuExternalLink
onClick={() =>
navigate(`/settings?page=debug&camera=${camera.name}`)
}
className="ml-2 inline-flex size-5 cursor-pointer"
/>
<LuExternalLink className="ml-2 inline-flex size-5" />
</div>
</div>
</div>