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