mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01: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