mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-24 20:59:02 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d268bfd130 |
@@ -211,7 +211,7 @@ jobs:
|
||||
with:
|
||||
string: ${{ github.repository }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
|
||||
uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
with:
|
||||
string: ${{ github.repository }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1
|
||||
uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -34,13 +34,12 @@ edgeTPU:
|
||||
type: edgetpu
|
||||
device: usb
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize of the model, typically 320
|
||||
height: 320 # <--- should match the imgsize of the model, typically 320
|
||||
path: /config/model_cache/yolov9-s-relu6-best_320_int8_edgetpu.tflite
|
||||
labelmap_path: /config/labels-coco17.txt
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize of the model, typically 320
|
||||
height: 320 # <--- should match the imgsize of the model, typically 320
|
||||
path: /config/model_cache/yolov9-s-relu6-best_320_int8_edgetpu.tflite
|
||||
labelmap_path: /config/labels-coco17.txt
|
||||
hailo8l:
|
||||
title: Hailo-8/Hailo-8L
|
||||
models:
|
||||
@@ -68,28 +67,27 @@ hailo8l:
|
||||
type: hailo8l
|
||||
device: PCIe
|
||||
|
||||
models:
|
||||
default:
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: rgb
|
||||
input_dtype: int
|
||||
model_type: yolo-generic
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nhwc
|
||||
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)
|
||||
# - For Hailo-8L hardware: YOLOv6n (default: yolov6n.hef)
|
||||
#
|
||||
# Optionally, you can specify a local model path to override the default.
|
||||
# If a local path is provided and the file exists, it will be used instead of downloading.
|
||||
# Example:
|
||||
# path: /config/model_cache/hailo/yolov6n.hef
|
||||
#
|
||||
# You can also override using a custom URL:
|
||||
# path: https://hailo-model-zoo.s3.eu-west-2.amazonaws.com/ModelZoo/Compiled/v2.14.0/hailo8/yolov6n.hef
|
||||
# just make sure to give it the write configuration based on the model
|
||||
# The detector automatically selects the default model based on your hardware:
|
||||
# - For Hailo-8 hardware: YOLOv6n (default: yolov6n.hef)
|
||||
# - For Hailo-8L hardware: YOLOv6n (default: yolov6n.hef)
|
||||
#
|
||||
# Optionally, you can specify a local model path to override the default.
|
||||
# If a local path is provided and the file exists, it will be used instead of downloading.
|
||||
# Example:
|
||||
# path: /config/model_cache/hailo/yolov6n.hef
|
||||
#
|
||||
# You can also override using a custom URL:
|
||||
# path: https://hailo-model-zoo.s3.eu-west-2.amazonaws.com/ModelZoo/Compiled/v2.14.0/hailo8/yolov6n.hef
|
||||
# just make sure to give it the write configuration based on the model
|
||||
- key: ssd
|
||||
label: SSD MobileNet v1
|
||||
recommended: false
|
||||
@@ -113,19 +111,18 @@ hailo8l:
|
||||
type: hailo8l
|
||||
device: PCIe
|
||||
|
||||
models:
|
||||
default:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: rgb
|
||||
model_type: ssd
|
||||
# Specify the local model path (if available) or URL for SSD MobileNet v1.
|
||||
# Example with a local path:
|
||||
# path: /config/model_cache/h8l_cache/ssd_mobilenet_v1.hef
|
||||
#
|
||||
# Or override using a custom URL:
|
||||
# path: https://hailo-model-zoo.s3.eu-west-2.amazonaws.com/ModelZoo/Compiled/v2.14.0/hailo8l/ssd_mobilenet_v1.hef
|
||||
model:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: rgb
|
||||
model_type: ssd
|
||||
# Specify the local model path (if available) or URL for SSD MobileNet v1.
|
||||
# Example with a local path:
|
||||
# path: /config/model_cache/h8l_cache/ssd_mobilenet_v1.hef
|
||||
#
|
||||
# Or override using a custom URL:
|
||||
# path: https://hailo-model-zoo.s3.eu-west-2.amazonaws.com/ModelZoo/Compiled/v2.14.0/hailo8l/ssd_mobilenet_v1.hef
|
||||
openvino:
|
||||
title: OpenVINO
|
||||
models:
|
||||
@@ -174,15 +171,14 @@ openvino:
|
||||
type: openvino
|
||||
device: GPU # or NPU
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: ssd
|
||||
label: SSDLite MobileNet v2
|
||||
recommended: false
|
||||
@@ -206,14 +202,13 @@ openvino:
|
||||
type: openvino
|
||||
device: GPU # Or NPU
|
||||
|
||||
models:
|
||||
default:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: bgr
|
||||
path: /openvino-model/ssdlite_mobilenet_v2.xml
|
||||
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
|
||||
model:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: bgr
|
||||
path: /openvino-model/ssdlite_mobilenet_v2.xml
|
||||
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
|
||||
- key: yolo-legacy
|
||||
label: YOLO (v3, v4, v7)
|
||||
recommended: false
|
||||
@@ -245,15 +240,14 @@ openvino:
|
||||
type: openvino
|
||||
device: GPU # or NPU
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: yolonas
|
||||
label: YOLO-NAS
|
||||
recommended: false
|
||||
@@ -286,15 +280,14 @@ openvino:
|
||||
type: openvino
|
||||
device: GPU
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolonas
|
||||
width: 320 # <--- should match whatever was set in notebook
|
||||
height: 320 # <--- should match whatever was set in notebook
|
||||
input_tensor: nchw
|
||||
input_pixel_format: bgr
|
||||
path: /config/yolo_nas_s.onnx
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolonas
|
||||
width: 320 # <--- should match whatever was set in notebook
|
||||
height: 320 # <--- should match whatever was set in notebook
|
||||
input_tensor: nchw
|
||||
input_pixel_format: bgr
|
||||
path: /config/yolo_nas_s.onnx
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: yolox
|
||||
label: YOLOX
|
||||
recommended: false
|
||||
@@ -315,11 +308,10 @@ openvino:
|
||||
type: openvino
|
||||
device: GPU
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolox
|
||||
path: /config/model_cache/yolox.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolox
|
||||
path: /config/model_cache/yolox.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: rfdetr
|
||||
label: RF-DETR
|
||||
recommended: false
|
||||
@@ -358,14 +350,13 @@ openvino:
|
||||
type: openvino
|
||||
device: GPU
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: rfdetr
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/rfdetr.onnx # use the filename you generated above
|
||||
model:
|
||||
model_type: rfdetr
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/rfdetr.onnx # use the filename you generated above
|
||||
- key: dfine
|
||||
label: D-FINE / DEIMv2
|
||||
recommended: false
|
||||
@@ -457,15 +448,14 @@ openvino:
|
||||
type: openvino
|
||||
device: CPU
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: dfine
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/dfine-s.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: dfine
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/dfine-s.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
appleSilicon:
|
||||
title: Apple Silicon
|
||||
models:
|
||||
@@ -514,15 +504,14 @@ appleSilicon:
|
||||
type: zmq
|
||||
endpoint: tcp://host.docker.internal:5555
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: yolo-legacy
|
||||
label: YOLO (v3, v4, v7)
|
||||
recommended: false
|
||||
@@ -554,15 +543,14 @@ appleSilicon:
|
||||
type: zmq
|
||||
endpoint: tcp://host.docker.internal:5555
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
onnx:
|
||||
title: ONNX
|
||||
models:
|
||||
@@ -610,15 +598,14 @@ onnx:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: rfdetr
|
||||
label: RF-DETR
|
||||
recommended: false
|
||||
@@ -656,14 +643,13 @@ onnx:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: rfdetr
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/rfdetr.onnx # use the filename you generated above
|
||||
model:
|
||||
model_type: rfdetr
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/rfdetr.onnx # use the filename you generated above
|
||||
- key: yolonas
|
||||
label: YOLO-NAS
|
||||
recommended: false
|
||||
@@ -695,15 +681,14 @@ onnx:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolonas
|
||||
width: 320 # <--- should match whatever was set in notebook
|
||||
height: 320 # <--- should match whatever was set in notebook
|
||||
input_pixel_format: bgr
|
||||
input_tensor: nchw
|
||||
path: /config/yolo_nas_s.onnx
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolonas
|
||||
width: 320 # <--- should match whatever was set in notebook
|
||||
height: 320 # <--- should match whatever was set in notebook
|
||||
input_pixel_format: bgr
|
||||
input_tensor: nchw
|
||||
path: /config/yolo_nas_s.onnx
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: yolox
|
||||
label: YOLOX
|
||||
recommended: false
|
||||
@@ -726,15 +711,14 @@ onnx:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolox
|
||||
width: 416 # <--- should match the imgsize set during model export
|
||||
height: 416 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float_denorm
|
||||
path: /config/model_cache/yolox_tiny.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolox
|
||||
width: 416 # <--- should match the imgsize set during model export
|
||||
height: 416 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float_denorm
|
||||
path: /config/model_cache/yolox_tiny.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: dfine
|
||||
label: D-FINE / DEIMv2
|
||||
recommended: false
|
||||
@@ -825,15 +809,14 @@ onnx:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: dfine
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/dfine_m_obj2coco.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: dfine
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/dfine_m_obj2coco.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
- key: yolo-legacy
|
||||
label: YOLO (v3, v4, v7)
|
||||
recommended: false
|
||||
@@ -864,15 +847,14 @@ onnx:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # <--- should match the imgsize set during model export
|
||||
height: 320 # <--- should match the imgsize set during model export
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/yolo.onnx # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
cpu:
|
||||
title: CPU
|
||||
models:
|
||||
@@ -946,19 +928,18 @@ memryx:
|
||||
type: memryx
|
||||
device: PCIe:0
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolonas
|
||||
width: 320 # (Can be set to 640 for higher resolution)
|
||||
height: 320 # (Can be set to 640 for higher resolution)
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/yolonas.zip
|
||||
# The .zip file must contain:
|
||||
# ├── yolonas.dfp (a file ending with .dfp)
|
||||
# └── yolonas_post.onnx (optional; only if the model includes a cropped post-processing network)
|
||||
model:
|
||||
model_type: yolonas
|
||||
width: 320 # (Can be set to 640 for higher resolution)
|
||||
height: 320 # (Can be set to 640 for higher resolution)
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/yolonas.zip
|
||||
# The .zip file must contain:
|
||||
# ├── yolonas.dfp (a file ending with .dfp)
|
||||
# └── yolonas_post.onnx (optional; only if the model includes a cropped post-processing network)
|
||||
- key: yolov9
|
||||
label: YOLOv9
|
||||
recommended: false
|
||||
@@ -984,18 +965,17 @@ memryx:
|
||||
type: memryx
|
||||
device: PCIe:0
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolo-generic
|
||||
width: 320 # (Can be set to 640 for higher resolution)
|
||||
height: 320 # (Can be set to 640 for higher resolution)
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/yolov9.zip
|
||||
# The .zip file must contain:
|
||||
# ├── yolov9.dfp (a file ending with .dfp)
|
||||
model:
|
||||
model_type: yolo-generic
|
||||
width: 320 # (Can be set to 640 for higher resolution)
|
||||
height: 320 # (Can be set to 640 for higher resolution)
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/yolov9.zip
|
||||
# The .zip file must contain:
|
||||
# ├── yolov9.dfp (a file ending with .dfp)
|
||||
- key: yolox
|
||||
label: YOLOX
|
||||
recommended: false
|
||||
@@ -1021,18 +1001,17 @@ memryx:
|
||||
type: memryx
|
||||
device: PCIe:0
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: yolox
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float_denorm
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/yolox.zip
|
||||
# The .zip file must contain:
|
||||
# ├── yolox.dfp (a file ending with .dfp)
|
||||
model:
|
||||
model_type: yolox
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float_denorm
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/yolox.zip
|
||||
# The .zip file must contain:
|
||||
# ├── yolox.dfp (a file ending with .dfp)
|
||||
- key: ssd
|
||||
label: SSDLite MobileNet v2
|
||||
recommended: false
|
||||
@@ -1058,19 +1037,18 @@ memryx:
|
||||
type: memryx
|
||||
device: PCIe:0
|
||||
|
||||
models:
|
||||
default:
|
||||
model_type: ssd
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/ssdlite_mobilenet.zip
|
||||
# The .zip file must contain:
|
||||
# ├── ssdlite_mobilenet.dfp (a file ending with .dfp)
|
||||
# └── ssdlite_mobilenet_post.onnx (optional; only if the model includes a cropped post-processing network)
|
||||
model:
|
||||
model_type: ssd
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
# Optional: The model is normally fetched through the runtime, so 'path' can be omitted unless you want to use a custom or local model.
|
||||
# path: /config/ssdlite_mobilenet.zip
|
||||
# The .zip file must contain:
|
||||
# ├── ssdlite_mobilenet.dfp (a file ending with .dfp)
|
||||
# └── ssdlite_mobilenet_post.onnx (optional; only if the model includes a cropped post-processing network)
|
||||
tensorrt:
|
||||
title: TensorRT
|
||||
models:
|
||||
@@ -1109,14 +1087,13 @@ tensorrt:
|
||||
type: tensorrt
|
||||
device: 0 #This is the default, select the first GPU
|
||||
|
||||
models:
|
||||
default:
|
||||
path: /config/model_cache/tensorrt/yolov7-320.trt # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
input_tensor: nchw
|
||||
input_pixel_format: rgb
|
||||
width: 320 # MUST match the chosen model i.e yolov7-320 -> 320, yolov4-416 -> 416
|
||||
height: 320 # MUST match the chosen model i.e yolov7-320 -> 320 yolov4-416 -> 416
|
||||
model:
|
||||
path: /config/model_cache/tensorrt/yolov7-320.trt # use the filename you generated above
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
input_tensor: nchw
|
||||
input_pixel_format: rgb
|
||||
width: 320 # MUST match the chosen model i.e yolov7-320 -> 320, yolov4-416 -> 416
|
||||
height: 320 # MUST match the chosen model i.e yolov7-320 -> 320 yolov4-416 -> 416
|
||||
synaptics:
|
||||
title: Synaptics
|
||||
models:
|
||||
@@ -1284,15 +1261,14 @@ axengine:
|
||||
axengine:
|
||||
type: axengine
|
||||
|
||||
models:
|
||||
default:
|
||||
path: frigate-yolov9-tiny
|
||||
model_type: yolo-generic
|
||||
width: 320
|
||||
height: 320
|
||||
input_dtype: int
|
||||
input_pixel_format: bgr
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
model:
|
||||
path: frigate-yolov9-tiny
|
||||
model_type: yolo-generic
|
||||
width: 320
|
||||
height: 320
|
||||
input_dtype: int
|
||||
input_pixel_format: bgr
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
degirumAiServer:
|
||||
title: DeGirum AI Server
|
||||
models:
|
||||
|
||||
@@ -157,51 +157,44 @@ auth:
|
||||
- front_door
|
||||
- back_yard
|
||||
|
||||
# Optional: named object detection models (default: a single model named "default")
|
||||
# Each entry defines a model; cameras choose which model to use with detect.model.
|
||||
# Detectors that support multiple models (openvino, onnx, tensorrt, cpu, rknn) run
|
||||
# one instance per model in use. Detectors that only support a single model
|
||||
# (edgetpu, hailo8l, memryx, and others) are assigned to models round robin, so at
|
||||
# least as many of those detectors as models are required when no multi-model
|
||||
# capable detector is configured.
|
||||
# Optional: model modifications
|
||||
# NOTE: The default values are for the EdgeTPU detector.
|
||||
# Other detectors will require the model config to be set.
|
||||
models:
|
||||
default:
|
||||
# Required: path to the model. Frigate+ models use plus://<model_id> (default: automatic based on detector)
|
||||
path: /edgetpu_model.tflite
|
||||
# Required: path to the labelmap (default: shown below)
|
||||
labelmap_path: /labelmap.txt
|
||||
# Required: Object detection model input width (default: shown below)
|
||||
width: 320
|
||||
# Required: Object detection model input height (default: shown below)
|
||||
height: 320
|
||||
# Required: Object detection model input colorspace
|
||||
# Valid values are rgb, bgr, or yuv. (default: shown below)
|
||||
input_pixel_format: rgb
|
||||
# Required: Object detection model input tensor format
|
||||
# Valid values are nhwc, nchw, hwnc, or hwcn (default: shown below)
|
||||
input_tensor: nhwc
|
||||
# Optional: Data type of the model input tensor
|
||||
# Valid values are float, float_denorm, or int (default: shown below)
|
||||
input_dtype: int
|
||||
# Required: Object detection model architecture, used by detectors that support more
|
||||
# than one model type (openvino, onnx, rknn, memryx, axengine, synaptics, and others)
|
||||
# Valid values are ssd, yolox, yolonas, yolo-generic, rfdetr, dfine (default: shown below)
|
||||
model_type: ssd
|
||||
# Required: Label name modifications. These are merged into the standard labelmap.
|
||||
labelmap:
|
||||
2: vehicle
|
||||
# Optional: Map of object labels to their attribute labels (default: depends on model)
|
||||
attributes_map:
|
||||
person:
|
||||
- amazon
|
||||
- face
|
||||
car:
|
||||
- amazon
|
||||
- fedex
|
||||
- license_plate
|
||||
- ups
|
||||
model:
|
||||
# Required: path to the model. Frigate+ models use plus://<model_id> (default: automatic based on detector)
|
||||
path: /edgetpu_model.tflite
|
||||
# Required: path to the labelmap (default: shown below)
|
||||
labelmap_path: /labelmap.txt
|
||||
# Required: Object detection model input width (default: shown below)
|
||||
width: 320
|
||||
# Required: Object detection model input height (default: shown below)
|
||||
height: 320
|
||||
# Required: Object detection model input colorspace
|
||||
# Valid values are rgb, bgr, or yuv. (default: shown below)
|
||||
input_pixel_format: rgb
|
||||
# Required: Object detection model input tensor format
|
||||
# Valid values are nhwc, nchw, hwnc, or hwcn (default: shown below)
|
||||
input_tensor: nhwc
|
||||
# Optional: Data type of the model input tensor
|
||||
# Valid values are float, float_denorm, or int (default: shown below)
|
||||
input_dtype: int
|
||||
# Required: Object detection model architecture, used by detectors that support more
|
||||
# than one model type (openvino, onnx, rknn, memryx, axengine, synaptics, and others)
|
||||
# Valid values are ssd, yolox, yolonas, yolo-generic, rfdetr, dfine (default: shown below)
|
||||
model_type: ssd
|
||||
# Required: Label name modifications. These are merged into the standard labelmap.
|
||||
labelmap:
|
||||
2: vehicle
|
||||
# Optional: Map of object labels to their attribute labels (default: depends on model)
|
||||
attributes_map:
|
||||
person:
|
||||
- amazon
|
||||
- face
|
||||
car:
|
||||
- amazon
|
||||
- fedex
|
||||
- license_plate
|
||||
- ups
|
||||
|
||||
# Optional: Audio Events Configuration
|
||||
# NOTE: Can be overridden at the camera level
|
||||
@@ -309,9 +302,6 @@ ffmpeg:
|
||||
detect:
|
||||
# Optional: enables detection for the camera (default: shown below)
|
||||
enabled: False
|
||||
# Optional: name of the model (key under models) used by this camera
|
||||
# (default: the only defined model, or the model named "default")
|
||||
model: default
|
||||
# Optional: width of the frame for the input with the detect role (default: use native stream resolution)
|
||||
width: 1280
|
||||
# Optional: height of the frame for the input with the detect role (default: use native stream resolution)
|
||||
|
||||
@@ -192,13 +192,12 @@ Navigate to <NavPath path="Settings > System > Detectors and model" /> and open
|
||||
|
||||
```yaml
|
||||
# Optional: model config
|
||||
models:
|
||||
default:
|
||||
path: /path/to/model
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: "nhwc"
|
||||
input_pixel_format: "bgr"
|
||||
model:
|
||||
path: /path/to/model
|
||||
width: 320
|
||||
height: 320
|
||||
input_tensor: "nhwc"
|
||||
input_pixel_format: "bgr"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -215,16 +214,15 @@ If the labelmap is customized then the labels used for alerts will need to be ad
|
||||
The labelmap can be customized to your needs. A common reason to do this is to combine multiple object types that are easily confused when you don't need to be as granular such as car/truck. By default, truck is renamed to car because they are often confused. You cannot add new object types, but you can change the names of existing objects in the model.
|
||||
|
||||
```yaml
|
||||
models:
|
||||
default:
|
||||
labelmap:
|
||||
2: vehicle
|
||||
3: vehicle
|
||||
5: vehicle
|
||||
7: vehicle
|
||||
15: animal
|
||||
16: animal
|
||||
17: animal
|
||||
model:
|
||||
labelmap:
|
||||
2: vehicle
|
||||
3: vehicle
|
||||
5: vehicle
|
||||
7: vehicle
|
||||
15: animal
|
||||
16: animal
|
||||
17: animal
|
||||
```
|
||||
|
||||
Note that if you rename objects in the labelmap, you will also need to update your `objects -> track` list as well.
|
||||
|
||||
@@ -334,14 +334,13 @@ detectors:
|
||||
type: openvino
|
||||
device: AUTO
|
||||
|
||||
models:
|
||||
default:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: bgr
|
||||
path: /openvino-model/ssdlite_mobilenet_v2.xml
|
||||
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
|
||||
model:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: bgr
|
||||
path: /openvino-model/ssdlite_mobilenet_v2.xml
|
||||
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
|
||||
|
||||
record:
|
||||
enabled: True
|
||||
|
||||
@@ -91,41 +91,6 @@ The best detection accuracy comes from a model trained on images that look like
|
||||
|
||||
:::
|
||||
|
||||
### Running multiple models
|
||||
|
||||
Models are defined as named entries under `models`, and each camera selects the model it uses with `detect.model`. This makes it possible to run different models for different groups of cameras, for example a dedicated model for indoor cameras, outdoor cameras, or thermal cameras.
|
||||
|
||||
```yaml
|
||||
detectors:
|
||||
ov:
|
||||
type: openvino
|
||||
device: GPU
|
||||
|
||||
models:
|
||||
indoor:
|
||||
path: /config/model_cache/indoor-model.xml
|
||||
model_type: yolo-generic
|
||||
width: 320
|
||||
height: 320
|
||||
outdoor:
|
||||
path: plus://<your_model_id>
|
||||
|
||||
cameras:
|
||||
living_room:
|
||||
detect:
|
||||
model: indoor
|
||||
driveway:
|
||||
detect:
|
||||
model: outdoor
|
||||
```
|
||||
|
||||
When only one model is defined, all cameras use it automatically. With multiple models, cameras use the model named `default` unless `detect.model` selects another one; `detect.model` can also be set globally and overridden per camera.
|
||||
|
||||
How detectors handle multiple models depends on the hardware:
|
||||
|
||||
- **Detectors that support multiple models** (`openvino`, `onnx`, `tensorrt`, `cpu`, `rknn`): a single detector entry is automatically expanded into one instance per model in use. For example, detector `ov` with models `indoor` and `outdoor` runs as `ov_indoor` and `ov_outdoor`, and each instance appears separately in the System Metrics page. Keep in mind that each instance loads its own copy of the model, which increases GPU memory usage.
|
||||
- **Detectors that only support a single model** (`edgetpu`, `hailo8l`, `memryx`, and other single-session hardware): each detector entry serves exactly one model. Detector entries are assigned to models round robin, so running two models on Coral hardware requires two Corals. If these are the only detectors configured and there are fewer of them than models in use, Frigate will fail to start with an error explaining the options.
|
||||
|
||||
# Officially Supported Detectors
|
||||
|
||||
Frigate provides a number of builtin detector types. By default, Frigate will use a single CPU detector. Other detectors may require additional configuration as described below. When using multiple detectors they will run in dedicated processes, but pull from a common queue of detection requests from across all cameras.
|
||||
@@ -824,12 +789,11 @@ You can set it to:
|
||||
- A path to some model.json.
|
||||
|
||||
```yaml
|
||||
models:
|
||||
default:
|
||||
path: ./mobilenet_v2_ssd_coco--300x300_quant_n2x_orca1_1 # directory to model .json and file
|
||||
width: 300 # width is in the model name as the first number in the "int"x"int" section
|
||||
height: 300 # height is in the model name as the second number in the "int"x"int" section
|
||||
input_pixel_format: rgb/bgr # look at the model.json to figure out which to put here
|
||||
model:
|
||||
path: ./mobilenet_v2_ssd_coco--300x300_quant_n2x_orca1_1 # directory to model .json and file
|
||||
width: 300 # width is in the model name as the first number in the "int"x"int" section
|
||||
height: 300 # height is in the model name as the second number in the "int"x"int" section
|
||||
input_pixel_format: rgb/bgr # look at the model.json to figure out which to put here
|
||||
```
|
||||
|
||||
#### Local Inference
|
||||
@@ -845,12 +809,11 @@ It is also possible to eliminate the need for an AI server and run the hardware
|
||||
Once `degirum_detector` is setup, you can choose a model through 'model' section in the `config.yml` file.
|
||||
|
||||
```yaml
|
||||
models:
|
||||
default:
|
||||
path: mobilenet_v2_ssd_coco--300x300_quant_n2x_orca1_1
|
||||
width: 300 # width is in the model name as the first number in the "int"x"int" section
|
||||
height: 300 # height is in the model name as the second number in the "int"x"int" section
|
||||
input_pixel_format: rgb/bgr # look at the model.json to figure out which to put here
|
||||
model:
|
||||
path: mobilenet_v2_ssd_coco--300x300_quant_n2x_orca1_1
|
||||
width: 300 # width is in the model name as the first number in the "int"x"int" section
|
||||
height: 300 # height is in the model name as the second number in the "int"x"int" section
|
||||
input_pixel_format: rgb/bgr # look at the model.json to figure out which to put here
|
||||
```
|
||||
|
||||
#### AI Hub Cloud Inference
|
||||
@@ -866,12 +829,11 @@ If you do not possess whatever hardware you want to run, there's also the option
|
||||
Once `degirum_detector` is setup, you can choose a model through 'model' section in the `config.yml` file.
|
||||
|
||||
```yaml
|
||||
models:
|
||||
default:
|
||||
path: mobilenet_v2_ssd_coco--300x300_quant_n2x_orca1_1
|
||||
width: 300 # width is in the model name as the first number in the "int"x"int" section
|
||||
height: 300 # height is in the model name as the second number in the "int"x"int" section
|
||||
input_pixel_format: rgb/bgr # look at the model.json to figure out which to put here
|
||||
model:
|
||||
path: mobilenet_v2_ssd_coco--300x300_quant_n2x_orca1_1
|
||||
width: 300 # width is in the model name as the first number in the "int"x"int" section
|
||||
height: 300 # height is in the model name as the second number in the "int"x"int" section
|
||||
input_pixel_format: rgb/bgr # look at the model.json to figure out which to put here
|
||||
```
|
||||
|
||||
## AXERA
|
||||
|
||||
@@ -228,14 +228,13 @@ detectors: # <---- add detectors
|
||||
device: GPU
|
||||
|
||||
# We will use the default MobileNet_v2 model from OpenVINO.
|
||||
models:
|
||||
default:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: bgr
|
||||
path: /openvino-model/ssdlite_mobilenet_v2.xml
|
||||
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
|
||||
model:
|
||||
width: 300
|
||||
height: 300
|
||||
input_tensor: nhwc
|
||||
input_pixel_format: bgr
|
||||
path: /openvino-model/ssdlite_mobilenet_v2.xml
|
||||
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
|
||||
|
||||
cameras:
|
||||
name_of_your_camera:
|
||||
|
||||
@@ -64,9 +64,8 @@ You can either choose the new model from the <NavPath path="Settings > System >
|
||||
```yaml
|
||||
detectors: ...
|
||||
|
||||
models:
|
||||
default:
|
||||
path: plus://<your_model_id>
|
||||
model:
|
||||
path: plus://<your_model_id>
|
||||
```
|
||||
|
||||
:::note
|
||||
@@ -80,11 +79,10 @@ Models are downloaded into the `/config/model_cache` folder and only downloaded
|
||||
If needed, you can override the labelmap for Frigate+ models. This is not recommended as renaming labels will break the Submit to Frigate+ feature if the labels are not available in Frigate+.
|
||||
|
||||
```yaml
|
||||
models:
|
||||
default:
|
||||
path: plus://<your_model_id>
|
||||
labelmap:
|
||||
3: animal
|
||||
4: animal
|
||||
5: animal
|
||||
model:
|
||||
path: plus://<your_model_id>
|
||||
labelmap:
|
||||
3: animal
|
||||
4: animal
|
||||
5: animal
|
||||
```
|
||||
|
||||
@@ -38,9 +38,8 @@ Navigate to <NavPath path="Settings > System > Detectors and model" />. In the *
|
||||
```yaml
|
||||
detectors: ...
|
||||
|
||||
models:
|
||||
default:
|
||||
path: plus://<your_model_id>
|
||||
model:
|
||||
path: plus://<your_model_id>
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
+25
-37
@@ -372,40 +372,31 @@ def config(request: Request):
|
||||
config["go2rtc"]["streams"][stream_name] = cleaned
|
||||
|
||||
config["plus"] = {"enabled": request.app.frigate_config.plus_api.is_active()}
|
||||
config["model"]["colormap"] = config_obj.model.colormap
|
||||
config["model"]["all_attributes"] = config_obj.model.all_attributes
|
||||
config["model"]["non_logo_attributes"] = config_obj.model.non_logo_attributes
|
||||
|
||||
for model_key, model in config_obj.models.items():
|
||||
model_dict = config["models"][model_key]
|
||||
model_dict["colormap"] = model.colormap
|
||||
model_dict["all_attributes"] = model.all_attributes
|
||||
model_dict["non_logo_attributes"] = model.non_logo_attributes
|
||||
# Add model plus data if plus is enabled
|
||||
if config["plus"]["enabled"]:
|
||||
model_path = config.get("model", {}).get("path")
|
||||
if model_path:
|
||||
model_json_path = FilePath(model_path).with_suffix(".json")
|
||||
try:
|
||||
with open(model_json_path) as f:
|
||||
model_plus_data = json.load(f)
|
||||
config["model"]["plus"] = model_plus_data
|
||||
except FileNotFoundError:
|
||||
config["model"]["plus"] = None
|
||||
except json.JSONDecodeError:
|
||||
config["model"]["plus"] = None
|
||||
else:
|
||||
config["model"]["plus"] = None
|
||||
|
||||
# Add model plus data if plus is enabled
|
||||
if config["plus"]["enabled"]:
|
||||
model_plus_data = None
|
||||
|
||||
if model.path:
|
||||
model_json_path = FilePath(model.path).with_suffix(".json")
|
||||
try:
|
||||
with open(model_json_path) as f:
|
||||
model_plus_data = json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
model_plus_data = None
|
||||
|
||||
model_dict["plus"] = model_plus_data
|
||||
|
||||
# legacy single-model block kept for frontend compatibility, remove
|
||||
# once the UI is fully multi-model aware
|
||||
default_model_key = (
|
||||
"default" if "default" in config_obj.models else next(iter(config_obj.models))
|
||||
)
|
||||
config["model"] = config["models"][default_model_key]
|
||||
|
||||
# use each detector's assigned merged labelmap
|
||||
for key, detector_config in config["detectors"].items():
|
||||
if config_obj.detectors[key].model:
|
||||
detector_config["model"]["labelmap"] = config_obj.detectors[
|
||||
key
|
||||
].model.merged_labelmap
|
||||
# use merged labelamp
|
||||
for detector_config in config["detectors"].values():
|
||||
detector_config["model"]["labelmap"] = (
|
||||
request.app.frigate_config.model.merged_labelmap
|
||||
)
|
||||
|
||||
return JSONResponse(content=config)
|
||||
|
||||
@@ -1332,11 +1323,8 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
|
||||
|
||||
modelList = models["list"]
|
||||
|
||||
# current model type, based on the default model until the UI is
|
||||
# fully multi-model aware
|
||||
config_models = request.app.frigate_config.models
|
||||
default_model = config_models.get("default") or next(iter(config_models.values()))
|
||||
modelType = default_model.model_type
|
||||
# current model type
|
||||
modelType = request.app.frigate_config.model.model_type
|
||||
|
||||
# current detectorType for comparing to supportedDetectors
|
||||
detectorType = list(request.app.frigate_config.detectors.values())[0].type
|
||||
|
||||
@@ -813,7 +813,7 @@ async def event_snapshot(
|
||||
timestamp_style=request.app.frigate_config.cameras[
|
||||
event.camera
|
||||
].timestamp_style,
|
||||
colormap=request.app.frigate_config.model_for_camera(event.camera).colormap,
|
||||
colormap=request.app.frigate_config.model.colormap,
|
||||
)
|
||||
except DoesNotExist:
|
||||
# see if the object is currently being tracked
|
||||
|
||||
+17
-19
@@ -97,9 +97,7 @@ class FrigateApp:
|
||||
self.metrics_manager = manager
|
||||
self.audio_process: mp.Process | None = None
|
||||
self.stop_event = stop_event
|
||||
self.detection_queues: dict[str, Queue] = {
|
||||
model_key: mp.Queue() for model_key in config.models
|
||||
}
|
||||
self.detection_queue: Queue = mp.Queue()
|
||||
self.detectors: dict[str, ObjectDetectProcess] = {}
|
||||
self.detection_shms: list[mp.shared_memory.SharedMemory] = []
|
||||
self.log_queue: Queue = mp.Queue()
|
||||
@@ -365,14 +363,20 @@ class FrigateApp:
|
||||
)
|
||||
|
||||
def start_detectors(self) -> None:
|
||||
for name, camera_config in self.config.cameras.items():
|
||||
camera_model = self.config.models[camera_config.detect.model]
|
||||
|
||||
for name in self.config.cameras.keys():
|
||||
try:
|
||||
largest_frame = max(
|
||||
[
|
||||
det.model.height * det.model.width * 3
|
||||
if det.model is not None
|
||||
else 320
|
||||
for det in self.config.detectors.values()
|
||||
]
|
||||
)
|
||||
shm_in = UntrackedSharedMemory(
|
||||
name=name,
|
||||
create=True,
|
||||
size=camera_model.height * camera_model.width * 3,
|
||||
size=largest_frame,
|
||||
)
|
||||
except FileExistsError:
|
||||
shm_in = UntrackedSharedMemory(name=name)
|
||||
@@ -387,16 +391,11 @@ class FrigateApp:
|
||||
self.detection_shms.append(shm_in)
|
||||
self.detection_shms.append(shm_out)
|
||||
|
||||
for name, detector_config in self.config.detector_instances.items():
|
||||
cameras_using_model = [
|
||||
camera_name
|
||||
for camera_name, camera_config in self.config.cameras.items()
|
||||
if camera_config.detect.model == detector_config.model_key
|
||||
]
|
||||
for name, detector_config in self.config.detectors.items():
|
||||
self.detectors[name] = ObjectDetectProcess(
|
||||
name,
|
||||
self.detection_queues[detector_config.model_key],
|
||||
cameras_using_model,
|
||||
self.detection_queue,
|
||||
list(self.config.cameras.keys()),
|
||||
self.config,
|
||||
detector_config,
|
||||
self.stop_event,
|
||||
@@ -431,7 +430,7 @@ class FrigateApp:
|
||||
def start_camera_processor(self) -> None:
|
||||
self.camera_maintainer = CameraMaintainer(
|
||||
self.config,
|
||||
self.detection_queues,
|
||||
self.detection_queue,
|
||||
self.detected_frames_queue,
|
||||
self.camera_metrics,
|
||||
self.ptz_metrics,
|
||||
@@ -686,9 +685,8 @@ class FrigateApp:
|
||||
for detector in self.detectors.values():
|
||||
detector.stop()
|
||||
|
||||
for detection_queue in self.detection_queues.values():
|
||||
empty_and_close_queue(detection_queue)
|
||||
logger.info("Detection queues closed")
|
||||
empty_and_close_queue(self.detection_queue)
|
||||
logger.info("Detection queue closed")
|
||||
|
||||
self.detected_frames_processor.join()
|
||||
empty_and_close_queue(self.detected_frames_queue)
|
||||
|
||||
@@ -178,7 +178,7 @@ class CameraActivityManager:
|
||||
return
|
||||
|
||||
for label in camera_config.objects.track:
|
||||
if label in self.config.model_for_camera(camera).non_logo_attributes:
|
||||
if label in self.config.model.non_logo_attributes:
|
||||
continue
|
||||
|
||||
new_count = all_objects[label]
|
||||
|
||||
@@ -29,7 +29,7 @@ class CameraMaintainer(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
config: FrigateConfig,
|
||||
detection_queues: dict[str, Queue],
|
||||
detection_queue: Queue,
|
||||
detected_frames_queue: Queue,
|
||||
camera_metrics: DictProxy,
|
||||
ptz_metrics: dict[str, PTZMetrics],
|
||||
@@ -38,7 +38,7 @@ class CameraMaintainer(threading.Thread):
|
||||
):
|
||||
super().__init__(name="camera_processor")
|
||||
self.config = config
|
||||
self.detection_queues = detection_queues
|
||||
self.detection_queue = detection_queue
|
||||
self.detected_frames_queue = detected_frames_queue
|
||||
self.stop_event = stop_event
|
||||
self.camera_metrics = camera_metrics
|
||||
@@ -79,11 +79,10 @@ class CameraMaintainer(threading.Thread):
|
||||
# create or update region grids for each camera
|
||||
for camera in self.config.cameras.values():
|
||||
assert camera.name is not None
|
||||
camera_model = self.config.models[camera.detect.model]
|
||||
self.region_grids[camera.name] = get_camera_regions_grid(
|
||||
camera.name,
|
||||
camera.detect,
|
||||
max(camera_model.width, camera_model.height),
|
||||
max(self.config.model.width, self.config.model.height),
|
||||
)
|
||||
|
||||
def __calculate_shm_frame_count(self) -> int:
|
||||
@@ -116,8 +115,6 @@ class CameraMaintainer(threading.Thread):
|
||||
|
||||
camera_stop_event = self.__ensure_camera_stop_event(name)
|
||||
|
||||
camera_model = self.config.models[config.detect.model]
|
||||
|
||||
if runtime:
|
||||
self.camera_metrics[name] = CameraMetrics(self.metrics_manager)
|
||||
self.ptz_metrics[name] = PTZMetrics(
|
||||
@@ -126,24 +123,32 @@ class CameraMaintainer(threading.Thread):
|
||||
self.region_grids[name] = get_camera_regions_grid(
|
||||
name,
|
||||
config.detect,
|
||||
max(camera_model.width, camera_model.height),
|
||||
max(self.config.model.width, self.config.model.height),
|
||||
)
|
||||
|
||||
try:
|
||||
largest_frame = max(
|
||||
[
|
||||
det.model.height * det.model.width * 3
|
||||
if det.model is not None
|
||||
else 320
|
||||
for det in self.config.detectors.values()
|
||||
]
|
||||
)
|
||||
UntrackedSharedMemory(name=f"out-{name}", create=True, size=20 * 6 * 4)
|
||||
UntrackedSharedMemory(
|
||||
name=name,
|
||||
create=True,
|
||||
size=camera_model.height * camera_model.width * 3,
|
||||
size=largest_frame,
|
||||
)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
camera_process = CameraTracker(
|
||||
config,
|
||||
camera_model,
|
||||
camera_model.merged_labelmap,
|
||||
self.detection_queues[config.detect.model],
|
||||
self.config.model,
|
||||
self.config.model.merged_labelmap,
|
||||
self.detection_queue,
|
||||
self.detected_frames_queue,
|
||||
self.camera_metrics[name],
|
||||
self.ptz_metrics[name],
|
||||
|
||||
@@ -40,7 +40,6 @@ class CameraState:
|
||||
self.name = name
|
||||
self.config = config
|
||||
self.camera_config = config.cameras[name]
|
||||
self.model_config = config.model_for_camera(name)
|
||||
self.frame_manager = frame_manager
|
||||
self.best_objects: dict[str, TrackedObject] = {}
|
||||
self.tracked_objects: dict[str, TrackedObject] = {}
|
||||
@@ -102,7 +101,7 @@ class CameraState:
|
||||
thickness = 1
|
||||
else:
|
||||
thickness = 2
|
||||
color = self.model_config.colormap.get(
|
||||
color = self.config.model.colormap.get(
|
||||
obj["label"], (255, 255, 255)
|
||||
)
|
||||
else:
|
||||
@@ -126,7 +125,7 @@ class CameraState:
|
||||
and obj["frame_time"] == frame_time
|
||||
):
|
||||
thickness = 5
|
||||
color = self.model_config.colormap.get(
|
||||
color = self.config.model.colormap.get(
|
||||
obj["label"], (255, 255, 255)
|
||||
)
|
||||
|
||||
@@ -262,7 +261,7 @@ class CameraState:
|
||||
if draw_options.get("paths"):
|
||||
for obj in tracked_objects.values():
|
||||
if obj["frame_time"] == frame_time and obj["path_data"]:
|
||||
color = self.model_config.colormap.get(
|
||||
color = self.config.model.colormap.get(
|
||||
obj["label"], (255, 255, 255)
|
||||
)
|
||||
|
||||
@@ -367,7 +366,7 @@ class CameraState:
|
||||
for id in new_ids:
|
||||
logger.debug(f"{self.name}: New tracked object ID: {id}")
|
||||
new_obj = tracked_objects[id] = TrackedObject(
|
||||
self.model_config,
|
||||
self.config.model,
|
||||
self.camera_config,
|
||||
self.config.ui,
|
||||
self.frame_cache,
|
||||
@@ -511,7 +510,7 @@ class CameraState:
|
||||
sub_label = None
|
||||
|
||||
if obj.obj_data.get("sub_label"):
|
||||
if obj.obj_data["sub_label"][0] in self.model_config.all_attributes:
|
||||
if obj.obj_data["sub_label"][0] in self.config.model.all_attributes:
|
||||
label = obj.obj_data["sub_label"][0]
|
||||
else:
|
||||
label = f"{object_type}-verified"
|
||||
|
||||
@@ -156,11 +156,10 @@ class Dispatcher:
|
||||
if camera not in self.config.cameras:
|
||||
return None
|
||||
|
||||
camera_model = self.config.model_for_camera(camera)
|
||||
grid = get_camera_regions_grid(
|
||||
camera,
|
||||
self.config.cameras[camera].detect,
|
||||
max(camera_model.width, camera_model.height),
|
||||
max(self.config.model.width, self.config.model.height),
|
||||
)
|
||||
return grid
|
||||
|
||||
|
||||
@@ -50,11 +50,6 @@ class DetectConfig(FrigateBaseModel):
|
||||
title="Enable object detection",
|
||||
description="Enable or disable object detection for all cameras; can be overridden per-camera.",
|
||||
)
|
||||
model: str | None = Field(
|
||||
default=None,
|
||||
title="Detection model name",
|
||||
description="Name of the model (key under `models`) used by this camera. Defaults to the only defined model, or the model named 'default'.",
|
||||
)
|
||||
height: int | None = Field(
|
||||
default=None,
|
||||
title="Detect height",
|
||||
|
||||
+32
-154
@@ -4,7 +4,6 @@ import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Self
|
||||
|
||||
import numpy as np
|
||||
@@ -12,7 +11,6 @@ from pydantic import (
|
||||
BaseModel,
|
||||
ConfigDict,
|
||||
Field,
|
||||
PrivateAttr,
|
||||
TypeAdapter,
|
||||
ValidationInfo,
|
||||
field_validator,
|
||||
@@ -21,11 +19,7 @@ from pydantic import (
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
from frigate.const import REGEX_JSON
|
||||
from frigate.detectors import (
|
||||
DetectorConfig,
|
||||
ModelConfig,
|
||||
assign_detector_instances,
|
||||
)
|
||||
from frigate.detectors import DetectorConfig, ModelConfig
|
||||
from frigate.detectors.detector_config import BaseDetectorConfig
|
||||
from frigate.plus import PlusApi
|
||||
from frigate.util.builtin import (
|
||||
@@ -115,7 +109,7 @@ DEFAULT_CONFIG = f"""
|
||||
mqtt:
|
||||
enabled: False
|
||||
|
||||
{_render_default_yaml({"detectors": NEW_CONFIG_DETECTORS, "models": {"default": DEFAULT_MODEL}})}
|
||||
{_render_default_yaml({"detectors": NEW_CONFIG_DETECTORS, "model": DEFAULT_MODEL})}
|
||||
cameras: {{}} # No cameras defined, UI wizard should be used
|
||||
version: {CURRENT_CONFIG_VERSION}
|
||||
"""
|
||||
@@ -509,10 +503,10 @@ class FrigateConfig(FrigateBaseModel):
|
||||
title="Detector hardware",
|
||||
description="Configuration for object detectors (CPU, GPU, ONNX backends) and any detector-specific model settings.",
|
||||
)
|
||||
models: dict[str, ModelConfig] = Field(
|
||||
default_factory=lambda: {"default": ModelConfig()},
|
||||
title="Detection models",
|
||||
description="Named object detection models. Cameras select a model with detect.model; detectors are assigned to models automatically.",
|
||||
model: ModelConfig = Field(
|
||||
default_factory=ModelConfig,
|
||||
title="Detection model",
|
||||
description="Settings to configure a custom object detection model and its input shape.",
|
||||
)
|
||||
|
||||
# GenAI config (named provider configs: name -> GenAIConfig)
|
||||
@@ -627,37 +621,11 @@ class FrigateConfig(FrigateBaseModel):
|
||||
)
|
||||
|
||||
_plus_api: PlusApi
|
||||
_detector_instances: dict[str, BaseDetectorConfig] = PrivateAttr(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
@property
|
||||
def plus_api(self) -> PlusApi:
|
||||
return self._plus_api
|
||||
|
||||
@property
|
||||
def detector_instances(self) -> dict[str, BaseDetectorConfig]:
|
||||
"""Runtime detector instances expanded per assigned model."""
|
||||
return self._detector_instances
|
||||
|
||||
def model_for_camera(self, camera_name: str) -> ModelConfig:
|
||||
"""Return the detection model config used by the given camera."""
|
||||
return self.models[self.cameras[camera_name].detect.model]
|
||||
|
||||
@field_validator("models")
|
||||
@classmethod
|
||||
def validate_model_names(cls, v: dict[str, ModelConfig]):
|
||||
if not v:
|
||||
raise ValueError("At least one model must be defined under models")
|
||||
|
||||
for name in v.keys():
|
||||
if not re.match(r"^[a-zA-Z0-9_-]+$", name):
|
||||
raise ValueError(
|
||||
f"Invalid model name '{name}'. Model names can only contain letters, numbers, underscores, and hyphens"
|
||||
)
|
||||
|
||||
return v
|
||||
|
||||
@model_validator(mode="after")
|
||||
def post_validation(self, info: ValidationInfo) -> Self:
|
||||
# Load plus api from context, if possible.
|
||||
@@ -703,12 +671,7 @@ class FrigateConfig(FrigateBaseModel):
|
||||
)
|
||||
|
||||
# set default min_score for object attributes
|
||||
all_model_attributes = {
|
||||
attribute
|
||||
for model in self.models.values()
|
||||
for attribute in model.all_attributes
|
||||
}
|
||||
for attribute in sorted(all_model_attributes):
|
||||
for attribute in self.model.all_attributes:
|
||||
existing = self.objects.filters.get(attribute)
|
||||
if existing is None:
|
||||
self.objects.filters[attribute] = FilterConfig(min_score=0.7)
|
||||
@@ -758,18 +721,8 @@ class FrigateConfig(FrigateBaseModel):
|
||||
exclude_unset=True,
|
||||
)
|
||||
|
||||
# capture raw model dumps before plus models are loaded so detector
|
||||
# instances can run their own detector-specific plus validation
|
||||
raw_model_dumps = {
|
||||
name: model.model_dump(exclude_unset=True, warnings="none")
|
||||
for name, model in self.models.items()
|
||||
}
|
||||
|
||||
for model in self.models.values():
|
||||
model.check_and_load_plus_model(self.plus_api)
|
||||
|
||||
adapter = TypeAdapter(DetectorConfig)
|
||||
for key, detector in self.detectors.items():
|
||||
adapter = TypeAdapter(DetectorConfig)
|
||||
model_dict = (
|
||||
detector
|
||||
if isinstance(detector, dict)
|
||||
@@ -784,6 +737,27 @@ class FrigateConfig(FrigateBaseModel):
|
||||
)
|
||||
detector_config.model = None
|
||||
|
||||
model_config = self.model.model_dump(exclude_unset=True, warnings="none")
|
||||
|
||||
if detector_config.model_path:
|
||||
model_config["path"] = detector_config.model_path
|
||||
|
||||
if "path" not in model_config:
|
||||
if detector_config.type == "cpu" or detector_config.type.endswith(
|
||||
"_tfl"
|
||||
):
|
||||
model_config["path"] = "/cpu_model.tflite"
|
||||
elif detector_config.type == "edgetpu":
|
||||
model_config["path"] = "/edgetpu_model.tflite"
|
||||
elif detector_config.type == "openvino":
|
||||
for default_key, default_value in DEFAULT_MODEL.items():
|
||||
model_config.setdefault(default_key, default_value)
|
||||
|
||||
model = ModelConfig.model_validate(model_config)
|
||||
model.check_and_load_plus_model(self.plus_api, detector_config.type)
|
||||
model.compute_model_hash()
|
||||
labelmap_objects = model.merged_labelmap.values()
|
||||
detector_config.model = model
|
||||
self.detectors[key] = detector_config
|
||||
|
||||
for name, camera in self.cameras.items():
|
||||
@@ -811,21 +785,6 @@ class FrigateConfig(FrigateBaseModel):
|
||||
{"name": name, **merged_config}
|
||||
)
|
||||
|
||||
# resolve which named model this camera uses
|
||||
if camera_config.detect.model is not None:
|
||||
if camera_config.detect.model not in self.models:
|
||||
raise ValueError(
|
||||
f"Camera {name} references model '{camera_config.detect.model}' which is not defined under models. Defined models: {', '.join(self.models.keys())}"
|
||||
)
|
||||
elif len(self.models) == 1:
|
||||
camera_config.detect.model = next(iter(self.models))
|
||||
elif "default" in self.models:
|
||||
camera_config.detect.model = "default"
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Camera {name} does not specify detect.model and multiple models are defined. Set detect.model on the camera or globally, or name one of the models 'default'."
|
||||
)
|
||||
|
||||
if camera_config.ffmpeg.hwaccel_args == "auto":
|
||||
camera_config.ffmpeg.hwaccel_args = self.ffmpeg.hwaccel_args
|
||||
|
||||
@@ -1046,10 +1005,7 @@ class FrigateConfig(FrigateBaseModel):
|
||||
verify_profile_overrides_match_base(camera_config)
|
||||
verify_autotrack_zones(camera_config)
|
||||
verify_motion_and_detect(camera_config)
|
||||
verify_objects_track(
|
||||
camera_config,
|
||||
self.models[camera_config.detect.model].merged_labelmap.values(),
|
||||
)
|
||||
verify_objects_track(camera_config, labelmap_objects)
|
||||
verify_lpr_and_face(self, camera_config)
|
||||
|
||||
# Validate camera profiles reference top-level profile definitions
|
||||
@@ -1066,11 +1022,8 @@ class FrigateConfig(FrigateBaseModel):
|
||||
config.name = name
|
||||
|
||||
self.objects.parse_all_objects(self.cameras)
|
||||
for model in self.models.values():
|
||||
model.create_colormap(sorted(self.objects.all_objects))
|
||||
|
||||
# expand detectors into per-model runtime instances
|
||||
self.__build_detector_instances(raw_model_dumps)
|
||||
self.model.create_colormap(sorted(self.objects.all_objects))
|
||||
self.model.check_and_load_plus_model(self.plus_api)
|
||||
|
||||
# Check audio transcription and audio detection requirements
|
||||
if self.audio_transcription.enabled:
|
||||
@@ -1101,81 +1054,6 @@ class FrigateConfig(FrigateBaseModel):
|
||||
|
||||
return self
|
||||
|
||||
def __build_detector_instances(
|
||||
self, raw_model_dumps: dict[str, dict[str, Any]]
|
||||
) -> None:
|
||||
"""Expand detector entries into runtime instances, one per assigned model."""
|
||||
used_models = list(
|
||||
dict.fromkeys(camera.detect.model for camera in self.cameras.values())
|
||||
) or list(self.models.keys())
|
||||
|
||||
unused_models = set(self.models.keys()) - set(used_models)
|
||||
if unused_models:
|
||||
logger.warning(
|
||||
f"Models {', '.join(sorted(unused_models))} are defined but not used by any camera, no detector instances will be created for them"
|
||||
)
|
||||
|
||||
assignments = assign_detector_instances(
|
||||
{key: detector.type for key, detector in self.detectors.items()},
|
||||
used_models,
|
||||
)
|
||||
|
||||
models_per_detector: dict[str, int] = {}
|
||||
for _, detector_key, _ in assignments:
|
||||
models_per_detector[detector_key] = (
|
||||
models_per_detector.get(detector_key, 0) + 1
|
||||
)
|
||||
|
||||
instances: dict[str, BaseDetectorConfig] = {}
|
||||
|
||||
for instance_name, detector_key, model_key in assignments:
|
||||
instance = self.detectors[detector_key].model_copy(deep=True)
|
||||
instance.model_key = model_key
|
||||
|
||||
model_dict = raw_model_dumps[model_key].copy()
|
||||
|
||||
if instance.model_path:
|
||||
if models_per_detector[detector_key] > 1:
|
||||
logger.warning(
|
||||
f"Detector {detector_key} runs multiple models, its model_path will be ignored"
|
||||
)
|
||||
else:
|
||||
model_dict["path"] = instance.model_path
|
||||
|
||||
if "path" not in model_dict:
|
||||
if instance.type == "cpu" or instance.type.endswith("_tfl"):
|
||||
model_dict["path"] = "/cpu_model.tflite"
|
||||
elif instance.type == "edgetpu":
|
||||
model_dict["path"] = "/edgetpu_model.tflite"
|
||||
elif instance.type == "openvino":
|
||||
for default_key, default_value in DEFAULT_MODEL.items():
|
||||
model_dict.setdefault(default_key, default_value)
|
||||
|
||||
model = ModelConfig.model_validate(model_dict)
|
||||
|
||||
try:
|
||||
model.check_and_load_plus_model(self.plus_api, instance.type)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Model '{model_key}': {e}") from e
|
||||
|
||||
model.compute_model_hash()
|
||||
instance.model = model
|
||||
instances[instance_name] = instance
|
||||
logger.log(
|
||||
logging.INFO if len(used_models) > 1 else logging.DEBUG,
|
||||
f"Detector instance {instance_name} ({instance.type}) will run model '{model_key}'",
|
||||
)
|
||||
|
||||
# populate user-facing detector entries with their first assigned
|
||||
# model for display purposes
|
||||
for instance_name, detector_key, model_key in assignments:
|
||||
detector = self.detectors[detector_key]
|
||||
if detector.model is None:
|
||||
detector.model = instances[instance_name].model
|
||||
detector.model_key = model_key
|
||||
|
||||
self._detector_instances = instances
|
||||
|
||||
@field_validator("cameras")
|
||||
@classmethod
|
||||
def ensure_zones_and_cameras_have_different_names(cls, v: dict[str, CameraConfig]):
|
||||
|
||||
@@ -72,10 +72,9 @@ class LicensePlateProcessingMixin:
|
||||
# Object config
|
||||
self.lp_objects: list[str] = []
|
||||
|
||||
for model in self.config.models.values():
|
||||
for obj, attributes in model.attributes_map.items():
|
||||
if "license_plate" in attributes and obj not in self.lp_objects:
|
||||
self.lp_objects.append(obj)
|
||||
for obj, attributes in self.config.model.attributes_map.items():
|
||||
if "license_plate" in attributes:
|
||||
self.lp_objects.append(obj)
|
||||
|
||||
# Detection specific parameters
|
||||
self.min_size = 8
|
||||
|
||||
@@ -231,12 +231,8 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
||||
final_data,
|
||||
thumbs,
|
||||
camera_config.review.genai,
|
||||
list(
|
||||
self.config.model_for_camera(
|
||||
camera_config.name
|
||||
).merged_labelmap.values()
|
||||
),
|
||||
self.config.model_for_camera(camera_config.name).all_attributes,
|
||||
list(self.config.model.merged_labelmap.values()),
|
||||
self.config.model.all_attributes,
|
||||
),
|
||||
).start()
|
||||
|
||||
|
||||
@@ -1,69 +1,11 @@
|
||||
import logging
|
||||
|
||||
from .detector_config import InputTensorEnum, ModelConfig, PixelFormatEnum # noqa: F401
|
||||
from .detector_types import ( # noqa: F401
|
||||
DetectorConfig,
|
||||
DetectorTypeEnum,
|
||||
api_types,
|
||||
detector_supports_multiple_models,
|
||||
)
|
||||
from .detector_types import DetectorConfig, DetectorTypeEnum, api_types # noqa: F401
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def assign_detector_instances(
|
||||
detector_types: dict[str, str],
|
||||
used_models: list[str],
|
||||
) -> list[tuple[str, str, str]]:
|
||||
"""Assign detector entries to models.
|
||||
|
||||
Detector types that support multiple models get one instance per model.
|
||||
Single-model detector entries are round-robin assigned across the models,
|
||||
wrapping around so that every detector entry is assigned.
|
||||
|
||||
Args:
|
||||
detector_types: Detector key to detector type, in config order
|
||||
used_models: Ordered model keys in use by cameras
|
||||
|
||||
Returns:
|
||||
List of (instance_name, detector_key, model_key) assignments
|
||||
"""
|
||||
multi = [
|
||||
key
|
||||
for key, type_key in detector_types.items()
|
||||
if detector_supports_multiple_models(type_key)
|
||||
]
|
||||
single = [key for key in detector_types if key not in multi]
|
||||
|
||||
if not multi and len(single) < len(used_models):
|
||||
single_types = sorted({detector_types[key] for key in single})
|
||||
raise ValueError(
|
||||
f"Detectors {', '.join(single)} (types: {', '.join(single_types)}) can each only run a single model, "
|
||||
f"but {len(used_models)} models are in use ({', '.join(used_models)}). "
|
||||
"Add more detectors, use a detector type that supports multiple models, or reduce the number of models assigned to cameras."
|
||||
)
|
||||
|
||||
assignments: list[tuple[str, str, str]] = []
|
||||
|
||||
for key in multi:
|
||||
for model_key in used_models:
|
||||
instance_name = key if len(used_models) == 1 else f"{key}_{model_key}"
|
||||
assignments.append((instance_name, key, model_key))
|
||||
|
||||
for i, key in enumerate(single):
|
||||
assignments.append((key, key, used_models[i % len(used_models)]))
|
||||
|
||||
instance_names = [name for name, _, _ in assignments]
|
||||
duplicates = {name for name in instance_names if instance_names.count(name) > 1}
|
||||
if duplicates:
|
||||
raise ValueError(
|
||||
f"Detector instance names collide: {', '.join(sorted(duplicates))}. "
|
||||
"Rename the conflicting detectors or models so that expanded instance names (detector_model) are unique."
|
||||
)
|
||||
|
||||
return assignments
|
||||
|
||||
|
||||
def create_detector(detector_config):
|
||||
if detector_config.type == DetectorTypeEnum.cpu:
|
||||
logger.warning(
|
||||
|
||||
@@ -11,10 +11,6 @@ logger = logging.getLogger(__name__)
|
||||
class DetectionApi(ABC):
|
||||
type_key: str
|
||||
supported_models: list[ModelTypeEnum]
|
||||
# whether this detector type can run multiple model instances concurrently
|
||||
# on the same hardware (one detector config entry can be expanded to an
|
||||
# instance per model); single-model detectors serve exactly one model each
|
||||
supports_multiple_models: bool = False
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, detector_config: BaseDetectorConfig):
|
||||
|
||||
@@ -250,11 +250,6 @@ class BaseDetectorConfig(BaseModel):
|
||||
title="Detector specific model path",
|
||||
description="File path to the detector model binary if required by the chosen detector.",
|
||||
)
|
||||
model_key: str | None = Field(
|
||||
default=None,
|
||||
title="Assigned model name",
|
||||
description="Name of the model (key under `models`) this detector instance serves. Set automatically at runtime, users should not set this.",
|
||||
)
|
||||
model_config = ConfigDict(
|
||||
extra="allow", arbitrary_types_allowed=True, protected_namespaces=()
|
||||
)
|
||||
|
||||
@@ -29,12 +29,6 @@ for _, name, _ in _included_modules:
|
||||
api_types = {det.type_key: det for det in DetectionApi.__subclasses__()}
|
||||
|
||||
|
||||
def detector_supports_multiple_models(type_key: str) -> bool:
|
||||
"""Return whether the given detector type can run multiple model instances."""
|
||||
detector = api_types.get(type_key)
|
||||
return bool(detector and getattr(detector, "supports_multiple_models", False))
|
||||
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
pass
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ class CpuDetectorConfig(BaseDetectorConfig):
|
||||
|
||||
class CpuTfl(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
supports_multiple_models = True
|
||||
|
||||
def __init__(self, detector_config: CpuDetectorConfig):
|
||||
# Suppress TFLite delegate creation messages that bypass Python logging
|
||||
|
||||
@@ -41,7 +41,6 @@ class ONNXDetectorConfig(BaseDetectorConfig):
|
||||
|
||||
class ONNXDetector(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
supports_multiple_models = True
|
||||
|
||||
def __init__(self, detector_config: ONNXDetectorConfig):
|
||||
super().__init__(detector_config)
|
||||
|
||||
@@ -36,7 +36,6 @@ class OvDetectorConfig(BaseDetectorConfig):
|
||||
|
||||
class OvDetector(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
supports_multiple_models = True
|
||||
supported_models = [
|
||||
ModelTypeEnum.dfine,
|
||||
ModelTypeEnum.rfdetr,
|
||||
|
||||
@@ -47,7 +47,6 @@ class RknnDetectorConfig(BaseDetectorConfig):
|
||||
|
||||
class Rknn(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
supports_multiple_models = True
|
||||
|
||||
def __init__(self, config: RknnDetectorConfig):
|
||||
super().__init__(config)
|
||||
|
||||
@@ -30,7 +30,6 @@ class TeflonDetectorConfig(BaseDetectorConfig):
|
||||
|
||||
class TeflonTfl(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
supports_multiple_models = True
|
||||
|
||||
def __init__(self, detector_config: TeflonDetectorConfig):
|
||||
# Location in Debian's mesa-teflon-delegate
|
||||
|
||||
@@ -82,7 +82,6 @@ class HostDeviceMem:
|
||||
|
||||
class TensorRtDetector(DetectionApi):
|
||||
type_key = DETECTOR_KEY
|
||||
supports_multiple_models = True
|
||||
|
||||
def _load_engine(self, model_path):
|
||||
try:
|
||||
|
||||
@@ -159,16 +159,7 @@ class EventProcessor(threading.Thread):
|
||||
if width is None or height is None:
|
||||
return
|
||||
|
||||
# find a detector instance running this camera's model so the
|
||||
# event records the model that produced it
|
||||
camera_detector = next(
|
||||
(
|
||||
detector
|
||||
for detector in self.config.detector_instances.values()
|
||||
if detector.model_key == camera_config.detect.model
|
||||
),
|
||||
list(self.config.detectors.values())[0],
|
||||
)
|
||||
first_detector = list(self.config.detectors.values())[0]
|
||||
|
||||
start_time = event_data["start_time"]
|
||||
end_time = (
|
||||
@@ -238,13 +229,13 @@ class EventProcessor(threading.Thread):
|
||||
Event.thumbnail: event_data.get("thumbnail"),
|
||||
Event.has_clip: event_data["has_clip"],
|
||||
Event.has_snapshot: event_data["has_snapshot"],
|
||||
Event.model_hash: camera_detector.model.model_hash
|
||||
if camera_detector.model
|
||||
Event.model_hash: first_detector.model.model_hash
|
||||
if first_detector.model
|
||||
else None,
|
||||
Event.model_type: camera_detector.model.model_type
|
||||
if camera_detector.model
|
||||
Event.model_type: first_detector.model.model_type
|
||||
if first_detector.model
|
||||
else None,
|
||||
Event.detector_type: camera_detector.type,
|
||||
Event.detector_type: first_detector.type,
|
||||
Event.data: {
|
||||
"box": box,
|
||||
"region": region,
|
||||
|
||||
@@ -436,10 +436,7 @@ class ReviewSegmentMaintainer(threading.Thread):
|
||||
|
||||
if not object["sub_label"]:
|
||||
segment.detections[object["id"]] = object["label"]
|
||||
elif (
|
||||
object["sub_label"][0]
|
||||
in self.config.model_for_camera(segment.camera).all_attributes
|
||||
):
|
||||
elif object["sub_label"][0] in self.config.model.all_attributes:
|
||||
segment.detections[object["id"]] = object["sub_label"][0]
|
||||
else:
|
||||
segment.detections[object["id"]] = f"{object['label']}-verified"
|
||||
@@ -577,10 +574,7 @@ class ReviewSegmentMaintainer(threading.Thread):
|
||||
for object in activity.get_all_objects():
|
||||
if not object["sub_label"]:
|
||||
detections[object["id"]] = object["label"]
|
||||
elif (
|
||||
object["sub_label"][0]
|
||||
in self.config.model_for_camera(camera).all_attributes
|
||||
):
|
||||
elif object["sub_label"][0] in self.config.model.all_attributes:
|
||||
detections[object["id"]] = object["sub_label"][0]
|
||||
else:
|
||||
detections[object["id"]] = f"{object['label']}-verified"
|
||||
|
||||
+9
-181
@@ -86,7 +86,7 @@ class TestConfig(unittest.TestCase):
|
||||
},
|
||||
},
|
||||
# needs to be a file that will exist, doesn't matter what
|
||||
"models": {"default": {"path": "/etc/hosts", "width": 512}},
|
||||
"model": {"path": "/etc/hosts", "width": 512},
|
||||
}
|
||||
|
||||
frigate_config = FrigateConfig(**(deep_merge(config, self.minimal)))
|
||||
@@ -103,7 +103,7 @@ class TestConfig(unittest.TestCase):
|
||||
assert frigate_config.detectors["edgetpu"].device is None
|
||||
assert frigate_config.detectors["openvino"].device is None
|
||||
|
||||
assert frigate_config.models["default"].path == "/etc/hosts"
|
||||
assert frigate_config.model.path == "/etc/hosts"
|
||||
assert frigate_config.detectors["cpu"].model.path == "/cpu_model.tflite"
|
||||
assert frigate_config.detectors["edgetpu"].model.path == "/edgetpu_model.tflite"
|
||||
assert frigate_config.detectors["openvino"].model.path == "/etc/hosts"
|
||||
@@ -956,7 +956,7 @@ class TestConfig(unittest.TestCase):
|
||||
def test_merge_labelmap(self):
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"models": {"default": {"labelmap": {7: "truck"}}},
|
||||
"model": {"labelmap": {7: "truck"}},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
@@ -977,7 +977,7 @@ class TestConfig(unittest.TestCase):
|
||||
}
|
||||
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert frigate_config.models["default"].merged_labelmap[7] == "truck"
|
||||
assert frigate_config.model.merged_labelmap[7] == "truck"
|
||||
|
||||
def test_default_labelmap_empty(self):
|
||||
config = {
|
||||
@@ -1002,12 +1002,12 @@ class TestConfig(unittest.TestCase):
|
||||
}
|
||||
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert frigate_config.models["default"].merged_labelmap[0] == "person"
|
||||
assert frigate_config.model.merged_labelmap[0] == "person"
|
||||
|
||||
def test_default_labelmap(self):
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"models": {"default": {"width": 320, "height": 320}},
|
||||
"model": {"width": 320, "height": 320},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
@@ -1028,7 +1028,7 @@ class TestConfig(unittest.TestCase):
|
||||
}
|
||||
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert frigate_config.models["default"].merged_labelmap[0] == "person"
|
||||
assert frigate_config.model.merged_labelmap[0] == "person"
|
||||
|
||||
def test_plus_labelmap(self):
|
||||
with open(os.path.join(MODEL_CACHE_DIR, "test"), "w") as f:
|
||||
@@ -1039,7 +1039,7 @@ class TestConfig(unittest.TestCase):
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"detectors": {"cpu": {"type": "cpu"}},
|
||||
"models": {"default": {"path": "plus://test"}},
|
||||
"model": {"path": "plus://test"},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
@@ -1060,7 +1060,7 @@ class TestConfig(unittest.TestCase):
|
||||
}
|
||||
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert frigate_config.models["default"].merged_labelmap[0] == "amazon"
|
||||
assert frigate_config.model.merged_labelmap[0] == "amazon"
|
||||
|
||||
def test_fails_on_invalid_role(self):
|
||||
config = {
|
||||
@@ -1765,177 +1765,5 @@ class TestAttributeFilterDefaults(unittest.TestCase):
|
||||
self.assertEqual(face_filter.min_score, 0.3)
|
||||
|
||||
|
||||
class TestMultiModelConfig(unittest.TestCase):
|
||||
"""Tests for named models and detector instance assignment."""
|
||||
|
||||
def setUp(self):
|
||||
self.base = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"models": {
|
||||
"indoor": {"width": 320, "height": 320},
|
||||
"outdoor": {"width": 640, "height": 640},
|
||||
},
|
||||
"cameras": {
|
||||
"living_room": self._camera("indoor"),
|
||||
"driveway": self._camera("outdoor"),
|
||||
},
|
||||
}
|
||||
|
||||
def _camera(self, model=None):
|
||||
camera = {
|
||||
"ffmpeg": {
|
||||
"inputs": [{"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]}]
|
||||
},
|
||||
"detect": {"height": 1080, "width": 1920, "fps": 5},
|
||||
}
|
||||
|
||||
if model:
|
||||
camera["detect"]["model"] = model
|
||||
|
||||
return camera
|
||||
|
||||
def test_single_model_resolves_implicitly(self):
|
||||
config = FrigateConfig(
|
||||
mqtt={"host": "mqtt"},
|
||||
models={"custom": {"width": 320, "height": 320}},
|
||||
cameras={"back": self._camera()},
|
||||
)
|
||||
assert config.cameras["back"].detect.model == "custom"
|
||||
|
||||
def test_multiple_models_resolve_to_default(self):
|
||||
config = FrigateConfig(
|
||||
mqtt={"host": "mqtt"},
|
||||
models={
|
||||
"default": {"width": 320, "height": 320},
|
||||
"outdoor": {"width": 640, "height": 640},
|
||||
},
|
||||
cameras={"back": self._camera()},
|
||||
)
|
||||
assert config.cameras["back"].detect.model == "default"
|
||||
|
||||
def test_multiple_models_without_default_requires_selection(self):
|
||||
config = self.base.copy()
|
||||
config["cameras"] = {"back": self._camera()}
|
||||
self.assertRaises(ValidationError, lambda: FrigateConfig(**config))
|
||||
|
||||
def test_camera_references_missing_model(self):
|
||||
config = self.base.copy()
|
||||
config["cameras"] = {"back": self._camera("thermal")}
|
||||
self.assertRaises(ValidationError, lambda: FrigateConfig(**config))
|
||||
|
||||
def test_global_detect_model_inherited_and_overridden(self):
|
||||
config = self.base.copy()
|
||||
config["detect"] = {"model": "indoor"}
|
||||
config["cameras"] = {
|
||||
"living_room": self._camera(),
|
||||
"driveway": self._camera("outdoor"),
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert frigate_config.cameras["living_room"].detect.model == "indoor"
|
||||
assert frigate_config.cameras["driveway"].detect.model == "outdoor"
|
||||
|
||||
def test_invalid_model_name(self):
|
||||
config = self.base.copy()
|
||||
config["models"] = {"bad name!": {"width": 320, "height": 320}}
|
||||
self.assertRaises(ValidationError, lambda: FrigateConfig(**config))
|
||||
|
||||
def test_multi_model_detector_expands_instances(self):
|
||||
config = self.base.copy()
|
||||
config["detectors"] = {
|
||||
"ov0": {"type": "openvino", "device": "GPU"},
|
||||
"ov1": {"type": "openvino", "device": "GPU.1"},
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert sorted(frigate_config.detector_instances.keys()) == [
|
||||
"ov0_indoor",
|
||||
"ov0_outdoor",
|
||||
"ov1_indoor",
|
||||
"ov1_outdoor",
|
||||
]
|
||||
assert frigate_config.detector_instances["ov0_indoor"].model_key == "indoor"
|
||||
assert frigate_config.detector_instances["ov0_indoor"].model.width == 320
|
||||
assert frigate_config.detector_instances["ov0_outdoor"].model.width == 640
|
||||
|
||||
def test_multi_model_detector_single_model_keeps_name(self):
|
||||
config = self.base.copy()
|
||||
config["models"] = {"default": {"width": 320, "height": 320}}
|
||||
config["cameras"] = {"back": self._camera()}
|
||||
config["detectors"] = {"ov": {"type": "openvino", "device": "GPU"}}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert list(frigate_config.detector_instances.keys()) == ["ov"]
|
||||
assert frigate_config.detector_instances["ov"].model_key == "default"
|
||||
|
||||
def test_single_model_detectors_round_robin(self):
|
||||
config = self.base.copy()
|
||||
config["detectors"] = {
|
||||
"coral0": {"type": "edgetpu", "device": "usb:0"},
|
||||
"coral1": {"type": "edgetpu", "device": "usb:1"},
|
||||
"coral2": {"type": "edgetpu", "device": "usb:2"},
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assignments = {
|
||||
key: instance.model_key
|
||||
for key, instance in frigate_config.detector_instances.items()
|
||||
}
|
||||
assert assignments == {
|
||||
"coral0": "indoor",
|
||||
"coral1": "outdoor",
|
||||
"coral2": "indoor",
|
||||
}
|
||||
|
||||
def test_single_model_detectors_insufficient_coverage(self):
|
||||
config = self.base.copy()
|
||||
config["detectors"] = {"coral": {"type": "edgetpu", "device": "usb"}}
|
||||
self.assertRaises(ValidationError, lambda: FrigateConfig(**config))
|
||||
|
||||
def test_single_model_detector_with_multi_model_detector(self):
|
||||
config = self.base.copy()
|
||||
config["detectors"] = {
|
||||
"coral": {"type": "edgetpu", "device": "usb"},
|
||||
"ov": {"type": "openvino", "device": "GPU"},
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert frigate_config.detector_instances["coral"].model_key == "indoor"
|
||||
assert frigate_config.detector_instances["ov_indoor"].model_key == "indoor"
|
||||
assert frigate_config.detector_instances["ov_outdoor"].model_key == "outdoor"
|
||||
|
||||
def test_unused_model_gets_no_instances(self):
|
||||
config = self.base.copy()
|
||||
config["models"] = {
|
||||
**config["models"],
|
||||
"thermal": {"width": 320, "height": 320},
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
model_keys = {
|
||||
instance.model_key
|
||||
for instance in frigate_config.detector_instances.values()
|
||||
}
|
||||
assert "thermal" not in model_keys
|
||||
|
||||
def test_model_path_ignored_when_detector_runs_multiple_models(self):
|
||||
config = self.base.copy()
|
||||
config["detectors"] = {
|
||||
"cpu": {"type": "cpu", "model_path": "/custom_model.tflite"}
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert (
|
||||
frigate_config.detector_instances["cpu_indoor"].model.path
|
||||
== "/cpu_model.tflite"
|
||||
)
|
||||
|
||||
def test_model_path_applied_when_detector_runs_one_model(self):
|
||||
config = self.base.copy()
|
||||
config["models"] = {"default": {"width": 320, "height": 320}}
|
||||
config["cameras"] = {"back": self._camera()}
|
||||
config["detectors"] = {
|
||||
"cpu": {"type": "cpu", "model_path": "/custom_model.tflite"}
|
||||
}
|
||||
frigate_config = FrigateConfig(**config)
|
||||
assert (
|
||||
frigate_config.detector_instances["cpu"].model.path
|
||||
== "/custom_model.tflite"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
"""Tests for config file migration functions."""
|
||||
|
||||
import unittest
|
||||
|
||||
from frigate.util.config import migrate_019_0
|
||||
|
||||
|
||||
class TestMigrate019(unittest.TestCase):
|
||||
def test_migrates_model_to_named_models(self):
|
||||
config = {
|
||||
"version": "0.18-0",
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"model": {"path": "/config/model.tflite", "width": 320, "height": 320},
|
||||
}
|
||||
new_config = migrate_019_0(config)
|
||||
assert "model" not in new_config
|
||||
assert new_config["models"] == {
|
||||
"default": {"path": "/config/model.tflite", "width": 320, "height": 320}
|
||||
}
|
||||
assert new_config["version"] == "0.19-0"
|
||||
|
||||
def test_no_model_defined(self):
|
||||
config = {"version": "0.18-0", "mqtt": {"host": "mqtt"}}
|
||||
new_config = migrate_019_0(config)
|
||||
assert "model" not in new_config
|
||||
assert "models" not in new_config
|
||||
assert new_config["version"] == "0.19-0"
|
||||
|
||||
def test_existing_models_not_overwritten(self):
|
||||
config = {
|
||||
"version": "0.18-0",
|
||||
"model": {"width": 320},
|
||||
"models": {"custom": {"width": 640}},
|
||||
}
|
||||
new_config = migrate_019_0(config)
|
||||
assert "model" not in new_config
|
||||
assert new_config["models"] == {"custom": {"width": 640}}
|
||||
assert new_config["version"] == "0.19-0"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
@@ -209,10 +209,7 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
if obj.obj_data.get("sub_label"):
|
||||
sub_label = obj.obj_data["sub_label"][0]
|
||||
|
||||
if (
|
||||
sub_label
|
||||
in self.config.model_for_camera(camera).all_attribute_logos
|
||||
):
|
||||
if sub_label in self.config.model.all_attribute_logos:
|
||||
self.dispatcher.publish(
|
||||
f"{camera}/{sub_label}/snapshot",
|
||||
jpg_bytes,
|
||||
|
||||
+1
-29
@@ -20,7 +20,7 @@ from frigate.util.services import get_video_properties
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CURRENT_CONFIG_VERSION = "0.19-0"
|
||||
CURRENT_CONFIG_VERSION = "0.18-0"
|
||||
DEFAULT_CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yml")
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ def migrate_frigate_config(config_file: str):
|
||||
new_config = migrate_014(config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
config = new_config
|
||||
previous_version = "0.14"
|
||||
|
||||
logger.info("Migrating export file names...")
|
||||
@@ -118,7 +117,6 @@ def migrate_frigate_config(config_file: str):
|
||||
new_config = migrate_015_0(config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
config = new_config
|
||||
previous_version = "0.15-0"
|
||||
|
||||
if previous_version < "0.15-1":
|
||||
@@ -126,7 +124,6 @@ def migrate_frigate_config(config_file: str):
|
||||
new_config = migrate_015_1(config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
config = new_config
|
||||
previous_version = "0.15-1"
|
||||
|
||||
if previous_version < "0.16-0":
|
||||
@@ -134,7 +131,6 @@ def migrate_frigate_config(config_file: str):
|
||||
new_config = migrate_016_0(config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
config = new_config
|
||||
previous_version = "0.16-0"
|
||||
|
||||
if previous_version < "0.17-0":
|
||||
@@ -142,7 +138,6 @@ def migrate_frigate_config(config_file: str):
|
||||
new_config = migrate_017_0(config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
config = new_config
|
||||
previous_version = "0.17-0"
|
||||
|
||||
if previous_version < "0.18-0":
|
||||
@@ -150,17 +145,8 @@ def migrate_frigate_config(config_file: str):
|
||||
new_config = migrate_018_0(config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
config = new_config
|
||||
previous_version = "0.18-0"
|
||||
|
||||
if previous_version < "0.19-0":
|
||||
logger.info(f"Migrating frigate config from {previous_version} to 0.19-0...")
|
||||
new_config = migrate_019_0(config)
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(new_config, f)
|
||||
config = new_config
|
||||
previous_version = "0.19-0"
|
||||
|
||||
logger.info("Finished frigate config migration...")
|
||||
|
||||
|
||||
@@ -672,20 +658,6 @@ def migrate_018_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]
|
||||
return new_config
|
||||
|
||||
|
||||
def migrate_019_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||||
"""Handle migrating frigate config to 0.19-0"""
|
||||
new_config = config.copy()
|
||||
|
||||
# Migrate the single model config to named models
|
||||
model = new_config.pop("model", None)
|
||||
|
||||
if model is not None and "models" not in new_config:
|
||||
new_config["models"] = {"default": model}
|
||||
|
||||
new_config["version"] = "0.19-0"
|
||||
return new_config
|
||||
|
||||
|
||||
def get_relative_coordinates(
|
||||
mask: str | list | None,
|
||||
frame_shape: tuple[int, int],
|
||||
|
||||
@@ -1 +1 @@
|
||||
[{"id": "case-001", "name": "Package Theft Investigation", "description": "Review of suspicious activity near the front porch", "created_at": 1784761296.1184616, "updated_at": 1784836896.1184616}]
|
||||
[{"id": "case-001", "name": "Package Theft Investigation", "description": "Review of suspicious activity near the front porch", "created_at": 1780597809.365581, "updated_at": 1780673409.365581}]
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
[{"id": "event-person-001", "label": "person", "sub_label": null, "camera": "front_door", "start_time": 1784840496.1184616, "end_time": 1784840526.1184616, "false_positive": false, "zones": ["front_yard"], "thumbnail": null, "has_clip": true, "has_snapshot": true, "retain_indefinitely": false, "plus_id": null, "model_hash": "abc123", "detector_type": "cpu", "model_type": "ssd", "data": {"top_score": 0.92, "score": 0.92, "region": [0.1, 0.1, 0.5, 0.8], "box": [0.2, 0.15, 0.45, 0.75], "area": 0.18, "ratio": 0.6, "type": "object", "description": "A person walking toward the front door", "average_estimated_speed": 1.2, "velocity_angle": 45.0, "path_data": [[[0.2, 0.5], 0.0], [[0.3, 0.5], 1.0]]}}, {"id": "event-car-001", "label": "car", "sub_label": null, "camera": "backyard", "start_time": 1784836896.1184616, "end_time": 1784836941.1184616, "false_positive": false, "zones": ["driveway"], "thumbnail": null, "has_clip": true, "has_snapshot": true, "retain_indefinitely": false, "plus_id": null, "model_hash": "def456", "detector_type": "cpu", "model_type": "ssd", "data": {"top_score": 0.87, "score": 0.87, "region": [0.3, 0.2, 0.9, 0.7], "box": [0.35, 0.25, 0.85, 0.65], "area": 0.2, "ratio": 1.25, "type": "object", "description": "A car parked in the driveway", "average_estimated_speed": 0.0, "velocity_angle": 0.0, "path_data": []}}, {"id": "event-person-002", "label": "person", "sub_label": null, "camera": "garage", "start_time": 1784833296.1184616, "end_time": 1784833316.1184616, "false_positive": false, "zones": [], "thumbnail": null, "has_clip": false, "has_snapshot": true, "retain_indefinitely": false, "plus_id": null, "model_hash": "ghi789", "detector_type": "cpu", "model_type": "ssd", "data": {"top_score": 0.78, "score": 0.78, "region": [0.0, 0.0, 0.6, 0.9], "box": [0.1, 0.05, 0.5, 0.85], "area": 0.32, "ratio": 0.5, "type": "object", "description": null, "average_estimated_speed": 0.5, "velocity_angle": 90.0, "path_data": [[[0.1, 0.4], 0.0]]}}]
|
||||
[{"id": "event-person-001", "label": "person", "sub_label": null, "camera": "front_door", "start_time": 1780677009.365581, "end_time": 1780677039.365581, "false_positive": false, "zones": ["front_yard"], "thumbnail": null, "has_clip": true, "has_snapshot": true, "retain_indefinitely": false, "plus_id": null, "model_hash": "abc123", "detector_type": "cpu", "model_type": "ssd", "data": {"top_score": 0.92, "score": 0.92, "region": [0.1, 0.1, 0.5, 0.8], "box": [0.2, 0.15, 0.45, 0.75], "area": 0.18, "ratio": 0.6, "type": "object", "description": "A person walking toward the front door", "average_estimated_speed": 1.2, "velocity_angle": 45.0, "path_data": [[[0.2, 0.5], 0.0], [[0.3, 0.5], 1.0]]}}, {"id": "event-car-001", "label": "car", "sub_label": null, "camera": "backyard", "start_time": 1780673409.365581, "end_time": 1780673454.365581, "false_positive": false, "zones": ["driveway"], "thumbnail": null, "has_clip": true, "has_snapshot": true, "retain_indefinitely": false, "plus_id": null, "model_hash": "def456", "detector_type": "cpu", "model_type": "ssd", "data": {"top_score": 0.87, "score": 0.87, "region": [0.3, 0.2, 0.9, 0.7], "box": [0.35, 0.25, 0.85, 0.65], "area": 0.2, "ratio": 1.25, "type": "object", "description": "A car parked in the driveway", "average_estimated_speed": 0.0, "velocity_angle": 0.0, "path_data": []}}, {"id": "event-person-002", "label": "person", "sub_label": null, "camera": "garage", "start_time": 1780669809.365581, "end_time": 1780669829.365581, "false_positive": false, "zones": [], "thumbnail": null, "has_clip": false, "has_snapshot": true, "retain_indefinitely": false, "plus_id": null, "model_hash": "ghi789", "detector_type": "cpu", "model_type": "ssd", "data": {"top_score": 0.78, "score": 0.78, "region": [0.0, 0.0, 0.6, 0.9], "box": [0.1, 0.05, 0.5, 0.85], "area": 0.32, "ratio": 0.5, "type": "object", "description": null, "average_estimated_speed": 0.5, "velocity_angle": 90.0, "path_data": [[[0.1, 0.4], 0.0]]}}]
|
||||
@@ -1 +1 @@
|
||||
[{"id": "export-001", "camera": "front_door", "name": "Front Door - Person Alert", "date": 1784844096.1184616, "video_path": "/exports/export-001.mp4", "thumb_path": "/exports/export-001-thumb.jpg", "in_progress": false, "export_case_id": null}, {"id": "export-002", "camera": "backyard", "name": "Backyard - Car Detection", "date": 1784836896.1184616, "video_path": "/exports/export-002.mp4", "thumb_path": "/exports/export-002-thumb.jpg", "in_progress": false, "export_case_id": "case-001"}, {"id": "export-003", "camera": "garage", "name": "Garage - In Progress", "date": 1784845896.1184616, "video_path": "/exports/export-003.mp4", "thumb_path": "/exports/export-003-thumb.jpg", "in_progress": true, "export_case_id": null}]
|
||||
[{"id": "export-001", "camera": "front_door", "name": "Front Door - Person Alert", "date": 1780680609.365581, "video_path": "/exports/export-001.mp4", "thumb_path": "/exports/export-001-thumb.jpg", "in_progress": false, "export_case_id": null}, {"id": "export-002", "camera": "backyard", "name": "Backyard - Car Detection", "date": 1780673409.365581, "video_path": "/exports/export-002.mp4", "thumb_path": "/exports/export-002-thumb.jpg", "in_progress": false, "export_case_id": "case-001"}, {"id": "export-003", "camera": "garage", "name": "Garage - In Progress", "date": 1780682409.365581, "video_path": "/exports/export-003.mp4", "thumb_path": "/exports/export-003-thumb.jpg", "in_progress": true, "export_case_id": null}]
|
||||
@@ -102,17 +102,11 @@ def generate_config():
|
||||
snapshot = config.model_dump()
|
||||
|
||||
# Runtime-computed fields not in the Pydantic dump
|
||||
for model_dict in snapshot.get("models", {}).values():
|
||||
all_attrs = set()
|
||||
for attrs in model_dict.get("attributes_map", {}).values():
|
||||
all_attrs.update(attrs)
|
||||
model_dict["all_attributes"] = sorted(all_attrs)
|
||||
model_dict["colormap"] = {}
|
||||
|
||||
# legacy single-model block mirrors the default model, matching /api/config
|
||||
models = snapshot.get("models", {})
|
||||
default_key = "default" if "default" in models else next(iter(models))
|
||||
snapshot["model"] = models[default_key]
|
||||
all_attrs = set()
|
||||
for attrs in snapshot.get("model", {}).get("attributes_map", {}).values():
|
||||
all_attrs.update(attrs)
|
||||
snapshot["model"]["all_attributes"] = sorted(all_attrs)
|
||||
snapshot["model"]["colormap"] = {}
|
||||
|
||||
return snapshot
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"2026-07-23": {"day": "2026-07-23", "reviewed_alert": 1, "reviewed_detection": 0, "total_alert": 2, "total_detection": 2}, "2026-07-22": {"day": "2026-07-22", "reviewed_alert": 3, "reviewed_detection": 2, "total_alert": 3, "total_detection": 4}}
|
||||
{"2026-06-05": {"day": "2026-06-05", "reviewed_alert": 1, "reviewed_detection": 0, "total_alert": 2, "total_detection": 2}, "2026-06-04": {"day": "2026-06-04", "reviewed_alert": 3, "reviewed_detection": 2, "total_alert": 3, "total_detection": 4}}
|
||||
@@ -1 +1 @@
|
||||
[{"id": "review-alert-001", "camera": "front_door", "start_time": "2026-07-23T21:01:36.118462", "end_time": "2026-07-23T21:02:06.118462", "has_been_reviewed": false, "severity": "alert", "thumb_path": "/clips/front_door/review-alert-001-thumb.jpg", "data": {"audio": [], "detections": ["person-abc123"], "objects": ["person"], "sub_labels": [], "significant_motion_areas": [], "zones": ["front_yard"]}}, {"id": "review-alert-002", "camera": "backyard", "start_time": "2026-07-23T20:01:36.118462", "end_time": "2026-07-23T20:02:21.118462", "has_been_reviewed": true, "severity": "alert", "thumb_path": "/clips/backyard/review-alert-002-thumb.jpg", "data": {"audio": [], "detections": ["car-def456"], "objects": ["car"], "sub_labels": [], "significant_motion_areas": [], "zones": ["driveway"]}}, {"id": "review-detect-001", "camera": "garage", "start_time": "2026-07-23T19:01:36.118462", "end_time": "2026-07-23T19:01:56.118462", "has_been_reviewed": false, "severity": "detection", "thumb_path": "/clips/garage/review-detect-001-thumb.jpg", "data": {"audio": [], "detections": ["person-ghi789"], "objects": ["person"], "sub_labels": [], "significant_motion_areas": [], "zones": []}}, {"id": "review-detect-002", "camera": "front_door", "start_time": "2026-07-23T18:01:36.118462", "end_time": "2026-07-23T18:01:51.118462", "has_been_reviewed": false, "severity": "detection", "thumb_path": "/clips/front_door/review-detect-002-thumb.jpg", "data": {"audio": [], "detections": ["car-jkl012"], "objects": ["car"], "sub_labels": [], "significant_motion_areas": [], "zones": ["front_yard"]}}]
|
||||
[{"id": "review-alert-001", "camera": "front_door", "start_time": "2026-06-05T11:30:09.365581", "end_time": "2026-06-05T11:30:39.365581", "has_been_reviewed": false, "severity": "alert", "thumb_path": "/clips/front_door/review-alert-001-thumb.jpg", "data": {"audio": [], "detections": ["person-abc123"], "objects": ["person"], "sub_labels": [], "significant_motion_areas": [], "zones": ["front_yard"]}}, {"id": "review-alert-002", "camera": "backyard", "start_time": "2026-06-05T10:30:09.365581", "end_time": "2026-06-05T10:30:54.365581", "has_been_reviewed": true, "severity": "alert", "thumb_path": "/clips/backyard/review-alert-002-thumb.jpg", "data": {"audio": [], "detections": ["car-def456"], "objects": ["car"], "sub_labels": [], "significant_motion_areas": [], "zones": ["driveway"]}}, {"id": "review-detect-001", "camera": "garage", "start_time": "2026-06-05T09:30:09.365581", "end_time": "2026-06-05T09:30:29.365581", "has_been_reviewed": false, "severity": "detection", "thumb_path": "/clips/garage/review-detect-001-thumb.jpg", "data": {"audio": [], "detections": ["person-ghi789"], "objects": ["person"], "sub_labels": [], "significant_motion_areas": [], "zones": []}}, {"id": "review-detect-002", "camera": "front_door", "start_time": "2026-06-05T08:30:09.365581", "end_time": "2026-06-05T08:30:24.365581", "has_been_reviewed": false, "severity": "detection", "thumb_path": "/clips/front_door/review-detect-002-thumb.jpg", "data": {"audio": [], "detections": ["car-jkl012"], "objects": ["car"], "sub_labels": [], "significant_motion_areas": [], "zones": ["front_yard"]}}]
|
||||
@@ -86,10 +86,6 @@
|
||||
"label": "Enable object detection",
|
||||
"description": "Enable or disable object detection for this camera."
|
||||
},
|
||||
"model": {
|
||||
"label": "Detection model name",
|
||||
"description": "Name of the model (key under `models`) used by this camera. Defaults to the only defined model, or the model named 'default'."
|
||||
},
|
||||
"height": {
|
||||
"label": "Detect height",
|
||||
"description": "Height (pixels) of frames used for the detect stream; leave empty to use the native stream resolution."
|
||||
|
||||
@@ -329,10 +329,6 @@
|
||||
"label": "Detector specific model path",
|
||||
"description": "File path to the detector model binary if required by the chosen detector."
|
||||
},
|
||||
"model_key": {
|
||||
"label": "Assigned model name",
|
||||
"description": "Name of the model (key under `models`) this detector instance serves. Set automatically at runtime, users should not set this."
|
||||
},
|
||||
"axengine": {
|
||||
"label": "AXEngine NPU",
|
||||
"description": "AXERA AX650N/AX8850N NPU detector running compiled .axmodel files via the AXEngine runtime."
|
||||
@@ -458,9 +454,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"label": "Detection models",
|
||||
"description": "Named object detection models. Cameras select a model with detect.model; detectors are assigned to models automatically.",
|
||||
"model": {
|
||||
"label": "Detection model",
|
||||
"description": "Settings to configure a custom object detection model and its input shape.",
|
||||
"path": {
|
||||
"label": "Custom object detector model path",
|
||||
"description": "Path to a custom detection model file (or plus://<model_id> for Frigate+ models)."
|
||||
@@ -629,10 +625,6 @@
|
||||
"label": "Enable object detection",
|
||||
"description": "Enable or disable object detection for all cameras; can be overridden per-camera."
|
||||
},
|
||||
"model": {
|
||||
"label": "Detection model name",
|
||||
"description": "Name of the model (key under `models`) used by this camera. Defaults to the only defined model, or the model named 'default'."
|
||||
},
|
||||
"height": {
|
||||
"label": "Detect height",
|
||||
"description": "Height (pixels) of frames used for the detect stream; leave empty to use the native stream resolution."
|
||||
|
||||
@@ -74,7 +74,6 @@ const SECTIONS_WITHOUT_OVERRIDE_BADGE = new Set([
|
||||
"birdseye",
|
||||
"detectors",
|
||||
"model",
|
||||
"models",
|
||||
]);
|
||||
|
||||
type CameraEntryProps = {
|
||||
|
||||
@@ -70,17 +70,7 @@ export function extractSectionSchema(
|
||||
// For global level, get from root properties
|
||||
if (schemaObj.properties) {
|
||||
const props = schemaObj.properties;
|
||||
let sectionProp = props[sectionPath];
|
||||
|
||||
// the model editor edits a single entry of the `models` map, so
|
||||
// resolve the map value schema since there is no root `model` property
|
||||
if (!sectionProp && sectionPath === "model") {
|
||||
const modelsProp = props["models"] as SchemaWithDefinitions | undefined;
|
||||
const additional = modelsProp?.additionalProperties;
|
||||
if (additional && typeof additional === "object") {
|
||||
sectionProp = additional as RJSFSchema;
|
||||
}
|
||||
}
|
||||
const sectionProp = props[sectionPath];
|
||||
|
||||
if (sectionProp && typeof sectionProp === "object") {
|
||||
if ("$ref" in sectionProp && typeof sectionProp.$ref === "string") {
|
||||
|
||||
@@ -59,7 +59,6 @@ export interface CameraConfig {
|
||||
height: number;
|
||||
max_disappeared: number;
|
||||
min_initialized: number;
|
||||
model: string;
|
||||
stationary: {
|
||||
interval: number;
|
||||
max_frames: {
|
||||
@@ -395,30 +394,6 @@ export type GenAIAgentConfig = {
|
||||
runtime_options?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export interface ModelConfig {
|
||||
height: number;
|
||||
input_pixel_format: string;
|
||||
input_tensor: string;
|
||||
labelmap: Record<string, unknown>;
|
||||
labelmap_path: string | null;
|
||||
model_type: string;
|
||||
path: string | null;
|
||||
width: number;
|
||||
colormap: { [key: string]: [number, number, number] };
|
||||
attributes_map: { [key: string]: string[] };
|
||||
all_attributes: string[];
|
||||
plus?: {
|
||||
name: string;
|
||||
id: string;
|
||||
trainDate: string;
|
||||
baseModel: string;
|
||||
isBaseModel: boolean;
|
||||
supportedDetectors: string[];
|
||||
width: number;
|
||||
height: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface FrigateConfig {
|
||||
version: string;
|
||||
safe_mode: boolean;
|
||||
@@ -471,7 +446,6 @@ export interface FrigateConfig {
|
||||
height: number | null;
|
||||
max_disappeared: number | null;
|
||||
min_initialized: number | null;
|
||||
model: string | null;
|
||||
stationary: {
|
||||
interval: number | null;
|
||||
max_frames: {
|
||||
@@ -538,10 +512,29 @@ export interface FrigateConfig {
|
||||
logs: Record<string, string>;
|
||||
};
|
||||
|
||||
// legacy single-model block, mirrors the default entry of `models`
|
||||
model: ModelConfig;
|
||||
|
||||
models: { [modelKey: string]: ModelConfig };
|
||||
model: {
|
||||
height: number;
|
||||
input_pixel_format: string;
|
||||
input_tensor: string;
|
||||
labelmap: Record<string, unknown>;
|
||||
labelmap_path: string | null;
|
||||
model_type: string;
|
||||
path: string | null;
|
||||
width: number;
|
||||
colormap: { [key: string]: [number, number, number] };
|
||||
attributes_map: { [key: string]: string[] };
|
||||
all_attributes: string[];
|
||||
plus?: {
|
||||
name: string;
|
||||
id: string;
|
||||
trainDate: string;
|
||||
baseModel: string;
|
||||
isBaseModel: boolean;
|
||||
supportedDetectors: string[];
|
||||
width: number;
|
||||
height: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
motion: Record<string, unknown> | null;
|
||||
|
||||
|
||||
@@ -115,10 +115,8 @@ const STATUS_BAR_KEY = "detectors_and_model";
|
||||
const EMPTY_PENDING: Record<string, ConfigSectionData> = {};
|
||||
|
||||
const deriveInitialState = (config: FrigateConfig): PageState => {
|
||||
// this view edits the default model; other named models are untouched
|
||||
const defaultModel = config.models?.default ?? config.model;
|
||||
const plusModelId = defaultModel?.plus?.id;
|
||||
const modelPath = defaultModel?.path;
|
||||
const plusModelId = config.model?.plus?.id;
|
||||
const modelPath = config.model?.path;
|
||||
const plusEnabled = Boolean(config.plus?.enabled);
|
||||
|
||||
// The reliable signal that a Plus model is currently active is the
|
||||
@@ -138,8 +136,10 @@ const deriveInitialState = (config: FrigateConfig): PageState => {
|
||||
modelTab = "custom";
|
||||
}
|
||||
|
||||
const { plus: _plus, ...modelWithoutPlus } = (defaultModel ??
|
||||
{}) as unknown as Record<string, unknown>;
|
||||
const { plus: _plus, ...modelWithoutPlus } = (config.model ?? {}) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
// If a Plus model is active, the resolved `model.path` is auto-derived from
|
||||
// `plus.id` — drop it so the Custom tab starts clean and doesn't silently
|
||||
// re-save the same Plus model when the user thinks they switched modes.
|
||||
@@ -476,7 +476,7 @@ export default function DetectorsAndModelSettingsView({
|
||||
try {
|
||||
await axios.put("config/set", {
|
||||
requires_restart: 0,
|
||||
config_data: { detectors: null, models: { default: null } },
|
||||
config_data: { detectors: null, model: null },
|
||||
});
|
||||
preCleared = true;
|
||||
} catch {
|
||||
@@ -488,7 +488,7 @@ export default function DetectorsAndModelSettingsView({
|
||||
requires_restart: 0,
|
||||
config_data: {
|
||||
detectors: sanitizedDetectors,
|
||||
models: { default: modelPayload },
|
||||
model: modelPayload,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -541,7 +541,7 @@ export default function DetectorsAndModelSettingsView({
|
||||
snapshot.detectors,
|
||||
detectorHiddenFields,
|
||||
),
|
||||
models: { default: restoreModel },
|
||||
model: restoreModel,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user