mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-30 23:59:02 +03:00
Compare commits
225
Commits
v0.16.0-beta3
...
v0.16.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d582062fb | ||
|
|
e0a8445bac | ||
|
|
2a271c0f5e | ||
|
|
925bf78811 | ||
|
|
59102794e8 | ||
|
|
20e5e3bdc0 | ||
|
|
b94ebda9e5 | ||
|
|
8cdaef307a | ||
|
|
4914029a50 | ||
|
|
bafdab9d67 | ||
|
|
b08db4913f | ||
|
|
7c7ff49b90 | ||
|
|
037c4d1cc0 | ||
|
|
1613499218 | ||
|
|
205fdf3ae3 | ||
|
|
f46f8a2160 | ||
|
|
880902cdd7 | ||
|
|
c5ed95ec52 | ||
|
|
751de141d5 | ||
|
|
0eb441fe50 | ||
|
|
7566aecb0b | ||
|
|
60714a733e | ||
|
|
d7f7cd7be1 | ||
|
|
6591210050 | ||
|
|
7e7b3288a8 | ||
|
|
fe3eb24dfe | ||
|
|
e664cb2285 | ||
|
|
62047c80d5 | ||
|
|
198e53bd42 | ||
|
|
f7ed8b4cab | ||
|
|
e9dc30235b | ||
|
|
16b7f7f6e7 | ||
|
|
281c461647 | ||
|
|
667c302a7d | ||
|
|
ad694f5511 | ||
|
|
fa6956c46e | ||
|
|
b5aa1b2c21 | ||
|
|
f62feeb50c | ||
|
|
0dda37ac43 | ||
|
|
4fcb1ea7ac | ||
|
|
4347402fcc | ||
|
|
4fe246f472 | ||
|
|
7cf439e010 | ||
|
|
8a01643acf | ||
|
|
664a6fd0cb | ||
|
|
2b185a1105 | ||
|
|
75e33d8a56 | ||
|
|
8f4b5b4bdb | ||
|
|
95cea06dd3 | ||
|
|
ec2543c23f | ||
|
|
d27e8c1bbf | ||
|
|
353ee1228c | ||
|
|
ba20b61c43 | ||
|
|
b45f642868 | ||
|
|
9ed7ccab75 | ||
|
|
ceced7cc91 | ||
|
|
1db26cb41e | ||
|
|
6840415b6c | ||
|
|
06539c925c | ||
|
|
addb4e6891 | ||
|
|
fb290c411b | ||
|
|
89db960c05 | ||
|
|
2cde58037d | ||
|
|
d1be614a10 | ||
|
|
93c7c8c518 | ||
|
|
c83a35d090 | ||
|
|
d31a4e3443 | ||
|
|
c2f8de94e8 | ||
|
|
f46560d2bf | ||
|
|
a1acb504ee | ||
|
|
3d79eef227 | ||
|
|
efe0d2a931 | ||
|
|
cbd5bdfd88 | ||
|
|
6f2e6c4cb2 | ||
|
|
84f48ee3eb | ||
|
|
49793aa655 | ||
|
|
4869f46ab6 | ||
|
|
5e5beb9837 | ||
|
|
f7184c8ed5 | ||
|
|
2e1b81b380 | ||
|
|
30cf274815 | ||
|
|
b15c799d8c | ||
|
|
b6b3178e3d | ||
|
|
334b6670e1 | ||
|
|
5fc030c3f6 | ||
|
|
ca3afa8ac4 | ||
|
|
878f401ad2 | ||
|
|
99f9c1529d | ||
|
|
369e6ba2c2 | ||
|
|
8254602449 | ||
|
|
a0a5aad3c2 | ||
|
|
f107aa3cea | ||
|
|
cd7b9b604e | ||
|
|
1bbc8823da | ||
|
|
30de45a6f4 | ||
|
|
f9e5350d5a | ||
|
|
1471491124 | ||
|
|
c1da7128da | ||
|
|
f33468767e | ||
|
|
76fbc103b0 | ||
|
|
972206eb0f | ||
|
|
b5067c07f8 | ||
|
|
c3410cd13e | ||
|
|
d18f2282c8 | ||
|
|
23b32cbacf | ||
|
|
21e9b2f2ce | ||
|
|
92a0dad2c2 | ||
|
|
4f4c8a4fb9 | ||
|
|
898d1de875 | ||
|
|
0b7a33d670 | ||
|
|
d697e4f354 | ||
|
|
4ad1f6e610 | ||
|
|
072d9fc111 | ||
|
|
9653aead10 | ||
|
|
e910f5c97c | ||
|
|
146de123b9 | ||
|
|
2586b8d299 | ||
|
|
24fd95cff4 | ||
|
|
7aa57ebcae | ||
|
|
d5ce0ba73e | ||
|
|
0c92c3ccfa | ||
|
|
480072b22d | ||
|
|
455423e7f2 | ||
|
|
6d5fb65a68 | ||
|
|
eb8eee038f | ||
|
|
7680b050b4 | ||
|
|
60923ac1e3 | ||
|
|
1763dba9c2 | ||
|
|
ca1c98eab8 | ||
|
|
3eb3797bc5 | ||
|
|
4a94b43e52 | ||
|
|
f81ac43be6 | ||
|
|
4c8f4ef9fa | ||
|
|
4b72c86e77 | ||
|
|
d574d1edae | ||
|
|
d96efdbf2e | ||
|
|
0d78aca388 | ||
|
|
c0b2693c5b | ||
|
|
39ab6c8a10 | ||
|
|
0dd3713af8 | ||
|
|
52460a8fbb | ||
|
|
fea9901ae5 | ||
|
|
4db703cc3d | ||
|
|
b63a3d327d | ||
|
|
5b32ec4fbd | ||
|
|
12a80f5713 | ||
|
|
598520b5c2 | ||
|
|
89e5f60fab | ||
|
|
aa7c9f441f | ||
|
|
2aaf96d043 | ||
|
|
b7d705a028 | ||
|
|
68688a6c23 | ||
|
|
1fac827dde | ||
|
|
a1048654ee | ||
|
|
f75e55ee74 | ||
|
|
3bda638678 | ||
|
|
5cf98824f5 | ||
|
|
687e118b58 | ||
|
|
95daf0ba05 | ||
|
|
213dc97c17 | ||
|
|
ee1db240d7 | ||
|
|
79655379be | ||
|
|
f29cf43f52 | ||
|
|
cd54a81150 | ||
|
|
178117183e | ||
|
|
d69916694b | ||
|
|
fc608b31d7 | ||
|
|
9ffa7f140c | ||
|
|
6563f78ab0 | ||
|
|
4339f26ded | ||
|
|
86a3e31edf | ||
|
|
7238b3ea22 | ||
|
|
ea576e7468 | ||
|
|
5593495abd | ||
|
|
fd96cd5dae | ||
|
|
e0c1fea2ac | ||
|
|
f97629433d | ||
|
|
aabd5b0077 | ||
|
|
623bc72633 | ||
|
|
f3a0d519fb | ||
|
|
cc368dd20f | ||
|
|
8a9ebe9292 | ||
|
|
4ff81d5877 | ||
|
|
fe571dc217 | ||
|
|
42cae5d9ee | ||
|
|
ce2b60a4df | ||
|
|
02f75d3b1f | ||
|
|
c935a76a45 | ||
|
|
d727adddde | ||
|
|
2d586eeeb7 | ||
|
|
25cc2f31d4 | ||
|
|
4a21b2f16d | ||
|
|
99e7533de2 | ||
|
|
03768e0050 | ||
|
|
ed43df9c13 | ||
|
|
460e291bf1 | ||
|
|
dba9206898 | ||
|
|
af5a9e7634 | ||
|
|
afaac33c60 | ||
|
|
ee51326d35 | ||
|
|
948b087d3c | ||
|
|
77589c18f4 | ||
|
|
6a62467998 | ||
|
|
6857cc2b97 | ||
|
|
37618b0f57 | ||
|
|
e7f6e069f6 | ||
|
|
ee4767b1ce | ||
|
|
6cb5cfb0c9 | ||
|
|
7cfa818e63 | ||
|
|
0764fea159 | ||
|
|
e3ed1ab8ec | ||
|
|
b01b1faa3f | ||
|
|
efbc1f836b | ||
|
|
7c33f9c579 | ||
|
|
a9255bddb5 | ||
|
|
6d80a19518 | ||
|
|
011a2dbfaf | ||
|
|
9a54c8ca49 | ||
|
|
cc99330063 | ||
|
|
7e6a241e03 | ||
|
|
2d281855fc | ||
|
|
22cc698b4e | ||
|
|
5a5a54fc66 | ||
|
|
6536368467 | ||
|
|
dc79af2d98 |
@@ -6,7 +6,7 @@ body:
|
|||||||
value: |
|
value: |
|
||||||
Use this form to submit a reproducible bug in Frigate or Frigate's UI.
|
Use this form to submit a reproducible bug in Frigate or Frigate's UI.
|
||||||
|
|
||||||
Before submitting your bug report, please [search the discussions][discussions], look at recent open and closed [pull requests][prs], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your bug has already been fixed by the developers or reported by the community.
|
Before submitting your bug report, please ask the AI with the "Ask AI" button on the [official documentation site][ai] about your issue, [search the discussions][discussions], look at recent open and closed [pull requests][prs], read the [official Frigate documentation][docs], and read the [Frigate FAQ][faq] pinned at the Discussion page to see if your bug has already been fixed by the developers or reported by the community.
|
||||||
|
|
||||||
**If you are unsure if your issue is actually a bug or not, please submit a support request first.**
|
**If you are unsure if your issue is actually a bug or not, please submit a support request first.**
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ body:
|
|||||||
[prs]: https://www.github.com/blakeblackshear/frigate/pulls
|
[prs]: https://www.github.com/blakeblackshear/frigate/pulls
|
||||||
[docs]: https://docs.frigate.video
|
[docs]: https://docs.frigate.video
|
||||||
[faq]: https://github.com/blakeblackshear/frigate/discussions/12724
|
[faq]: https://github.com/blakeblackshear/frigate/discussions/12724
|
||||||
|
[ai]: https://docs.frigate.video
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Checklist
|
label: Checklist
|
||||||
@@ -26,6 +27,8 @@ body:
|
|||||||
- label: I have tried a different browser to see if it is related to my browser.
|
- label: I have tried a different browser to see if it is related to my browser.
|
||||||
required: true
|
required: true
|
||||||
- label: I have tried reproducing the issue in [incognito mode](https://www.computerworld.com/article/1719851/how-to-go-incognito-in-chrome-firefox-safari-and-edge.html) to rule out problems with any third party extensions or plugins I have installed.
|
- label: I have tried reproducing the issue in [incognito mode](https://www.computerworld.com/article/1719851/how-to-go-incognito-in-chrome-firefox-safari-and-edge.html) to rule out problems with any third party extensions or plugins I have installed.
|
||||||
|
- label: I have asked the AI at https://docs.frigate.video about my issue.
|
||||||
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ jobs:
|
|||||||
target: frigate
|
target: frigate
|
||||||
tags: ${{ steps.setup.outputs.image-name }}-amd64
|
tags: ${{ steps.setup.outputs.image-name }}-amd64
|
||||||
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
|
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
|
||||||
|
cache-to: type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64,mode=max
|
||||||
arm64_build:
|
arm64_build:
|
||||||
runs-on: ubuntu-22.04-arm
|
runs-on: ubuntu-22.04-arm
|
||||||
name: ARM Build
|
name: ARM Build
|
||||||
@@ -161,8 +162,8 @@ jobs:
|
|||||||
files: docker/tensorrt/trt.hcl
|
files: docker/tensorrt/trt.hcl
|
||||||
set: |
|
set: |
|
||||||
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt
|
tensorrt.tags=${{ steps.setup.outputs.image-name }}-tensorrt
|
||||||
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64
|
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt
|
||||||
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-amd64,mode=max
|
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-tensorrt,mode=max
|
||||||
- name: AMD/ROCm general build
|
- name: AMD/ROCm general build
|
||||||
env:
|
env:
|
||||||
AMDGPU: gfx
|
AMDGPU: gfx
|
||||||
@@ -176,7 +177,7 @@ jobs:
|
|||||||
set: |
|
set: |
|
||||||
rocm.tags=${{ steps.setup.outputs.image-name }}-rocm
|
rocm.tags=${{ steps.setup.outputs.image-name }}-rocm
|
||||||
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm,mode=max
|
*.cache-to=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm,mode=max
|
||||||
*.cache-from=type=gha
|
*.cache-from=type=registry,ref=${{ steps.setup.outputs.cache-name }}-rocm
|
||||||
arm64_extra_builds:
|
arm64_extra_builds:
|
||||||
runs-on: ubuntu-22.04-arm
|
runs-on: ubuntu-22.04-arm
|
||||||
name: ARM Extra Build
|
name: ARM Extra Build
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
default_target: local
|
default_target: local
|
||||||
|
|
||||||
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
|
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
|
||||||
VERSION = 0.16.0
|
VERSION = 0.16.2
|
||||||
IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate
|
IMAGE_REPO ?= ghcr.io/blakeblackshear/frigate
|
||||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
BOARDS= #Initialized empty
|
BOARDS= #Initialized empty
|
||||||
|
|||||||
+7
-2
@@ -39,7 +39,7 @@
|
|||||||
<img width="800" alt="实时监控面板" src="https://github.com/blakeblackshear/frigate/assets/569905/5e713cb9-9db5-41dc-947a-6937c3bc376e">
|
<img width="800" alt="实时监控面板" src="https://github.com/blakeblackshear/frigate/assets/569905/5e713cb9-9db5-41dc-947a-6937c3bc376e">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### 简单的审查工作流程
|
### 简单的核查工作流程
|
||||||
<div>
|
<div>
|
||||||
<img width="800" alt="简单的审查工作流程" src="https://github.com/blakeblackshear/frigate/assets/569905/6fed96e8-3b18-40e5-9ddc-31e6f3c9f2ff">
|
<img width="800" alt="简单的审查工作流程" src="https://github.com/blakeblackshear/frigate/assets/569905/6fed96e8-3b18-40e5-9ddc-31e6f3c9f2ff">
|
||||||
</div>
|
</div>
|
||||||
@@ -60,6 +60,11 @@
|
|||||||
|
|
||||||
|
|
||||||
## 非官方中文讨论社区
|
## 非官方中文讨论社区
|
||||||
欢迎加入中文讨论QQ群:1043861059
|
欢迎加入中文讨论QQ群:[1043861059](https://qm.qq.com/q/7vQKsTmSz)
|
||||||
|
|
||||||
Bilibili:https://space.bilibili.com/3546894915602564
|
Bilibili:https://space.bilibili.com/3546894915602564
|
||||||
|
|
||||||
|
|
||||||
|
## 中文社区赞助商
|
||||||
|
[](https://edgeone.ai/zh?from=github)
|
||||||
|
本项目 CDN 加速及安全防护由 Tencent EdgeOne 赞助
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential cmake git wget
|
sudo apt-get install -y build-essential cmake git wget
|
||||||
|
|
||||||
hailo_version="4.20.1"
|
hailo_version="4.21.0"
|
||||||
arch=$(uname -m)
|
arch=$(uname -m)
|
||||||
|
|
||||||
if [[ $arch == "x86_64" ]]; then
|
if [[ $arch == "x86_64" ]]; then
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ ARG TARGETARCH
|
|||||||
# Use a separate container to build wheels to prevent build dependencies in final image
|
# Use a separate container to build wheels to prevent build dependencies in final image
|
||||||
RUN apt-get -qq update \
|
RUN apt-get -qq update \
|
||||||
&& apt-get -qq install -y \
|
&& apt-get -qq install -y \
|
||||||
apt-transport-https wget \
|
apt-transport-https wget unzip \
|
||||||
&& apt-get -qq update \
|
&& apt-get -qq update \
|
||||||
&& apt-get -qq install -y \
|
&& apt-get -qq install -y \
|
||||||
python3.11 \
|
python3.11 \
|
||||||
|
|||||||
@@ -2,18 +2,25 @@
|
|||||||
|
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
SQLITE3_VERSION="96c92aba00c8375bc32fafcdf12429c58bd8aabfcadab6683e35bbb9cdebf19e" # 3.46.0
|
SQLITE3_VERSION="3.46.1"
|
||||||
PYSQLITE3_VERSION="0.5.3"
|
PYSQLITE3_VERSION="0.5.3"
|
||||||
|
|
||||||
# Fetch the source code for the latest release of Sqlite.
|
# Fetch the pre-built sqlite amalgamation instead of building from source
|
||||||
if [[ ! -d "sqlite" ]]; then
|
if [[ ! -d "sqlite" ]]; then
|
||||||
wget https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=${SQLITE3_VERSION} -O sqlite.tar.gz
|
mkdir sqlite
|
||||||
tar xzf sqlite.tar.gz
|
cd sqlite
|
||||||
cd sqlite/
|
|
||||||
LIBS="-lm" ./configure --disable-tcl --enable-tempstore=always
|
# Download the pre-built amalgamation from sqlite.org
|
||||||
make sqlite3.c
|
# For SQLite 3.46.1, the amalgamation version is 3460100
|
||||||
|
SQLITE_AMALGAMATION_VERSION="3460100"
|
||||||
|
|
||||||
|
wget https://www.sqlite.org/2024/sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}.zip -O sqlite-amalgamation.zip
|
||||||
|
unzip sqlite-amalgamation.zip
|
||||||
|
mv sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}/* .
|
||||||
|
rmdir sqlite-amalgamation-${SQLITE_AMALGAMATION_VERSION}
|
||||||
|
rm sqlite-amalgamation.zip
|
||||||
|
|
||||||
cd ../
|
cd ../
|
||||||
rm sqlite.tar.gz
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Grab the pysqlite3 source code.
|
# Grab the pysqlite3 source code.
|
||||||
|
|||||||
@@ -57,9 +57,16 @@ fi
|
|||||||
|
|
||||||
# arch specific packages
|
# arch specific packages
|
||||||
if [[ "${TARGETARCH}" == "amd64" ]]; then
|
if [[ "${TARGETARCH}" == "amd64" ]]; then
|
||||||
|
# Install non-free version of i965 driver
|
||||||
|
sed -i -E "/^Components: main$/s/main/main contrib non-free non-free-firmware/" "/etc/apt/sources.list.d/debian.sources" \
|
||||||
|
&& apt-get -qq update \
|
||||||
|
&& apt-get install --no-install-recommends --no-install-suggests -y i965-va-driver-shaders \
|
||||||
|
&& sed -i -E "/^Components: main contrib non-free non-free-firmware$/s/main contrib non-free non-free-firmware/main/" "/etc/apt/sources.list.d/debian.sources" \
|
||||||
|
&& apt-get update
|
||||||
|
|
||||||
# install amd / intel-i965 driver packages
|
# install amd / intel-i965 driver packages
|
||||||
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
apt-get -qq install --no-install-recommends --no-install-suggests -y \
|
||||||
i965-va-driver intel-gpu-tools onevpl-tools \
|
intel-gpu-tools onevpl-tools \
|
||||||
libva-drm2 \
|
libva-drm2 \
|
||||||
mesa-va-drivers radeontop
|
mesa-va-drivers radeontop
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
hailo_version="4.20.1"
|
hailo_version="4.21.0"
|
||||||
|
|
||||||
if [[ "${TARGETARCH}" == "amd64" ]]; then
|
if [[ "${TARGETARCH}" == "amd64" ]]; then
|
||||||
arch="x86_64"
|
arch="x86_64"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ set -o errexit -o nounset -o pipefail
|
|||||||
|
|
||||||
# opt out of openvino telemetry
|
# opt out of openvino telemetry
|
||||||
if [ -e /usr/local/bin/opt_in_out ]; then
|
if [ -e /usr/local/bin/opt_in_out ]; then
|
||||||
/usr/local/bin/opt_in_out --opt_out
|
/usr/local/bin/opt_in_out --opt_out > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Logs should be sent to stdout so that s6 can collect them
|
# Logs should be sent to stdout so that s6 can collect them
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ RUN sed -i "/https:\/\//d" /requirements-wheels.txt
|
|||||||
RUN sed -i "/onnxruntime/d" /requirements-wheels.txt
|
RUN sed -i "/onnxruntime/d" /requirements-wheels.txt
|
||||||
RUN pip3 wheel --wheel-dir=/rk-wheels -c /requirements-wheels.txt -r /requirements-wheels-rk.txt
|
RUN pip3 wheel --wheel-dir=/rk-wheels -c /requirements-wheels.txt -r /requirements-wheels-rk.txt
|
||||||
RUN rm -rf /rk-wheels/opencv_python-*
|
RUN rm -rf /rk-wheels/opencv_python-*
|
||||||
|
RUN rm -rf /rk-wheels/torch-*
|
||||||
|
|
||||||
FROM deps AS rk-frigate
|
FROM deps AS rk-frigate
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
@@ -28,7 +29,9 @@ COPY docker/rockchip/conv2rknn.py /opt/conv2rknn.py
|
|||||||
|
|
||||||
ADD https://github.com/MarcA711/rknn-toolkit2/releases/download/v2.3.2/librknnrt.so /usr/lib/
|
ADD https://github.com/MarcA711/rknn-toolkit2/releases/download/v2.3.2/librknnrt.so /usr/lib/
|
||||||
|
|
||||||
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-7/ffmpeg /usr/lib/ffmpeg/6.0/bin/
|
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-11/ffmpeg /usr/lib/ffmpeg/6.0/bin/
|
||||||
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-7/ffprobe /usr/lib/ffmpeg/6.0/bin/
|
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/6.1-11/ffprobe /usr/lib/ffmpeg/6.0/bin/
|
||||||
|
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/7.1-1/ffmpeg /usr/lib/ffmpeg/7.0/bin/
|
||||||
|
ADD --chmod=111 https://github.com/MarcA711/Rockchip-FFmpeg-Builds/releases/download/7.1-1/ffprobe /usr/lib/ffmpeg/7.0/bin/
|
||||||
ENV DEFAULT_FFMPEG_VERSION="6.0"
|
ENV DEFAULT_FFMPEG_VERSION="6.0"
|
||||||
ENV INCLUDED_FFMPEG_VERSIONS="${DEFAULT_FFMPEG_VERSION}:${INCLUDED_FFMPEG_VERSIONS}"
|
ENV INCLUDED_FFMPEG_VERSIONS="${DEFAULT_FFMPEG_VERSION}:${INCLUDED_FFMPEG_VERSIONS}"
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ COPY --from=rocm /opt/rocm-dist/ /
|
|||||||
FROM deps-prelim AS rocm-prelim-hsa-override0
|
FROM deps-prelim AS rocm-prelim-hsa-override0
|
||||||
ENV HSA_ENABLE_SDMA=0
|
ENV HSA_ENABLE_SDMA=0
|
||||||
ENV MIGRAPHX_ENABLE_NHWC=1
|
ENV MIGRAPHX_ENABLE_NHWC=1
|
||||||
|
ENV TF_ROCM_USE_IMMEDIATE_MODE=1
|
||||||
|
|
||||||
COPY --from=rocm-dist / /
|
COPY --from=rocm-dist / /
|
||||||
|
|
||||||
|
|||||||
@@ -6,24 +6,29 @@ ARG DEBIAN_FRONTEND=noninteractive
|
|||||||
# Globally set pip break-system-packages option to avoid having to specify it every time
|
# Globally set pip break-system-packages option to avoid having to specify it every time
|
||||||
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
ARG PIP_BREAK_SYSTEM_PACKAGES=1
|
||||||
|
|
||||||
FROM tensorrt-base AS frigate-tensorrt
|
FROM wheels AS trt-wheels
|
||||||
ARG PIP_BREAK_SYSTEM_PACKAGES
|
ARG PIP_BREAK_SYSTEM_PACKAGES
|
||||||
ENV TRT_VER=8.6.1
|
|
||||||
|
|
||||||
# Install TensorRT wheels
|
# Install TensorRT wheels
|
||||||
COPY docker/tensorrt/requirements-amd64.txt /requirements-tensorrt.txt
|
COPY docker/tensorrt/requirements-amd64.txt /requirements-tensorrt.txt
|
||||||
RUN pip3 install -U -r /requirements-tensorrt.txt && ldconfig
|
COPY docker/main/requirements-wheels.txt /requirements-wheels.txt
|
||||||
|
RUN pip3 wheel --wheel-dir=/trt-wheels -c /requirements-wheels.txt -r /requirements-tensorrt.txt
|
||||||
|
|
||||||
|
FROM deps AS frigate-tensorrt
|
||||||
|
ARG PIP_BREAK_SYSTEM_PACKAGES
|
||||||
|
|
||||||
|
RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \
|
||||||
|
pip3 uninstall -y onnxruntime-openvino tensorflow-cpu \
|
||||||
|
&& pip3 install -U /deps/trt-wheels/*.whl
|
||||||
|
|
||||||
|
COPY --from=rootfs / /
|
||||||
|
COPY docker/tensorrt/detector/rootfs/etc/ld.so.conf.d /etc/ld.so.conf.d
|
||||||
|
RUN ldconfig
|
||||||
|
|
||||||
WORKDIR /opt/frigate/
|
WORKDIR /opt/frigate/
|
||||||
COPY --from=rootfs / /
|
|
||||||
|
|
||||||
# Dev Container w/ TRT
|
# Dev Container w/ TRT
|
||||||
FROM devcontainer AS devcontainer-trt
|
FROM devcontainer AS devcontainer-trt
|
||||||
|
|
||||||
COPY --from=trt-deps /usr/local/lib/libyolo_layer.so /usr/local/lib/libyolo_layer.so
|
|
||||||
COPY --from=trt-deps /usr/local/src/tensorrt_demos /usr/local/src/tensorrt_demos
|
|
||||||
COPY --from=trt-deps /usr/local/cuda-12.1 /usr/local/cuda
|
|
||||||
COPY docker/tensorrt/detector/rootfs/ /
|
|
||||||
COPY --from=trt-deps /usr/local/lib/libyolo_layer.so /usr/local/lib/libyolo_layer.so
|
|
||||||
RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \
|
RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \
|
||||||
pip3 install -U /deps/trt-wheels/*.whl
|
pip3 install -U /deps/trt-wheels/*.whl
|
||||||
|
|||||||
@@ -1,9 +1,61 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.6
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
ARG BASE_IMAGE
|
ARG BASE_IMAGE
|
||||||
|
ARG TRT_BASE=nvcr.io/nvidia/tensorrt:23.12-py3
|
||||||
|
|
||||||
|
# Build TensorRT-specific library
|
||||||
|
FROM ${TRT_BASE} AS trt-deps
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG COMPUTE_LEVEL
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y git build-essential cuda-nvcc-* cuda-nvtx-* libnvinfer-dev libnvinfer-plugin-dev libnvparsers-dev libnvonnxparsers-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN --mount=type=bind,source=docker/tensorrt/detector/tensorrt_libyolo.sh,target=/tensorrt_libyolo.sh \
|
||||||
|
/tensorrt_libyolo.sh
|
||||||
|
|
||||||
|
# COPY required individual CUDA deps
|
||||||
|
RUN mkdir -p /usr/local/cuda-deps
|
||||||
|
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||||
|
cp /usr/local/cuda-12.3/targets/x86_64-linux/lib/libcurand.so.* /usr/local/cuda-deps/ && \
|
||||||
|
cp /usr/local/cuda-12.3/targets/x86_64-linux/lib/libnvrtc.so.* /usr/local/cuda-deps/ && \
|
||||||
|
cd /usr/local/cuda-deps/ && \
|
||||||
|
for lib in libnvrtc.so.*; do \
|
||||||
|
if [[ "$lib" =~ libnvrtc.so\.([0-9]+\.[0-9]+\.[0-9]+) ]]; then \
|
||||||
|
version="${BASH_REMATCH[1]}"; \
|
||||||
|
ln -sf "libnvrtc.so.$version" libnvrtc.so; \
|
||||||
|
fi; \
|
||||||
|
done && \
|
||||||
|
for lib in libcurand.so.*; do \
|
||||||
|
if [[ "$lib" =~ libcurand.so\.([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) ]]; then \
|
||||||
|
version="${BASH_REMATCH[1]}"; \
|
||||||
|
ln -sf "libcurand.so.$version" libcurand.so; \
|
||||||
|
fi; \
|
||||||
|
done; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Frigate w/ TensorRT Support as separate image
|
||||||
|
FROM deps AS tensorrt-base
|
||||||
|
|
||||||
|
#Disable S6 Global timeout
|
||||||
|
ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
|
||||||
|
|
||||||
|
# COPY TensorRT Model Generation Deps
|
||||||
|
COPY --from=trt-deps /usr/local/lib/libyolo_layer.so /usr/local/lib/libyolo_layer.so
|
||||||
|
COPY --from=trt-deps /usr/local/src/tensorrt_demos /usr/local/src/tensorrt_demos
|
||||||
|
|
||||||
|
# COPY Individual CUDA deps folder
|
||||||
|
COPY --from=trt-deps /usr/local/cuda-deps /usr/local/cuda
|
||||||
|
|
||||||
|
COPY docker/tensorrt/detector/rootfs/ /
|
||||||
|
ENV YOLO_MODELS=""
|
||||||
|
|
||||||
|
HEALTHCHECK --start-period=600s --start-interval=5s --interval=15s --timeout=5s --retries=3 \
|
||||||
|
CMD curl --fail --silent --show-error http://127.0.0.1:5000/api/version || exit 1
|
||||||
|
|
||||||
FROM ${BASE_IMAGE} AS build-wheels
|
FROM ${BASE_IMAGE} AS build-wheels
|
||||||
ARG DEBIAN_FRONTEND
|
ARG DEBIAN_FRONTEND
|
||||||
|
|
||||||
@@ -100,4 +152,4 @@ WORKDIR /opt/frigate/
|
|||||||
COPY --from=rootfs / /
|
COPY --from=rootfs / /
|
||||||
|
|
||||||
# Fixes "Error importing detector runtime: /usr/lib/aarch64-linux-gnu/libstdc++.so.6: cannot allocate memory in static TLS block"
|
# Fixes "Error importing detector runtime: /usr/lib/aarch64-linux-gnu/libstdc++.so.6: cannot allocate memory in static TLS block"
|
||||||
ENV LD_PRELOAD /usr/lib/aarch64-linux-gnu/libstdc++.so.6
|
ENV LD_PRELOAD /usr/lib/aarch64-linux-gnu/libstdc++.so.6
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# syntax=docker/dockerfile:1.6
|
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
ARG TRT_BASE=nvcr.io/nvidia/tensorrt:23.12-py3
|
|
||||||
|
|
||||||
# Build TensorRT-specific library
|
|
||||||
FROM ${TRT_BASE} AS trt-deps
|
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG COMPUTE_LEVEL
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install -y git build-essential cuda-nvcc-* cuda-nvtx-* libnvinfer-dev libnvinfer-plugin-dev libnvparsers-dev libnvonnxparsers-dev \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
RUN --mount=type=bind,source=docker/tensorrt/detector/tensorrt_libyolo.sh,target=/tensorrt_libyolo.sh \
|
|
||||||
/tensorrt_libyolo.sh
|
|
||||||
|
|
||||||
# COPY required individual CUDA deps
|
|
||||||
RUN mkdir -p /usr/local/cuda-deps
|
|
||||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
|
||||||
cp /usr/local/cuda-12.3/targets/x86_64-linux/lib/libcurand.so.* /usr/local/cuda-deps/ && \
|
|
||||||
cp /usr/local/cuda-12.3/targets/x86_64-linux/lib/libnvrtc.so.* /usr/local/cuda-deps/ && \
|
|
||||||
cd /usr/local/cuda-deps/ && \
|
|
||||||
for lib in libnvrtc.so.*; do \
|
|
||||||
if [[ "$lib" =~ libnvrtc.so\.([0-9]+\.[0-9]+\.[0-9]+) ]]; then \
|
|
||||||
version="${BASH_REMATCH[1]}"; \
|
|
||||||
ln -sf "libnvrtc.so.$version" libnvrtc.so; \
|
|
||||||
fi; \
|
|
||||||
done && \
|
|
||||||
for lib in libcurand.so.*; do \
|
|
||||||
if [[ "$lib" =~ libcurand.so\.([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) ]]; then \
|
|
||||||
version="${BASH_REMATCH[1]}"; \
|
|
||||||
ln -sf "libcurand.so.$version" libcurand.so; \
|
|
||||||
fi; \
|
|
||||||
done; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Frigate w/ TensorRT Support as separate image
|
|
||||||
FROM deps AS tensorrt-base
|
|
||||||
|
|
||||||
#Disable S6 Global timeout
|
|
||||||
ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0
|
|
||||||
|
|
||||||
# COPY TensorRT Model Generation Deps
|
|
||||||
COPY --from=trt-deps /usr/local/lib/libyolo_layer.so /usr/local/lib/libyolo_layer.so
|
|
||||||
COPY --from=trt-deps /usr/local/src/tensorrt_demos /usr/local/src/tensorrt_demos
|
|
||||||
|
|
||||||
# COPY Individual CUDA deps folder
|
|
||||||
COPY --from=trt-deps /usr/local/cuda-deps /usr/local/cuda
|
|
||||||
|
|
||||||
COPY docker/tensorrt/detector/rootfs/ /
|
|
||||||
ENV YOLO_MODELS=""
|
|
||||||
|
|
||||||
HEALTHCHECK --start-period=600s --start-interval=5s --interval=15s --timeout=5s --retries=3 \
|
|
||||||
CMD curl --fail --silent --show-error http://127.0.0.1:5000/api/version || exit 1
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
/usr/local/lib
|
|
||||||
/usr/local/cuda
|
|
||||||
/usr/local/lib/python3.11/dist-packages/tensorrt
|
|
||||||
/usr/local/lib/python3.11/dist-packages/nvidia/cudnn/lib
|
/usr/local/lib/python3.11/dist-packages/nvidia/cudnn/lib
|
||||||
/usr/local/lib/python3.11/dist-packages/nvidia/cuda_runtime/lib
|
/usr/local/lib/python3.11/dist-packages/nvidia/cuda_runtime/lib
|
||||||
/usr/local/lib/python3.11/dist-packages/nvidia/cublas/lib
|
/usr/local/lib/python3.11/dist-packages/nvidia/cublas/lib
|
||||||
/usr/local/lib/python3.11/dist-packages/nvidia/cufft/lib
|
/usr/local/lib/python3.11/dist-packages/nvidia/cufft/lib
|
||||||
|
/usr/local/lib/python3.11/dist-packages/nvidia/curand/lib/
|
||||||
|
/usr/local/lib/python3.11/dist-packages/nvidia/cuda_nvrtc/lib/
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
# NVidia TensorRT Support (amd64 only)
|
# NVidia TensorRT Support (amd64 only)
|
||||||
--extra-index-url 'https://pypi.nvidia.com'
|
--extra-index-url 'https://pypi.nvidia.com'
|
||||||
numpy < 1.24; platform_machine == 'x86_64'
|
cython==3.0.*; platform_machine == 'x86_64'
|
||||||
tensorrt == 8.6.1; platform_machine == 'x86_64'
|
nvidia_cuda_cupti_cu12==12.5.82; platform_machine == 'x86_64'
|
||||||
tensorrt_bindings == 8.6.1; platform_machine == 'x86_64'
|
nvidia-cublas-cu12==12.5.3.*; platform_machine == 'x86_64'
|
||||||
cuda-python == 11.8.*; platform_machine == 'x86_64'
|
nvidia-cudnn-cu12==9.3.0.*; platform_machine == 'x86_64'
|
||||||
cython == 3.0.*; platform_machine == 'x86_64'
|
nvidia-cufft-cu12==11.2.3.*; platform_machine == 'x86_64'
|
||||||
nvidia-cuda-runtime-cu12 == 12.1.*; platform_machine == 'x86_64'
|
nvidia-curand-cu12==10.3.6.*; platform_machine == 'x86_64'
|
||||||
nvidia-cuda-runtime-cu11 == 11.8.*; platform_machine == 'x86_64'
|
nvidia_cuda_nvcc_cu12==12.5.82; platform_machine == 'x86_64'
|
||||||
nvidia-cublas-cu11 == 11.11.3.6; platform_machine == 'x86_64'
|
nvidia-cuda-nvrtc-cu12==12.5.82; platform_machine == 'x86_64'
|
||||||
nvidia-cudnn-cu11 == 8.6.0.*; platform_machine == 'x86_64'
|
nvidia_cuda_runtime_cu12==12.5.82; platform_machine == 'x86_64'
|
||||||
nvidia-cudnn-cu12 == 9.5.0.*; platform_machine == 'x86_64'
|
nvidia_cusolver_cu12==11.6.3.*; platform_machine == 'x86_64'
|
||||||
nvidia-cufft-cu11==10.*; platform_machine == 'x86_64'
|
nvidia_cusparse_cu12==12.5.1.*; platform_machine == 'x86_64'
|
||||||
nvidia-cufft-cu12==11.*; platform_machine == 'x86_64'
|
nvidia_nccl_cu12==2.23.4; platform_machine == 'x86_64'
|
||||||
|
nvidia_nvjitlink_cu12==12.5.82; platform_machine == 'x86_64'
|
||||||
onnx==1.16.*; platform_machine == 'x86_64'
|
onnx==1.16.*; platform_machine == 'x86_64'
|
||||||
onnxruntime-gpu==1.20.*; platform_machine == 'x86_64'
|
onnxruntime-gpu==1.20.*; platform_machine == 'x86_64'
|
||||||
protobuf==3.20.3; platform_machine == 'x86_64'
|
protobuf==3.20.3; platform_machine == 'x86_64'
|
||||||
|
|||||||
+2
-10
@@ -79,21 +79,13 @@ target "trt-deps" {
|
|||||||
inherits = ["_build_args"]
|
inherits = ["_build_args"]
|
||||||
}
|
}
|
||||||
|
|
||||||
target "tensorrt-base" {
|
|
||||||
dockerfile = "docker/tensorrt/Dockerfile.base"
|
|
||||||
context = "."
|
|
||||||
contexts = {
|
|
||||||
deps = "target:deps",
|
|
||||||
}
|
|
||||||
inherits = ["_build_args"]
|
|
||||||
}
|
|
||||||
|
|
||||||
target "tensorrt" {
|
target "tensorrt" {
|
||||||
dockerfile = "docker/tensorrt/Dockerfile.${ARCH}"
|
dockerfile = "docker/tensorrt/Dockerfile.${ARCH}"
|
||||||
context = "."
|
context = "."
|
||||||
contexts = {
|
contexts = {
|
||||||
wget = "target:wget",
|
wget = "target:wget",
|
||||||
tensorrt-base = "target:tensorrt-base",
|
wheels = "target:wheels",
|
||||||
|
deps = "target:deps",
|
||||||
rootfs = "target:rootfs"
|
rootfs = "target:rootfs"
|
||||||
}
|
}
|
||||||
target = "frigate-tensorrt"
|
target = "frigate-tensorrt"
|
||||||
|
|||||||
@@ -52,6 +52,21 @@ auth:
|
|||||||
- 172.18.0.0/16 # <---- this is the subnet for the internal Docker Compose network
|
- 172.18.0.0/16 # <---- this is the subnet for the internal Docker Compose network
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Session Length
|
||||||
|
|
||||||
|
The default session length for user authentication in Frigate is 24 hours. This setting determines how long a user's authenticated session remains active before a token refresh is required — otherwise, the user will need to log in again.
|
||||||
|
|
||||||
|
While the default provides a balance of security and convenience, you can customize this duration to suit your specific security requirements and user experience preferences. The session length is configured in seconds.
|
||||||
|
|
||||||
|
The default value of `86400` will expire the authentication session after 24 hours. Some other examples:
|
||||||
|
- `0`: Setting the session length to 0 will require a user to log in every time they access the application or after a very short, immediate timeout.
|
||||||
|
- `604800`: Setting the session length to 604800 will require a user to log in if the token is not refreshed for 7 days.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
auth:
|
||||||
|
session_length: 86400
|
||||||
|
```
|
||||||
|
|
||||||
## JWT Token Secret
|
## JWT Token Secret
|
||||||
|
|
||||||
The JWT token secret needs to be kept secure. Anyone with this secret can generate valid JWT tokens to authenticate with Frigate. This should be a cryptographically random string of at least 64 characters.
|
The JWT token secret needs to be kept secure. Anyone with this secret can generate valid JWT tokens to authenticate with Frigate. This should be a cryptographically random string of at least 64 characters.
|
||||||
@@ -97,7 +112,7 @@ python3 -c 'import secrets; print(secrets.token_hex(64))'
|
|||||||
|
|
||||||
### Header mapping
|
### Header mapping
|
||||||
|
|
||||||
If you have disabled Frigate's authentication and your proxy supports passing a header with authenticated usernames and/or roles, you can use the `header_map` config to specify the header name so it is passed to Frigate. For example, the following will map the `X-Forwarded-User` and `X-Forwarded-Role` values. Header names are not case sensitive. Multiple values can be included in the role header. Frigate expects that the character separating the roles is a comma, but this can be specified using the `separator` config entry.
|
If you have disabled Frigate's authentication and your proxy supports passing a header with authenticated usernames and/or roles, you can use the `header_map` config to specify the header name so it is passed to Frigate. For example, the following will map the `X-Forwarded-User` and `X-Forwarded-Groups` values. Header names are not case sensitive. Multiple values can be included in the role header. Frigate expects that the character separating the roles is a comma, but this can be specified using the `separator` config entry.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
proxy:
|
proxy:
|
||||||
@@ -105,7 +120,7 @@ proxy:
|
|||||||
separator: "|" # This value defaults to a comma, but Authentik uses a pipe, for example.
|
separator: "|" # This value defaults to a comma, but Authentik uses a pipe, for example.
|
||||||
header_map:
|
header_map:
|
||||||
user: x-forwarded-user
|
user: x-forwarded-user
|
||||||
role: x-forwarded-role
|
role: x-forwarded-groups
|
||||||
```
|
```
|
||||||
|
|
||||||
Frigate supports both `admin` and `viewer` roles (see below). When using port `8971`, Frigate validates these headers and subsequent requests use the headers `remote-user` and `remote-role` for authorization.
|
Frigate supports both `admin` and `viewer` roles (see below). When using port `8971`, Frigate validates these headers and subsequent requests use the headers `remote-user` and `remote-role` for authorization.
|
||||||
|
|||||||
@@ -144,7 +144,14 @@ WEB Digest Algorithm - MD5
|
|||||||
|
|
||||||
### Reolink Cameras
|
### Reolink Cameras
|
||||||
|
|
||||||
Reolink has older cameras (ex: 410 & 520) as well as newer camera (ex: 520a & 511wa) which support different subsets of options. In both cases using the http stream is recommended.
|
Reolink has many different camera models with inconsistently supported features and behavior. The below table shows a summary of various features and recommendations.
|
||||||
|
|
||||||
|
| Camera Resolution | Camera Generation | Recommended Stream Type | Additional Notes |
|
||||||
|
| ---------------- | ------------------------- | -------------------------------- | ----------------------------------------------------------------------- |
|
||||||
|
| 5MP or lower | All | http-flv | Stream is h264 |
|
||||||
|
| 6MP or higher | Latest (ex: Duo3, CX-8##) | http-flv with ffmpeg 8.0, or rtsp | This uses the new http-flv-enhanced over H265 which requires ffmpeg 8.0 |
|
||||||
|
| 6MP or higher | Older (ex: RLC-8##) | rtsp | |
|
||||||
|
|
||||||
Frigate works much better with newer reolink cameras that are setup with the below options:
|
Frigate works much better with newer reolink cameras that are setup with the below options:
|
||||||
|
|
||||||
If available, recommended settings are:
|
If available, recommended settings are:
|
||||||
@@ -157,12 +164,6 @@ According to [this discussion](https://github.com/blakeblackshear/frigate/issues
|
|||||||
Cameras connected via a Reolink NVR can be connected with the http stream, use `channel[0..15]` in the stream url for the additional channels.
|
Cameras connected via a Reolink NVR can be connected with the http stream, use `channel[0..15]` in the stream url for the additional channels.
|
||||||
The setup of main stream can be also done via RTSP, but isn't always reliable on all hardware versions. The example configuration is working with the oldest HW version RLN16-410 device with multiple types of cameras.
|
The setup of main stream can be also done via RTSP, but isn't always reliable on all hardware versions. The example configuration is working with the oldest HW version RLN16-410 device with multiple types of cameras.
|
||||||
|
|
||||||
:::warning
|
|
||||||
|
|
||||||
The below configuration only works for reolink cameras with stream resolution of 5MP or lower, 8MP+ cameras need to use RTSP as http-flv is not supported in this case.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
go2rtc:
|
go2rtc:
|
||||||
streams:
|
streams:
|
||||||
@@ -212,7 +213,7 @@ go2rtc:
|
|||||||
streams:
|
streams:
|
||||||
your_reolink_doorbell:
|
your_reolink_doorbell:
|
||||||
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password#video=copy#audio=copy#audio=opus"
|
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_main.bcs&user=username&password=password#video=copy#audio=copy#audio=opus"
|
||||||
- rtsp://reolink_ip/Preview_01_sub
|
- rtsp://username:password@reolink_ip/Preview_01_sub
|
||||||
your_reolink_doorbell_sub:
|
your_reolink_doorbell_sub:
|
||||||
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password"
|
- "ffmpeg:http://reolink_ip/flv?port=1935&app=bcs&stream=channel0_ext.bcs&user=username&password=password"
|
||||||
```
|
```
|
||||||
@@ -243,3 +244,38 @@ ffmpeg:
|
|||||||
### TP-Link VIGI Cameras
|
### TP-Link VIGI Cameras
|
||||||
|
|
||||||
TP-Link VIGI cameras need some adjustments to the main stream settings on the camera itself to avoid issues. The stream needs to be configured as `H264` with `Smart Coding` set to `off`. Without these settings you may have problems when trying to watch recorded footage. For example Firefox will stop playback after a few seconds and show the following error message: `The media playback was aborted due to a corruption problem or because the media used features your browser did not support.`.
|
TP-Link VIGI cameras need some adjustments to the main stream settings on the camera itself to avoid issues. The stream needs to be configured as `H264` with `Smart Coding` set to `off`. Without these settings you may have problems when trying to watch recorded footage. For example Firefox will stop playback after a few seconds and show the following error message: `The media playback was aborted due to a corruption problem or because the media used features your browser did not support.`.
|
||||||
|
|
||||||
|
## USB Cameras (aka Webcams)
|
||||||
|
|
||||||
|
To use a USB camera (webcam) with Frigate, the recommendation is to use go2rtc's [FFmpeg Device](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#source-ffmpeg-device) support:
|
||||||
|
|
||||||
|
- Preparation outside of Frigate:
|
||||||
|
- Get USB camera path. Run `v4l2-ctl --list-devices` to get a listing of locally-connected cameras available. (You may need to install `v4l-utils` in a way appropriate for your Linux distribution). In the sample configuration below, we use `video=0` to correlate with a detected device path of `/dev/video0`
|
||||||
|
- Get USB camera formats & resolutions. Run `ffmpeg -f v4l2 -list_formats all -i /dev/video0` to get an idea of what formats and resolutions the USB Camera supports. In the sample configuration below, we use a width of 1024 and height of 576 in the stream and detection settings based on what was reported back.
|
||||||
|
- If using Frigate in a container (e.g. Docker on TrueNAS), ensure you have USB Passthrough support enabled, along with a specific Host Device (`/dev/video0`) + Container Device (`/dev/video0`) listed.
|
||||||
|
|
||||||
|
- In your Frigate Configuration File, add the go2rtc stream and roles as appropriate:
|
||||||
|
|
||||||
|
```
|
||||||
|
go2rtc:
|
||||||
|
streams:
|
||||||
|
usb_camera:
|
||||||
|
- "ffmpeg:device?video=0&video_size=1024x576#video=h264"
|
||||||
|
|
||||||
|
cameras:
|
||||||
|
usb_camera:
|
||||||
|
enabled: true
|
||||||
|
ffmpeg:
|
||||||
|
inputs:
|
||||||
|
- path: rtsp://127.0.0.1:8554/usb_camera
|
||||||
|
input_args: preset-rtsp-restream
|
||||||
|
roles:
|
||||||
|
- detect
|
||||||
|
- record
|
||||||
|
detect:
|
||||||
|
enabled: false # <---- disable detection until you have a working camera feed
|
||||||
|
width: 1024
|
||||||
|
height: 576
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,17 +97,17 @@ This list of working and non-working PTZ cameras is based on user feedback.
|
|||||||
| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 |
|
| Amcrest ASH21 | ✅ | ❌ | ONVIF service port: 80 |
|
||||||
| Amcrest IP4M-S2112EW-AI | ✅ | ❌ | FOV relative movement not supported. |
|
| Amcrest IP4M-S2112EW-AI | ✅ | ❌ | FOV relative movement not supported. |
|
||||||
| Amcrest IP5M-1190EW | ✅ | ❌ | ONVIF Port: 80. FOV relative movement not supported. |
|
| Amcrest IP5M-1190EW | ✅ | ❌ | ONVIF Port: 80. FOV relative movement not supported. |
|
||||||
|
| Annke CZ504 | ✅ | ✅ | Annke support provide specific firmware ([V5.7.1 build 250227](https://github.com/pierrepinon/annke_cz504/raw/refs/heads/main/digicap_V5-7-1_build_250227.dav)) to fix issue with ONVIF "TranslationSpaceFov" |
|
||||||
| Ctronics PTZ | ✅ | ❌ | |
|
| Ctronics PTZ | ✅ | ❌ | |
|
||||||
| Dahua | ✅ | ✅ | |
|
| Dahua | ✅ | ✅ | Some low-end Dahuas (lite series, among others) have been reported to not support autotracking |
|
||||||
| Dahua DH-SD2A500HB | ✅ | ❌ | |
|
| Dahua DH-SD2A500HB | ✅ | ❌ | |
|
||||||
|
| Dahua DH-SD49825GB-HNR | ✅ | ✅ | |
|
||||||
|
| Dahua DH-P5AE-PV | ❌ | ❌ | |
|
||||||
| Foscam R5 | ✅ | ❌ | |
|
| Foscam R5 | ✅ | ❌ | |
|
||||||
| Hanwha XNP-6550RH | ✅ | ❌ | |
|
| Hanwha XNP-6550RH | ✅ | ❌ | |
|
||||||
| Hikvision | ✅ | ❌ | Incomplete ONVIF support (MoveStatus won't update even on latest firmware) - reported with HWP-N4215IH-DE and DS-2DE3304W-DE, but likely others |
|
| Hikvision | ✅ | ❌ | Incomplete ONVIF support (MoveStatus won't update even on latest firmware) - reported with HWP-N4215IH-DE and DS-2DE3304W-DE, but likely others |
|
||||||
| Hikvision DS-2DE3A404IWG-E/W | ✅ | ✅ | |
|
| Hikvision DS-2DE3A404IWG-E/W | ✅ | ✅ | |
|
||||||
| Reolink 511WA | ✅ | ❌ | Zoom only |
|
| Reolink | ✅ | ❌ | |
|
||||||
| Reolink E1 Pro | ✅ | ❌ | |
|
|
||||||
| Reolink E1 Zoom | ✅ | ❌ | |
|
|
||||||
| Reolink RLC-823A 16x | ✅ | ❌ | |
|
|
||||||
| Speco O8P32X | ✅ | ❌ | |
|
| Speco O8P32X | ✅ | ❌ | |
|
||||||
| Sunba 405-D20X | ✅ | ❌ | Incomplete ONVIF support reported on original, and 4k models. All models are suspected incompatable. |
|
| Sunba 405-D20X | ✅ | ❌ | Incomplete ONVIF support reported on original, and 4k models. All models are suspected incompatable. |
|
||||||
| Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 |
|
| Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 |
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ face_recognition:
|
|||||||
enabled: true
|
enabled: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Like the other real-time processors in Frigate, face recognition runs on the camera stream defined by the `detect` role in your config. To ensure optimal performance, select a suitable resolution for this stream in your camera's firmware that fits your specific scene and requirements.
|
||||||
|
|
||||||
## Advanced Configuration
|
## Advanced Configuration
|
||||||
|
|
||||||
Fine-tune face recognition with these optional parameters at the global level of your config. The only optional parameters that can be set at the camera level are `enabled` and `min_area`.
|
Fine-tune face recognition with these optional parameters at the global level of your config. The only optional parameters that can be set at the camera level are `enabled` and `min_area`.
|
||||||
@@ -65,7 +67,7 @@ Fine-tune face recognition with these optional parameters at the global level of
|
|||||||
- Default: `0.8`.
|
- Default: `0.8`.
|
||||||
- `recognition_threshold`: Recognition confidence score required to add the face to the object as a sub label.
|
- `recognition_threshold`: Recognition confidence score required to add the face to the object as a sub label.
|
||||||
- Default: `0.9`.
|
- Default: `0.9`.
|
||||||
- `min_faces`: Min face attempts for the sub label to be applied to the person object.
|
- `min_faces`: Min face recognitions for the sub label to be applied to the person object.
|
||||||
- Default: `1`
|
- Default: `1`
|
||||||
- `save_attempts`: Number of images of recognized faces to save for training.
|
- `save_attempts`: Number of images of recognized faces to save for training.
|
||||||
- Default: `100`.
|
- Default: `100`.
|
||||||
@@ -131,10 +133,33 @@ Once front-facing images are performing well, start choosing slightly off-angle
|
|||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
### How do I debug Face Recognition issues?
|
||||||
|
|
||||||
|
Start with the [Usage](#usage) section and re-read the [Model Requirements](#model-requirements) above.
|
||||||
|
|
||||||
|
1. Ensure `person` is being _detected_. A `person` will automatically be scanned by Frigate for a face. Any detected faces will appear in the Train tab in the Frigate UI's Face Library.
|
||||||
|
|
||||||
|
If you are using a Frigate+ or `face` detecting model:
|
||||||
|
|
||||||
|
- Watch the debug view (Settings --> Debug) to ensure that `face` is being detected along with `person`.
|
||||||
|
- You may need to adjust the `min_score` for the `face` object if faces are not being detected.
|
||||||
|
|
||||||
|
If you are **not** using a Frigate+ or `face` detecting model:
|
||||||
|
|
||||||
|
- Check your `detect` stream resolution and ensure it is sufficiently high enough to capture face details on `person` objects.
|
||||||
|
- You may need to lower your `detection_threshold` if faces are not being detected.
|
||||||
|
|
||||||
|
2. Any detected faces will then be _recognized_.
|
||||||
|
|
||||||
|
- Make sure you have trained at least one face per the recommendations above.
|
||||||
|
- Adjust `recognition_threshold` settings per the suggestions [above](#advanced-configuration).
|
||||||
|
|
||||||
### Detection does not work well with blurry images?
|
### Detection does not work well with blurry images?
|
||||||
|
|
||||||
Accuracy is definitely a going to be improved with higher quality cameras / streams. It is important to look at the DORI (Detection Observation Recognition Identification) range of your camera, if that specification is posted. This specification explains the distance from the camera that a person can be detected, observed, recognized, and identified. The identification range is the most relevant here, and the distance listed by the camera is the furthest that face recognition will realistically work.
|
Accuracy is definitely a going to be improved with higher quality cameras / streams. It is important to look at the DORI (Detection Observation Recognition Identification) range of your camera, if that specification is posted. This specification explains the distance from the camera that a person can be detected, observed, recognized, and identified. The identification range is the most relevant here, and the distance listed by the camera is the furthest that face recognition will realistically work.
|
||||||
|
|
||||||
|
Some users have also noted that setting the stream in camera firmware to a constant bit rate (CBR) leads to better image clarity than with a variable bit rate (VBR).
|
||||||
|
|
||||||
### Why can't I bulk upload photos?
|
### Why can't I bulk upload photos?
|
||||||
|
|
||||||
It is important to methodically add photos to the library, bulk importing photos (especially from a general photo library) will lead to over-fitting in that particular scenario and hurt recognition performance.
|
It is important to methodically add photos to the library, bulk importing photos (especially from a general photo library) will lead to over-fitting in that particular scenario and hurt recognition performance.
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ See [the hwaccel docs](/configuration/hardware_acceleration_video.md) for more i
|
|||||||
| preset-nvidia | Nvidia GPU | |
|
| preset-nvidia | Nvidia GPU | |
|
||||||
| preset-jetson-h264 | Nvidia Jetson with h264 stream | |
|
| preset-jetson-h264 | Nvidia Jetson with h264 stream | |
|
||||||
| preset-jetson-h265 | Nvidia Jetson with h265 stream | |
|
| preset-jetson-h265 | Nvidia Jetson with h265 stream | |
|
||||||
| preset-rk-h264 | Rockchip MPP with h264 stream | Use image with \*-rk suffix and privileged mode |
|
| preset-rkmpp | Rockchip MPP | Use image with \*-rk suffix and privileged mode |
|
||||||
| preset-rk-h265 | Rockchip MPP with h265 stream | Use image with \*-rk suffix and privileged mode |
|
|
||||||
|
|
||||||
### Input Args Presets
|
### Input Args Presets
|
||||||
|
|
||||||
@@ -71,11 +70,11 @@ cameras:
|
|||||||
|
|
||||||
Output args presets help make the config more readable and handle use cases for different types of streams to ensure consistent recordings.
|
Output args presets help make the config more readable and handle use cases for different types of streams to ensure consistent recordings.
|
||||||
|
|
||||||
| Preset | Usage | Other Notes |
|
| Preset | Usage | Other Notes |
|
||||||
| -------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
| -------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| preset-record-generic | Record WITHOUT audio | This is the default when nothing is specified |
|
| preset-record-generic | Record WITHOUT audio | If your camera doesn’t have audio, or if you don’t want to record audio, use this option |
|
||||||
| preset-record-generic-audio-copy | Record WITH original audio | Use this to enable audio in recordings |
|
| preset-record-generic-audio-copy | Record WITH original audio | Use this to enable audio in recordings |
|
||||||
| preset-record-generic-audio-aac | Record WITH transcoded aac audio | Use this to transcode to aac audio. If your source is already aac, use preset-record-generic-audio-copy instead to avoid re-encoding |
|
| preset-record-generic-audio-aac | Record WITH transcoded aac audio | This is the default when no option is specified. Use it to transcode audio to AAC. If the source is already in AAC format, use preset-record-generic-audio-copy instead to avoid unnecessary re-encoding |
|
||||||
| preset-record-mjpeg | Record an mjpeg stream | Recommend restreaming mjpeg stream instead |
|
| preset-record-mjpeg | Record an mjpeg stream | Recommend restreaming mjpeg stream instead |
|
||||||
| preset-record-jpeg | Record live jpeg | Recommend restreaming live jpeg instead |
|
| preset-record-jpeg | Record live jpeg | Recommend restreaming live jpeg instead |
|
||||||
| preset-record-ubiquiti | Record ubiquiti stream with audio | Recordings with ubiquiti non-standard audio |
|
| preset-record-ubiquiti | Record ubiquiti stream with audio | Recordings with ubiquiti non-standard audio |
|
||||||
|
|||||||
@@ -18,15 +18,26 @@ genai:
|
|||||||
enabled: True
|
enabled: True
|
||||||
provider: gemini
|
provider: gemini
|
||||||
api_key: "{FRIGATE_GEMINI_API_KEY}"
|
api_key: "{FRIGATE_GEMINI_API_KEY}"
|
||||||
model: gemini-1.5-flash
|
model: gemini-2.0-flash
|
||||||
|
|
||||||
cameras:
|
cameras:
|
||||||
front_camera: ...
|
front_camera:
|
||||||
|
genai:
|
||||||
|
enabled: True # <- enable GenAI for your front camera
|
||||||
|
use_snapshot: True
|
||||||
|
objects:
|
||||||
|
- person
|
||||||
|
required_zones:
|
||||||
|
- steps
|
||||||
indoor_camera:
|
indoor_camera:
|
||||||
genai: # <- disable GenAI for your indoor camera
|
genai:
|
||||||
enabled: False
|
enabled: False # <- disable GenAI for your indoor camera
|
||||||
```
|
```
|
||||||
|
|
||||||
|
By default, descriptions will be generated for all tracked objects and all zones. But you can also optionally specify `objects` and `required_zones` to only generate descriptions for certain tracked objects or zones.
|
||||||
|
|
||||||
|
Optionally, you can generate the description using a snapshot (if enabled) by setting `use_snapshot` to `True`. By default, this is set to `False`, which sends the uncompressed images from the `detect` stream collected over the object's lifetime to the model. Once the object lifecycle ends, only a single compressed and cropped thumbnail is saved with the tracked object. Using a snapshot might be useful when you want to _regenerate_ a tracked object's description as it will provide the AI with a higher-quality image (typically downscaled by the AI itself) than the cropped/compressed thumbnail. Using a snapshot otherwise has a trade-off in that only a single image is sent to your provider, which will limit the model's ability to determine object movement or direction.
|
||||||
|
|
||||||
## Ollama
|
## Ollama
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
@@ -67,7 +78,7 @@ Google Gemini has a free tier allowing [15 queries per minute](https://ai.google
|
|||||||
|
|
||||||
### Supported Models
|
### Supported Models
|
||||||
|
|
||||||
You must use a vision capable model with Frigate. Current model variants can be found [in their documentation](https://ai.google.dev/gemini-api/docs/models/gemini). At the time of writing, this includes `gemini-1.5-pro` and `gemini-1.5-flash`.
|
You must use a vision capable model with Frigate. Current model variants can be found [in their documentation](https://ai.google.dev/gemini-api/docs/models/gemini).
|
||||||
|
|
||||||
### Get API Key
|
### Get API Key
|
||||||
|
|
||||||
@@ -85,9 +96,15 @@ genai:
|
|||||||
enabled: True
|
enabled: True
|
||||||
provider: gemini
|
provider: gemini
|
||||||
api_key: "{FRIGATE_GEMINI_API_KEY}"
|
api_key: "{FRIGATE_GEMINI_API_KEY}"
|
||||||
model: gemini-1.5-flash
|
model: gemini-2.0-flash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
To use a different Gemini-compatible API endpoint, set the `GEMINI_BASE_URL` environment variable to your provider's API URL.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## OpenAI
|
## OpenAI
|
||||||
|
|
||||||
OpenAI does not have a free tier for their API. With the release of gpt-4o, pricing has been reduced and each generation should cost fractions of a cent if you choose to go this route.
|
OpenAI does not have a free tier for their API. With the release of gpt-4o, pricing has been reduced and each generation should cost fractions of a cent if you choose to go this route.
|
||||||
@@ -185,9 +202,7 @@ genai:
|
|||||||
car: "Observe the primary vehicle in these images. Focus on its movement, direction, or purpose (e.g., parking, approaching, circling). If it's a delivery vehicle, mention the company."
|
car: "Observe the primary vehicle in these images. Focus on its movement, direction, or purpose (e.g., parking, approaching, circling). If it's a delivery vehicle, mention the company."
|
||||||
```
|
```
|
||||||
|
|
||||||
Prompts can also be overriden at the camera level to provide a more detailed prompt to the model about your specific camera, if you desire. By default, descriptions will be generated for all tracked objects and all zones. But you can also optionally specify `objects` and `required_zones` to only generate descriptions for certain tracked objects or zones.
|
Prompts can also be overriden at the camera level to provide a more detailed prompt to the model about your specific camera, if you desire.
|
||||||
|
|
||||||
Optionally, you can generate the description using a snapshot (if enabled) by setting `use_snapshot` to `True`. By default, this is set to `False`, which sends the uncompressed images from the `detect` stream collected over the object's lifetime to the model. Once the object lifecycle ends, only a single compressed and cropped thumbnail is saved with the tracked object. Using a snapshot might be useful when you want to _regenerate_ a tracked object's description as it will provide the AI with a higher-quality image (typically downscaled by the AI itself) than the cropped/compressed thumbnail. Using a snapshot otherwise has a trade-off in that only a single image is sent to your provider, which will limit the model's ability to determine object movement or direction.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
cameras:
|
cameras:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Object detection and enrichments (like Semantic Search, Face Recognition, and Li
|
|||||||
- Nvidia GPUs will automatically be detected and used for enrichments in the `-tensorrt` Frigate image.
|
- Nvidia GPUs will automatically be detected and used for enrichments in the `-tensorrt` Frigate image.
|
||||||
- Jetson devices will automatically be detected and used for enrichments in the `-tensorrt-jp6` Frigate image.
|
- Jetson devices will automatically be detected and used for enrichments in the `-tensorrt-jp6` Frigate image.
|
||||||
|
|
||||||
Utilizing a GPU for enrichments does not require you to use the same GPU for object detection. For example, you can run the `tensorrt` Docker image for enrichments and still use other dedicated hardware for object detection.
|
Utilizing a GPU for enrichments does not require you to use the same GPU for object detection. For example, you can run the `tensorrt` Docker image for enrichments and still use other dedicated hardware like a Coral or Hailo for object detection. However, one combination that is not supported is TensorRT for object detection and OpenVINO for enrichments.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ It is highly recommended to use a GPU for hardware acceleration video decoding i
|
|||||||
|
|
||||||
Depending on your system, these parameters may not be compatible. More information on hardware accelerated decoding for ffmpeg can be found here: https://trac.ffmpeg.org/wiki/HWAccelIntro
|
Depending on your system, these parameters may not be compatible. More information on hardware accelerated decoding for ffmpeg can be found here: https://trac.ffmpeg.org/wiki/HWAccelIntro
|
||||||
|
|
||||||
# Object Detection
|
|
||||||
|
|
||||||
## Raspberry Pi 3/4
|
## Raspberry Pi 3/4
|
||||||
|
|
||||||
@@ -71,7 +70,8 @@ Or map in all the `/dev/video*` devices.
|
|||||||
|
|
||||||
| CPU Generation | Intel Driver | Recommended Preset | Notes |
|
| CPU Generation | Intel Driver | Recommended Preset | Notes |
|
||||||
| -------------- | ------------ | ------------------- | ------------------------------------ |
|
| -------------- | ------------ | ------------------- | ------------------------------------ |
|
||||||
| gen1 - gen7 | i965 | preset-vaapi | qsv is not supported |
|
| gen1 - gen5 | i965 | preset-vaapi | qsv is not supported |
|
||||||
|
| gen6 - gen7 | iHD | preset-vaapi | qsv is not supported |
|
||||||
| gen8 - gen12 | iHD | preset-vaapi | preset-intel-qsv-\* can also be used |
|
| gen8 - gen12 | iHD | preset-vaapi | preset-intel-qsv-\* can also be used |
|
||||||
| gen13+ | iHD / Xe | preset-intel-qsv-\* | |
|
| gen13+ | iHD / Xe | preset-intel-qsv-\* | |
|
||||||
| Intel Arc GPU | iHD / Xe | preset-intel-qsv-\* | |
|
| Intel Arc GPU | iHD / Xe | preset-intel-qsv-\* | |
|
||||||
@@ -175,16 +175,26 @@ For more information on the various values across different distributions, see h
|
|||||||
|
|
||||||
Depending on your OS and kernel configuration, you may need to change the `/proc/sys/kernel/perf_event_paranoid` kernel tunable. You can test the change by running `sudo sh -c 'echo 2 >/proc/sys/kernel/perf_event_paranoid'` which will persist until a reboot. Make it permanent by running `sudo sh -c 'echo kernel.perf_event_paranoid=2 >> /etc/sysctl.d/local.conf'`
|
Depending on your OS and kernel configuration, you may need to change the `/proc/sys/kernel/perf_event_paranoid` kernel tunable. You can test the change by running `sudo sh -c 'echo 2 >/proc/sys/kernel/perf_event_paranoid'` which will persist until a reboot. Make it permanent by running `sudo sh -c 'echo kernel.perf_event_paranoid=2 >> /etc/sysctl.d/local.conf'`
|
||||||
|
|
||||||
#### Stats for SR-IOV devices
|
#### Stats for SR-IOV or other devices
|
||||||
|
|
||||||
When using virtualized GPUs via SR-IOV, additional args are needed for GPU stats to function. This can be enabled with the following config:
|
When using virtualized GPUs via SR-IOV, you need to specify the device path to use to gather stats from `intel_gpu_top`. This example may work for some systems using SR-IOV:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
telemetry:
|
telemetry:
|
||||||
stats:
|
stats:
|
||||||
sriov: True
|
intel_gpu_device: "sriov"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For other virtualized GPUs, try specifying the direct path to the device instead:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
telemetry:
|
||||||
|
stats:
|
||||||
|
intel_gpu_device: "drm:/dev/dri/card0"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are passing in a device path, make sure you've passed the device through to the container.
|
||||||
|
|
||||||
## AMD/ATI GPUs (Radeon HD 2000 and newer GPUs) via libva-mesa-driver
|
## AMD/ATI GPUs (Radeon HD 2000 and newer GPUs) via libva-mesa-driver
|
||||||
|
|
||||||
VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams.
|
VAAPI supports automatic profile selection so it will work automatically with both H.264 and H.265 streams.
|
||||||
@@ -218,7 +228,7 @@ Additional configuration is needed for the Docker container to be able to access
|
|||||||
services:
|
services:
|
||||||
frigate:
|
frigate:
|
||||||
...
|
...
|
||||||
image: ghcr.io/blakeblackshear/frigate:stable
|
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt
|
||||||
deploy: # <------------- Add this section
|
deploy: # <------------- Add this section
|
||||||
resources:
|
resources:
|
||||||
reservations:
|
reservations:
|
||||||
@@ -236,7 +246,7 @@ docker run -d \
|
|||||||
--name frigate \
|
--name frigate \
|
||||||
...
|
...
|
||||||
--gpus=all \
|
--gpus=all \
|
||||||
ghcr.io/blakeblackshear/frigate:stable
|
ghcr.io/blakeblackshear/frigate:stable-tensorrt
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setup Decoder
|
### Setup Decoder
|
||||||
@@ -375,13 +385,8 @@ Make sure to follow the [Rockchip specific installation instructions](/frigate/i
|
|||||||
Add one of the following FFmpeg presets to your `config.yml` to enable hardware video processing:
|
Add one of the following FFmpeg presets to your `config.yml` to enable hardware video processing:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# if you try to decode a h264 encoded stream
|
|
||||||
ffmpeg:
|
ffmpeg:
|
||||||
hwaccel_args: preset-rk-h264
|
hwaccel_args: preset-rkmpp
|
||||||
|
|
||||||
# if you try to decode a h265 (hevc) encoded stream
|
|
||||||
ffmpeg:
|
|
||||||
hwaccel_args: preset-rk-h265
|
|
||||||
```
|
```
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
@@ -389,3 +394,36 @@ ffmpeg:
|
|||||||
Make sure that your SoC supports hardware acceleration for your input stream. For example, if your camera streams with h265 encoding and a 4k resolution, your SoC must be able to de- and encode h265 with a 4k resolution or higher. If you are unsure whether your SoC meets the requirements, take a look at the datasheet.
|
Make sure that your SoC supports hardware acceleration for your input stream. For example, if your camera streams with h265 encoding and a 4k resolution, your SoC must be able to de- and encode h265 with a 4k resolution or higher. If you are unsure whether your SoC meets the requirements, take a look at the datasheet.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
If one or more of your cameras are not properly processed and this error is shown in the logs:
|
||||||
|
|
||||||
|
```
|
||||||
|
[segment @ 0xaaaaff694790] Timestamps are unset in a packet for stream 0. This is deprecated and will stop working in the future. Fix your code to set the timestamps properly
|
||||||
|
[Parsed_scale_rkrga_0 @ 0xaaaaff819070] No hw context provided on input
|
||||||
|
[Parsed_scale_rkrga_0 @ 0xaaaaff819070] Failed to configure output pad on Parsed_scale_rkrga_0
|
||||||
|
Error initializing filters!
|
||||||
|
Error marking filters as finished
|
||||||
|
[out#1/rawvideo @ 0xaaaaff3d8730] Nothing was written into output file, because at least one of its streams received no packets.
|
||||||
|
Restarting ffmpeg...
|
||||||
|
```
|
||||||
|
|
||||||
|
you should try to uprade to FFmpeg 7. This can be done using this config option:
|
||||||
|
|
||||||
|
```
|
||||||
|
ffmpeg:
|
||||||
|
path: "7.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set this option globally to use FFmpeg 7 for all cameras or on camera level to use it only for specific cameras. Do not confuse this option with:
|
||||||
|
|
||||||
|
```
|
||||||
|
cameras:
|
||||||
|
name:
|
||||||
|
ffmpeg:
|
||||||
|
inputs:
|
||||||
|
- path: rtsp://viewer:{FRIGATE_RTSP_PASSWORD}@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ In the default mode, Frigate's LPR needs to first detect a `car` or `motorcycle`
|
|||||||
|
|
||||||
## Minimum System Requirements
|
## Minimum System Requirements
|
||||||
|
|
||||||
License plate recognition works by running AI models locally on your system. The models are relatively lightweight and can run on your CPU or GPU, depending on your configuration. At least 4GB of RAM is required.
|
License plate recognition works by running AI models locally on your system. The YOLOv9 plate detector model and the OCR models ([PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)) are relatively lightweight and can run on your CPU or GPU, depending on your configuration. At least 4GB of RAM is required.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
License plate recognition is disabled by default. Enable it in your config file:
|
License plate recognition is disabled by default. Enable it in your config file:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ If you are using go2rtc, you should adjust the following settings in your camera
|
|||||||
|
|
||||||
- Video codec: **H.264** - provides the most compatible video codec with all Live view technologies and browsers. Avoid any kind of "smart codec" or "+" codec like _H.264+_ or _H.265+_. as these non-standard codecs remove keyframes (see below).
|
- Video codec: **H.264** - provides the most compatible video codec with all Live view technologies and browsers. Avoid any kind of "smart codec" or "+" codec like _H.264+_ or _H.265+_. as these non-standard codecs remove keyframes (see below).
|
||||||
- Audio codec: **AAC** - provides the most compatible audio codec with all Live view technologies and browsers that support audio.
|
- Audio codec: **AAC** - provides the most compatible audio codec with all Live view technologies and browsers that support audio.
|
||||||
- I-frame interval (sometimes called the keyframe interval, the interframe space, or the GOP length): match your camera's frame rate, or choose "1x" (for interframe space on Reolink cameras). For example, if your stream outputs 20fps, your i-frame interval should be 20 (or 1x on Reolink). Values higher than the frame rate will cause the stream to take longer to begin playback. See [this page](https://gardinal.net/understanding-the-keyframe-interval/) for more on keyframes. For many users this may not be an issue, but it should be noted that that a 1x i-frame interval will cause more storage utilization if you are using the stream for the `record` role as well.
|
- I-frame interval (sometimes called the keyframe interval, the interframe space, or the GOP length): match your camera's frame rate, or choose "1x" (for interframe space on Reolink cameras). For example, if your stream outputs 20fps, your i-frame interval should be 20 (or 1x on Reolink). Values higher than the frame rate will cause the stream to take longer to begin playback. See [this page](https://gardinal.net/understanding-the-keyframe-interval/) for more on keyframes. For many users this may not be an issue, but it should be noted that a 1x i-frame interval will cause more storage utilization if you are using the stream for the `record` role as well.
|
||||||
|
|
||||||
The default video and audio codec on your camera may not always be compatible with your browser, which is why setting them to H.264 and AAC is recommended. See the [go2rtc docs](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#codecs-madness) for codec support information.
|
The default video and audio codec on your camera may not always be compatible with your browser, which is why setting them to H.264 and AAC is recommended. See the [go2rtc docs](https://github.com/AlexxIT/go2rtc?tab=readme-ov-file#codecs-madness) for codec support information.
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ For devices that support two way talk, Frigate can be configured to use the feat
|
|||||||
|
|
||||||
- Set up go2rtc with [WebRTC](#webrtc-extra-configuration).
|
- Set up go2rtc with [WebRTC](#webrtc-extra-configuration).
|
||||||
- Ensure you access Frigate via https (may require [opening port 8971](/frigate/installation/#ports)).
|
- Ensure you access Frigate via https (may require [opening port 8971](/frigate/installation/#ports)).
|
||||||
- For the Home Assistant Frigate card, [follow the docs](https://github.com/dermotduffy/frigate-hass-card?tab=readme-ov-file#using-2-way-audio) for the correct source.
|
- For the Home Assistant Frigate card, [follow the docs](http://card.camera/#/usage/2-way-audio) for the correct source.
|
||||||
|
|
||||||
To use the Reolink Doorbell with two way talk, you should use the [recommended Reolink configuration](/configuration/camera_specific#reolink-doorbell)
|
To use the Reolink Doorbell with two way talk, you should use the [recommended Reolink configuration](/configuration/camera_specific#reolink-doorbell)
|
||||||
|
|
||||||
@@ -189,7 +189,12 @@ Frigate provides a dialog in the Camera Group Edit pane with several options for
|
|||||||
|
|
||||||
:::note
|
:::note
|
||||||
|
|
||||||
The default dashboard ("All Cameras") will always use Smart Streaming and the first entry set in your `streams` configuration, if defined. Use a camera group if you want to change any of these settings from the defaults.
|
The default dashboard ("All Cameras") will always use:
|
||||||
|
|
||||||
|
- Smart Streaming, unless you've disabled the global Automatic Live View in Settings.
|
||||||
|
- The first entry set in your `streams` configuration, if defined.
|
||||||
|
|
||||||
|
Use a camera group if you want to change any of these settings from the defaults.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@@ -246,3 +251,7 @@ Note that disabling a camera through the config file (`enabled: False`) removes
|
|||||||
6. **I have unmuted some cameras on my dashboard, but I do not hear sound. Why?**
|
6. **I have unmuted some cameras on my dashboard, but I do not hear sound. Why?**
|
||||||
|
|
||||||
If your camera is streaming (as indicated by a red dot in the upper right, or if it has been set to continuous streaming mode), your browser may be blocking audio until you interact with the page. This is an intentional browser limitation. See [this article](https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability). Many browsers have a whitelist feature to change this behavior.
|
If your camera is streaming (as indicated by a red dot in the upper right, or if it has been set to continuous streaming mode), your browser may be blocking audio until you interact with the page. This is an intentional browser limitation. See [this article](https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide#autoplay_availability). Many browsers have a whitelist feature to change this behavior.
|
||||||
|
|
||||||
|
7. **My camera streams have lots of visual artifacts / distortion.**
|
||||||
|
|
||||||
|
Some cameras don't include the hardware to support multiple connections to the high resolution stream, and this can cause unexpected behavior. In this case it is recommended to [restream](./restream.md) the high resolution stream so that it can be used for live view and recordings.
|
||||||
|
|||||||
@@ -104,4 +104,4 @@ Lightning threshold does not stop motion based recordings from being saved.
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Large changes in motion like PTZ moves and camera switches between Color and IR mode should result in no motion detection. This is done via the `lightning_threshold` configuration. It is defined as the percentage of the image used to detect lightning or other substantial changes where motion detection needs to recalibrate. Increasing this value will make motion detection more likely to consider lightning or IR mode changes as valid motion. Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching a doorbell camera.
|
Large changes in motion like PTZ moves and camera switches between Color and IR mode should result in a pause in object detection. This is done via the `lightning_threshold` configuration. It is defined as the percentage of the image used to detect lightning or other substantial changes where motion detection needs to recalibrate. Increasing this value will make motion detection more likely to consider lightning or IR mode changes as valid motion. Decreasing this value will make motion detection more likely to ignore large amounts of motion such as a person approaching a doorbell camera.
|
||||||
|
|||||||
@@ -24,10 +24,14 @@ Frigate supports multiple different detectors that work on different types of ha
|
|||||||
- [OpenVino](#openvino-detector): OpenVino can run on Intel Arc GPUs, Intel integrated GPUs, and Intel CPUs to provide efficient object detection.
|
- [OpenVino](#openvino-detector): OpenVino can run on Intel Arc GPUs, Intel integrated GPUs, and Intel CPUs to provide efficient object detection.
|
||||||
- [ONNX](#onnx): OpenVINO will automatically be detected and used as a detector in the default Frigate image when a supported ONNX model is configured.
|
- [ONNX](#onnx): OpenVINO will automatically be detected and used as a detector in the default Frigate image when a supported ONNX model is configured.
|
||||||
|
|
||||||
**Nvidia**
|
**Nvidia GPU**
|
||||||
|
|
||||||
- [TensortRT](#nvidia-tensorrt-detector): TensorRT can run on Nvidia GPUs and Jetson devices, using one of many default models.
|
- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` Frigate image when a supported ONNX model is configured.
|
||||||
- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` or `-tensorrt-jp6` Frigate images when a supported ONNX model is configured.
|
|
||||||
|
**Nvidia Jetson**
|
||||||
|
|
||||||
|
- [TensortRT](#nvidia-tensorrt-detector): TensorRT can run on Jetson devices, using one of many default models.
|
||||||
|
- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt-jp6` Frigate image when a supported ONNX model is configured.
|
||||||
|
|
||||||
**Rockchip**
|
**Rockchip**
|
||||||
|
|
||||||
@@ -322,6 +326,12 @@ The YOLO detector has been designed to support YOLOv3, YOLOv4, YOLOv7, and YOLOv
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
If you are using a Frigate+ YOLOv9 model, you should not define any of the below `model` parameters in your config except for `path`. See [the Frigate+ model docs](/plus/first_model#step-3-set-your-model-id-in-the-config) for more information on setting up your model.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
After placing the downloaded onnx model in your config folder, you can use the following configuration:
|
After placing the downloaded onnx model in your config folder, you can use the following configuration:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -362,8 +372,8 @@ detectors:
|
|||||||
|
|
||||||
model:
|
model:
|
||||||
model_type: rfdetr
|
model_type: rfdetr
|
||||||
width: 560
|
width: 320
|
||||||
height: 560
|
height: 320
|
||||||
input_tensor: nchw
|
input_tensor: nchw
|
||||||
input_dtype: float
|
input_dtype: float
|
||||||
path: /config/model_cache/rfdetr.onnx
|
path: /config/model_cache/rfdetr.onnx
|
||||||
@@ -399,111 +409,6 @@ model:
|
|||||||
|
|
||||||
Note that the labelmap uses a subset of the complete COCO label set that has only 80 objects.
|
Note that the labelmap uses a subset of the complete COCO label set that has only 80 objects.
|
||||||
|
|
||||||
## NVidia TensorRT Detector
|
|
||||||
|
|
||||||
Nvidia GPUs may be used for object detection using the TensorRT libraries. Due to the size of the additional libraries, this detector is only provided in images with the `-tensorrt` tag suffix, e.g. `ghcr.io/blakeblackshear/frigate:stable-tensorrt`. This detector is designed to work with Yolo models for object detection.
|
|
||||||
|
|
||||||
### Minimum Hardware Support
|
|
||||||
|
|
||||||
The TensorRT detector uses the 12.x series of CUDA libraries which have minor version compatibility. The minimum driver version on the host system must be `>=545`. Also the GPU must support a Compute Capability of `5.0` or greater. This generally correlates to a Maxwell-era GPU or newer, check the NVIDIA GPU Compute Capability table linked below.
|
|
||||||
|
|
||||||
To use the TensorRT detector, make sure your host system has the [nvidia-container-runtime](https://docs.docker.com/config/containers/resource_constraints/#access-an-nvidia-gpu) installed to pass through the GPU to the container and the host system has a compatible driver installed for your GPU.
|
|
||||||
|
|
||||||
There are improved capabilities in newer GPU architectures that TensorRT can benefit from, such as INT8 operations and Tensor cores. The features compatible with your hardware will be optimized when the model is converted to a trt file. Currently the script provided for generating the model provides a switch to enable/disable FP16 operations. If you wish to use newer features such as INT8 optimization, more work is required.
|
|
||||||
|
|
||||||
#### Compatibility References:
|
|
||||||
|
|
||||||
[NVIDIA TensorRT Support Matrix](https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-841/support-matrix/index.html)
|
|
||||||
|
|
||||||
[NVIDIA CUDA Compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/index.html)
|
|
||||||
|
|
||||||
[NVIDIA GPU Compute Capability](https://developer.nvidia.com/cuda-gpus)
|
|
||||||
|
|
||||||
### Generate Models
|
|
||||||
|
|
||||||
The model used for TensorRT must be preprocessed on the same hardware platform that they will run on. This means that each user must run additional setup to generate a model file for the TensorRT library. A script is included that will build several common models.
|
|
||||||
|
|
||||||
The Frigate image will generate model files during startup if the specified model is not found. Processed models are stored in the `/config/model_cache` folder. Typically the `/config` path is mapped to a directory on the host already and the `model_cache` does not need to be mapped separately unless the user wants to store it in a different location on the host.
|
|
||||||
|
|
||||||
By default, no models will be generated, but this can be overridden by specifying the `YOLO_MODELS` environment variable in Docker. One or more models may be listed in a comma-separated format, and each one will be generated. Models will only be generated if the corresponding `{model}.trt` file is not present in the `model_cache` folder, so you can force a model to be regenerated by deleting it from your Frigate data folder.
|
|
||||||
|
|
||||||
If you have a Jetson device with DLAs (Xavier or Orin), you can generate a model that will run on the DLA by appending `-dla` to your model name, e.g. specify `YOLO_MODELS=yolov7-320-dla`. The model will run on DLA0 (Frigate does not currently support DLA1). DLA-incompatible layers will fall back to running on the GPU.
|
|
||||||
|
|
||||||
If your GPU does not support FP16 operations, you can pass the environment variable `USE_FP16=False` to disable it.
|
|
||||||
|
|
||||||
Specific models can be selected by passing an environment variable to the `docker run` command or in your `docker-compose.yml` file. Use the form `-e YOLO_MODELS=yolov4-416,yolov4-tiny-416` to select one or more model names. The models available are shown below.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Available Models</summary>
|
|
||||||
```
|
|
||||||
yolov3-288
|
|
||||||
yolov3-416
|
|
||||||
yolov3-608
|
|
||||||
yolov3-spp-288
|
|
||||||
yolov3-spp-416
|
|
||||||
yolov3-spp-608
|
|
||||||
yolov3-tiny-288
|
|
||||||
yolov3-tiny-416
|
|
||||||
yolov4-288
|
|
||||||
yolov4-416
|
|
||||||
yolov4-608
|
|
||||||
yolov4-csp-256
|
|
||||||
yolov4-csp-512
|
|
||||||
yolov4-p5-448
|
|
||||||
yolov4-p5-896
|
|
||||||
yolov4-tiny-288
|
|
||||||
yolov4-tiny-416
|
|
||||||
yolov4x-mish-320
|
|
||||||
yolov4x-mish-640
|
|
||||||
yolov7-tiny-288
|
|
||||||
yolov7-tiny-416
|
|
||||||
yolov7-640
|
|
||||||
yolov7-416
|
|
||||||
yolov7-320
|
|
||||||
yolov7x-640
|
|
||||||
yolov7x-320
|
|
||||||
```
|
|
||||||
</details>
|
|
||||||
|
|
||||||
An example `docker-compose.yml` fragment that converts the `yolov4-608` and `yolov7x-640` models for a Pascal card would look something like this:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
frigate:
|
|
||||||
environment:
|
|
||||||
- YOLO_MODELS=yolov7-320,yolov7x-640
|
|
||||||
- USE_FP16=false
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have multiple GPUs passed through to Frigate, you can specify which one to use for the model conversion. The conversion script will use the first visible GPU, however in systems with mixed GPU models you may not want to use the default index for object detection. Add the `TRT_MODEL_PREP_DEVICE` environment variable to select a specific GPU.
|
|
||||||
|
|
||||||
```yml
|
|
||||||
frigate:
|
|
||||||
environment:
|
|
||||||
- TRT_MODEL_PREP_DEVICE=0 # Optionally, select which GPU is used for model optimization
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration Parameters
|
|
||||||
|
|
||||||
The TensorRT detector can be selected by specifying `tensorrt` as the model type. The GPU will need to be passed through to the docker container using the same methods described in the [Hardware Acceleration](hardware_acceleration_video.md#nvidia-gpus) section. If you pass through multiple GPUs, you can select which GPU is used for a detector with the `device` configuration parameter. The `device` parameter is an integer value of the GPU index, as shown by `nvidia-smi` within the container.
|
|
||||||
|
|
||||||
The TensorRT detector uses `.trt` model files that are located in `/config/model_cache/tensorrt` by default. These model path and dimensions used will depend on which model you have generated.
|
|
||||||
|
|
||||||
Use the config below to work with generated TRT models:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
detectors:
|
|
||||||
tensorrt:
|
|
||||||
type: tensorrt
|
|
||||||
device: 0 #This is the default, select the first GPU
|
|
||||||
|
|
||||||
model:
|
|
||||||
path: /config/model_cache/tensorrt/yolov7-320.trt
|
|
||||||
input_tensor: nchw
|
|
||||||
input_pixel_format: rgb
|
|
||||||
width: 320
|
|
||||||
height: 320
|
|
||||||
```
|
|
||||||
|
|
||||||
## AMD/ROCm GPU detector
|
## AMD/ROCm GPU detector
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
@@ -542,14 +447,13 @@ Also AMD/ROCm does not "officially" support integrated GPUs. It still does work
|
|||||||
|
|
||||||
For the rocm frigate build there is some automatic detection:
|
For the rocm frigate build there is some automatic detection:
|
||||||
|
|
||||||
- gfx90c -> 9.0.0
|
|
||||||
- gfx1031 -> 10.3.0
|
- gfx1031 -> 10.3.0
|
||||||
- gfx1103 -> 11.0.0
|
- gfx1103 -> 11.0.0
|
||||||
|
|
||||||
If you have something else you might need to override the `HSA_OVERRIDE_GFX_VERSION` at Docker launch. Suppose the version you want is `9.0.0`, then you should configure it from command line as:
|
If you have something else you might need to override the `HSA_OVERRIDE_GFX_VERSION` at Docker launch. Suppose the version you want is `10.0.0`, then you should configure it from command line as:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker run -e HSA_OVERRIDE_GFX_VERSION=9.0.0 \
|
$ docker run -e HSA_OVERRIDE_GFX_VERSION=10.0.0 \
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -560,7 +464,7 @@ services:
|
|||||||
frigate:
|
frigate:
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
HSA_OVERRIDE_GFX_VERSION: "9.0.0"
|
HSA_OVERRIDE_GFX_VERSION: "10.0.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
Figuring out what version you need can be complicated as you can't tell the chipset name and driver from the AMD brand name.
|
Figuring out what version you need can be complicated as you can't tell the chipset name and driver from the AMD brand name.
|
||||||
@@ -636,6 +540,12 @@ There is no default model provided, the following formats are supported:
|
|||||||
|
|
||||||
[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. See [the models section](#downloading-yolo-nas-model) for more information on downloading the YOLO-NAS model for use in Frigate.
|
[YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) models are supported, but not included by default. See [the models section](#downloading-yolo-nas-model) for more information on downloading the YOLO-NAS model for use in Frigate.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
If you are using a Frigate+ YOLO-NAS model, you should not define any of the below `model` parameters in your config except for `path`. See [the Frigate+ model docs](/plus/first_model#step-3-set-your-model-id-in-the-config) for more information on setting up your model.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
After placing the downloaded onnx model in your config folder, you can use the following configuration:
|
After placing the downloaded onnx model in your config folder, you can use the following configuration:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -663,6 +573,12 @@ The YOLO detector has been designed to support YOLOv3, YOLOv4, YOLOv7, and YOLOv
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
If you are using a Frigate+ YOLOv9 model, you should not define any of the below `model` parameters in your config except for `path`. See [the Frigate+ model docs](/plus/first_model#step-3-set-your-model-id-in-the-config) for more information on setting up your model.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
After placing the downloaded onnx model in your config folder, you can use the following configuration:
|
After placing the downloaded onnx model in your config folder, you can use the following configuration:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -718,8 +634,8 @@ detectors:
|
|||||||
|
|
||||||
model:
|
model:
|
||||||
model_type: rfdetr
|
model_type: rfdetr
|
||||||
width: 560
|
width: 320
|
||||||
height: 560
|
height: 320
|
||||||
input_tensor: nchw
|
input_tensor: nchw
|
||||||
input_dtype: float
|
input_dtype: float
|
||||||
path: /config/model_cache/rfdetr.onnx
|
path: /config/model_cache/rfdetr.onnx
|
||||||
@@ -801,6 +717,88 @@ To verify that the integration is working correctly, start Frigate and observe t
|
|||||||
|
|
||||||
# Community Supported Detectors
|
# Community Supported Detectors
|
||||||
|
|
||||||
|
## NVidia TensorRT Detector
|
||||||
|
|
||||||
|
Nvidia Jetson devices may be used for object detection using the TensorRT libraries. Due to the size of the additional libraries, this detector is only provided in images with the `-tensorrt-jp6` tag suffix, e.g. `ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp6`. This detector is designed to work with Yolo models for object detection.
|
||||||
|
|
||||||
|
### Generate Models
|
||||||
|
|
||||||
|
The model used for TensorRT must be preprocessed on the same hardware platform that they will run on. This means that each user must run additional setup to generate a model file for the TensorRT library. A script is included that will build several common models.
|
||||||
|
|
||||||
|
The Frigate image will generate model files during startup if the specified model is not found. Processed models are stored in the `/config/model_cache` folder. Typically the `/config` path is mapped to a directory on the host already and the `model_cache` does not need to be mapped separately unless the user wants to store it in a different location on the host.
|
||||||
|
|
||||||
|
By default, no models will be generated, but this can be overridden by specifying the `YOLO_MODELS` environment variable in Docker. One or more models may be listed in a comma-separated format, and each one will be generated. Models will only be generated if the corresponding `{model}.trt` file is not present in the `model_cache` folder, so you can force a model to be regenerated by deleting it from your Frigate data folder.
|
||||||
|
|
||||||
|
If you have a Jetson device with DLAs (Xavier or Orin), you can generate a model that will run on the DLA by appending `-dla` to your model name, e.g. specify `YOLO_MODELS=yolov7-320-dla`. The model will run on DLA0 (Frigate does not currently support DLA1). DLA-incompatible layers will fall back to running on the GPU.
|
||||||
|
|
||||||
|
If your GPU does not support FP16 operations, you can pass the environment variable `USE_FP16=False` to disable it.
|
||||||
|
|
||||||
|
Specific models can be selected by passing an environment variable to the `docker run` command or in your `docker-compose.yml` file. Use the form `-e YOLO_MODELS=yolov4-416,yolov4-tiny-416` to select one or more model names. The models available are shown below.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Available Models</summary>
|
||||||
|
```
|
||||||
|
yolov3-288
|
||||||
|
yolov3-416
|
||||||
|
yolov3-608
|
||||||
|
yolov3-spp-288
|
||||||
|
yolov3-spp-416
|
||||||
|
yolov3-spp-608
|
||||||
|
yolov3-tiny-288
|
||||||
|
yolov3-tiny-416
|
||||||
|
yolov4-288
|
||||||
|
yolov4-416
|
||||||
|
yolov4-608
|
||||||
|
yolov4-csp-256
|
||||||
|
yolov4-csp-512
|
||||||
|
yolov4-p5-448
|
||||||
|
yolov4-p5-896
|
||||||
|
yolov4-tiny-288
|
||||||
|
yolov4-tiny-416
|
||||||
|
yolov4x-mish-320
|
||||||
|
yolov4x-mish-640
|
||||||
|
yolov7-tiny-288
|
||||||
|
yolov7-tiny-416
|
||||||
|
yolov7-640
|
||||||
|
yolov7-416
|
||||||
|
yolov7-320
|
||||||
|
yolov7x-640
|
||||||
|
yolov7x-320
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
An example `docker-compose.yml` fragment that converts the `yolov4-608` and `yolov7x-640` models would look something like this:
|
||||||
|
|
||||||
|
```yml
|
||||||
|
frigate:
|
||||||
|
environment:
|
||||||
|
- YOLO_MODELS=yolov7-320,yolov7x-640
|
||||||
|
- USE_FP16=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Parameters
|
||||||
|
|
||||||
|
The TensorRT detector can be selected by specifying `tensorrt` as the model type. The GPU will need to be passed through to the docker container using the same methods described in the [Hardware Acceleration](hardware_acceleration_video.md#nvidia-gpus) section. If you pass through multiple GPUs, you can select which GPU is used for a detector with the `device` configuration parameter. The `device` parameter is an integer value of the GPU index, as shown by `nvidia-smi` within the container.
|
||||||
|
|
||||||
|
The TensorRT detector uses `.trt` model files that are located in `/config/model_cache/tensorrt` by default. These model path and dimensions used will depend on which model you have generated.
|
||||||
|
|
||||||
|
Use the config below to work with generated TRT models:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
detectors:
|
||||||
|
tensorrt:
|
||||||
|
type: tensorrt
|
||||||
|
device: 0 #This is the default, select the first GPU
|
||||||
|
|
||||||
|
model:
|
||||||
|
path: /config/model_cache/tensorrt/yolov7-320.trt
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
## Rockchip platform
|
## Rockchip platform
|
||||||
|
|
||||||
Hardware accelerated object detection is supported on the following SoCs:
|
Hardware accelerated object detection is supported on the following SoCs:
|
||||||
@@ -914,7 +912,6 @@ model: # required
|
|||||||
width: 320
|
width: 320
|
||||||
height: 320
|
height: 320
|
||||||
input_tensor: nhwc
|
input_tensor: nhwc
|
||||||
input_dtype: float
|
|
||||||
labelmap_path: /labelmap/coco-80.txt
|
labelmap_path: /labelmap/coco-80.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -981,45 +978,47 @@ Here are some tips for getting different model types
|
|||||||
|
|
||||||
### Downloading D-FINE Model
|
### Downloading D-FINE Model
|
||||||
|
|
||||||
To export as ONNX:
|
D-FINE can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=s` in the first line to `s`, `m`, or `l` size.
|
||||||
|
|
||||||
1. Clone: https://github.com/Peterande/D-FINE and install all dependencies.
|
|
||||||
2. Select and download a checkpoint from the [readme](https://github.com/Peterande/D-FINE).
|
|
||||||
3. Modify line 58 of `tools/deployment/export_onnx.py` and change batch size to 1: `data = torch.rand(1, 3, 640, 640)`
|
|
||||||
4. Run the export, making sure you select the right config, for your checkpoint.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker build . --build-arg MODEL_SIZE=s --output . -f- <<'EOF'
|
||||||
|
FROM python:3.11 AS build
|
||||||
|
RUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/
|
||||||
|
WORKDIR /dfine
|
||||||
|
RUN git clone https://github.com/Peterande/D-FINE.git .
|
||||||
|
RUN uv pip install --system -r requirements.txt
|
||||||
|
RUN uv pip install --system onnx onnxruntime onnxsim
|
||||||
|
# Create output directory and download checkpoint
|
||||||
|
RUN mkdir -p output
|
||||||
|
ARG MODEL_SIZE
|
||||||
|
RUN wget https://github.com/Peterande/storage/releases/download/dfinev1.0/dfine_${MODEL_SIZE}_obj2coco.pth -O output/dfine_${MODEL_SIZE}_obj2coco.pth
|
||||||
|
# Modify line 58 of export_onnx.py to change batch size to 1
|
||||||
|
RUN sed -i '58s/data = torch.rand(.*)/data = torch.rand(1, 3, 640, 640)/' tools/deployment/export_onnx.py
|
||||||
|
RUN python3 tools/deployment/export_onnx.py -c configs/dfine/objects365/dfine_hgnetv2_${MODEL_SIZE}_obj2coco.yml -r output/dfine_${MODEL_SIZE}_obj2coco.pth
|
||||||
|
FROM scratch
|
||||||
|
ARG MODEL_SIZE
|
||||||
|
COPY --from=build /dfine/output/dfine_${MODEL_SIZE}_obj2coco.onnx /dfine-${MODEL_SIZE}.onnx
|
||||||
|
EOF
|
||||||
```
|
```
|
||||||
python3 tools/deployment/export_onnx.py -c configs/dfine/objects365/dfine_hgnetv2_m_obj2coco.yml -r output/dfine_m_obj2coco.pth
|
|
||||||
```
|
|
||||||
|
|
||||||
:::tip
|
|
||||||
|
|
||||||
Model export has only been tested on Linux (or WSL2). Not all dependencies are in `requirements.txt`. Some live in the deployment folder, and some are still missing entirely and must be installed manually.
|
|
||||||
|
|
||||||
Make sure you change the batch size to 1 before exporting.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
### Download RF-DETR Model
|
### Download RF-DETR Model
|
||||||
|
|
||||||
To export as ONNX:
|
RF-DETR can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=Nano` in the first line to `Nano`, `Small`, or `Medium` size.
|
||||||
|
|
||||||
1. `pip3 install rfdetr`
|
```sh
|
||||||
2. `python3`
|
docker build . --build-arg MODEL_SIZE=Nano --output . -f- <<'EOF'
|
||||||
3. `from rfdetr import RFDETRBase`
|
FROM python:3.11 AS build
|
||||||
4. `x = RFDETRBase()`
|
RUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/*
|
||||||
5. `x.export()`
|
COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/
|
||||||
|
WORKDIR /rfdetr
|
||||||
#### Additional Configuration
|
RUN uv pip install --system rfdetr[onnxexport]
|
||||||
|
ARG MODEL_SIZE
|
||||||
The input tensor resolution can be customized:
|
RUN python3 -c "from rfdetr import RFDETR${MODEL_SIZE}; x = RFDETR${MODEL_SIZE}(resolution=320); x.export(simplify=True)"
|
||||||
|
FROM scratch
|
||||||
```python
|
ARG MODEL_SIZE
|
||||||
from rfdetr import RFDETRBase
|
COPY --from=build /rfdetr/output/inference_model.onnx /rfdetr-${MODEL_SIZE}.onnx
|
||||||
x = RFDETRBase(resolution=560) # resolution must be a multiple of 56
|
EOF
|
||||||
x.export()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Downloading YOLO-NAS Model
|
### Downloading YOLO-NAS Model
|
||||||
@@ -1053,22 +1052,25 @@ python3 yolo_to_onnx.py -m yolov7-320
|
|||||||
|
|
||||||
#### YOLOv9
|
#### YOLOv9
|
||||||
|
|
||||||
YOLOv9 models can be exported using the below code
|
YOLOv9 model can be exported as ONNX using the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=t` and `IMG_SIZE=320` in the first line to the [model size](https://github.com/WongKinYiu/yolov9#performance) you would like to convert (available model sizes are `t`, `s`, `m`, `c`, and `e`, common image sizes are `320` and `640`).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/WongKinYiu/yolov9
|
docker build . --build-arg MODEL_SIZE=t --build-arg IMG_SIZE=320 --output . -f- <<'EOF'
|
||||||
cd yolov9
|
FROM python:3.11 AS build
|
||||||
|
RUN apt-get update && apt-get install --no-install-recommends -y libgl1 && rm -rf /var/lib/apt/lists/*
|
||||||
# setup the virtual environment so installation doesn't affect main system
|
COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/
|
||||||
# NOTE: Virtual environment must be using Python 3.11 or older.
|
WORKDIR /yolov9
|
||||||
python3 -m venv ./
|
ADD https://github.com/WongKinYiu/yolov9.git .
|
||||||
bin/pip install -r requirements.txt
|
RUN uv pip install --system -r requirements.txt
|
||||||
bin/pip install onnx onnxruntime onnx-simplifier>=0.4.1
|
RUN uv pip install --system onnx==1.18.0 onnxruntime onnx-simplifier>=0.4.1
|
||||||
|
ARG MODEL_SIZE
|
||||||
# download the weights
|
ARG IMG_SIZE
|
||||||
wget -O yolov9-t.pt "https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-t-converted.pt" # download the weights
|
ADD https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-${MODEL_SIZE}-converted.pt yolov9-${MODEL_SIZE}.pt
|
||||||
|
RUN sed -i "s/ckpt = torch.load(attempt_download(w), map_location='cpu')/ckpt = torch.load(attempt_download(w), map_location='cpu', weights_only=False)/g" models/experimental.py
|
||||||
# prepare and run export script
|
RUN python3 export.py --weights ./yolov9-${MODEL_SIZE}.pt --imgsz ${IMG_SIZE} --simplify --include onnx
|
||||||
sed -i "s/ckpt = torch.load(attempt_download(w), map_location='cpu')/ckpt = torch.load(attempt_download(w), map_location='cpu', weights_only=False)/g" ./models/experimental.py
|
FROM scratch
|
||||||
bin/python3 export.py --weights ./yolov9-t.pt --imgsz 320 --simplify --include onnx
|
ARG MODEL_SIZE
|
||||||
|
ARG IMG_SIZE
|
||||||
|
COPY --from=build /yolov9/yolov9-${MODEL_SIZE}.onnx /yolov9-${MODEL_SIZE}-${IMG_SIZE}.onnx
|
||||||
|
EOF
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ In order to install Frigate as a PWA, the following requirements must be met:
|
|||||||
Installation varies slightly based on the device that is being used:
|
Installation varies slightly based on the device that is being used:
|
||||||
|
|
||||||
- Desktop: Use the install button typically found in right edge of the address bar
|
- Desktop: Use the install button typically found in right edge of the address bar
|
||||||
- Android: Use the `Install as App` button in the more options menu
|
- Android: Use the `Install as App` button in the more options menu for Chrome, and the `Add app to Home screen` button for Firefox
|
||||||
- iOS: Use the `Add to Homescreen` button in the share menu
|
- iOS: Use the `Add to Homescreen` button in the share menu
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ record:
|
|||||||
# Optional: Number of minutes to wait between cleanup runs (default: shown below)
|
# Optional: Number of minutes to wait between cleanup runs (default: shown below)
|
||||||
# This can be used to reduce the frequency of deleting recording segments from disk if you want to minimize i/o
|
# This can be used to reduce the frequency of deleting recording segments from disk if you want to minimize i/o
|
||||||
expire_interval: 60
|
expire_interval: 60
|
||||||
# Optional: Sync recordings with disk on startup and once a day (default: shown below).
|
# Optional: Two-way sync recordings database with disk on startup and once a day (default: shown below).
|
||||||
sync_recordings: False
|
sync_recordings: False
|
||||||
# Optional: Retention settings for recording
|
# Optional: Retention settings for recording
|
||||||
retain:
|
retain:
|
||||||
@@ -561,7 +561,7 @@ face_recognition:
|
|||||||
recognition_threshold: 0.9
|
recognition_threshold: 0.9
|
||||||
# Optional: Min area of detected face box to consider running face recognition (default: shown below)
|
# Optional: Min area of detected face box to consider running face recognition (default: shown below)
|
||||||
min_area: 500
|
min_area: 500
|
||||||
# Optional: Min face attempts for the sub label to be applied to the person object (default: shown below)
|
# Optional: Min face recognitions for the sub label to be applied to the person object (default: shown below)
|
||||||
min_faces: 1
|
min_faces: 1
|
||||||
# Optional: Number of images of recognized faces to save for training (default: shown below)
|
# Optional: Number of images of recognized faces to save for training (default: shown below)
|
||||||
save_attempts: 100
|
save_attempts: 100
|
||||||
@@ -903,7 +903,7 @@ telemetry:
|
|||||||
# Optional: Enable Intel GPU stats (default: shown below)
|
# Optional: Enable Intel GPU stats (default: shown below)
|
||||||
intel_gpu_stats: True
|
intel_gpu_stats: True
|
||||||
# Optional: Treat GPU as SR-IOV to fix GPU stats (default: shown below)
|
# Optional: Treat GPU as SR-IOV to fix GPU stats (default: shown below)
|
||||||
sriov: False
|
intel_gpu_device: None
|
||||||
# Optional: Enable network bandwidth stats monitoring for camera ffmpeg processes, go2rtc, and object detectors. (default: shown below)
|
# Optional: Enable network bandwidth stats monitoring for camera ffmpeg processes, go2rtc, and object detectors. (default: shown below)
|
||||||
# NOTE: The container must either be privileged or have cap_net_admin, cap_net_raw capabilities enabled.
|
# NOTE: The container must either be privileged or have cap_net_admin, cap_net_raw capabilities enabled.
|
||||||
network_bandwidth: False
|
network_bandwidth: False
|
||||||
|
|||||||
@@ -21,6 +21,21 @@ In 0.14 and later, all of that is bundled into a single review item which starts
|
|||||||
|
|
||||||
Not every segment of video captured by Frigate may be of the same level of interest to you. Video of people who enter your property may be a different priority than those walking by on the sidewalk. For this reason, Frigate 0.14 categorizes review items as _alerts_ and _detections_. By default, all person and car objects are considered alerts. You can refine categorization of your review items by configuring required zones for them.
|
Not every segment of video captured by Frigate may be of the same level of interest to you. Video of people who enter your property may be a different priority than those walking by on the sidewalk. For this reason, Frigate 0.14 categorizes review items as _alerts_ and _detections_. By default, all person and car objects are considered alerts. You can refine categorization of your review items by configuring required zones for them.
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Alerts and detections categorize the tracked objects in review items, but Frigate must first detect those objects with your configured object detector (Coral, OpenVINO, etc). By default, the object tracker only detects `person`. Setting `labels` for `alerts` and `detections` does not automatically enable detection of new objects. To detect more than `person`, you should add the following to your config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
objects:
|
||||||
|
track:
|
||||||
|
- person
|
||||||
|
- car
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [objects documentation](objects.md) for the list of objects that Frigate's default model tracks.
|
||||||
|
:::
|
||||||
|
|
||||||
## Restricting alerts to specific labels
|
## Restricting alerts to specific labels
|
||||||
|
|
||||||
By default a review item will only be marked as an alert if a person or car is detected. This can be configured to include any object or audio label using the following config:
|
By default a review item will only be marked as an alert if a person or car is detected. This can be configured to include any object or audio label using the following config:
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ Note that certbot uses symlinks, and those can't be followed by the container un
|
|||||||
frigate:
|
frigate:
|
||||||
...
|
...
|
||||||
volumes:
|
volumes:
|
||||||
- /etc/letsencrypt/live/frigate:/etc/letsencrypt/live/frigate:ro
|
- /etc/letsencrypt/live/your.fqdn.net:/etc/letsencrypt/live/frigate:ro
|
||||||
- /etc/letsencrypt/archive/frigate:/etc/letsencrypt/archive/frigate:ro
|
- /etc/letsencrypt/archive/your.fqdn.net:/etc/letsencrypt/archive/your.fqdn.net:ro
|
||||||
...
|
...
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ id: camera_setup
|
|||||||
title: Camera setup
|
title: Camera setup
|
||||||
---
|
---
|
||||||
|
|
||||||
Cameras configured to output H.264 video and AAC audio will offer the most compatibility with all features of Frigate and Home Assistant. H.265 has better compression, but less compatibility. Chrome 108+, Safari and Edge are the only browsers able to play H.265 and only support a limited number of H.265 profiles. Ideally, cameras should be configured directly for the desired resolutions and frame rates you want to use in Frigate. Reducing frame rates within Frigate will waste CPU resources decoding extra frames that are discarded. There are three different goals that you want to tune your stream configurations around.
|
Cameras configured to output H.264 video and AAC audio will offer the most compatibility with all features of Frigate and Home Assistant. H.265 has better compression, but less compatibility. Firefox 134+/136+/137+ (Windows/Mac/Linux & Android), Chrome 108+, Safari and Edge are the only browsers able to play H.265 and only support a limited number of H.265 profiles. Ideally, cameras should be configured directly for the desired resolutions and frame rates you want to use in Frigate. Reducing frame rates within Frigate will waste CPU resources decoding extra frames that are discarded. There are three different goals that you want to tune your stream configurations around.
|
||||||
|
|
||||||
- **Detection**: This is the only stream that Frigate will decode for processing. Also, this is the stream where snapshots will be generated from. The resolution for detection should be tuned for the size of the objects you want to detect. See [Choosing a detect resolution](#choosing-a-detect-resolution) for more details. The recommended frame rate is 5fps, but may need to be higher (10fps is the recommended maximum for most users) for very fast moving objects. Higher resolutions and frame rates will drive higher CPU usage on your server.
|
- **Detection**: This is the only stream that Frigate will decode for processing. Also, this is the stream where snapshots will be generated from. The resolution for detection should be tuned for the size of the objects you want to detect. See [Choosing a detect resolution](#choosing-a-detect-resolution) for more details. The recommended frame rate is 5fps, but may need to be higher (10fps is the recommended maximum for most users) for very fast moving objects. Higher resolutions and frame rates will drive higher CPU usage on your server.
|
||||||
|
|
||||||
|
|||||||
@@ -66,4 +66,4 @@ The time period starting when a tracked object entered the frame and ending when
|
|||||||
|
|
||||||
## Zone
|
## Zone
|
||||||
|
|
||||||
Zones are areas of interest, zones can be used for notifications and for limiting the areas where Frigate will create an [event](#event). [See the zone docs for more info](/configuration/zones)
|
Zones are areas of interest, zones can be used for notifications and for limiting the areas where Frigate will create a [review item](#review-item). [See the zone docs for more info](/configuration/zones)
|
||||||
|
|||||||
@@ -9,23 +9,36 @@ Cameras that output H.264 video and AAC audio will offer the most compatibility
|
|||||||
|
|
||||||
I recommend Dahua, Hikvision, and Amcrest in that order. Dahua edges out Hikvision because they are easier to find and order, not because they are better cameras. I personally use Dahua cameras because they are easier to purchase directly. In my experience Dahua and Hikvision both have multiple streams with configurable resolutions and frame rates and rock solid streams. They also both have models with large sensors well known for excellent image quality at night. Not all the models are equal. Larger sensors are better than higher resolutions; especially at night. Amcrest is the fallback recommendation because they are rebranded Dahuas. They are rebranding the lower end models with smaller sensors or less configuration options.
|
I recommend Dahua, Hikvision, and Amcrest in that order. Dahua edges out Hikvision because they are easier to find and order, not because they are better cameras. I personally use Dahua cameras because they are easier to purchase directly. In my experience Dahua and Hikvision both have multiple streams with configurable resolutions and frame rates and rock solid streams. They also both have models with large sensors well known for excellent image quality at night. Not all the models are equal. Larger sensors are better than higher resolutions; especially at night. Amcrest is the fallback recommendation because they are rebranded Dahuas. They are rebranding the lower end models with smaller sensors or less configuration options.
|
||||||
|
|
||||||
Many users have reported various issues with Reolink cameras, so I do not recommend them. If you are using Reolink, I suggest the [Reolink specific configuration](../configuration/camera_specific.md#reolink-cameras). Wifi cameras are also not recommended. Their streams are less reliable and cause connection loss and/or lost video data.
|
WiFi cameras are not recommended as [their streams are less reliable and cause connection loss and/or lost video data](https://ipcamtalk.com/threads/camera-conflicts.68142/#post-738821), especially when more than a few WiFi cameras will be used at the same time.
|
||||||
|
|
||||||
Here are some of the camera's I recommend:
|
Many users have reported various issues with 4K-plus Reolink cameras, it is best to stick with 5MP and lower for Reolink cameras. If you are using Reolink, I suggest the [Reolink specific configuration](../configuration/camera_specific.md#reolink-cameras).
|
||||||
|
|
||||||
|
Here are some of the cameras I recommend:
|
||||||
|
|
||||||
- <a href="https://amzn.to/4fwoNWA" target="_blank" rel="nofollow noopener sponsored">Loryta(Dahua) IPC-T549M-ALED-S3</a> (affiliate link)
|
- <a href="https://amzn.to/4fwoNWA" target="_blank" rel="nofollow noopener sponsored">Loryta(Dahua) IPC-T549M-ALED-S3</a> (affiliate link)
|
||||||
- <a href="https://amzn.to/3YXpcMw" target="_blank" rel="nofollow noopener sponsored">Loryta(Dahua) IPC-T54IR-AS</a> (affiliate link)
|
- <a href="https://amzn.to/3YXpcMw" target="_blank" rel="nofollow noopener sponsored">Loryta(Dahua) IPC-T54IR-AS</a> (affiliate link)
|
||||||
- <a href="https://amzn.to/3AvBHoY" target="_blank" rel="nofollow noopener sponsored">Amcrest IP5M-T1179EW-AI-V3</a> (affiliate link)
|
- <a href="https://amzn.to/3AvBHoY" target="_blank" rel="nofollow noopener sponsored">Amcrest IP5M-T1179EW-AI-V3</a> (affiliate link)
|
||||||
|
- <a href="https://amzn.to/4ltOpaC" target="_blank" rel="nofollow noopener sponsored">HIKVISION DS-2CD2387G2P-LSU/SL ColorVu 8MP Panoramic Turret IP Camera</a> (affiliate link)
|
||||||
|
|
||||||
I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website.
|
I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website.
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
|
|
||||||
My current favorite is the Beelink EQ13 because of the efficient N100 CPU and dual NICs that allow you to setup a dedicated private network for your cameras where they can be blocked from accessing the internet. There are many used workstation options on eBay that work very well. Anything with an Intel CPU and capable of running Debian should work fine. As a bonus, you may want to look for devices with a M.2 or PCIe express slot that is compatible with the Hailo8 or Google Coral. I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website.
|
My current favorite is the Beelink EQ13 because of the efficient N100 CPU and dual NICs that allow you to setup a dedicated private network for your cameras where they can be blocked from accessing the internet. There are many used workstation options on eBay that work very well. Anything with an Intel CPU and capable of running Debian should work fine. As a bonus, you may want to look for devices with a M.2 or PCIe express slot that is compatible with the Google Coral, Hailo, or other AI accelerators.
|
||||||
|
|
||||||
| Name | Notes |
|
Note that many of these mini PCs come with Windows pre-installed, and you will need to install Linux according to the [getting started guide](../guides/getting_started.md).
|
||||||
| ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
||||||
| Beelink EQ13 (<a href="https://amzn.to/4iQaBKu" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
I may earn a small commission for my endorsement, recommendation, testimonial, or link to any products or services from this website.
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
|
||||||
|
If the EQ13 is out of stock, the link below may take you to a suggested alternative on Amazon. The Beelink EQ14 has some known compatibility issues, so you should avoid that model for now.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
| Name | Coral Inference Speed | Coral Compatibility | Notes |
|
||||||
|
| ------------------------------------------------------------------------------------------------------------- | --------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
|
||||||
|
| Beelink EQ13 (<a href="https://amzn.to/4jn2qVr" target="_blank" rel="nofollow noopener sponsored">Amazon</a>) | 5-10ms | USB | Dual gigabit NICs for easy isolated camera network. Easily handles several 1080p cameras. |
|
||||||
|
|
||||||
## Detectors
|
## Detectors
|
||||||
|
|
||||||
@@ -47,13 +60,13 @@ Frigate supports multiple different detectors that work on different types of ha
|
|||||||
|
|
||||||
**AMD**
|
**AMD**
|
||||||
|
|
||||||
- [ROCm](#amd-gpus): ROCm can run on AMD Discrete GPUs to provide efficient object detection
|
- [ROCm](#rocm---amd-gpu): ROCm can run on AMD Discrete GPUs to provide efficient object detection
|
||||||
- [Supports limited model architectures](../../configuration/object_detectors#supported-models-1)
|
- [Supports limited model architectures](../../configuration/object_detectors#supported-models-1)
|
||||||
- Runs best on discrete AMD GPUs
|
- Runs best on discrete AMD GPUs
|
||||||
|
|
||||||
**Intel**
|
**Intel**
|
||||||
|
|
||||||
- [OpenVino](#openvino): OpenVino can run on Intel Arc GPUs, Intel integrated GPUs, and Intel CPUs to provide efficient object detection.
|
- [OpenVino](#openvino---intel): OpenVino can run on Intel Arc GPUs, Intel integrated GPUs, and Intel CPUs to provide efficient object detection.
|
||||||
- [Supports majority of model architectures](../../configuration/object_detectors#supported-models)
|
- [Supports majority of model architectures](../../configuration/object_detectors#supported-models)
|
||||||
- Runs best with tiny, small, or medium models
|
- Runs best with tiny, small, or medium models
|
||||||
|
|
||||||
@@ -86,6 +99,7 @@ In real-world deployments, even with multiple cameras running concurrently, Frig
|
|||||||
| Name | Hailo‑8 Inference Time | Hailo‑8L Inference Time |
|
| Name | Hailo‑8 Inference Time | Hailo‑8L Inference Time |
|
||||||
| ---------------- | ---------------------- | ----------------------- |
|
| ---------------- | ---------------------- | ----------------------- |
|
||||||
| ssd mobilenet v1 | ~ 6 ms | ~ 10 ms |
|
| ssd mobilenet v1 | ~ 6 ms | ~ 10 ms |
|
||||||
|
| yolov9-tiny | | 320: 18ms |
|
||||||
| yolov6n | ~ 7 ms | ~ 11 ms |
|
| yolov6n | ~ 7 ms | ~ 11 ms |
|
||||||
|
|
||||||
### Google Coral TPU
|
### Google Coral TPU
|
||||||
@@ -97,55 +111,79 @@ Frigate supports both the USB and M.2 versions of the Google Coral.
|
|||||||
|
|
||||||
A single Coral can handle many cameras using the default model and will be sufficient for the majority of users. You can calculate the maximum performance of your Coral based on the inference speed reported by Frigate. With an inference speed of 10, your Coral will top out at `1000/10=100`, or 100 frames per second. If your detection fps is regularly getting close to that, you should first consider tuning motion masks. If those are already properly configured, a second Coral may be needed.
|
A single Coral can handle many cameras using the default model and will be sufficient for the majority of users. You can calculate the maximum performance of your Coral based on the inference speed reported by Frigate. With an inference speed of 10, your Coral will top out at `1000/10=100`, or 100 frames per second. If your detection fps is regularly getting close to that, you should first consider tuning motion masks. If those are already properly configured, a second Coral may be needed.
|
||||||
|
|
||||||
### OpenVINO
|
### OpenVINO - Intel
|
||||||
|
|
||||||
The OpenVINO detector type is able to run on:
|
The OpenVINO detector type is able to run on:
|
||||||
|
|
||||||
- 6th Gen Intel Platforms and newer that have an iGPU
|
- 6th Gen Intel Platforms and newer that have an iGPU
|
||||||
- x86 & Arm64 hosts with VPU Hardware (ex: Intel NCS2)
|
- x86 hosts with an Intel Arc GPU
|
||||||
- Most modern AMD CPUs (though this is officially not supported by Intel)
|
- Most modern AMD CPUs (though this is officially not supported by Intel)
|
||||||
|
- x86 & Arm64 hosts via CPU (generally not recommended)
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
Intel NPUs have seen [limited success in community deployments](https://github.com/blakeblackshear/frigate/discussions/13248#discussioncomment-12347357), although they remain officially unsupported.
|
||||||
|
|
||||||
|
In testing, the NPU delivered performance that was only comparable to — or in some cases worse than — the integrated GPU.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
More information is available [in the detector docs](/configuration/object_detectors#openvino-detector)
|
More information is available [in the detector docs](/configuration/object_detectors#openvino-detector)
|
||||||
|
|
||||||
Inference speeds vary greatly depending on the CPU or GPU used, some known examples of GPU inference times are below:
|
Inference speeds vary greatly depending on the CPU or GPU used, some known examples of GPU inference times are below:
|
||||||
|
|
||||||
| Name | MobileNetV2 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time | Notes |
|
| Name | MobileNetV2 Inference Time | YOLOv9 | YOLO-NAS Inference Time | RF-DETR Inference Time | Notes |
|
||||||
| -------------- | -------------------------- | ------------------------- | ---------------------- | ---------------------------------- |
|
| -------------- | -------------------------- | ------------------------------------------------- | ------------------------- | ---------------------- | ---------------------------------- |
|
||||||
| Intel HD 530 | 15 - 35 ms | | | Can only run one detector instance |
|
| Intel HD 530 | 15 - 35 ms | | | | Can only run one detector instance |
|
||||||
| Intel HD 620 | 15 - 25 ms | 320: ~ 35 ms | | |
|
| Intel HD 620 | 15 - 25 ms | | 320: ~ 35 ms | | |
|
||||||
| Intel HD 630 | ~ 15 ms | 320: ~ 30 ms | | |
|
| Intel HD 630 | ~ 15 ms | | 320: ~ 30 ms | | |
|
||||||
| Intel UHD 730 | ~ 10 ms | 320: ~ 19 ms 640: ~ 54 ms | | |
|
| Intel UHD 730 | ~ 10 ms | | 320: ~ 19 ms 640: ~ 54 ms | | |
|
||||||
| Intel UHD 770 | ~ 15 ms | 320: ~ 20 ms 640: ~ 46 ms | | |
|
| Intel UHD 770 | ~ 15 ms | t-320: ~ 16 ms s-320: ~ 20 ms s-640: ~ 40 ms | 320: ~ 20 ms 640: ~ 46 ms | | |
|
||||||
| Intel N100 | ~ 15 ms | 320: ~ 20 ms | | |
|
| Intel N100 | ~ 15 ms | s-320: 30 ms | 320: ~ 25 ms | | Can only run one detector instance |
|
||||||
| Intel Iris XE | ~ 10 ms | 320: ~ 18 ms 640: ~ 50 ms | | |
|
| Intel N150 | ~ 15 ms | t-320: 16 ms s-320: 24 ms | | | |
|
||||||
| Intel Arc A380 | ~ 6 ms | 320: ~ 10 ms 640: ~ 22 ms | 336: 20 ms 448: 27 ms | |
|
| Intel Iris XE | ~ 10 ms | s-320: 12 ms s-640: 30 ms | 320: ~ 18 ms 640: ~ 50 ms | | |
|
||||||
| Intel Arc A750 | ~ 4 ms | 320: ~ 8 ms | | |
|
| Intel Arc A310 | ~ 5 ms | t-320: 7 ms t-640: 11 ms s-320: 8 ms s-640: 15 ms | 320: ~ 8 ms 640: ~ 14 ms | | |
|
||||||
|
| Intel Arc A380 | ~ 6 ms | | 320: ~ 10 ms 640: ~ 22 ms | 336: 20 ms 448: 27 ms | |
|
||||||
|
| Intel Arc A750 | ~ 4 ms | | 320: ~ 8 ms | | |
|
||||||
|
|
||||||
### TensorRT - Nvidia GPU
|
### TensorRT - Nvidia GPU
|
||||||
|
|
||||||
The TensortRT detector is able to run on x86 hosts that have an Nvidia GPU which supports the 12.x series of CUDA libraries. The minimum driver version on the host system must be `>=525.60.13`. Also the GPU must support a Compute Capability of `5.0` or greater. This generally correlates to a Maxwell-era GPU or newer, check the [TensorRT docs for more info](/configuration/object_detectors#nvidia-tensorrt-detector).
|
Frigate is able to utilize an Nvidia GPU which supports the 12.x series of CUDA libraries.
|
||||||
|
|
||||||
|
#### Minimum Hardware Support
|
||||||
|
|
||||||
|
12.x series of CUDA libraries are used which have minor version compatibility. The minimum driver version on the host system must be `>=545`. Also the GPU must support a Compute Capability of `5.0` or greater. This generally correlates to a Maxwell-era GPU or newer, check the NVIDIA GPU Compute Capability table linked below.
|
||||||
|
|
||||||
|
Make sure your host system has the [nvidia-container-runtime](https://docs.docker.com/config/containers/resource_constraints/#access-an-nvidia-gpu) installed to pass through the GPU to the container and the host system has a compatible driver installed for your GPU.
|
||||||
|
|
||||||
|
There are improved capabilities in newer GPU architectures that TensorRT can benefit from, such as INT8 operations and Tensor cores. The features compatible with your hardware will be optimized when the model is converted to a trt file. Currently the script provided for generating the model provides a switch to enable/disable FP16 operations. If you wish to use newer features such as INT8 optimization, more work is required.
|
||||||
|
|
||||||
|
#### Compatibility References:
|
||||||
|
|
||||||
|
[NVIDIA TensorRT Support Matrix](https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-841/support-matrix/index.html)
|
||||||
|
|
||||||
|
[NVIDIA CUDA Compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/index.html)
|
||||||
|
|
||||||
|
[NVIDIA GPU Compute Capability](https://developer.nvidia.com/cuda-gpus)
|
||||||
|
|
||||||
Inference speeds will vary greatly depending on the GPU and the model used.
|
Inference speeds will vary greatly depending on the GPU and the model used.
|
||||||
`tiny` variants are faster than the equivalent non-tiny model, some known examples are below:
|
`tiny` variants are faster than the equivalent non-tiny model, some known examples are below:
|
||||||
|
|
||||||
| Name | YOLOv7 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time |
|
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time |
|
||||||
| --------------- | --------------------- | ------------------------- | ------------------------- |
|
| --------------- | ------------------------- | ------------------------- | ---------------------- |
|
||||||
| GTX 1060 6GB | ~ 7 ms | | |
|
| GTX 1070 | s-320: 16 ms | 320: 14 ms | |
|
||||||
| GTX 1070 | ~ 6 ms | | |
|
| RTX 3050 | t-320: 15 ms s-320: 17 ms | 320: ~ 10 ms 640: ~ 16 ms | Nano-320: ~ 12 ms |
|
||||||
| GTX 1660 SUPER | ~ 4 ms | | |
|
| RTX 3070 | t-320: 11 ms s-320: 13 ms | 320: ~ 8 ms 640: ~ 14 ms | Nano-320: ~ 9 ms |
|
||||||
| RTX 3050 | 5 - 7 ms | 320: ~ 10 ms 640: ~ 16 ms | 336: ~ 16 ms 560: ~ 40 ms |
|
| RTX A4000 | | 320: ~ 15 ms | |
|
||||||
| RTX 3070 Mobile | ~ 5 ms | | |
|
| Tesla P40 | | 320: ~ 105 ms | |
|
||||||
| RTX 3070 | 4 - 6 ms | 320: ~ 6 ms 640: ~ 12 ms | 336: ~ 14 ms 560: ~ 36 ms |
|
|
||||||
| Quadro P400 2GB | 20 - 25 ms | | |
|
|
||||||
| Quadro P2000 | ~ 12 ms | | |
|
|
||||||
|
|
||||||
### AMD GPUs
|
### ROCm - AMD GPU
|
||||||
|
|
||||||
With the [rocm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many discrete AMD GPUs.
|
With the [rocm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many discrete AMD GPUs.
|
||||||
|
|
||||||
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time |
|
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time |
|
||||||
| --------- | --------------------- | ------------------------- |
|
| --------- | --------------------- | ------------------------- |
|
||||||
| AMD 780M | ~ 14 ms | 320: ~ 25 ms 640: ~ 50 ms |
|
| AMD 780M | 320: ~ 14 ms | 320: ~ 25 ms 640: ~ 50 ms |
|
||||||
| AMD 8700G | | 320: ~ 20 ms 640: ~ 40 ms |
|
| AMD 8700G | | 320: ~ 20 ms 640: ~ 40 ms |
|
||||||
|
|
||||||
## Community Supported Detectors
|
## Community Supported Detectors
|
||||||
@@ -193,4 +231,4 @@ Basically - When you increase the resolution and/or the frame rate of the stream
|
|||||||
|
|
||||||
YES! The Coral does not help with decoding video streams.
|
YES! The Coral does not help with decoding video streams.
|
||||||
|
|
||||||
Decompressing video streams takes a significant amount of CPU power. Video compression uses key frames (also known as I-frames) to send a full frame in the video stream. The following frames only include the difference from the key frame, and the CPU has to compile each frame by merging the differences with the key frame. [More detailed explanation](https://blog.video.ibm.com/streaming-video-tips/keyframes-interframe-video-compression/). Higher resolutions and frame rates mean more processing power is needed to decode the video stream, so try and set them on the camera to avoid unnecessary decoding work.
|
Decompressing video streams takes a significant amount of CPU power. Video compression uses key frames (also known as I-frames) to send a full frame in the video stream. The following frames only include the difference from the key frame, and the CPU has to compile each frame by merging the differences with the key frame. [More detailed explanation](https://support.video.ibm.com/hc/en-us/articles/18106203580316-Keyframes-InterFrame-Video-Compression). Higher resolutions and frame rates mean more processing power is needed to decode the video stream, so try and set them on the camera to avoid unnecessary decoding work.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ The following ports are used by Frigate and can be mapped via docker as required
|
|||||||
| `8971` | Authenticated UI and API access without TLS. Reverse proxies should use this port. |
|
| `8971` | Authenticated UI and API access without TLS. Reverse proxies should use this port. |
|
||||||
| `5000` | Internal unauthenticated UI and API access. Access to this port should be limited. Intended to be used within the docker network for services that integrate with Frigate. |
|
| `5000` | Internal unauthenticated UI and API access. Access to this port should be limited. Intended to be used within the docker network for services that integrate with Frigate. |
|
||||||
| `8554` | RTSP restreaming. By default, these streams are unauthenticated. Authentication can be configured in go2rtc section of config. |
|
| `8554` | RTSP restreaming. By default, these streams are unauthenticated. Authentication can be configured in go2rtc section of config. |
|
||||||
| `8555` | WebRTC connections for low latency live views. |
|
| `8555` | WebRTC connections for cameras with two-way talk support. |
|
||||||
|
|
||||||
#### Common Docker Compose storage configurations
|
#### Common Docker Compose storage configurations
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ $ sudo cat /sys/kernel/debug/rknpu/version
|
|||||||
RKNPU driver: v0.9.2 # or later version
|
RKNPU driver: v0.9.2 # or later version
|
||||||
```
|
```
|
||||||
|
|
||||||
I recommend [Joshua Riek's Ubuntu for Rockchip](https://github.com/Joshua-Riek/ubuntu-rockchip), if your board is supported.
|
I recommend [Armbian](https://www.armbian.com/download/?arch=aarch64), if your board is supported.
|
||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
id: planning_setup
|
||||||
|
title: Planning a New Installation
|
||||||
|
---
|
||||||
|
|
||||||
|
Choosing the right hardware for your Frigate NVR setup is important for optimal performance and a smooth experience. This guide will walk you through the key considerations, focusing on the number of cameras and the hardware required for efficient object detection.
|
||||||
|
|
||||||
|
## Key Considerations
|
||||||
|
|
||||||
|
### Number of Cameras and Simultaneous Activity
|
||||||
|
|
||||||
|
The most fundamental factor in your hardware decision is the number of cameras you plan to use. However, it's not just about the raw count; it's also about how many of those cameras are likely to see activity and require object detection simultaneously.
|
||||||
|
|
||||||
|
When motion is detected in a camera's feed, regions of that frame are sent to your chosen [object detection hardware](/configuration/object_detectors).
|
||||||
|
|
||||||
|
- **Low Simultaneous Activity (1-6 cameras with occasional motion)**: If you have a few cameras in areas with infrequent activity (e.g., a seldom-used backyard, a quiet interior), the demand on your object detection hardware will be lower. A single, entry-level AI accelerator will suffice.
|
||||||
|
- **Moderate Simultaneous Activity (6-12 cameras with some overlapping motion)**: For setups with more cameras, especially in areas like a busy street or a property with multiple access points, it's more likely that several cameras will capture activity at the same time. This increases the load on your object detection hardware, requiring more processing power.
|
||||||
|
- **High Simultaneous Activity (12+ cameras or highly active zones)**: Large installations or scenarios where many cameras frequently capture activity (e.g., busy street with overview, identification, dedicated LPR cameras, etc.) will necessitate robust object detection capabilities. You'll likely need multiple entry-level AI accelerators or a more powerful single unit such as a discrete GPU.
|
||||||
|
- **Commercial Installations (40+ cameras)**: Commercial installations or scenarios where a substantial number of cameras capture activity (e.g., a commercial property, an active public space) will necessitate robust object detection capabilities. You'll likely need a modern discrete GPU.
|
||||||
|
|
||||||
|
### Video Decoding
|
||||||
|
|
||||||
|
Modern CPUs with integrated GPUs (Intel Quick Sync, AMD VCN) or dedicated GPUs can significantly offload video decoding from the main CPU, freeing up resources. This is highly recommended, especially for multiple cameras.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
For commercial installations it is important to verify the number of supported concurrent streams on your GPU, many consumer GPUs max out at ~20 concurrent camera streams.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Hardware Considerations
|
||||||
|
|
||||||
|
### Object Detection
|
||||||
|
|
||||||
|
There are many different hardware options for object detection depending on priorities and available hardware. See [the recommended hardware page](./hardware.md#detectors) for more specifics on what hardware is recommended for object detection.
|
||||||
|
|
||||||
|
### Storage
|
||||||
|
|
||||||
|
Storage is an important consideration when planning a new installation. To get a more precise estimate of your storage requirements, you can use an IP camera storage calculator. Websites like [IPConfigure Storage Calculator](https://calculator.ipconfigure.com/) can help you determine the necessary disk space based on your camera settings.
|
||||||
|
|
||||||
|
|
||||||
|
#### SSDs (Solid State Drives)
|
||||||
|
|
||||||
|
SSDs are an excellent choice for Frigate, offering high speed and responsiveness. The older concern that SSDs would quickly "wear out" from constant video recording is largely no longer valid for modern consumer and enterprise-grade SSDs.
|
||||||
|
|
||||||
|
- Longevity: Modern SSDs are designed with advanced wear-leveling algorithms and significantly higher "Terabytes Written" (TBW) ratings than earlier models. For typical home NVR use, a good quality SSD will likely outlast the useful life of your NVR hardware itself.
|
||||||
|
- Performance: SSDs excel at handling the numerous small write operations that occur during continuous video recording and can significantly improve the responsiveness of the Frigate UI and clip retrieval.
|
||||||
|
- Silence and Efficiency: SSDs produce no noise and consume less power than traditional HDDs.
|
||||||
|
|
||||||
|
#### HDDs (Hard Disk Drives)
|
||||||
|
|
||||||
|
Traditional Hard Disk Drives (HDDs) remain a great and often more cost-effective option for long-term video storage, especially for larger setups where raw capacity is prioritized.
|
||||||
|
|
||||||
|
- Cost-Effectiveness: HDDs offer the best cost per gigabyte, making them ideal for storing many days, weeks, or months of continuous footage.
|
||||||
|
- Capacity: HDDs are available in much larger capacities than most consumer SSDs, which is beneficial for extensive video archives.
|
||||||
|
- NVR-Rated Drives: If choosing an HDD, consider drives specifically designed for surveillance (NVR) use, such as Western Digital Purple or Seagate SkyHawk. These drives are engineered for 24/7 operation and continuous write workloads, offering improved reliability compared to standard desktop drives.
|
||||||
|
|
||||||
|
Determining Your Storage Needs
|
||||||
|
The amount of storage you need will depend on several factors:
|
||||||
|
|
||||||
|
- Number of Cameras: More cameras naturally require more space.
|
||||||
|
- Resolution and Framerate: Higher resolution (e.g., 4K) and higher framerate (e.g., 30fps) streams consume significantly more storage.
|
||||||
|
- Recording Method: Continuous recording uses the most space. motion-only recording or object-triggered recording can save space, but may miss some footage.
|
||||||
|
- Retention Period: How many days, weeks, or months of footage do you want to keep?
|
||||||
|
|
||||||
|
#### Network Storage (NFS/SMB)
|
||||||
|
|
||||||
|
While supported, using network-attached storage (NAS) for recordings can introduce latency and network dependency considerations. For optimal performance and reliability, it is generally recommended to have local storage for your Frigate recordings. If using a NAS, ensure your network connection to it is robust and fast (Gigabit Ethernet at minimum) and that the NAS itself can handle the continuous write load.
|
||||||
|
|
||||||
|
### RAM (Memory)
|
||||||
|
|
||||||
|
- **Basic Minimum: 4GB RAM**: This is generally sufficient for a very basic Frigate setup with a few cameras and a dedicated object detection accelerator, without running any enrichments. Performance might be tight, especially with higher resolution streams or numerous detections.
|
||||||
|
- **Minimum for Enrichments: 8GB RAM**: If you plan to utilize Frigate's enrichment features (e.g., facial recognition, license plate recognition, or other AI models that run alongside standard object detection), 8GB of RAM should be considered the minimum. Enrichments require additional memory to load and process their respective models and data.
|
||||||
|
- **Recommended: 16GB RAM**: For most users, especially those with many cameras (8+) or who plan to heavily leverage enrichments, 16GB of RAM is highly recommended. This provides ample headroom for smooth operation, reduces the likelihood of swapping to disk (which can impact performance), and allows for future expansion.
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
id: updating
|
||||||
|
title: Updating
|
||||||
|
---
|
||||||
|
|
||||||
|
# Updating Frigate
|
||||||
|
|
||||||
|
The current stable version of Frigate is **0.16.1**. The release notes and any breaking changes for this version can be found on the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases/tag/v0.16.1).
|
||||||
|
|
||||||
|
Keeping Frigate up to date ensures you benefit from the latest features, performance improvements, and bug fixes. The update process varies slightly depending on your installation method (Docker, Home Assistant Addon, etc.). Below are instructions for the most common setups.
|
||||||
|
|
||||||
|
## Before You Begin
|
||||||
|
|
||||||
|
- **Stop Frigate**: For most methods, you’ll need to stop the running Frigate instance before backing up and updating.
|
||||||
|
- **Backup Your Configuration**: Always back up your `/config` directory (e.g., `config.yml` and `frigate.db`, the SQLite database) before updating. This ensures you can roll back if something goes wrong.
|
||||||
|
- **Check Release Notes**: Carefully review the [Frigate GitHub releases page](https://github.com/blakeblackshear/frigate/releases) for breaking changes or configuration updates that might affect your setup.
|
||||||
|
|
||||||
|
## Updating with Docker
|
||||||
|
|
||||||
|
If you’re running Frigate via Docker (recommended method), follow these steps:
|
||||||
|
|
||||||
|
1. **Stop the Container**:
|
||||||
|
|
||||||
|
- If using Docker Compose:
|
||||||
|
```bash
|
||||||
|
docker compose down frigate
|
||||||
|
```
|
||||||
|
- If using `docker run`:
|
||||||
|
```bash
|
||||||
|
docker stop frigate
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update and Pull the Latest Image**:
|
||||||
|
|
||||||
|
- If using Docker Compose:
|
||||||
|
- Edit your `docker-compose.yml` file to specify the desired version tag (e.g., `0.16.1` instead of `0.15.2`). For example:
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
frigate:
|
||||||
|
image: ghcr.io/blakeblackshear/frigate:0.16.1
|
||||||
|
```
|
||||||
|
- Then pull the image:
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/blakeblackshear/frigate:0.16.1
|
||||||
|
```
|
||||||
|
- **Note for `stable` Tag Users**: If your `docker-compose.yml` uses the `stable` tag (e.g., `ghcr.io/blakeblackshear/frigate:stable`), you don’t need to update the tag manually. The `stable` tag always points to the latest stable release after pulling.
|
||||||
|
- If using `docker run`:
|
||||||
|
- Pull the image with the appropriate tag (e.g., `0.16.1`, `0.16.1-tensorrt`, or `stable`):
|
||||||
|
```bash
|
||||||
|
docker pull ghcr.io/blakeblackshear/frigate:0.16.1
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Start the Container**:
|
||||||
|
|
||||||
|
- If using Docker Compose:
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
- If using `docker run`, re-run your original command (e.g., from the [Installation](./installation.md#docker) section) with the updated image tag.
|
||||||
|
|
||||||
|
4. **Verify the Update**:
|
||||||
|
- Check the container logs to ensure Frigate starts successfully:
|
||||||
|
```bash
|
||||||
|
docker logs frigate
|
||||||
|
```
|
||||||
|
- Visit the Frigate Web UI (default: `http://<your-ip>:5000`) to confirm the new version is running. The version number is displayed at the top of the System Metrics page.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- If you’ve customized other settings (e.g., `shm-size`), ensure they’re still appropriate after the update.
|
||||||
|
- Docker will automatically use the updated image when you restart the container, as long as you pulled the correct version.
|
||||||
|
|
||||||
|
## Updating the Home Assistant Addon
|
||||||
|
|
||||||
|
For users running Frigate as a Home Assistant Addon:
|
||||||
|
|
||||||
|
1. **Check for Updates**:
|
||||||
|
|
||||||
|
- Navigate to **Settings > Add-ons** in Home Assistant.
|
||||||
|
- Find your installed Frigate addon (e.g., "Frigate NVR" or "Frigate NVR (Full Access)").
|
||||||
|
- If an update is available, you’ll see an "Update" button.
|
||||||
|
|
||||||
|
2. **Update the Addon**:
|
||||||
|
|
||||||
|
- Click the "Update" button next to the Frigate addon.
|
||||||
|
- Wait for the process to complete. Home Assistant will handle downloading and installing the new version.
|
||||||
|
|
||||||
|
3. **Restart the Addon**:
|
||||||
|
|
||||||
|
- After updating, go to the addon’s page and click "Restart" to apply the changes.
|
||||||
|
|
||||||
|
4. **Verify the Update**:
|
||||||
|
- Check the addon logs (under the "Log" tab) to ensure Frigate starts without errors.
|
||||||
|
- Access the Frigate Web UI to confirm the new version is running.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Ensure your `/config/frigate.yml` is compatible with the new version by reviewing the [Release notes](https://github.com/blakeblackshear/frigate/releases).
|
||||||
|
- If using custom hardware (e.g., Coral or GPU), verify that configurations still work, as addon updates don’t modify your hardware settings.
|
||||||
|
|
||||||
|
## Rolling Back
|
||||||
|
|
||||||
|
If an update causes issues:
|
||||||
|
|
||||||
|
1. Stop Frigate.
|
||||||
|
2. Restore your backed-up config file and database.
|
||||||
|
3. Revert to the previous image version:
|
||||||
|
- For Docker: Specify an older tag (e.g., `ghcr.io/blakeblackshear/frigate:0.15.2`) in your `docker run` command.
|
||||||
|
- For Docker Compose: Edit your `docker-compose.yml`, specify the older version tag (e.g., `ghcr.io/blakeblackshear/frigate:0.15.2`), and re-run `docker compose up -d`.
|
||||||
|
- For Home Assistant: Reinstall the previous addon version manually via the repository if needed and restart the addon.
|
||||||
|
4. Verify the old version is running again.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Container Fails to Start**: Check logs (`docker logs frigate`) for errors.
|
||||||
|
- **UI Not Loading**: Ensure ports (e.g., 5000, 8971) are still mapped correctly and the service is running.
|
||||||
|
- **Hardware Issues**: Revisit hardware-specific setup (e.g., Coral, GPU) if detection or decoding fails post-update.
|
||||||
|
|
||||||
|
Common questions are often answered in the [FAQ](https://github.com/blakeblackshear/frigate/discussions), pinned at the top of the support discussions.
|
||||||
@@ -15,10 +15,10 @@ At a high level, there are five processing steps that could be applied to a came
|
|||||||
%%{init: {"themeVariables": {"edgeLabelBackground": "transparent"}}}%%
|
%%{init: {"themeVariables": {"edgeLabelBackground": "transparent"}}}%%
|
||||||
|
|
||||||
flowchart LR
|
flowchart LR
|
||||||
Feed(Feed\nacquisition) --> Decode(Video\ndecoding)
|
Feed(Feed acquisition) --> Decode(Video decoding)
|
||||||
Decode --> Motion(Motion\ndetection)
|
Decode --> Motion(Motion detection)
|
||||||
Motion --> Object(Object\ndetection)
|
Motion --> Object(Object detection)
|
||||||
Feed --> Recording(Recording\nand\nvisualization)
|
Feed --> Recording(Recording and visualization)
|
||||||
Motion --> Recording
|
Motion --> Recording
|
||||||
Object --> Recording
|
Object --> Recording
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ section.
|
|||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
1. If the stream you added to go2rtc is also used by Frigate for the `record` or `detect` role, you can migrate your config to pull from the RTSP restream to reduce the number of connections to your camera as shown [here](/configuration/restream#reduce-connections-to-camera).
|
1. If the stream you added to go2rtc is also used by Frigate for the `record` or `detect` role, you can migrate your config to pull from the RTSP restream to reduce the number of connections to your camera as shown [here](/configuration/restream#reduce-connections-to-camera).
|
||||||
2. You may also prefer to [setup WebRTC](/configuration/live#webrtc-extra-configuration) for slightly lower latency than MSE. Note that WebRTC only supports h264 and specific audio formats and may require opening ports on your router.
|
2. You can [set up WebRTC](/configuration/live#webrtc-extra-configuration) if your camera supports two-way talk. Note that WebRTC only supports specific audio formats and may require opening ports on your router.
|
||||||
|
|
||||||
## Important considerations
|
## Important considerations
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ There are many solutions available to implement reverse proxies and the communit
|
|||||||
* [Apache2](#apache2-reverse-proxy)
|
* [Apache2](#apache2-reverse-proxy)
|
||||||
* [Nginx](#nginx-reverse-proxy)
|
* [Nginx](#nginx-reverse-proxy)
|
||||||
* [Traefik](#traefik-reverse-proxy)
|
* [Traefik](#traefik-reverse-proxy)
|
||||||
|
* [Caddy](#caddy-reverse-proxy)
|
||||||
|
|
||||||
## Apache2 Reverse Proxy
|
## Apache2 Reverse Proxy
|
||||||
|
|
||||||
@@ -117,7 +118,8 @@ server {
|
|||||||
set $port 8971;
|
set $port 8971;
|
||||||
|
|
||||||
listen 80;
|
listen 80;
|
||||||
listen 443 ssl http2;
|
listen 443 ssl;
|
||||||
|
http2 on;
|
||||||
|
|
||||||
server_name frigate.domain.com;
|
server_name frigate.domain.com;
|
||||||
}
|
}
|
||||||
@@ -177,3 +179,33 @@ The above configuration will create a "service" in Traefik, automatically adding
|
|||||||
It will also add a router, routing requests to "traefik.example.com" to your local container.
|
It will also add a router, routing requests to "traefik.example.com" to your local container.
|
||||||
|
|
||||||
Note that with this approach, you don't need to expose any ports for the Frigate instance since all traffic will be routed over the internal Docker network.
|
Note that with this approach, you don't need to expose any ports for the Frigate instance since all traffic will be routed over the internal Docker network.
|
||||||
|
|
||||||
|
## Caddy Reverse Proxy
|
||||||
|
|
||||||
|
This example shows Frigate running under a subdomain with logging and a tls cert (in this case a wildcard domain cert obtained independently of caddy) handled via imports
|
||||||
|
|
||||||
|
```caddy
|
||||||
|
(logging) {
|
||||||
|
log {
|
||||||
|
output file /var/log/caddy/{args[0]}.log {
|
||||||
|
roll_size 10MiB
|
||||||
|
roll_keep 5
|
||||||
|
roll_keep_for 10d
|
||||||
|
}
|
||||||
|
format json
|
||||||
|
level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(tls) {
|
||||||
|
tls /var/lib/caddy/wildcard.YOUR_DOMAIN.TLD.fullchain.pem /var/lib/caddy/wildcard.YOUR_DOMAIN.TLD.privkey.pem
|
||||||
|
}
|
||||||
|
|
||||||
|
frigate.YOUR_DOMAIN.TLD {
|
||||||
|
reverse_proxy http://localhost:8971
|
||||||
|
import tls
|
||||||
|
import logging frigate.YOUR_DOMAIN.TLD
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|||||||
@@ -110,6 +110,14 @@ If you run Frigate on a separate device within your local network, Home Assistan
|
|||||||
|
|
||||||
Use `http://<frigate_device_ip>:8971` as the URL for the integration so that authentication is required.
|
Use `http://<frigate_device_ip>:8971` as the URL for the integration so that authentication is required.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
The above URL assumes you have [disabled TLS](../configuration/tls).
|
||||||
|
By default, TLS is enabled and Frigate will be using a self-signed certificate. HomeAssistant will fail to connect HTTPS to port 8971 since it fails to verify the self-signed certificate.
|
||||||
|
Either disable TLS and use HTTP from HomeAssistant, or configure Frigate to be acessible with a valid certificate.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
frigate:
|
frigate:
|
||||||
@@ -177,6 +185,26 @@ For clips to be castable to media devices, audio is required and may need to be
|
|||||||
|
|
||||||
<a name="api"></a>
|
<a name="api"></a>
|
||||||
|
|
||||||
|
## Camera API
|
||||||
|
|
||||||
|
To disable a camera dynamically
|
||||||
|
|
||||||
|
```
|
||||||
|
action: camera.turn_off
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: camera.back_deck_cam # your Frigate camera entity ID
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable a camera that has been disabled dynamically
|
||||||
|
|
||||||
|
```
|
||||||
|
action: camera.turn_on
|
||||||
|
data: {}
|
||||||
|
target:
|
||||||
|
entity_id: camera.back_deck_cam # your Frigate camera entity ID
|
||||||
|
```
|
||||||
|
|
||||||
## Notification API
|
## Notification API
|
||||||
|
|
||||||
Many people do not want to expose Frigate to the web, so the integration creates some public API endpoints that can be used for notifications.
|
Many people do not want to expose Frigate to the web, so the integration creates some public API endpoints that can be used for notifications.
|
||||||
|
|||||||
@@ -28,7 +28,14 @@ Message published for each changed tracked object. The first message is publishe
|
|||||||
"id": "1607123955.475377-mxklsc",
|
"id": "1607123955.475377-mxklsc",
|
||||||
"camera": "front_door",
|
"camera": "front_door",
|
||||||
"frame_time": 1607123961.837752,
|
"frame_time": 1607123961.837752,
|
||||||
"snapshot_time": 1607123961.837752,
|
"snapshot": {
|
||||||
|
"frame_time": 1607123965.975463,
|
||||||
|
"box": [415, 489, 528, 700],
|
||||||
|
"area": 12728,
|
||||||
|
"region": [260, 446, 660, 846],
|
||||||
|
"score": 0.77546,
|
||||||
|
"attributes": []
|
||||||
|
},
|
||||||
"label": "person",
|
"label": "person",
|
||||||
"sub_label": null,
|
"sub_label": null,
|
||||||
"top_score": 0.958984375,
|
"top_score": 0.958984375,
|
||||||
@@ -54,6 +61,7 @@ Message published for each changed tracked object. The first message is publishe
|
|||||||
}, // attributes with top score that have been identified on the object at any point
|
}, // attributes with top score that have been identified on the object at any point
|
||||||
"current_attributes": [], // detailed data about the current attributes in this frame
|
"current_attributes": [], // detailed data about the current attributes in this frame
|
||||||
"current_estimated_speed": 0.71, // current estimated speed (mph or kph) for objects moving through zones with speed estimation enabled
|
"current_estimated_speed": 0.71, // current estimated speed (mph or kph) for objects moving through zones with speed estimation enabled
|
||||||
|
"average_estimated_speed": 14.3, // average estimated speed (mph or kph) for objects moving through zones with speed estimation enabled
|
||||||
"velocity_angle": 180, // direction of travel relative to the frame for objects moving through zones with speed estimation enabled
|
"velocity_angle": 180, // direction of travel relative to the frame for objects moving through zones with speed estimation enabled
|
||||||
"recognized_license_plate": "ABC12345", // a recognized license plate for car objects
|
"recognized_license_plate": "ABC12345", // a recognized license plate for car objects
|
||||||
"recognized_license_plate_score": 0.933451
|
"recognized_license_plate_score": 0.933451
|
||||||
@@ -62,7 +70,14 @@ Message published for each changed tracked object. The first message is publishe
|
|||||||
"id": "1607123955.475377-mxklsc",
|
"id": "1607123955.475377-mxklsc",
|
||||||
"camera": "front_door",
|
"camera": "front_door",
|
||||||
"frame_time": 1607123962.082975,
|
"frame_time": 1607123962.082975,
|
||||||
"snapshot_time": 1607123961.837752,
|
"snapshot": {
|
||||||
|
"frame_time": 1607123965.975463,
|
||||||
|
"box": [415, 489, 528, 700],
|
||||||
|
"area": 12728,
|
||||||
|
"region": [260, 446, 660, 846],
|
||||||
|
"score": 0.77546,
|
||||||
|
"attributes": []
|
||||||
|
},
|
||||||
"label": "person",
|
"label": "person",
|
||||||
"sub_label": ["John Smith", 0.79],
|
"sub_label": ["John Smith", 0.79],
|
||||||
"top_score": 0.958984375,
|
"top_score": 0.958984375,
|
||||||
@@ -95,6 +110,7 @@ Message published for each changed tracked object. The first message is publishe
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"current_estimated_speed": 0.77, // current estimated speed (mph or kph) for objects moving through zones with speed estimation enabled
|
"current_estimated_speed": 0.77, // current estimated speed (mph or kph) for objects moving through zones with speed estimation enabled
|
||||||
|
"average_estimated_speed": 14.31, // average estimated speed (mph or kph) for objects moving through zones with speed estimation enabled
|
||||||
"velocity_angle": 180, // direction of travel relative to the frame for objects moving through zones with speed estimation enabled
|
"velocity_angle": 180, // direction of travel relative to the frame for objects moving through zones with speed estimation enabled
|
||||||
"recognized_license_plate": "ABC12345", // a recognized license plate for car objects
|
"recognized_license_plate": "ABC12345", // a recognized license plate for car objects
|
||||||
"recognized_license_plate_score": 0.933451
|
"recognized_license_plate_score": 0.933451
|
||||||
@@ -125,7 +141,7 @@ Message published for updates to tracked object metadata, for example:
|
|||||||
"name": "John",
|
"name": "John",
|
||||||
"score": 0.95,
|
"score": 0.95,
|
||||||
"camera": "front_door_cam",
|
"camera": "front_door_cam",
|
||||||
"timestamp": 1607123958.748393,
|
"timestamp": 1607123958.748393
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -139,13 +155,20 @@ Message published for updates to tracked object metadata, for example:
|
|||||||
"plate": "123ABC",
|
"plate": "123ABC",
|
||||||
"score": 0.95,
|
"score": 0.95,
|
||||||
"camera": "driveway_cam",
|
"camera": "driveway_cam",
|
||||||
"timestamp": 1607123958.748393,
|
"timestamp": 1607123958.748393
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `frigate/reviews`
|
### `frigate/reviews`
|
||||||
|
|
||||||
Message published for each changed review item. The first message is published when the `detection` or `alert` is initiated. When additional objects are detected or when a zone change occurs, it will publish a, `update` message with the same id. When the review activity has ended a final `end` message is published.
|
Message published for each changed review item. The first message is published when the `detection` or `alert` is initiated.
|
||||||
|
|
||||||
|
An `update` with the same ID will be published when:
|
||||||
|
- The severity changes from `detection` to `alert`
|
||||||
|
- Additional objects are detected
|
||||||
|
- An object is recognized via face, lpr, etc.
|
||||||
|
|
||||||
|
When the review activity has ended a final `end` message is published.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Snapshots must be enabled to be able to submit examples to Frigate+
|
|||||||
|
|
||||||
### Annotate and verify
|
### Annotate and verify
|
||||||
|
|
||||||
You can view all of your submitted images at [https://plus.frigate.video](https://plus.frigate.video). Annotations can be added by clicking an image. For more detailed information about labeling, see the documentation on [improving your model](../plus/improving_model.md).
|
You can view all of your submitted images at [https://plus.frigate.video](https://plus.frigate.video). Annotations can be added by clicking an image. For more detailed information about labeling, see the documentation on [annotating](../plus/annotating.md).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ Please use your own knowledge to assess and vet them before you install anything
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## [Advanced Camera Card (formerly known as Frigate Card](https://card.camera/#/README)
|
||||||
|
|
||||||
|
The [Advanced Camera Card](https://card.camera/#/README) is a Home Assistant dashboard card with deep Frigate integration.
|
||||||
|
|
||||||
## [Double Take](https://github.com/skrashevich/double-take)
|
## [Double Take](https://github.com/skrashevich/double-take)
|
||||||
|
|
||||||
[Double Take](https://github.com/skrashevich/double-take) provides an unified UI and API for processing and training images for facial recognition.
|
[Double Take](https://github.com/skrashevich/double-take) provides an unified UI and API for processing and training images for facial recognition.
|
||||||
@@ -23,6 +27,10 @@ This is a fork (with fixed errors and new features) of [original Double Take](ht
|
|||||||
|
|
||||||
[Frigate Notify](https://github.com/0x2142/frigate-notify) is a simple app designed to send notifications from Frigate to your favorite platforms. Intended to be used with standalone Frigate installations - Home Assistant not required, MQTT is optional but recommended.
|
[Frigate Notify](https://github.com/0x2142/frigate-notify) is a simple app designed to send notifications from Frigate to your favorite platforms. Intended to be used with standalone Frigate installations - Home Assistant not required, MQTT is optional but recommended.
|
||||||
|
|
||||||
|
## [Frigate Snap-Sync](https://github.com/thequantumphysicist/frigate-snap-sync/)
|
||||||
|
|
||||||
|
[Frigate Snap-Sync](https://github.com/thequantumphysicist/frigate-snap-sync/) is a program that works in tandem with Frigate. It responds to Frigate when a snapshot or a review is made (and more can be added), and uploads them to one or more remote server(s) of your choice.
|
||||||
|
|
||||||
## [Frigate telegram](https://github.com/OldTyT/frigate-telegram)
|
## [Frigate telegram](https://github.com/OldTyT/frigate-telegram)
|
||||||
|
|
||||||
[Frigate telegram](https://github.com/OldTyT/frigate-telegram) makes it possible to send events from Frigate to Telegram. Events are sent as a message with a text description, video, and thumbnail.
|
[Frigate telegram](https://github.com/OldTyT/frigate-telegram) makes it possible to send events from Frigate to Telegram. Events are sent as a message with a text description, video, and thumbnail.
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
---
|
---
|
||||||
id: improving_model
|
id: annotating
|
||||||
title: Improving your model
|
title: Annotating your images
|
||||||
---
|
---
|
||||||
|
|
||||||
You may find that Frigate+ models result in more false positives initially, but by submitting true and false positives, the model will improve. With all the new images now being submitted by subscribers, future base models will improve as more and more examples are incorporated. Note that only images with at least one verified label will be used when training your model. Submitting an image from Frigate as a true or false positive will not verify the image. You still must verify the image in Frigate+ in order for it to be used in training.
|
For the best results, follow these guidelines. You may also want to review the documentation on [improving your model](./index.md#improving-your-model).
|
||||||
|
|
||||||
- **Submit both true positives and false positives**. This will help the model differentiate between what is and isn't correct. You should aim for a target of 80% true positive submissions and 20% false positives across all of your images. If you are experiencing false positives in a specific area, submitting true positives for any object type near that area in similar lighting conditions will help teach the model what that area looks like when no objects are present.
|
|
||||||
- **Lower your thresholds a little in order to generate more false/true positives near the threshold value**. For example, if you have some false positives that are scoring at 68% and some true positives scoring at 72%, you can try lowering your threshold to 65% and submitting both true and false positives within that range. This will help the model learn and widen the gap between true and false positive scores.
|
|
||||||
- **Submit diverse images**. For the best results, you should provide at least 100 verified images per camera. Keep in mind that varying conditions should be included. You will want images from cloudy days, sunny days, dawn, dusk, and night. As circumstances change, you may need to submit new examples to address new types of false positives. For example, the change from summer days to snowy winter days or other changes such as a new grill or patio furniture may require additional examples and training.
|
|
||||||
|
|
||||||
## Properly labeling images
|
|
||||||
|
|
||||||
For the best results, follow the following guidelines.
|
|
||||||
|
|
||||||
**Label every object in the image**: It is important that you label all objects in each image before verifying. If you don't label a car for example, the model will be taught that part of the image is _not_ a car and it will start to get confused. You can exclude labels that you don't want detected on any of your cameras.
|
**Label every object in the image**: It is important that you label all objects in each image before verifying. If you don't label a car for example, the model will be taught that part of the image is _not_ a car and it will start to get confused. You can exclude labels that you don't want detected on any of your cameras.
|
||||||
|
|
||||||
@@ -25,9 +17,17 @@ For the best results, follow the following guidelines.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## AI suggested labels
|
||||||
|
|
||||||
|
If you have an active Frigate+ subscription, new uploads will be scanned for the objects configured for you camera and you will see suggested labels as light blue boxes when annotating in Frigate+. These suggestions are processed via a queue and typically complete within a minute after uploading, but processing times can be longer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Suggestions are converted to labels when saving, so you should remove any errant suggestions. There is already some logic designed to avoid duplicate labels, but you may still occasionally see some duplicate suggestions. You should keep the most accurate bounding box and delete any duplicates so that you have just one label per object remaining.
|
||||||
|
|
||||||
## False positive labels
|
## False positive labels
|
||||||
|
|
||||||
False positives will be shown with a read box and the label will have a strike through.
|
False positives will be shown with a red box and the label will have a strike through. These can't be adjusted, but they can be deleted if you accidentally submit a true positive as a false positive from Frigate.
|
||||||

|

|
||||||
|
|
||||||
Misidentified objects should have a correct label added. For example, if a person was mistakenly detected as a cat, you should submit it as a false positive in Frigate and add a label for the person. The boxes will overlap.
|
Misidentified objects should have a correct label added. For example, if a person was mistakenly detected as a cat, you should submit it as a false positive in Frigate and add a label for the person. The boxes will overlap.
|
||||||
@@ -42,6 +42,7 @@ Misidentified objects should have a correct label added. For example, if a perso
|
|||||||
| `w` | Add box |
|
| `w` | Add box |
|
||||||
| `d` | Toggle difficult |
|
| `d` | Toggle difficult |
|
||||||
| `s` | Switch to the next label |
|
| `s` | Switch to the next label |
|
||||||
|
| `Shift + s` | Switch to the previous label |
|
||||||
| `tab` | Select next largest box |
|
| `tab` | Select next largest box |
|
||||||
| `del` | Delete current box |
|
| `del` | Delete current box |
|
||||||
| `esc` | Deselect/Cancel |
|
| `esc` | Deselect/Cancel |
|
||||||
@@ -9,11 +9,11 @@ Before requesting your first model, you will need to upload and verify at least
|
|||||||
|
|
||||||
It is recommended to submit **both** true positives and false positives. This will help the model differentiate between what is and isn't correct. You should aim for a target of 80% true positive submissions and 20% false positives across all of your images. If you are experiencing false positives in a specific area, submitting true positives for any object type near that area in similar lighting conditions will help teach the model what that area looks like when no objects are present.
|
It is recommended to submit **both** true positives and false positives. This will help the model differentiate between what is and isn't correct. You should aim for a target of 80% true positive submissions and 20% false positives across all of your images. If you are experiencing false positives in a specific area, submitting true positives for any object type near that area in similar lighting conditions will help teach the model what that area looks like when no objects are present.
|
||||||
|
|
||||||
For more detailed recommendations, you can refer to the docs on [improving your model](./improving_model.md).
|
For more detailed recommendations, you can refer to the docs on [annotating](./annotating.md).
|
||||||
|
|
||||||
## Step 2: Submit a model request
|
## Step 2: Submit a model request
|
||||||
|
|
||||||
Once you have an initial set of verified images, you can request a model on the Models page. For guidance on choosing a model type, refer to [this part of the documentation](./index.md#available-model-types). Each model request requires 1 of the 12 trainings that you receive with your annual subscription. This model will support all [label types available](./index.md#available-label-types) even if you do not submit any examples for those labels. Model creation can take up to 36 hours.
|
Once you have an initial set of verified images, you can request a model on the Models page. For guidance on choosing a model type, refer to [this part of the documentation](./index.md#available-model-types). If you are unsure which type to request, you can test the base model for each version from the "Base Models" tab. Each model request requires 1 of the 12 trainings that you receive with your annual subscription. This model will support all [label types available](./index.md#available-label-types) even if you do not submit any examples for those labels. Model creation can take up to 36 hours.
|
||||||

|

|
||||||
|
|
||||||
## Step 3: Set your model id in the config
|
## Step 3: Set your model id in the config
|
||||||
@@ -34,6 +34,12 @@ Model IDs are not secret values and can be shared freely. Access to your model i
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
When setting the plus model id, all other fields should be removed as these are configured automatically with the Frigate+ model config
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Step 4: Adjust your object filters for higher scores
|
## Step 4: Adjust your object filters for higher scores
|
||||||
|
|
||||||
Frigate+ models generally have much higher scores than the default model provided in Frigate. You will likely need to increase your `threshold` and `min_score` values. Here is an example of how these values can be refined, but you should expect these to evolve as your model improves. For more information about how `threshold` and `min_score` are related, see the docs on [object filters](../configuration/object_filters.md#object-scores).
|
Frigate+ models generally have much higher scores than the default model provided in Frigate. You will likely need to increase your `threshold` and `min_score` values. Here is an example of how these values can be refined, but you should expect these to evolve as your model improves. For more information about how `threshold` and `min_score` are related, see the docs on [object filters](../configuration/object_filters.md#object-scores).
|
||||||
|
|||||||
+59
-30
@@ -3,61 +3,90 @@ id: index
|
|||||||
title: Models
|
title: Models
|
||||||
---
|
---
|
||||||
|
|
||||||
<a href="https://frigate.video/plus" target="_blank" rel="nofollow">Frigate+</a> offers models trained on images submitted by Frigate+ users from their security cameras and is specifically designed for the way Frigate analyzes video footage. These models offer higher accuracy with less resources. The images you upload are used to fine tune a baseline model trained from images uploaded by all Frigate+ users. This fine tuning process results in a model that is optimized for accuracy in your specific conditions.
|
<a href="https://frigate.video/plus" target="_blank" rel="nofollow">Frigate+</a> offers models trained on images submitted by Frigate+ users from their security cameras and is specifically designed for the way Frigate NVR analyzes video footage. These models offer higher accuracy with less resources. The images you upload are used to fine tune a base model trained from images uploaded by all Frigate+ users. This fine tuning process results in a model that is optimized for accuracy in your specific conditions.
|
||||||
|
|
||||||
:::info
|
With a subscription, 12 model trainings to fine tune your model per year are included. In addition, you will have access to any base models published while your subscription is active. If you cancel your subscription, you will retain access to any trained and base models in your account. An active subscription is required to submit model requests or purchase additional trainings. New base models are published quarterly with target dates of January 15th, April 15th, July 15th, and October 15th.
|
||||||
|
|
||||||
The baseline model isn't directly available after subscribing. This may change in the future, but for now you will need to submit a model request with the minimum number of images.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
With a subscription, 12 model trainings per year are included. If you cancel your subscription, you will retain access to any trained models. An active subscription is required to submit model requests or purchase additional trainings.
|
|
||||||
|
|
||||||
Information on how to integrate Frigate+ with Frigate can be found in the [integration docs](../integrations/plus.md).
|
Information on how to integrate Frigate+ with Frigate can be found in the [integration docs](../integrations/plus.md).
|
||||||
|
|
||||||
## Available model types
|
## Available model types
|
||||||
|
|
||||||
There are two model types offered in Frigate+, `mobiledet` and `yolonas`. Both of these models are object detection models and are trained to detect the same set of labels [listed below](#available-label-types).
|
There are three model types offered in Frigate+, `mobiledet`, `yolonas`, and `yolov9`. All of these models are object detection models and are trained to detect the same set of labels [listed below](#available-label-types).
|
||||||
|
|
||||||
Not all model types are supported by all detectors, so it's important to choose a model type to match your detector as shown in the table under [supported detector types](#supported-detector-types).
|
Not all model types are supported by all detectors, so it's important to choose a model type to match your detector as shown in the table under [supported detector types](#supported-detector-types). You can test model types for compatibility and speed on your hardware by using the base models.
|
||||||
|
|
||||||
| Model Type | Description |
|
| Model Type | Description |
|
||||||
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `mobiledet` | Based on the same architecture as the default model included with Frigate. Runs on Google Coral devices and CPUs. |
|
| `mobiledet` | Based on the same architecture as the default model included with Frigate. Runs on Google Coral devices and CPUs. |
|
||||||
| `yolonas` | A newer architecture that offers slightly higher accuracy and improved detection of small objects. Runs on Intel, NVidia GPUs, and AMD GPUs. |
|
| `yolonas` | A newer architecture that offers slightly higher accuracy and improved detection of small objects. Runs on Intel, NVidia GPUs, and AMD GPUs. |
|
||||||
|
| `yolov9` | A leading SOTA (state of the art) object detection model with similar performance to yolonas, but on a wider range of hardware options. Runs on Intel, NVidia GPUs, AMD GPUs, Hailo, MemryX\*, Apple Silicon\*, and Rockchip NPUs. |
|
||||||
|
|
||||||
## Supported detector types
|
_\* Support coming in 0.17_
|
||||||
|
|
||||||
Currently, Frigate+ models support CPU (`cpu`), Google Coral (`edgetpu`), OpenVino (`openvino`), and ONNX (`onnx`) detectors.
|
### YOLOv9 Details
|
||||||
|
|
||||||
:::warning
|
YOLOv9 models are available in `s` and `t` sizes. When requesting a `yolov9` model, you will be prompted to choose a size. If you are unsure what size to choose, you should perform some tests with the base models to find the performance level that suits you. The `s` size is most similar to the current `yolonas` models in terms of inference times and accuracy, and a good place to start is the `320x320` resolution model for `yolov9s`.
|
||||||
|
|
||||||
Using Frigate+ models with `onnx` is only available with Frigate 0.15 and later.
|
:::info
|
||||||
|
|
||||||
|
When switching to YOLOv9, you may need to adjust your thresholds for some objects.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
| Hardware | Recommended Detector Type | Recommended Model Type |
|
#### Hailo Support
|
||||||
| ---------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ---------------------- |
|
|
||||||
| [CPU](/configuration/object_detectors.md#cpu-detector-not-recommended) | `cpu` | `mobiledet` |
|
|
||||||
| [Coral (all form factors)](/configuration/object_detectors.md#edge-tpu-detector) | `edgetpu` | `mobiledet` |
|
|
||||||
| [Intel](/configuration/object_detectors.md#openvino-detector) | `openvino` | `yolonas` |
|
|
||||||
| [NVidia GPU](https://deploy-preview-13787--frigate-docs.netlify.app/configuration/object_detectors#onnx)\* | `onnx` | `yolonas` |
|
|
||||||
| [AMD ROCm GPU](https://deploy-preview-13787--frigate-docs.netlify.app/configuration/object_detectors#amdrocm-gpu-detector)\* | `onnx` | `yolonas` |
|
|
||||||
|
|
||||||
_\* Requires Frigate 0.15_
|
If you have a Hailo device, you will need to specify the hardware you have when submitting a model request because they are not cross compatible. Please test using the available base models before submitting your model request.
|
||||||
|
|
||||||
|
#### Rockchip (RKNN) Support
|
||||||
|
|
||||||
|
For 0.16, YOLOv9 onnx models will need to be manually converted. First, you will need to configure Frigate to use the model id for your YOLOv9 onnx model so it downloads the model to your `model_cache` directory. From there, you can follow the [documentation](/configuration/object_detectors.md#converting-your-own-onnx-model-to-rknn-format) to convert it. Automatic conversion is coming in 0.17.
|
||||||
|
|
||||||
|
## Supported detector types
|
||||||
|
|
||||||
|
Currently, Frigate+ models support CPU (`cpu`), Google Coral (`edgetpu`), OpenVino (`openvino`), ONNX (`onnx`), Hailo (`hailo8l`), and Rockchip\* (`rknn`) detectors.
|
||||||
|
|
||||||
|
| Hardware | Recommended Detector Type | Recommended Model Type |
|
||||||
|
| -------------------------------------------------------------------------------- | ------------------------- | ---------------------- |
|
||||||
|
| [CPU](/configuration/object_detectors.md#cpu-detector-not-recommended) | `cpu` | `mobiledet` |
|
||||||
|
| [Coral (all form factors)](/configuration/object_detectors.md#edge-tpu-detector) | `edgetpu` | `mobiledet` |
|
||||||
|
| [Intel](/configuration/object_detectors.md#openvino-detector) | `openvino` | `yolov9` |
|
||||||
|
| [NVidia GPU](/configuration/object_detectors#onnx) | `onnx` | `yolov9` |
|
||||||
|
| [AMD ROCm GPU](/configuration/object_detectors#amdrocm-gpu-detector) | `onnx` | `yolov9` |
|
||||||
|
| [Hailo8/Hailo8L/Hailo8R](/configuration/object_detectors#hailo-8) | `hailo8l` | `yolov9` |
|
||||||
|
| [Rockchip NPU](/configuration/object_detectors#rockchip-platform)\* | `rknn` | `yolov9` |
|
||||||
|
|
||||||
|
_\* Requires manual conversion in 0.16. Automatic conversion coming in 0.17._
|
||||||
|
|
||||||
|
## Improving your model
|
||||||
|
|
||||||
|
Some users may find that Frigate+ models result in more false positives initially, but by submitting true and false positives, the model will improve. With all the new images now being submitted by subscribers, future base models will improve as more and more examples are incorporated. Note that only images with at least one verified label will be used when training your model. Submitting an image from Frigate as a true or false positive will not verify the image. You still must verify the image in Frigate+ in order for it to be used in training.
|
||||||
|
|
||||||
|
- **Submit both true positives and false positives**. This will help the model differentiate between what is and isn't correct. You should aim for a target of 80% true positive submissions and 20% false positives across all of your images. If you are experiencing false positives in a specific area, submitting true positives for any object type near that area in similar lighting conditions will help teach the model what that area looks like when no objects are present.
|
||||||
|
- **Lower your thresholds a little in order to generate more false/true positives near the threshold value**. For example, if you have some false positives that are scoring at 68% and some true positives scoring at 72%, you can try lowering your threshold to 65% and submitting both true and false positives within that range. This will help the model learn and widen the gap between true and false positive scores.
|
||||||
|
- **Submit diverse images**. For the best results, you should provide at least 100 verified images per camera. Keep in mind that varying conditions should be included. You will want images from cloudy days, sunny days, dawn, dusk, and night. As circumstances change, you may need to submit new examples to address new types of false positives. For example, the change from summer days to snowy winter days or other changes such as a new grill or patio furniture may require additional examples and training.
|
||||||
|
|
||||||
## Available label types
|
## Available label types
|
||||||
|
|
||||||
Frigate+ models support a more relevant set of objects for security cameras. Currently, the following objects are supported:
|
Frigate+ models support a more relevant set of objects for security cameras. The labels for annotation in Frigate+ are configurable by editing the camera in the Cameras section of Frigate+. Currently, the following objects are supported:
|
||||||
|
|
||||||
- **People**: `person`, `face`
|
- **People**: `person`, `face`
|
||||||
- **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `license_plate`
|
- **Vehicles**: `car`, `motorcycle`, `bicycle`, `boat`, `school_bus`, `license_plate`
|
||||||
- **Delivery Logos**: `amazon`, `usps`, `ups`, `fedex`, `dhl`, `an_post`, `purolator`, `postnl`, `nzpost`, `postnord`, `gls`, `dpd`
|
- **Delivery Logos**: `amazon`, `usps`, `ups`, `fedex`, `dhl`, `an_post`, `purolator`, `postnl`, `nzpost`, `postnord`, `gls`, `dpd`, `canada_post`, `royal_mail`
|
||||||
- **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`
|
- **Animals**: `dog`, `cat`, `deer`, `horse`, `bird`, `raccoon`, `fox`, `bear`, `cow`, `squirrel`, `goat`, `rabbit`, `skunk`, `kangaroo`
|
||||||
- **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`
|
- **Other**: `package`, `waste_bin`, `bbq_grill`, `robot_lawnmower`, `umbrella`
|
||||||
|
|
||||||
Other object types available in the default Frigate model are not available. Additional object types will be added in future releases.
|
Other object types available in the default Frigate model are not available. Additional object types will be added in future releases.
|
||||||
|
|
||||||
|
### Candidate labels
|
||||||
|
|
||||||
|
Candidate labels are also available for annotation. These labels don't have enough data to be included in the model yet, but using them will help add support sooner. You can enable these labels by editing the camera settings.
|
||||||
|
|
||||||
|
Where possible, these labels are mapped to existing labels during training. For example, any `baby` labels are mapped to `person` until support for new labels is added.
|
||||||
|
|
||||||
|
The candidate labels are: `baby`, `bpost`, `badger`, `possum`, `rodent`, `chicken`, `groundhog`, `boar`, `hedgehog`, `tractor`, `golf cart`, `garbage truck`, `bus`, `sports ball`
|
||||||
|
|
||||||
|
Candidate labels are not available for automatic suggestions.
|
||||||
|
|
||||||
### Label attributes
|
### Label attributes
|
||||||
|
|
||||||
Frigate has special handling for some labels when using Frigate+ models. `face`, `license_plate`, and delivery logos such as `amazon`, `ups`, and `fedex` are considered attribute labels which are not tracked like regular objects and do not generate review items directly. In addition, the `threshold` filter will have no effect on these labels. You should adjust the `min_score` and other filter values as needed.
|
Frigate has special handling for some labels when using Frigate+ models. `face`, `license_plate`, and delivery logos such as `amazon`, `ups`, and `fedex` are considered attribute labels which are not tracked like regular objects and do not generate review items directly. In addition, the `threshold` filter will have no effect on these labels. You should adjust the `min_score` and other filter values as needed.
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ Some users have reported that this older device runs an older kernel causing iss
|
|||||||
6. Open the control panel - info scree. The coral TPU will now be recognised as a USB Device - google inc
|
6. Open the control panel - info scree. The coral TPU will now be recognised as a USB Device - google inc
|
||||||
7. Start the frigate container. Everything should work now!
|
7. Start the frigate container. Everything should work now!
|
||||||
|
|
||||||
|
### QNAP NAS
|
||||||
|
|
||||||
|
QNAP NAS devices, such as the TS-253A, may use connected Coral TPU devices if [QuMagie](https://www.qnap.com/en/software/qumagie) is installed along with its QNAP AI Core extension. If any of the features—`facial recognition`, `object recognition`, or `similar photo recognition`—are enabled, Container Station applications such as `Frigate` or `CodeProject.AI Server` will be unable to initialize the TPU device in use.
|
||||||
|
To allow the Coral TPU device to be discovered, the you must either:
|
||||||
|
|
||||||
|
1. [Disable the AI recognition features in QuMagie](https://docs.qnap.com/application/qumagie/2.x/en-us/configuring-qnap-ai-core-settings-FB13CE03.html),
|
||||||
|
2. Remove the QNAP AI Core extension or
|
||||||
|
3. Manually start the QNAP AI Core extension after Frigate has fully started (not recommended).
|
||||||
|
|
||||||
|
It is also recommended to restart the NAS once the changes have been made.
|
||||||
|
|
||||||
## USB Coral Detection Appears to be Stuck
|
## USB Coral Detection Appears to be Stuck
|
||||||
|
|
||||||
The USB Coral can become stuck and need to be restarted, this can happen for a number of reasons depending on hardware and software setup. Some common reasons are:
|
The USB Coral can become stuck and need to be restarted, this can happen for a number of reasons depending on hardware and software setup. Some common reasons are:
|
||||||
@@ -55,10 +66,10 @@ The USB Coral can become stuck and need to be restarted, this can happen for a n
|
|||||||
|
|
||||||
## PCIe Coral Not Detected
|
## PCIe Coral Not Detected
|
||||||
|
|
||||||
The most common reason for the PCIe Coral not being detected is that the driver has not been installed. This process varies based on what OS and kernel that is being run.
|
The most common reason for the PCIe Coral not being detected is that the driver has not been installed. This process varies based on what OS and kernel that is being run.
|
||||||
|
|
||||||
- In most cases [the Coral docs](https://coral.ai/docs/m2/get-started/#2-install-the-pcie-driver-and-edge-tpu-runtime) show how to install the driver for the PCIe based Coral.
|
- In most cases [the Coral docs](https://coral.ai/docs/m2/get-started/#2-install-the-pcie-driver-and-edge-tpu-runtime) show how to install the driver for the PCIe based Coral.
|
||||||
- For Ubuntu 22.04+ https://github.com/jnicolson/gasket-builder can be used to build and install the latest version of the driver.
|
- For some newer Linux distros (for example, Ubuntu 22.04+), https://github.com/jnicolson/gasket-builder can be used to build and install the latest version of the driver.
|
||||||
|
|
||||||
## Attempting to load TPU as pci & Fatal Python error: Illegal instruction
|
## Attempting to load TPU as pci & Fatal Python error: Illegal instruction
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
id: gpu
|
||||||
|
title: Troubleshooting GPU
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenVINO
|
||||||
|
|
||||||
|
### Can't get OPTIMIZATION_CAPABILITIES property as no supported devices found.
|
||||||
|
|
||||||
|
Some users have reported issues using some Intel iGPUs with OpenVINO, where the GPU would not be detected. This error can be caused by various problems, so it is important to ensure the configuration is setup correctly. Some solutions users have noted:
|
||||||
|
|
||||||
|
- In some cases users have noted that an HDMI dummy plug was necessary to be plugged into the motherboard's HDMI port.
|
||||||
|
- When mixing an Intel iGPU with Nvidia GPU, the devices can be mixed up between `/dev/dri/renderD128` and `/dev/dri/renderD129` so it is important to confirm the correct device, or map the entire `/dev/dri` directory into the Frigate container.
|
||||||
+112
-66
@@ -1,19 +1,24 @@
|
|||||||
import type * as Preset from '@docusaurus/preset-classic';
|
import type * as Preset from "@docusaurus/preset-classic";
|
||||||
import * as path from 'node:path';
|
import * as path from "node:path";
|
||||||
import type { Config, PluginConfig } from '@docusaurus/types';
|
import type { Config, PluginConfig } from "@docusaurus/types";
|
||||||
import type * as OpenApiPlugin from 'docusaurus-plugin-openapi-docs';
|
import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
|
||||||
|
|
||||||
const config: Config = {
|
const config: Config = {
|
||||||
title: 'Frigate',
|
title: "Frigate",
|
||||||
tagline: 'NVR With Realtime Object Detection for IP Cameras',
|
tagline: "NVR With Realtime Object Detection for IP Cameras",
|
||||||
url: 'https://docs.frigate.video',
|
url: "https://docs.frigate.video",
|
||||||
baseUrl: '/',
|
baseUrl: "/",
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: "throw",
|
||||||
onBrokenMarkdownLinks: 'warn',
|
onBrokenMarkdownLinks: "warn",
|
||||||
favicon: 'img/favicon.ico',
|
favicon: "img/favicon.ico",
|
||||||
organizationName: 'blakeblackshear',
|
organizationName: "blakeblackshear",
|
||||||
projectName: 'frigate',
|
projectName: "frigate",
|
||||||
themes: ['@docusaurus/theme-mermaid', 'docusaurus-theme-openapi-docs'],
|
themes: [
|
||||||
|
"@docusaurus/theme-mermaid",
|
||||||
|
"docusaurus-theme-openapi-docs",
|
||||||
|
"@inkeep/docusaurus/chatButton",
|
||||||
|
"@inkeep/docusaurus/searchBar",
|
||||||
|
],
|
||||||
markdown: {
|
markdown: {
|
||||||
mermaid: true,
|
mermaid: true,
|
||||||
},
|
},
|
||||||
@@ -27,39 +32,79 @@ const config: Config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
algolia: {
|
announcementBar: {
|
||||||
appId: 'WIURGBNBPY',
|
id: 'frigate_plus',
|
||||||
apiKey: 'd02cc0a6a61178b25da550212925226b',
|
content: `
|
||||||
indexName: 'frigate',
|
<span style="margin-right: 8px; display: inline-block; animation: pulse 2s infinite;">🚀</span>
|
||||||
|
Get more relevant and accurate detections with Frigate+ models.
|
||||||
|
<a style="margin-left: 12px; padding: 3px 10px; background: #94d2bd; color: #001219; text-decoration: none; border-radius: 4px; font-weight: 500; " target="_blank" rel="noopener noreferrer" href="https://frigate.video/plus/">Learn more</a>
|
||||||
|
<span style="margin-left: 8px; display: inline-block; animation: pulse 2s infinite;">✨</span>
|
||||||
|
<style>
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
</style>`,
|
||||||
|
backgroundColor: '#005f73',
|
||||||
|
textColor: '#e0fbfc',
|
||||||
|
isCloseable: false,
|
||||||
},
|
},
|
||||||
docs: {
|
docs: {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
hideable: true,
|
hideable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
inkeepConfig: {
|
||||||
|
baseSettings: {
|
||||||
|
apiKey: "b1a4c4d73c9b48aa5b3cdae6e4c81f0bb3d1134eeb5a7100",
|
||||||
|
integrationId: "cm6xmhn9h000gs601495fkkdx",
|
||||||
|
organizationId: "org_map2JQEOco8U1ZYY",
|
||||||
|
primaryBrandColor: "#010101",
|
||||||
|
},
|
||||||
|
aiChatSettings: {
|
||||||
|
chatSubjectName: "Frigate",
|
||||||
|
botAvatarSrcUrl: "https://frigate.video/images/favicon.png",
|
||||||
|
getHelpCallToActions: [
|
||||||
|
{
|
||||||
|
name: "GitHub",
|
||||||
|
url: "https://github.com/blakeblackshear/frigate",
|
||||||
|
icon: {
|
||||||
|
builtIn: "FaGithub",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
quickQuestions: [
|
||||||
|
"How to configure and setup camera settings?",
|
||||||
|
"How to setup notifications?",
|
||||||
|
"Supported builtin detectors?",
|
||||||
|
"How to restream video feed?",
|
||||||
|
"How can I get sound or audio in my recordings?",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
prism: {
|
prism: {
|
||||||
additionalLanguages: ['bash', 'json'],
|
additionalLanguages: ["bash", "json"],
|
||||||
},
|
},
|
||||||
languageTabs: [
|
languageTabs: [
|
||||||
{
|
{
|
||||||
highlight: 'python',
|
highlight: "python",
|
||||||
language: 'python',
|
language: "python",
|
||||||
logoClass: 'python',
|
logoClass: "python",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'javascript',
|
highlight: "javascript",
|
||||||
language: 'nodejs',
|
language: "nodejs",
|
||||||
logoClass: 'nodejs',
|
logoClass: "nodejs",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'javascript',
|
highlight: "javascript",
|
||||||
language: 'javascript',
|
language: "javascript",
|
||||||
logoClass: 'javascript',
|
logoClass: "javascript",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: 'bash',
|
highlight: "bash",
|
||||||
language: 'curl',
|
language: "curl",
|
||||||
logoClass: 'curl',
|
logoClass: "curl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
highlight: "rust",
|
highlight: "rust",
|
||||||
@@ -68,28 +113,28 @@ const config: Config = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
navbar: {
|
navbar: {
|
||||||
title: 'Frigate',
|
title: "Frigate",
|
||||||
logo: {
|
logo: {
|
||||||
alt: 'Frigate',
|
alt: "Frigate",
|
||||||
src: 'img/logo.svg',
|
src: "img/logo.svg",
|
||||||
srcDark: 'img/logo-dark.svg',
|
srcDark: "img/logo-dark.svg",
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
to: '/',
|
to: "/",
|
||||||
activeBasePath: 'docs',
|
activeBasePath: "docs",
|
||||||
label: 'Docs',
|
label: "Docs",
|
||||||
position: 'left',
|
position: "left",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: 'https://frigate.video',
|
href: "https://frigate.video",
|
||||||
label: 'Website',
|
label: "Website",
|
||||||
position: 'right',
|
position: "right",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: 'http://demo.frigate.video',
|
href: "http://demo.frigate.video",
|
||||||
label: 'Demo',
|
label: "Demo",
|
||||||
position: 'right',
|
position: "right",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'localeDropdown',
|
type: 'localeDropdown',
|
||||||
@@ -109,18 +154,18 @@ const config: Config = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
style: 'dark',
|
style: "dark",
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
title: 'Community',
|
title: "Community",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'GitHub',
|
label: "GitHub",
|
||||||
href: 'https://github.com/blakeblackshear/frigate',
|
href: "https://github.com/blakeblackshear/frigate",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Discussions',
|
label: "Discussions",
|
||||||
href: 'https://github.com/blakeblackshear/frigate/discussions',
|
href: "https://github.com/blakeblackshear/frigate/discussions",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -129,19 +174,19 @@ const config: Config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
path.resolve(__dirname, 'plugins', 'raw-loader'),
|
path.resolve(__dirname, "plugins", "raw-loader"),
|
||||||
[
|
[
|
||||||
'docusaurus-plugin-openapi-docs',
|
"docusaurus-plugin-openapi-docs",
|
||||||
{
|
{
|
||||||
id: 'openapi',
|
id: "openapi",
|
||||||
docsPluginId: 'classic', // configured for preset-classic
|
docsPluginId: "classic", // configured for preset-classic
|
||||||
config: {
|
config: {
|
||||||
frigateApi: {
|
frigateApi: {
|
||||||
specPath: 'static/frigate-api.yaml',
|
specPath: "static/frigate-api.yaml",
|
||||||
outputDir: 'docs/integrations/api',
|
outputDir: "docs/integrations/api",
|
||||||
sidebarOptions: {
|
sidebarOptions: {
|
||||||
groupPathsBy: 'tag',
|
groupPathsBy: "tag",
|
||||||
categoryLinkSource: 'tag',
|
categoryLinkSource: "tag",
|
||||||
sidebarCollapsible: true,
|
sidebarCollapsible: true,
|
||||||
sidebarCollapsed: true,
|
sidebarCollapsed: true,
|
||||||
},
|
},
|
||||||
@@ -149,23 +194,24 @@ const config: Config = {
|
|||||||
} satisfies OpenApiPlugin.Options,
|
} satisfies OpenApiPlugin.Options,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
] as PluginConfig[],
|
] as PluginConfig[],
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
'classic',
|
"classic",
|
||||||
{
|
{
|
||||||
docs: {
|
docs: {
|
||||||
routeBasePath: '/',
|
routeBasePath: "/",
|
||||||
sidebarPath: './sidebars.ts',
|
sidebarPath: "./sidebars.ts",
|
||||||
// Please change this to your repo.
|
// Please change this to your repo.
|
||||||
editUrl: 'https://github.com/blakeblackshear/frigate/edit/master/docs/',
|
editUrl:
|
||||||
|
"https://github.com/blakeblackshear/frigate/edit/master/docs/",
|
||||||
sidebarCollapsible: false,
|
sidebarCollapsible: false,
|
||||||
docItemComponent: '@theme/ApiItem', // Derived from docusaurus-theme-openapi
|
docItemComponent: "@theme/ApiItem", // Derived from docusaurus-theme-openapi
|
||||||
},
|
},
|
||||||
|
|
||||||
theme: {
|
theme: {
|
||||||
customCss: './src/css/custom.css',
|
customCss: "./src/css/custom.css",
|
||||||
},
|
},
|
||||||
} satisfies Preset.Options,
|
} satisfies Preset.Options,
|
||||||
],
|
],
|
||||||
|
|||||||
Generated
+7
@@ -12,6 +12,7 @@
|
|||||||
"@docusaurus/plugin-content-docs": "^3.6.3",
|
"@docusaurus/plugin-content-docs": "^3.6.3",
|
||||||
"@docusaurus/preset-classic": "^3.7.0",
|
"@docusaurus/preset-classic": "^3.7.0",
|
||||||
"@docusaurus/theme-mermaid": "^3.6.3",
|
"@docusaurus/theme-mermaid": "^3.6.3",
|
||||||
|
"@inkeep/docusaurus": "^2.0.16",
|
||||||
"@mdx-js/react": "^3.1.0",
|
"@mdx-js/react": "^3.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"docusaurus-plugin-openapi-docs": "^4.3.1",
|
"docusaurus-plugin-openapi-docs": "^4.3.1",
|
||||||
@@ -3954,6 +3955,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@inkeep/docusaurus": {
|
||||||
|
"version": "2.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@inkeep/docusaurus/-/docusaurus-2.0.16.tgz",
|
||||||
|
"integrity": "sha512-dQhjlvFnl3CVr0gWeJ/V/qLnDy1XYrCfkdVSa2D3gJTxI9/vOf9639Y1aPxTxO88DiXuW9CertLrZLB6SoJ2yg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@docusaurus/plugin-content-docs": "^3.6.3",
|
"@docusaurus/plugin-content-docs": "^3.6.3",
|
||||||
"@docusaurus/preset-classic": "^3.7.0",
|
"@docusaurus/preset-classic": "^3.7.0",
|
||||||
"@docusaurus/theme-mermaid": "^3.6.3",
|
"@docusaurus/theme-mermaid": "^3.6.3",
|
||||||
|
"@inkeep/docusaurus": "^2.0.16",
|
||||||
"@mdx-js/react": "^3.1.0",
|
"@mdx-js/react": "^3.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"docusaurus-plugin-openapi-docs": "^4.3.1",
|
"docusaurus-plugin-openapi-docs": "^4.3.1",
|
||||||
|
|||||||
+14
-11
@@ -5,12 +5,14 @@ import frigateHttpApiSidebar from "./docs/integrations/api/sidebar";
|
|||||||
const sidebars: SidebarsConfig = {
|
const sidebars: SidebarsConfig = {
|
||||||
docs: {
|
docs: {
|
||||||
Frigate: [
|
Frigate: [
|
||||||
"frigate/index",
|
'frigate/index',
|
||||||
"frigate/hardware",
|
'frigate/hardware',
|
||||||
"frigate/installation",
|
'frigate/planning_setup',
|
||||||
"frigate/camera_setup",
|
'frigate/installation',
|
||||||
"frigate/video_pipeline",
|
'frigate/updating',
|
||||||
"frigate/glossary",
|
'frigate/camera_setup',
|
||||||
|
'frigate/video_pipeline',
|
||||||
|
'frigate/glossary',
|
||||||
],
|
],
|
||||||
Guides: [
|
Guides: [
|
||||||
"guides/getting_started",
|
"guides/getting_started",
|
||||||
@@ -91,15 +93,16 @@ const sidebars: SidebarsConfig = {
|
|||||||
"configuration/metrics",
|
"configuration/metrics",
|
||||||
"integrations/third_party_extensions",
|
"integrations/third_party_extensions",
|
||||||
],
|
],
|
||||||
"Frigate+": [
|
'Frigate+': [
|
||||||
"plus/index",
|
'plus/index',
|
||||||
"plus/first_model",
|
'plus/annotating',
|
||||||
"plus/improving_model",
|
'plus/first_model',
|
||||||
"plus/faq",
|
'plus/faq',
|
||||||
],
|
],
|
||||||
Troubleshooting: [
|
Troubleshooting: [
|
||||||
"troubleshooting/faqs",
|
"troubleshooting/faqs",
|
||||||
"troubleshooting/recordings",
|
"troubleshooting/recordings",
|
||||||
|
"troubleshooting/gpu",
|
||||||
"troubleshooting/edgetpu",
|
"troubleshooting/edgetpu",
|
||||||
],
|
],
|
||||||
Development: [
|
Development: [
|
||||||
|
|||||||
Vendored
+176
-62
@@ -105,7 +105,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/users/{username}":
|
/users/{username}:
|
||||||
delete:
|
delete:
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
@@ -130,7 +130,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/users/{username}/password":
|
/users/{username}/password:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
@@ -161,7 +161,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/users/{username}/role":
|
/users/{username}/role:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
@@ -228,7 +228,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/faces/train/{name}/classify":
|
/faces/train/{name}/classify:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -259,7 +259,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/faces/{name}/create":
|
/faces/{name}/create:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -284,7 +284,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/faces/{name}/register":
|
/faces/{name}/register:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -340,7 +340,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/faces/{name}/delete":
|
/faces/{name}/delete:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -371,6 +371,37 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
|
/faces/{old_name}/rename:
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- Events
|
||||||
|
summary: Rename Face
|
||||||
|
operationId: rename_face_faces__old_name__rename_put
|
||||||
|
parameters:
|
||||||
|
- name: old_name
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
title: Old Name
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/RenameFaceBody"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {}
|
||||||
|
"422":
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
/lpr/reprocess:
|
/lpr/reprocess:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
@@ -659,7 +690,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/review/event/{event_id}":
|
/review/event/{event_id}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Review
|
- Review
|
||||||
@@ -685,7 +716,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/review/{review_id}":
|
/review/{review_id}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Review
|
- Review
|
||||||
@@ -711,7 +742,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/review/{review_id}/viewed":
|
/review/{review_id}/viewed:
|
||||||
delete:
|
delete:
|
||||||
tags:
|
tags:
|
||||||
- Review
|
- Review
|
||||||
@@ -774,7 +805,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema: {}
|
schema: {}
|
||||||
"/go2rtc/streams/{camera_name}":
|
/go2rtc/streams/{camera_name}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- App
|
- App
|
||||||
@@ -991,7 +1022,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema: {}
|
schema: {}
|
||||||
"/logs/{service}":
|
/logs/{service}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- App
|
- App
|
||||||
@@ -1287,7 +1318,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/preview/{camera_name}/start/{start_ts}/end/{end_ts}":
|
/preview/{camera_name}/start/{start_ts}/end/{end_ts}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Preview
|
- Preview
|
||||||
@@ -1325,7 +1356,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/preview/{year_month}/{day}/{hour}/{camera_name}/{tz_name}":
|
/preview/{year_month}/{day}/{hour}/{camera_name}/{tz_name}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Preview
|
- Preview
|
||||||
@@ -1376,7 +1407,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/preview/{camera_name}/start/{start_ts}/end/{end_ts}/frames":
|
/preview/{camera_name}/start/{start_ts}/end/{end_ts}/frames:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Preview
|
- Preview
|
||||||
@@ -1463,7 +1494,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema: {}
|
schema: {}
|
||||||
"/export/{camera_name}/start/{start_time}/end/{end_time}":
|
/export/{camera_name}/start/{start_time}/end/{end_time}:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Export
|
- Export
|
||||||
@@ -1507,7 +1538,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/export/{event_id}/rename":
|
/export/{event_id}/rename:
|
||||||
patch:
|
patch:
|
||||||
tags:
|
tags:
|
||||||
- Export
|
- Export
|
||||||
@@ -1538,7 +1569,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/export/{event_id}":
|
/export/{event_id}:
|
||||||
delete:
|
delete:
|
||||||
tags:
|
tags:
|
||||||
- Export
|
- Export
|
||||||
@@ -1563,7 +1594,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/exports/{export_id}":
|
/exports/{export_id}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Export
|
- Export
|
||||||
@@ -1699,7 +1730,7 @@ paths:
|
|||||||
anyOf:
|
anyOf:
|
||||||
- type: string
|
- type: string
|
||||||
- type: "null"
|
- type: "null"
|
||||||
default: "00:00,24:00"
|
default: 00:00,24:00
|
||||||
title: Time Range
|
title: Time Range
|
||||||
- name: has_clip
|
- name: has_clip
|
||||||
in: query
|
in: query
|
||||||
@@ -1728,6 +1759,10 @@ paths:
|
|||||||
- name: include_thumbnails
|
- name: include_thumbnails
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
|
description: >
|
||||||
|
Deprecated. Thumbnail data is no longer included in the response.
|
||||||
|
Use the /api/events/:event_id/thumbnail.:extension endpoint instead.
|
||||||
|
deprecated: true
|
||||||
schema:
|
schema:
|
||||||
anyOf:
|
anyOf:
|
||||||
- type: integer
|
- type: integer
|
||||||
@@ -1942,6 +1977,10 @@ paths:
|
|||||||
- name: include_thumbnails
|
- name: include_thumbnails
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
|
description: >
|
||||||
|
Deprecated. Thumbnail data is no longer included in the response.
|
||||||
|
Use the /api/events/:event_id/thumbnail.:extension endpoint instead.
|
||||||
|
deprecated: true
|
||||||
schema:
|
schema:
|
||||||
anyOf:
|
anyOf:
|
||||||
- type: integer
|
- type: integer
|
||||||
@@ -2007,7 +2046,7 @@ paths:
|
|||||||
anyOf:
|
anyOf:
|
||||||
- type: string
|
- type: string
|
||||||
- type: "null"
|
- type: "null"
|
||||||
default: "00:00,24:00"
|
default: 00:00,24:00
|
||||||
title: Time Range
|
title: Time Range
|
||||||
- name: has_clip
|
- name: has_clip
|
||||||
in: query
|
in: query
|
||||||
@@ -2147,7 +2186,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}":
|
/events/{event_id}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2198,7 +2237,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/retain":
|
/events/{event_id}/retain:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2249,7 +2288,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/plus":
|
/events/{event_id}/plus:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2280,7 +2319,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/false_positive":
|
/events/{event_id}/false_positive:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2306,7 +2345,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/sub_label":
|
/events/{event_id}/sub_label:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2338,7 +2377,39 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/description":
|
/events/{event_id}/recognized_license_plate:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Events
|
||||||
|
summary: Set Plate
|
||||||
|
operationId: set_plate_events__event_id__recognized_license_plate_post
|
||||||
|
parameters:
|
||||||
|
- name: event_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
title: Event Id
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/EventsLPRBody"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/GenericResponse"
|
||||||
|
"422":
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
|
/events/{event_id}/description:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2370,7 +2441,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/description/regenerate":
|
/events/{event_id}/description/regenerate:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2430,7 +2501,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{camera_name}/{label}/create":
|
/events/{camera_name}/{label}/create:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2473,7 +2544,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/end":
|
/events/{event_id}/end:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Events
|
- Events
|
||||||
@@ -2505,7 +2576,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}":
|
/{camera_name}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -2592,7 +2663,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/ptz/info":
|
/{camera_name}/ptz/info:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -2617,7 +2688,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/latest.{extension}":
|
/{camera_name}/latest.{extension}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -2720,7 +2791,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/recordings/{frame_time}/snapshot.{format}":
|
/{camera_name}/recordings/{frame_time}/snapshot.{format}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -2767,7 +2838,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/plus/{frame_time}":
|
/{camera_name}/plus/{frame_time}:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -2846,7 +2917,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/recordings/summary":
|
/{camera_name}/recordings/summary:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -2879,13 +2950,13 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/recordings":
|
/{camera_name}/recordings:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
summary: Recordings
|
summary: Recordings
|
||||||
description: >-
|
description: >-
|
||||||
Return specific camera recordings between the given 'after'/'end' times.
|
Return specific camera recordings between the given "after"/"end" times.
|
||||||
If not provided the last hour will be used
|
If not provided the last hour will be used
|
||||||
operationId: recordings__camera_name__recordings_get
|
operationId: recordings__camera_name__recordings_get
|
||||||
parameters:
|
parameters:
|
||||||
@@ -2900,14 +2971,14 @@ paths:
|
|||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: number
|
type: number
|
||||||
default: 1744227965.180043
|
default: 1752611870.43948
|
||||||
title: After
|
title: After
|
||||||
- name: before
|
- name: before
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: number
|
type: number
|
||||||
default: 1744231565.180048
|
default: 1752615470.43949
|
||||||
title: Before
|
title: Before
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
@@ -2921,13 +2992,14 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/start/{start_ts}/end/{end_ts}/clip.mp4":
|
/{camera_name}/start/{start_ts}/end/{end_ts}/clip.mp4:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
summary: Recording Clip
|
summary: Recording Clip
|
||||||
description: >-
|
description: >-
|
||||||
For iOS devices, use the master.m3u8 HLS link instead of clip.mp4. Safari does not reliably process progressive mp4 files.
|
For iOS devices, use the master.m3u8 HLS link instead of clip.mp4.
|
||||||
|
Safari does not reliably process progressive mp4 files.
|
||||||
operationId: recording_clip__camera_name__start__start_ts__end__end_ts__clip_mp4_get
|
operationId: recording_clip__camera_name__start__start_ts__end__end_ts__clip_mp4_get
|
||||||
parameters:
|
parameters:
|
||||||
- name: camera_name
|
- name: camera_name
|
||||||
@@ -2960,11 +3032,14 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/vod/{camera_name}/start/{start_ts}/end/{end_ts}":
|
/vod/{camera_name}/start/{start_ts}/end/{end_ts}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
summary: Vod Ts
|
summary: Vod Ts
|
||||||
|
description: >-
|
||||||
|
Returns an HLS playlist for the specified timestamp-range on the
|
||||||
|
specified camera. Append /master.m3u8 or /index.m3u8 for HLS playback.
|
||||||
operationId: vod_ts_vod__camera_name__start__start_ts__end__end_ts__get
|
operationId: vod_ts_vod__camera_name__start__start_ts__end__end_ts__get
|
||||||
parameters:
|
parameters:
|
||||||
- name: camera_name
|
- name: camera_name
|
||||||
@@ -2997,12 +3072,14 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/vod/{year_month}/{day}/{hour}/{camera_name}":
|
/vod/{year_month}/{day}/{hour}/{camera_name}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
summary: Vod Hour No Timezone
|
summary: Vod Hour No Timezone
|
||||||
description: VOD for specific hour. Uses the default timezone (UTC).
|
description: >-
|
||||||
|
Returns an HLS playlist for the specified date-time on the specified
|
||||||
|
camera. Append /master.m3u8 or /index.m3u8 for HLS playback.
|
||||||
operationId: vod_hour_no_timezone_vod__year_month___day___hour___camera_name__get
|
operationId: vod_hour_no_timezone_vod__year_month___day___hour___camera_name__get
|
||||||
parameters:
|
parameters:
|
||||||
- name: year_month
|
- name: year_month
|
||||||
@@ -3041,11 +3118,15 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/vod/{year_month}/{day}/{hour}/{camera_name}/{tz_name}":
|
/vod/{year_month}/{day}/{hour}/{camera_name}/{tz_name}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
summary: Vod Hour
|
summary: Vod Hour
|
||||||
|
description: >-
|
||||||
|
Returns an HLS playlist for the specified date-time (with timezone) on
|
||||||
|
the specified camera. Append /master.m3u8 or /index.m3u8 for HLS
|
||||||
|
playback.
|
||||||
operationId: vod_hour_vod__year_month___day___hour___camera_name___tz_name__get
|
operationId: vod_hour_vod__year_month___day___hour___camera_name___tz_name__get
|
||||||
parameters:
|
parameters:
|
||||||
- name: year_month
|
- name: year_month
|
||||||
@@ -3090,11 +3171,14 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/vod/event/{event_id}":
|
/vod/event/{event_id}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
summary: Vod Event
|
summary: Vod Event
|
||||||
|
description: >-
|
||||||
|
Returns an HLS playlist for the specified object. Append /master.m3u8 or
|
||||||
|
/index.m3u8 for HLS playback.
|
||||||
operationId: vod_event_vod_event__event_id__get
|
operationId: vod_event_vod_event__event_id__get
|
||||||
parameters:
|
parameters:
|
||||||
- name: event_id
|
- name: event_id
|
||||||
@@ -3115,11 +3199,15 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/snapshot.jpg":
|
/events/{event_id}/snapshot.jpg:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
summary: Event Snapshot
|
summary: Event Snapshot
|
||||||
|
description: >-
|
||||||
|
Returns a snapshot image for the specified object id. NOTE: The query
|
||||||
|
params only take affect while the event is in-progress. Once the event
|
||||||
|
has ended the snapshot configuration is used.
|
||||||
operationId: event_snapshot_events__event_id__snapshot_jpg_get
|
operationId: event_snapshot_events__event_id__snapshot_jpg_get
|
||||||
parameters:
|
parameters:
|
||||||
- name: event_id
|
- name: event_id
|
||||||
@@ -3190,7 +3278,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/thumbnail.{extension}":
|
/events/{event_id}/thumbnail.{extension}:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3240,7 +3328,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/grid.jpg":
|
/{camera_name}/grid.jpg:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3279,7 +3367,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/snapshot-clean.png":
|
/events/{event_id}/snapshot-clean.png:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3311,7 +3399,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/clip.mp4":
|
/events/{event_id}/clip.mp4:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3336,7 +3424,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/events/{event_id}/preview.gif":
|
/events/{event_id}/preview.gif:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3361,7 +3449,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/start/{start_ts}/end/{end_ts}/preview.gif":
|
/{camera_name}/start/{start_ts}/end/{end_ts}/preview.gif:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3407,7 +3495,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/start/{start_ts}/end/{end_ts}/preview.mp4":
|
/{camera_name}/start/{start_ts}/end/{end_ts}/preview.mp4:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3453,7 +3541,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/review/{event_id}/preview":
|
/review/{event_id}/preview:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3488,7 +3576,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/preview/{file_name}/thumbnail.webp":
|
/preview/{file_name}/thumbnail.webp:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3514,7 +3602,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/preview/{file_name}/thumbnail.jpg":
|
/preview/{file_name}/thumbnail.jpg:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3540,7 +3628,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/{label}/thumbnail.jpg":
|
/{camera_name}/{label}/thumbnail.jpg:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3571,7 +3659,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/{label}/best.jpg":
|
/{camera_name}/{label}/best.jpg:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3602,7 +3690,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/{label}/clip.mp4":
|
/{camera_name}/{label}/clip.mp4:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3633,7 +3721,7 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/HTTPValidationError"
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
"/{camera_name}/{label}/snapshot.jpg":
|
/{camera_name}/{label}/snapshot.jpg:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Media
|
- Media
|
||||||
@@ -3985,6 +4073,23 @@ components:
|
|||||||
title: End Time
|
title: End Time
|
||||||
type: object
|
type: object
|
||||||
title: EventsEndBody
|
title: EventsEndBody
|
||||||
|
EventsLPRBody:
|
||||||
|
properties:
|
||||||
|
recognizedLicensePlate:
|
||||||
|
type: string
|
||||||
|
maxLength: 100
|
||||||
|
title: Recognized License Plate
|
||||||
|
recognizedLicensePlateScore:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
maximum: 1
|
||||||
|
exclusiveMinimum: 0
|
||||||
|
- type: "null"
|
||||||
|
title: Score for recognized license plate
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- recognizedLicensePlate
|
||||||
|
title: EventsLPRBody
|
||||||
EventsSubLabelBody:
|
EventsSubLabelBody:
|
||||||
properties:
|
properties:
|
||||||
subLabel:
|
subLabel:
|
||||||
@@ -4105,6 +4210,15 @@ components:
|
|||||||
- thumbnails
|
- thumbnails
|
||||||
- snapshot
|
- snapshot
|
||||||
title: RegenerateDescriptionEnum
|
title: RegenerateDescriptionEnum
|
||||||
|
RenameFaceBody:
|
||||||
|
properties:
|
||||||
|
new_name:
|
||||||
|
type: string
|
||||||
|
title: New Name
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- new_name
|
||||||
|
title: RenameFaceBody
|
||||||
ReviewActivityMotionResponse:
|
ReviewActivityMotionResponse:
|
||||||
properties:
|
properties:
|
||||||
start_time:
|
start_time:
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
+9
-10
@@ -20,7 +20,7 @@ from fastapi.encoders import jsonable_encoder
|
|||||||
from fastapi.params import Depends
|
from fastapi.params import Depends
|
||||||
from fastapi.responses import JSONResponse, PlainTextResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, PlainTextResponse, StreamingResponse
|
||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
from peewee import operator
|
from peewee import SQL, operator
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from frigate.api.auth import require_role
|
from frigate.api.auth import require_role
|
||||||
@@ -685,7 +685,14 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
|
|||||||
@router.get("/recognized_license_plates")
|
@router.get("/recognized_license_plates")
|
||||||
def get_recognized_license_plates(split_joined: Optional[int] = None):
|
def get_recognized_license_plates(split_joined: Optional[int] = None):
|
||||||
try:
|
try:
|
||||||
events = Event.select(Event.data).distinct()
|
query = (
|
||||||
|
Event.select(
|
||||||
|
SQL("json_extract(data, '$.recognized_license_plate') AS plate")
|
||||||
|
)
|
||||||
|
.where(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
recognized_license_plates = [row[0] for row in query.tuples()]
|
||||||
except Exception:
|
except Exception:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=(
|
content=(
|
||||||
@@ -694,14 +701,6 @@ def get_recognized_license_plates(split_joined: Optional[int] = None):
|
|||||||
status_code=404,
|
status_code=404,
|
||||||
)
|
)
|
||||||
|
|
||||||
recognized_license_plates = []
|
|
||||||
for e in events:
|
|
||||||
if e.data is not None and "recognized_license_plate" in e.data:
|
|
||||||
recognized_license_plates.append(e.data["recognized_license_plate"])
|
|
||||||
|
|
||||||
while None in recognized_license_plates:
|
|
||||||
recognized_license_plates.remove(None)
|
|
||||||
|
|
||||||
if split_joined:
|
if split_joined:
|
||||||
original_recognized_license_plates = recognized_license_plates.copy()
|
original_recognized_license_plates = recognized_license_plates.copy()
|
||||||
for recognized_license_plate in original_recognized_license_plates:
|
for recognized_license_plate in original_recognized_license_plates:
|
||||||
|
|||||||
+12
-10
@@ -33,6 +33,7 @@ from frigate.models import User
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter(tags=[Tags.auth])
|
router = APIRouter(tags=[Tags.auth])
|
||||||
|
VALID_ROLES = ["admin", "viewer"]
|
||||||
|
|
||||||
|
|
||||||
class RateLimiter:
|
class RateLimiter:
|
||||||
@@ -70,7 +71,7 @@ def get_remote_addr(request: Request):
|
|||||||
)
|
)
|
||||||
if trusted_proxy.version == 4:
|
if trusted_proxy.version == 4:
|
||||||
ipv4 = ip.ipv4_mapped if ip.version == 6 else ip
|
ipv4 = ip.ipv4_mapped if ip.version == 6 else ip
|
||||||
if ipv4 in trusted_proxy:
|
if ipv4 is not None and ipv4 in trusted_proxy:
|
||||||
trusted = True
|
trusted = True
|
||||||
logger.debug(f"Trusted: {str(ip)} by {str(trusted_proxy)}")
|
logger.debug(f"Trusted: {str(ip)} by {str(trusted_proxy)}")
|
||||||
break
|
break
|
||||||
@@ -272,12 +273,13 @@ def auth(request: Request):
|
|||||||
else proxy_config.default_role
|
else proxy_config.default_role
|
||||||
)
|
)
|
||||||
|
|
||||||
# if comma-separated with "admin", use "admin", else use default role
|
# if comma-separated with "admin", use "admin",
|
||||||
success_response.headers["remote-role"] = (
|
# if comma-separated with "viewer", use "viewer",
|
||||||
"admin"
|
# else use default role
|
||||||
if role
|
|
||||||
and "admin" in [r.strip() for r in role.split(proxy_config.separator)]
|
roles = [r.strip() for r in role.split(proxy_config.separator)] if role else []
|
||||||
else proxy_config.default_role
|
success_response.headers["remote-role"] = next(
|
||||||
|
(r for r in VALID_ROLES if r in roles), proxy_config.default_role
|
||||||
)
|
)
|
||||||
|
|
||||||
return success_response
|
return success_response
|
||||||
@@ -402,7 +404,7 @@ def login(request: Request, body: AppPostLoginBody):
|
|||||||
password_hash = db_user.password_hash
|
password_hash = db_user.password_hash
|
||||||
if verify_password(password, password_hash):
|
if verify_password(password, password_hash):
|
||||||
role = getattr(db_user, "role", "viewer")
|
role = getattr(db_user, "role", "viewer")
|
||||||
if role not in ["admin", "viewer"]:
|
if role not in VALID_ROLES:
|
||||||
role = "viewer" # Enforce valid roles
|
role = "viewer" # Enforce valid roles
|
||||||
expiration = int(time.time()) + JWT_SESSION_LENGTH
|
expiration = int(time.time()) + JWT_SESSION_LENGTH
|
||||||
encoded_jwt = create_encoded_jwt(user, role, expiration, request.app.jwt_token)
|
encoded_jwt = create_encoded_jwt(user, role, expiration, request.app.jwt_token)
|
||||||
@@ -432,7 +434,7 @@ def create_user(
|
|||||||
if not re.match("^[A-Za-z0-9._]+$", body.username):
|
if not re.match("^[A-Za-z0-9._]+$", body.username):
|
||||||
return JSONResponse(content={"message": "Invalid username"}, status_code=400)
|
return JSONResponse(content={"message": "Invalid username"}, status_code=400)
|
||||||
|
|
||||||
role = body.role if body.role in ["admin", "viewer"] else "viewer"
|
role = body.role if body.role in VALID_ROLES else "viewer"
|
||||||
password_hash = hash_password(body.password, iterations=HASH_ITERATIONS)
|
password_hash = hash_password(body.password, iterations=HASH_ITERATIONS)
|
||||||
User.insert(
|
User.insert(
|
||||||
{
|
{
|
||||||
@@ -503,7 +505,7 @@ async def update_role(
|
|||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={"message": "Cannot modify admin user's role"}, status_code=403
|
content={"message": "Cannot modify admin user's role"}, status_code=403
|
||||||
)
|
)
|
||||||
if body.role not in ["admin", "viewer"]:
|
if body.role not in VALID_ROLES:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={"message": "Role must be 'admin' or 'viewer'"}, status_code=400
|
content={"message": "Role must be 'admin' or 'viewer'"}, status_code=400
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -154,7 +154,25 @@ def train_face(request: Request, name: str, body: dict = None):
|
|||||||
x2 = x1 + int(face_box[2] * detect_config.width) - 4
|
x2 = x1 + int(face_box[2] * detect_config.width) - 4
|
||||||
y2 = y1 + int(face_box[3] * detect_config.height) - 4
|
y2 = y1 + int(face_box[3] * detect_config.height) - 4
|
||||||
face = snapshot[y1:y2, x1:x2]
|
face = snapshot[y1:y2, x1:x2]
|
||||||
cv2.imwrite(os.path.join(new_file_folder, new_name), face)
|
success = True
|
||||||
|
|
||||||
|
if face.size > 0:
|
||||||
|
try:
|
||||||
|
cv2.imwrite(os.path.join(new_file_folder, new_name), face)
|
||||||
|
success = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
return JSONResponse(
|
||||||
|
content=(
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"message": "Invalid face box or no face exists",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
|
||||||
context: EmbeddingsContext = request.app.embeddings
|
context: EmbeddingsContext = request.app.embeddings
|
||||||
context.clear_face_classifier()
|
context.clear_face_classifier()
|
||||||
@@ -196,7 +214,7 @@ async def register_face(request: Request, name: str, file: UploadFile):
|
|||||||
)
|
)
|
||||||
|
|
||||||
context: EmbeddingsContext = request.app.embeddings
|
context: EmbeddingsContext = request.app.embeddings
|
||||||
result = context.register_face(name, await file.read())
|
result = None if context is None else context.register_face(name, await file.read())
|
||||||
|
|
||||||
if not isinstance(result, dict):
|
if not isinstance(result, dict):
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
DEFAULT_TIME_RANGE = "00:00,24:00"
|
DEFAULT_TIME_RANGE = "00:00,24:00"
|
||||||
|
|
||||||
@@ -21,7 +21,14 @@ class EventsQueryParams(BaseModel):
|
|||||||
has_clip: Optional[int] = None
|
has_clip: Optional[int] = None
|
||||||
has_snapshot: Optional[int] = None
|
has_snapshot: Optional[int] = None
|
||||||
in_progress: Optional[int] = None
|
in_progress: Optional[int] = None
|
||||||
include_thumbnails: Optional[int] = 1
|
include_thumbnails: Optional[int] = Field(
|
||||||
|
1,
|
||||||
|
description=(
|
||||||
|
"Deprecated. Thumbnail data is no longer included in the response. "
|
||||||
|
"Use the /api/events/:event_id/thumbnail.:extension endpoint instead."
|
||||||
|
),
|
||||||
|
deprecated=True,
|
||||||
|
)
|
||||||
favorites: Optional[int] = None
|
favorites: Optional[int] = None
|
||||||
min_score: Optional[float] = None
|
min_score: Optional[float] = None
|
||||||
max_score: Optional[float] = None
|
max_score: Optional[float] = None
|
||||||
@@ -40,7 +47,14 @@ class EventsSearchQueryParams(BaseModel):
|
|||||||
query: Optional[str] = None
|
query: Optional[str] = None
|
||||||
event_id: Optional[str] = None
|
event_id: Optional[str] = None
|
||||||
search_type: Optional[str] = "thumbnail"
|
search_type: Optional[str] = "thumbnail"
|
||||||
include_thumbnails: Optional[int] = 1
|
include_thumbnails: Optional[int] = Field(
|
||||||
|
1,
|
||||||
|
description=(
|
||||||
|
"Deprecated. Thumbnail data is no longer included in the response. "
|
||||||
|
"Use the /api/events/:event_id/thumbnail.:extension endpoint instead."
|
||||||
|
),
|
||||||
|
deprecated=True,
|
||||||
|
)
|
||||||
limit: Optional[int] = 50
|
limit: Optional[int] = 50
|
||||||
cameras: Optional[str] = "all"
|
cameras: Optional[str] = "all"
|
||||||
labels: Optional[str] = "all"
|
labels: Optional[str] = "all"
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ class Extension(str, Enum):
|
|||||||
jpg = "jpg"
|
jpg = "jpg"
|
||||||
jpeg = "jpeg"
|
jpeg = "jpeg"
|
||||||
|
|
||||||
|
def get_mime_type(self) -> str:
|
||||||
|
if self in (Extension.jpg, Extension.jpeg):
|
||||||
|
return "image/jpeg"
|
||||||
|
return f"image/{self.value}"
|
||||||
|
|
||||||
|
|
||||||
class MediaLatestFrameQueryParams(BaseModel):
|
class MediaLatestFrameQueryParams(BaseModel):
|
||||||
bbox: Optional[int] = None
|
bbox: Optional[int] = None
|
||||||
|
|||||||
+15
-6
@@ -724,15 +724,24 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
|
|
||||||
if (sort is None or sort == "relevance") and search_results:
|
if (sort is None or sort == "relevance") and search_results:
|
||||||
processed_events.sort(key=lambda x: x.get("search_distance", float("inf")))
|
processed_events.sort(key=lambda x: x.get("search_distance", float("inf")))
|
||||||
elif min_score is not None and max_score is not None and sort == "score_asc":
|
elif sort == "score_asc":
|
||||||
processed_events.sort(key=lambda x: x["data"]["score"])
|
processed_events.sort(key=lambda x: x["data"]["score"])
|
||||||
elif min_score is not None and max_score is not None and sort == "score_desc":
|
elif sort == "score_desc":
|
||||||
processed_events.sort(key=lambda x: x["data"]["score"], reverse=True)
|
processed_events.sort(key=lambda x: x["data"]["score"], reverse=True)
|
||||||
elif min_speed is not None and max_speed is not None and sort == "speed_asc":
|
elif sort == "speed_asc":
|
||||||
processed_events.sort(key=lambda x: x["data"]["average_estimated_speed"])
|
|
||||||
elif min_speed is not None and max_speed is not None and sort == "speed_desc":
|
|
||||||
processed_events.sort(
|
processed_events.sort(
|
||||||
key=lambda x: x["data"]["average_estimated_speed"], reverse=True
|
key=lambda x: (
|
||||||
|
x["data"].get("average_estimated_speed") is None,
|
||||||
|
x["data"].get("average_estimated_speed"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif sort == "speed_desc":
|
||||||
|
processed_events.sort(
|
||||||
|
key=lambda x: (
|
||||||
|
x["data"].get("average_estimated_speed") is None,
|
||||||
|
x["data"].get("average_estimated_speed", float("-inf")),
|
||||||
|
),
|
||||||
|
reverse=True,
|
||||||
)
|
)
|
||||||
elif sort == "date_asc":
|
elif sort == "date_asc":
|
||||||
processed_events.sort(key=lambda x: x["start_time"])
|
processed_events.sort(key=lambda x: x["start_time"])
|
||||||
|
|||||||
+10
-2
@@ -8,6 +8,7 @@ from pathlib import Path
|
|||||||
import psutil
|
import psutil
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
from pathvalidate import sanitize_filepath
|
||||||
from peewee import DoesNotExist
|
from peewee import DoesNotExist
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ from frigate.api.auth import require_role
|
|||||||
from frigate.api.defs.request.export_recordings_body import ExportRecordingsBody
|
from frigate.api.defs.request.export_recordings_body import ExportRecordingsBody
|
||||||
from frigate.api.defs.request.export_rename_body import ExportRenameBody
|
from frigate.api.defs.request.export_rename_body import ExportRenameBody
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.const import EXPORT_DIR
|
from frigate.const import CLIPS_DIR, EXPORT_DIR
|
||||||
from frigate.models import Export, Previews, Recordings
|
from frigate.models import Export, Previews, Recordings
|
||||||
from frigate.record.export import (
|
from frigate.record.export import (
|
||||||
PlaybackFactorEnum,
|
PlaybackFactorEnum,
|
||||||
@@ -54,7 +55,14 @@ def export_recording(
|
|||||||
playback_factor = body.playback
|
playback_factor = body.playback
|
||||||
playback_source = body.source
|
playback_source = body.source
|
||||||
friendly_name = body.name
|
friendly_name = body.name
|
||||||
existing_image = body.image_path
|
existing_image = sanitize_filepath(body.image_path) if body.image_path else None
|
||||||
|
|
||||||
|
# Ensure that existing_image is a valid path
|
||||||
|
if existing_image and not existing_image.startswith(CLIPS_DIR):
|
||||||
|
return JSONResponse(
|
||||||
|
content=({"success": False, "message": "Invalid image path"}),
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
if playback_source == "recordings":
|
if playback_source == "recordings":
|
||||||
recordings_count = (
|
recordings_count = (
|
||||||
|
|||||||
+55
-52
@@ -142,15 +142,13 @@ def latest_frame(
|
|||||||
"regions": params.regions,
|
"regions": params.regions,
|
||||||
}
|
}
|
||||||
quality = params.quality
|
quality = params.quality
|
||||||
mime_type = extension
|
|
||||||
|
|
||||||
if extension == "png":
|
if extension == Extension.png:
|
||||||
quality_params = None
|
quality_params = None
|
||||||
elif extension == "webp":
|
elif extension == Extension.webp:
|
||||||
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), quality]
|
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), quality]
|
||||||
else:
|
else: # jpg or jpeg
|
||||||
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
|
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
|
||||||
mime_type = "jpeg"
|
|
||||||
|
|
||||||
if camera_name in request.app.frigate_config.cameras:
|
if camera_name in request.app.frigate_config.cameras:
|
||||||
frame = frame_processor.get_current_frame(camera_name, draw_options)
|
frame = frame_processor.get_current_frame(camera_name, draw_options)
|
||||||
@@ -193,18 +191,21 @@ def latest_frame(
|
|||||||
|
|
||||||
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
||||||
|
|
||||||
_, img = cv2.imencode(f".{extension}", frame, quality_params)
|
_, img = cv2.imencode(f".{extension.value}", frame, quality_params)
|
||||||
return Response(
|
return Response(
|
||||||
content=img.tobytes(),
|
content=img.tobytes(),
|
||||||
media_type=f"image/{mime_type}",
|
media_type=extension.get_mime_type(),
|
||||||
headers={
|
headers={
|
||||||
"Content-Type": f"image/{mime_type}",
|
|
||||||
"Cache-Control": "no-store"
|
"Cache-Control": "no-store"
|
||||||
if not params.store
|
if not params.store
|
||||||
else "private, max-age=60",
|
else "private, max-age=60",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
elif camera_name == "birdseye" and request.app.frigate_config.birdseye.restream:
|
elif (
|
||||||
|
camera_name == "birdseye"
|
||||||
|
and request.app.frigate_config.birdseye.enabled
|
||||||
|
and request.app.frigate_config.birdseye.restream
|
||||||
|
):
|
||||||
frame = cv2.cvtColor(
|
frame = cv2.cvtColor(
|
||||||
frame_processor.get_current_frame(camera_name),
|
frame_processor.get_current_frame(camera_name),
|
||||||
cv2.COLOR_YUV2BGR_I420,
|
cv2.COLOR_YUV2BGR_I420,
|
||||||
@@ -215,12 +216,11 @@ def latest_frame(
|
|||||||
|
|
||||||
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
frame = cv2.resize(frame, dsize=(width, height), interpolation=cv2.INTER_AREA)
|
||||||
|
|
||||||
_, img = cv2.imencode(f".{extension}", frame, quality_params)
|
_, img = cv2.imencode(f".{extension.value}", frame, quality_params)
|
||||||
return Response(
|
return Response(
|
||||||
content=img.tobytes(),
|
content=img.tobytes(),
|
||||||
media_type=f"image/{mime_type}",
|
media_type=extension.get_mime_type(),
|
||||||
headers={
|
headers={
|
||||||
"Content-Type": f"image/{mime_type}",
|
|
||||||
"Cache-Control": "no-store"
|
"Cache-Control": "no-store"
|
||||||
if not params.store
|
if not params.store
|
||||||
else "private, max-age=60",
|
else "private, max-age=60",
|
||||||
@@ -598,7 +598,7 @@ def recording_clip(
|
|||||||
if clip.start_time < start_ts:
|
if clip.start_time < start_ts:
|
||||||
file.write(f"inpoint {int(start_ts - clip.start_time)}\n")
|
file.write(f"inpoint {int(start_ts - clip.start_time)}\n")
|
||||||
|
|
||||||
# if this is the ending clip and end trim is enabled, add an outpoint
|
# if this is the ending clip, add an outpoint
|
||||||
if clip.end_time > end_ts:
|
if clip.end_time > end_ts:
|
||||||
file.write(f"outpoint {int(end_ts - clip.start_time)}\n")
|
file.write(f"outpoint {int(end_ts - clip.start_time)}\n")
|
||||||
|
|
||||||
@@ -749,7 +749,10 @@ def vod_hour(year_month: str, day: int, hour: int, camera_name: str, tz_name: st
|
|||||||
"/vod/event/{event_id}",
|
"/vod/event/{event_id}",
|
||||||
description="Returns an HLS playlist for the specified object. Append /master.m3u8 or /index.m3u8 for HLS playback.",
|
description="Returns an HLS playlist for the specified object. Append /master.m3u8 or /index.m3u8 for HLS playback.",
|
||||||
)
|
)
|
||||||
def vod_event(event_id: str):
|
def vod_event(
|
||||||
|
event_id: str,
|
||||||
|
padding: int = Query(0, description="Padding to apply to the vod."),
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
@@ -772,35 +775,29 @@ def vod_event(event_id: str):
|
|||||||
status_code=404,
|
status_code=404,
|
||||||
)
|
)
|
||||||
|
|
||||||
clip_path = os.path.join(CLIPS_DIR, f"{event.camera}-{event.id}.mp4")
|
end_ts = (
|
||||||
|
datetime.now().timestamp()
|
||||||
if not os.path.isfile(clip_path):
|
if event.end_time is None
|
||||||
end_ts = (
|
else (event.end_time + padding)
|
||||||
datetime.now().timestamp() if event.end_time is None else event.end_time
|
|
||||||
)
|
|
||||||
vod_response = vod_ts(event.camera, event.start_time, end_ts)
|
|
||||||
# If the recordings are not found and the event started more than 5 minutes ago, set has_clip to false
|
|
||||||
if (
|
|
||||||
event.start_time < datetime.now().timestamp() - 300
|
|
||||||
and type(vod_response) is tuple
|
|
||||||
and len(vod_response) == 2
|
|
||||||
and vod_response[1] == 404
|
|
||||||
):
|
|
||||||
Event.update(has_clip=False).where(Event.id == event_id).execute()
|
|
||||||
return vod_response
|
|
||||||
|
|
||||||
duration = int((event.end_time - event.start_time) * 1000)
|
|
||||||
return JSONResponse(
|
|
||||||
content={
|
|
||||||
"cache": True,
|
|
||||||
"discontinuity": False,
|
|
||||||
"durations": [duration],
|
|
||||||
"sequences": [{"clips": [{"type": "source", "path": clip_path}]}],
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
vod_response = vod_ts(event.camera, event.start_time - padding, end_ts)
|
||||||
|
|
||||||
|
# If the recordings are not found and the event started more than 5 minutes ago, set has_clip to false
|
||||||
|
if (
|
||||||
|
event.start_time < datetime.now().timestamp() - 300
|
||||||
|
and type(vod_response) is tuple
|
||||||
|
and len(vod_response) == 2
|
||||||
|
and vod_response[1] == 404
|
||||||
|
):
|
||||||
|
Event.update(has_clip=False).where(Event.id == event_id).execute()
|
||||||
|
|
||||||
|
return vod_response
|
||||||
|
|
||||||
|
|
||||||
@router.get("/events/{event_id}/snapshot.jpg")
|
@router.get(
|
||||||
|
"/events/{event_id}/snapshot.jpg",
|
||||||
|
description="Returns a snapshot image for the specified object id. NOTE: The query params only take affect while the event is in-progress. Once the event has ended the snapshot configuration is used.",
|
||||||
|
)
|
||||||
def event_snapshot(
|
def event_snapshot(
|
||||||
request: Request,
|
request: Request,
|
||||||
event_id: str,
|
event_id: str,
|
||||||
@@ -875,7 +872,7 @@ def event_snapshot(
|
|||||||
def event_thumbnail(
|
def event_thumbnail(
|
||||||
request: Request,
|
request: Request,
|
||||||
event_id: str,
|
event_id: str,
|
||||||
extension: str,
|
extension: Extension,
|
||||||
max_cache_age: int = Query(
|
max_cache_age: int = Query(
|
||||||
2592000, description="Max cache age in seconds. Default 30 days in seconds."
|
2592000, description="Max cache age in seconds. Default 30 days in seconds."
|
||||||
),
|
),
|
||||||
@@ -900,7 +897,7 @@ def event_thumbnail(
|
|||||||
if event_id in camera_state.tracked_objects:
|
if event_id in camera_state.tracked_objects:
|
||||||
tracked_obj = camera_state.tracked_objects.get(event_id)
|
tracked_obj = camera_state.tracked_objects.get(event_id)
|
||||||
if tracked_obj is not None:
|
if tracked_obj is not None:
|
||||||
thumbnail_bytes = tracked_obj.get_thumbnail(extension)
|
thumbnail_bytes = tracked_obj.get_thumbnail(extension.value)
|
||||||
except Exception:
|
except Exception:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={"success": False, "message": "Event not found"},
|
content={"success": False, "message": "Event not found"},
|
||||||
@@ -928,23 +925,21 @@ def event_thumbnail(
|
|||||||
)
|
)
|
||||||
|
|
||||||
quality_params = None
|
quality_params = None
|
||||||
|
if extension in (Extension.jpg, Extension.jpeg):
|
||||||
if extension == "jpg" or extension == "jpeg":
|
|
||||||
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), 70]
|
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), 70]
|
||||||
elif extension == "webp":
|
elif extension == Extension.webp:
|
||||||
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), 60]
|
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), 60]
|
||||||
|
|
||||||
_, img = cv2.imencode(f".{extension}", thumbnail, quality_params)
|
_, img = cv2.imencode(f".{extension.value}", thumbnail, quality_params)
|
||||||
thumbnail_bytes = img.tobytes()
|
thumbnail_bytes = img.tobytes()
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
thumbnail_bytes,
|
thumbnail_bytes,
|
||||||
media_type=f"image/{extension}",
|
media_type=extension.get_mime_type(),
|
||||||
headers={
|
headers={
|
||||||
"Cache-Control": f"private, max-age={max_cache_age}"
|
"Cache-Control": f"private, max-age={max_cache_age}"
|
||||||
if event_complete
|
if event_complete
|
||||||
else "no-store",
|
else "no-store",
|
||||||
"Content-Type": f"image/{extension}",
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1155,7 +1150,11 @@ def event_snapshot_clean(request: Request, event_id: str, download: bool = False
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/events/{event_id}/clip.mp4")
|
@router.get("/events/{event_id}/clip.mp4")
|
||||||
def event_clip(request: Request, event_id: str):
|
def event_clip(
|
||||||
|
request: Request,
|
||||||
|
event_id: str,
|
||||||
|
padding: int = Query(0, description="Padding to apply to clip."),
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
@@ -1168,8 +1167,12 @@ def event_clip(request: Request, event_id: str):
|
|||||||
content={"success": False, "message": "Clip not available"}, status_code=404
|
content={"success": False, "message": "Clip not available"}, status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
end_ts = datetime.now().timestamp() if event.end_time is None else event.end_time
|
end_ts = (
|
||||||
return recording_clip(request, event.camera, event.start_time, end_ts)
|
datetime.now().timestamp()
|
||||||
|
if event.end_time is None
|
||||||
|
else event.end_time + padding
|
||||||
|
)
|
||||||
|
return recording_clip(request, event.camera, event.start_time - padding, end_ts)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/events/{event_id}/preview.gif")
|
@router.get("/events/{event_id}/preview.gif")
|
||||||
@@ -1595,7 +1598,7 @@ def label_thumbnail(request: Request, camera_name: str, label: str):
|
|||||||
try:
|
try:
|
||||||
event_id = event_query.scalar()
|
event_id = event_query.scalar()
|
||||||
|
|
||||||
return event_thumbnail(request, event_id, 60)
|
return event_thumbnail(request, event_id, Extension.jpg, 60)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
frame = np.zeros((175, 175, 3), np.uint8)
|
frame = np.zeros((175, 175, 3), np.uint8)
|
||||||
ret, jpg = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 70])
|
ret, jpg = cv2.imencode(".jpg", frame, [int(cv2.IMWRITE_JPEG_QUALITY), 70])
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ router = APIRouter(tags=[Tags.notifications])
|
|||||||
|
|
||||||
@router.get("/notifications/pubkey")
|
@router.get("/notifications/pubkey")
|
||||||
def get_vapid_pub_key(request: Request):
|
def get_vapid_pub_key(request: Request):
|
||||||
if not request.app.frigate_config.notifications.enabled:
|
config = request.app.frigate_config
|
||||||
|
notifications_enabled = config.notifications.enabled
|
||||||
|
camera_notifications_enabled = [
|
||||||
|
c for c in config.cameras.values() if c.enabled and c.notifications.enabled
|
||||||
|
]
|
||||||
|
if not (notifications_enabled or camera_notifications_enabled):
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=({"success": False, "message": "Notifications are not enabled."}),
|
content=({"success": False, "message": "Notifications are not enabled."}),
|
||||||
status_code=400,
|
status_code=400,
|
||||||
|
|||||||
@@ -58,9 +58,8 @@ async def review(
|
|||||||
)
|
)
|
||||||
|
|
||||||
clauses = [
|
clauses = [
|
||||||
(ReviewSegment.start_time > after)
|
(ReviewSegment.start_time < before)
|
||||||
& (ReviewSegment.start_time < before)
|
& ((ReviewSegment.end_time.is_null(True)) | (ReviewSegment.end_time > after))
|
||||||
& ((ReviewSegment.end_time.is_null(True)) | (ReviewSegment.end_time < before))
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ from frigate.track.object_processing import TrackedObjectProcessor
|
|||||||
from frigate.util.builtin import empty_and_close_queue
|
from frigate.util.builtin import empty_and_close_queue
|
||||||
from frigate.util.image import SharedMemoryFrameManager, UntrackedSharedMemory
|
from frigate.util.image import SharedMemoryFrameManager, UntrackedSharedMemory
|
||||||
from frigate.util.object import get_camera_regions_grid
|
from frigate.util.object import get_camera_regions_grid
|
||||||
|
from frigate.util.services import set_file_limit
|
||||||
from frigate.version import VERSION
|
from frigate.version import VERSION
|
||||||
from frigate.video import capture_camera, track_camera
|
from frigate.video import capture_camera, track_camera
|
||||||
from frigate.watchdog import FrigateWatchdog
|
from frigate.watchdog import FrigateWatchdog
|
||||||
@@ -249,6 +250,7 @@ class FrigateApp:
|
|||||||
and not genai_cameras
|
and not genai_cameras
|
||||||
and not self.config.lpr.enabled
|
and not self.config.lpr.enabled
|
||||||
and not self.config.face_recognition.enabled
|
and not self.config.face_recognition.enabled
|
||||||
|
and not self.config.classification.bird.enabled
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -632,6 +634,9 @@ class FrigateApp:
|
|||||||
# Ensure global state.
|
# Ensure global state.
|
||||||
self.ensure_dirs()
|
self.ensure_dirs()
|
||||||
|
|
||||||
|
# Set soft file limits.
|
||||||
|
set_file_limit()
|
||||||
|
|
||||||
# Start frigate services.
|
# Start frigate services.
|
||||||
self.init_camera_metrics()
|
self.init_camera_metrics()
|
||||||
self.init_queues()
|
self.init_queues()
|
||||||
|
|||||||
+47
-12
@@ -256,6 +256,7 @@ class CameraState:
|
|||||||
updated_ids = current_ids.intersection(previous_ids)
|
updated_ids = current_ids.intersection(previous_ids)
|
||||||
|
|
||||||
for id in new_ids:
|
for id in new_ids:
|
||||||
|
logger.debug(f"{self.name}: New tracked object ID: {id}")
|
||||||
new_obj = tracked_objects[id] = TrackedObject(
|
new_obj = tracked_objects[id] = TrackedObject(
|
||||||
self.config.model,
|
self.config.model,
|
||||||
self.camera_config,
|
self.camera_config,
|
||||||
@@ -265,7 +266,13 @@ class CameraState:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# add initial frame to frame cache
|
# add initial frame to frame cache
|
||||||
self.frame_cache[frame_time] = np.copy(current_frame)
|
logger.debug(
|
||||||
|
f"{self.name}: New object, adding {frame_time} to frame cache for {id}"
|
||||||
|
)
|
||||||
|
self.frame_cache[frame_time] = {
|
||||||
|
"frame": np.copy(current_frame),
|
||||||
|
"object_id": id,
|
||||||
|
}
|
||||||
|
|
||||||
# save initial thumbnail data and best object
|
# save initial thumbnail data and best object
|
||||||
thumbnail_data = {
|
thumbnail_data = {
|
||||||
@@ -284,11 +291,9 @@ class CameraState:
|
|||||||
new_obj.thumbnail_data = thumbnail_data
|
new_obj.thumbnail_data = thumbnail_data
|
||||||
tracked_objects[id].thumbnail_data = thumbnail_data
|
tracked_objects[id].thumbnail_data = thumbnail_data
|
||||||
object_type = new_obj.obj_data["label"]
|
object_type = new_obj.obj_data["label"]
|
||||||
self.best_objects[object_type] = new_obj
|
|
||||||
|
|
||||||
# call event handlers
|
# call event handlers
|
||||||
for c in self.callbacks["snapshot"]:
|
self.send_mqtt_snapshot(new_obj, object_type)
|
||||||
c(self.name, self.best_objects[object_type], frame_name)
|
|
||||||
|
|
||||||
for c in self.callbacks["start"]:
|
for c in self.callbacks["start"]:
|
||||||
c(self.name, new_obj, frame_name)
|
c(self.name, new_obj, frame_name)
|
||||||
@@ -311,7 +316,13 @@ class CameraState:
|
|||||||
updated_obj.thumbnail_data["frame_time"] == frame_time
|
updated_obj.thumbnail_data["frame_time"] == frame_time
|
||||||
and frame_time not in self.frame_cache
|
and frame_time not in self.frame_cache
|
||||||
):
|
):
|
||||||
self.frame_cache[frame_time] = np.copy(current_frame)
|
logger.debug(
|
||||||
|
f"{self.name}: Existing object, adding {frame_time} to frame cache for {id}"
|
||||||
|
)
|
||||||
|
self.frame_cache[frame_time] = {
|
||||||
|
"frame": np.copy(current_frame),
|
||||||
|
"object_id": id,
|
||||||
|
}
|
||||||
|
|
||||||
updated_obj.last_updated = frame_time
|
updated_obj.last_updated = frame_time
|
||||||
|
|
||||||
@@ -337,6 +348,7 @@ class CameraState:
|
|||||||
removed_obj = tracked_objects[id]
|
removed_obj = tracked_objects[id]
|
||||||
if "end_time" not in removed_obj.obj_data:
|
if "end_time" not in removed_obj.obj_data:
|
||||||
removed_obj.obj_data["end_time"] = frame_time
|
removed_obj.obj_data["end_time"] = frame_time
|
||||||
|
logger.debug(f"{self.name}: end callback for object {id}")
|
||||||
for c in self.callbacks["end"]:
|
for c in self.callbacks["end"]:
|
||||||
c(self.name, removed_obj, frame_name)
|
c(self.name, removed_obj, frame_name)
|
||||||
|
|
||||||
@@ -403,13 +415,9 @@ class CameraState:
|
|||||||
or (now - current_best.thumbnail_data["frame_time"])
|
or (now - current_best.thumbnail_data["frame_time"])
|
||||||
> self.camera_config.best_image_timeout
|
> self.camera_config.best_image_timeout
|
||||||
):
|
):
|
||||||
self.best_objects[object_type] = obj
|
self.send_mqtt_snapshot(obj, object_type)
|
||||||
for c in self.callbacks["snapshot"]:
|
|
||||||
c(self.name, self.best_objects[object_type], frame_name)
|
|
||||||
else:
|
else:
|
||||||
self.best_objects[object_type] = obj
|
self.send_mqtt_snapshot(obj, object_type)
|
||||||
for c in self.callbacks["snapshot"]:
|
|
||||||
c(self.name, self.best_objects[object_type], frame_name)
|
|
||||||
|
|
||||||
for c in self.callbacks["camera_activity"]:
|
for c in self.callbacks["camera_activity"]:
|
||||||
c(self.name, camera_activity)
|
c(self.name, camera_activity)
|
||||||
@@ -418,7 +426,7 @@ class CameraState:
|
|||||||
current_thumb_frames = {
|
current_thumb_frames = {
|
||||||
obj.thumbnail_data["frame_time"]
|
obj.thumbnail_data["frame_time"]
|
||||||
for obj in tracked_objects.values()
|
for obj in tracked_objects.values()
|
||||||
if not obj.false_positive and obj.thumbnail_data is not None
|
if obj.thumbnail_data is not None
|
||||||
}
|
}
|
||||||
current_best_frames = {
|
current_best_frames = {
|
||||||
obj.thumbnail_data["frame_time"] for obj in self.best_objects.values()
|
obj.thumbnail_data["frame_time"] for obj in self.best_objects.values()
|
||||||
@@ -428,7 +436,20 @@ class CameraState:
|
|||||||
for t in self.frame_cache.keys()
|
for t in self.frame_cache.keys()
|
||||||
if t not in current_thumb_frames and t not in current_best_frames
|
if t not in current_thumb_frames and t not in current_best_frames
|
||||||
]
|
]
|
||||||
|
if len(thumb_frames_to_delete) > 0:
|
||||||
|
logger.debug(f"{self.name}: Current frame cache contents:")
|
||||||
|
for k, v in self.frame_cache.items():
|
||||||
|
logger.debug(f" frame time: {k}, object id: {v['object_id']}")
|
||||||
|
for obj_id, obj in tracked_objects.items():
|
||||||
|
thumb_time = (
|
||||||
|
obj.thumbnail_data["frame_time"] if obj.thumbnail_data else None
|
||||||
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"{self.name}: Tracked object {obj_id} thumbnail frame_time: {thumb_time}, false positive: {obj.false_positive}"
|
||||||
|
)
|
||||||
for t in thumb_frames_to_delete:
|
for t in thumb_frames_to_delete:
|
||||||
|
object_id = self.frame_cache[t].get("object_id", "unknown")
|
||||||
|
logger.debug(f"{self.name}: Deleting {t} from frame cache for {object_id}")
|
||||||
del self.frame_cache[t]
|
del self.frame_cache[t]
|
||||||
|
|
||||||
with self.current_frame_lock:
|
with self.current_frame_lock:
|
||||||
@@ -445,6 +466,20 @@ class CameraState:
|
|||||||
|
|
||||||
self.previous_frame_id = frame_name
|
self.previous_frame_id = frame_name
|
||||||
|
|
||||||
|
def send_mqtt_snapshot(self, new_obj: TrackedObject, object_type: str) -> None:
|
||||||
|
for c in self.callbacks["snapshot"]:
|
||||||
|
updated = c(self.name, new_obj)
|
||||||
|
|
||||||
|
# if the snapshot was not updated, then this object is not a best object
|
||||||
|
# but all new objects should be considered the next best object
|
||||||
|
# so we remove the label from the best objects
|
||||||
|
if updated:
|
||||||
|
self.best_objects[object_type] = new_obj
|
||||||
|
else:
|
||||||
|
if object_type in self.best_objects:
|
||||||
|
self.best_objects.pop(object_type)
|
||||||
|
break
|
||||||
|
|
||||||
def save_manual_event_image(
|
def save_manual_event_image(
|
||||||
self,
|
self,
|
||||||
frame: np.ndarray | None,
|
frame: np.ndarray | None,
|
||||||
|
|||||||
@@ -241,6 +241,9 @@ class WebPushClient(Communicator): # type: ignore[misc]
|
|||||||
self.expired_subs.setdefault(notification.user, []).append(
|
self.expired_subs.setdefault(notification.user, []).append(
|
||||||
endpoint
|
endpoint
|
||||||
)
|
)
|
||||||
|
logger.debug(
|
||||||
|
f"Notification endpoint expired for {notification.user}, received {resp.status_code}"
|
||||||
|
)
|
||||||
elif resp.status_code != 201:
|
elif resp.status_code != 201:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Failed to send notification to {notification.user} :: {resp.status_code}"
|
f"Failed to send notification to {notification.user} :: {resp.status_code}"
|
||||||
@@ -257,6 +260,8 @@ class WebPushClient(Communicator): # type: ignore[misc]
|
|||||||
|
|
||||||
self.check_registrations()
|
self.check_registrations()
|
||||||
|
|
||||||
|
logger.debug("Sending test notification")
|
||||||
|
|
||||||
for user in self.web_pushers:
|
for user in self.web_pushers:
|
||||||
self.send_push_notification(
|
self.send_push_notification(
|
||||||
user=user,
|
user=user,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class FfmpegConfig(FrigateBaseModel):
|
|||||||
retry_interval: float = Field(
|
retry_interval: float = Field(
|
||||||
default=10.0,
|
default=10.0,
|
||||||
title="Time in seconds to wait before FFmpeg retries connecting to the camera.",
|
title="Time in seconds to wait before FFmpeg retries connecting to the camera.",
|
||||||
|
gt=0.0,
|
||||||
)
|
)
|
||||||
apple_compatibility: bool = Field(
|
apple_compatibility: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ class FaceRecognitionConfig(FrigateBaseModel):
|
|||||||
default=1,
|
default=1,
|
||||||
gt=0,
|
gt=0,
|
||||||
le=6,
|
le=6,
|
||||||
title="Min face attempts for the sub label to be applied to the person object.",
|
title="Min face recognitions for the sub label to be applied to the person object.",
|
||||||
)
|
)
|
||||||
save_attempts: int = Field(
|
save_attempts: int = Field(
|
||||||
default=100, ge=0, title="Number of face attempts to save in the train tab."
|
default=100, ge=0, title="Number of face attempts to save in the train tab."
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
from .base import FrigateBaseModel
|
from .base import FrigateBaseModel
|
||||||
@@ -11,8 +13,8 @@ class StatsConfig(FrigateBaseModel):
|
|||||||
network_bandwidth: bool = Field(
|
network_bandwidth: bool = Field(
|
||||||
default=False, title="Enable network bandwidth for ffmpeg processes."
|
default=False, title="Enable network bandwidth for ffmpeg processes."
|
||||||
)
|
)
|
||||||
sriov: bool = Field(
|
intel_gpu_device: Optional[str] = Field(
|
||||||
default=False, title="Treat device as SR-IOV to support GPU stats."
|
default=None, title="Define the device to use when gathering SR-IOV stats."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ DEFAULT_ATTRIBUTE_LABEL_MAP = {
|
|||||||
"car": [
|
"car": [
|
||||||
"amazon",
|
"amazon",
|
||||||
"an_post",
|
"an_post",
|
||||||
|
"canada_post",
|
||||||
"dhl",
|
"dhl",
|
||||||
"dpd",
|
"dpd",
|
||||||
"fedex",
|
"fedex",
|
||||||
@@ -35,6 +36,7 @@ DEFAULT_ATTRIBUTE_LABEL_MAP = {
|
|||||||
"postnl",
|
"postnl",
|
||||||
"postnord",
|
"postnord",
|
||||||
"purolator",
|
"purolator",
|
||||||
|
"royal_mail",
|
||||||
"ups",
|
"ups",
|
||||||
"usps",
|
"usps",
|
||||||
],
|
],
|
||||||
@@ -70,6 +72,7 @@ LIBAVFORMAT_VERSION_MAJOR = int(os.environ.get("LIBAVFORMAT_VERSION_MAJOR", "59"
|
|||||||
FFMPEG_HWACCEL_NVIDIA = "preset-nvidia"
|
FFMPEG_HWACCEL_NVIDIA = "preset-nvidia"
|
||||||
FFMPEG_HWACCEL_VAAPI = "preset-vaapi"
|
FFMPEG_HWACCEL_VAAPI = "preset-vaapi"
|
||||||
FFMPEG_HWACCEL_VULKAN = "preset-vulkan"
|
FFMPEG_HWACCEL_VULKAN = "preset-vulkan"
|
||||||
|
FFMPEG_HWACCEL_RKMPP = "preset-rkmpp"
|
||||||
FFMPEG_HVC1_ARGS = ["-tag:v", "hvc1"]
|
FFMPEG_HVC1_ARGS = ["-tag:v", "hvc1"]
|
||||||
|
|
||||||
# Regex constants
|
# Regex constants
|
||||||
|
|||||||
@@ -108,21 +108,24 @@ class FaceRecognizer(ABC):
|
|||||||
image, M, (output_width, output_height), flags=cv2.INTER_CUBIC
|
image, M, (output_width, output_height), flags=cv2.INTER_CUBIC
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_blur_factor(self, input: np.ndarray) -> float:
|
def get_blur_confidence_reduction(self, input: np.ndarray) -> float:
|
||||||
"""Calculates the factor for the confidence based on the blur of the image."""
|
"""Calculates the reduction in confidence based on the blur of the image."""
|
||||||
if not self.config.face_recognition.blur_confidence_filter:
|
if not self.config.face_recognition.blur_confidence_filter:
|
||||||
return 1.0
|
return 0.0
|
||||||
|
|
||||||
variance = cv2.Laplacian(input, cv2.CV_64F).var()
|
variance = cv2.Laplacian(input, cv2.CV_64F).var()
|
||||||
|
logger.debug(f"face detected with blurriness {variance}")
|
||||||
|
|
||||||
if variance < 60: # image is very blurry
|
if variance < 120: # image is very blurry
|
||||||
return 0.96
|
return 0.06
|
||||||
elif variance < 70: # image moderately blurry
|
elif variance < 160: # image moderately blurry
|
||||||
return 0.98
|
return 0.04
|
||||||
elif variance < 80: # image is slightly blurry
|
elif variance < 200: # image is slightly blurry
|
||||||
return 0.99
|
return 0.02
|
||||||
|
elif variance < 250: # image is mostly clear
|
||||||
|
return 0.01
|
||||||
else:
|
else:
|
||||||
return 1.0
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
def similarity_to_confidence(
|
def similarity_to_confidence(
|
||||||
@@ -234,8 +237,7 @@ class FaceNetRecognizer(FaceRecognizer):
|
|||||||
# face recognition is best run on grayscale images
|
# face recognition is best run on grayscale images
|
||||||
|
|
||||||
# get blur factor before aligning face
|
# get blur factor before aligning face
|
||||||
blur_factor = self.get_blur_factor(face_image)
|
blur_reduction = self.get_blur_confidence_reduction(face_image)
|
||||||
logger.debug(f"face detected with blurriness {blur_factor}")
|
|
||||||
|
|
||||||
# align face and run recognition
|
# align face and run recognition
|
||||||
img = self.align_face(face_image, face_image.shape[1], face_image.shape[0])
|
img = self.align_face(face_image, face_image.shape[1], face_image.shape[0])
|
||||||
@@ -258,7 +260,7 @@ class FaceNetRecognizer(FaceRecognizer):
|
|||||||
score = confidence
|
score = confidence
|
||||||
label = name
|
label = name
|
||||||
|
|
||||||
return label, round(score * blur_factor, 2)
|
return label, round(score - blur_reduction, 2)
|
||||||
|
|
||||||
|
|
||||||
class ArcFaceRecognizer(FaceRecognizer):
|
class ArcFaceRecognizer(FaceRecognizer):
|
||||||
@@ -344,9 +346,8 @@ class ArcFaceRecognizer(FaceRecognizer):
|
|||||||
|
|
||||||
# face recognition is best run on grayscale images
|
# face recognition is best run on grayscale images
|
||||||
|
|
||||||
# get blur factor before aligning face
|
# get blur reduction before aligning face
|
||||||
blur_factor = self.get_blur_factor(face_image)
|
blur_reduction = self.get_blur_confidence_reduction(face_image)
|
||||||
logger.debug(f"face detected with blurriness {blur_factor}")
|
|
||||||
|
|
||||||
# align face and run recognition
|
# align face and run recognition
|
||||||
img = self.align_face(face_image, face_image.shape[1], face_image.shape[0])
|
img = self.align_face(face_image, face_image.shape[1], face_image.shape[0])
|
||||||
@@ -367,4 +368,4 @@ class ArcFaceRecognizer(FaceRecognizer):
|
|||||||
score = confidence
|
score = confidence
|
||||||
label = name
|
label = name
|
||||||
|
|
||||||
return label, round(score * blur_factor, 2)
|
return label, round(score - blur_reduction, 2)
|
||||||
|
|||||||
@@ -167,6 +167,8 @@ class LicensePlateProcessingMixin:
|
|||||||
outputs = self.model_runner.recognition_model(norm_images)
|
outputs = self.model_runner.recognition_model(norm_images)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error running LPR recognition model: {e}")
|
logger.warning(f"Error running LPR recognition model: {e}")
|
||||||
|
return [], []
|
||||||
|
|
||||||
return self.ctc_decoder(outputs)
|
return self.ctc_decoder(outputs)
|
||||||
|
|
||||||
def _process_license_plate(
|
def _process_license_plate(
|
||||||
@@ -1498,18 +1500,24 @@ class LicensePlateProcessingMixin:
|
|||||||
|
|
||||||
# Determine subLabel based on known plates, use regex matching
|
# Determine subLabel based on known plates, use regex matching
|
||||||
# Default to the detected plate, use label name if there's a match
|
# Default to the detected plate, use label name if there's a match
|
||||||
sub_label = next(
|
try:
|
||||||
(
|
sub_label = next(
|
||||||
label
|
(
|
||||||
for label, plates in self.lpr_config.known_plates.items()
|
label
|
||||||
if any(
|
for label, plates in self.lpr_config.known_plates.items()
|
||||||
re.match(f"^{plate}$", top_plate)
|
if any(
|
||||||
or distance(plate, top_plate) <= self.lpr_config.match_distance
|
re.match(f"^{plate}$", top_plate)
|
||||||
for plate in plates
|
or distance(plate, top_plate) <= self.lpr_config.match_distance
|
||||||
)
|
for plate in plates
|
||||||
),
|
)
|
||||||
None,
|
),
|
||||||
)
|
None,
|
||||||
|
)
|
||||||
|
except re.error:
|
||||||
|
logger.error(
|
||||||
|
f"{camera}: Invalid regex in known plates configuration: {self.lpr_config.known_plates}"
|
||||||
|
)
|
||||||
|
sub_label = None
|
||||||
|
|
||||||
# If it's a known plate, publish to sub_label
|
# If it's a known plate, publish to sub_label
|
||||||
if sub_label is not None:
|
if sub_label is not None:
|
||||||
@@ -1577,19 +1585,6 @@ class LicensePlateProcessingMixin:
|
|||||||
if object_id in self.camera_current_cars.get(camera, []):
|
if object_id in self.camera_current_cars.get(camera, []):
|
||||||
self.camera_current_cars[camera].remove(object_id)
|
self.camera_current_cars[camera].remove(object_id)
|
||||||
|
|
||||||
if len(self.camera_current_cars[camera]) == 0:
|
|
||||||
self.requestor.send_data(
|
|
||||||
"tracked_object_update",
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"type": TrackedObjectUpdateTypesEnum.lpr,
|
|
||||||
"name": None,
|
|
||||||
"plate": None,
|
|
||||||
"camera": camera,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CTCDecoder:
|
class CTCDecoder:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -41,10 +41,13 @@ class BirdRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
self.detected_birds: dict[str, float] = {}
|
self.detected_birds: dict[str, float] = {}
|
||||||
self.labelmap: dict[int, str] = {}
|
self.labelmap: dict[int, str] = {}
|
||||||
|
|
||||||
|
GITHUB_RAW_ENDPOINT = os.environ.get(
|
||||||
|
"GITHUB_RAW_ENDPOINT", "https://raw.githubusercontent.com"
|
||||||
|
)
|
||||||
download_path = os.path.join(MODEL_CACHE_DIR, "bird")
|
download_path = os.path.join(MODEL_CACHE_DIR, "bird")
|
||||||
self.model_files = {
|
self.model_files = {
|
||||||
"bird.tflite": "https://raw.githubusercontent.com/google-coral/test_data/master/mobilenet_v2_1.0_224_inat_bird_quant.tflite",
|
"bird.tflite": f"{GITHUB_RAW_ENDPOINT}/google-coral/test_data/master/mobilenet_v2_1.0_224_inat_bird_quant.tflite",
|
||||||
"birdmap.txt": "https://raw.githubusercontent.com/google-coral/test_data/master/inat_bird_labels.txt",
|
"birdmap.txt": f"{GITHUB_RAW_ENDPOINT}/google-coral/test_data/master/inat_bird_labels.txt",
|
||||||
}
|
}
|
||||||
|
|
||||||
if not all(
|
if not all(
|
||||||
@@ -109,7 +112,13 @@ class BirdRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
obj_data["box"][1],
|
obj_data["box"][1],
|
||||||
obj_data["box"][2],
|
obj_data["box"][2],
|
||||||
obj_data["box"][3],
|
obj_data["box"][3],
|
||||||
224,
|
int(
|
||||||
|
max(
|
||||||
|
obj_data["box"][1] - obj_data["box"][0],
|
||||||
|
obj_data["box"][3] - obj_data["box"][2],
|
||||||
|
)
|
||||||
|
* 1.1
|
||||||
|
),
|
||||||
1.0,
|
1.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@@ -59,10 +60,12 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
self.faces_per_second = EventsPerSecond()
|
self.faces_per_second = EventsPerSecond()
|
||||||
self.inference_speed = InferenceSpeed(self.metrics.face_rec_speed)
|
self.inference_speed = InferenceSpeed(self.metrics.face_rec_speed)
|
||||||
|
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
|
|
||||||
download_path = os.path.join(MODEL_CACHE_DIR, "facedet")
|
download_path = os.path.join(MODEL_CACHE_DIR, "facedet")
|
||||||
self.model_files = {
|
self.model_files = {
|
||||||
"facedet.onnx": "https://github.com/NickM-27/facenet-onnx/releases/download/v1.0/facedet.onnx",
|
"facedet.onnx": f"{GITHUB_ENDPOINT}/NickM-27/facenet-onnx/releases/download/v1.0/facedet.onnx",
|
||||||
"landmarkdet.yaml": "https://github.com/NickM-27/facenet-onnx/releases/download/v1.0/landmarkdet.yaml",
|
"landmarkdet.yaml": f"{GITHUB_ENDPOINT}/NickM-27/facenet-onnx/releases/download/v1.0/landmarkdet.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
if not all(
|
if not all(
|
||||||
@@ -302,9 +305,6 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
self.person_face_history[id]
|
self.person_face_history[id]
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(self.person_face_history[id]) < self.face_config.min_faces:
|
|
||||||
weighted_sub_label = "unknown"
|
|
||||||
|
|
||||||
self.requestor.send_data(
|
self.requestor.send_data(
|
||||||
"tracked_object_update",
|
"tracked_object_update",
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@@ -443,18 +443,6 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
if object_id in self.camera_current_people.get(camera, []):
|
if object_id in self.camera_current_people.get(camera, []):
|
||||||
self.camera_current_people[camera].remove(object_id)
|
self.camera_current_people[camera].remove(object_id)
|
||||||
|
|
||||||
if len(self.camera_current_people[camera]) == 0:
|
|
||||||
self.requestor.send_data(
|
|
||||||
"tracked_object_update",
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"type": TrackedObjectUpdateTypesEnum.face,
|
|
||||||
"name": None,
|
|
||||||
"camera": camera,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def weighted_average(
|
def weighted_average(
|
||||||
self, results_list: list[tuple[str, float, int]], max_weight: int = 4000
|
self, results_list: list[tuple[str, float, int]], max_weight: int = 4000
|
||||||
):
|
):
|
||||||
@@ -500,6 +488,10 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
|
|
||||||
best_name = max(weighted_scores, key=weighted_scores.get)
|
best_name = max(weighted_scores, key=weighted_scores.get)
|
||||||
|
|
||||||
|
# If the number of faces for this person < min_faces, we are not confident it is a correct result
|
||||||
|
if counts[best_name] < self.face_config.min_faces:
|
||||||
|
return None, 0.0
|
||||||
|
|
||||||
# If the best name has the same number of results as another name, we are not confident it is a correct result
|
# If the best name has the same number of results as another name, we are not confident it is a correct result
|
||||||
for name, count in counts.items():
|
for name, count in counts.items():
|
||||||
if name != best_name and counts[best_name] == count:
|
if name != best_name and counts[best_name] == count:
|
||||||
@@ -538,4 +530,4 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
|||||||
|
|
||||||
# delete oldest face image if maximum is reached
|
# delete oldest face image if maximum is reached
|
||||||
if len(files) > self.config.face_recognition.save_attempts:
|
if len(files) > self.config.face_recognition.save_attempts:
|
||||||
os.unlink(os.path.join(folder, files[-1]))
|
Path(os.path.join(folder, files[-1])).unlink(missing_ok=True)
|
||||||
|
|||||||
@@ -158,6 +158,13 @@ class ModelConfig(BaseModel):
|
|||||||
self.input_pixel_format = model_info["pixelFormat"]
|
self.input_pixel_format = model_info["pixelFormat"]
|
||||||
self.model_type = model_info["type"]
|
self.model_type = model_info["type"]
|
||||||
|
|
||||||
|
if model_info.get("inputDataType"):
|
||||||
|
self.input_dtype = model_info["inputDataType"]
|
||||||
|
|
||||||
|
# RKNN always uses NHWC
|
||||||
|
if detector == "rknn":
|
||||||
|
self.input_tensor = InputTensorEnum.nhwc
|
||||||
|
|
||||||
# generate list of attribute labels
|
# generate list of attribute labels
|
||||||
self.attributes_map = {
|
self.attributes_map = {
|
||||||
**model_info.get("attributes", DEFAULT_ATTRIBUTE_LABEL_MAP),
|
**model_info.get("attributes", DEFAULT_ATTRIBUTE_LABEL_MAP),
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ class OvDetector(DetectionApi):
|
|||||||
)
|
)
|
||||||
self.model_invalid = True
|
self.model_invalid = True
|
||||||
|
|
||||||
# Ensure the SSD model has the right input and output shapes
|
|
||||||
if self.ov_model_type == ModelTypeEnum.ssd:
|
if self.ov_model_type == ModelTypeEnum.ssd:
|
||||||
model_inputs = self.interpreter.inputs
|
model_inputs = self.interpreter.inputs
|
||||||
model_outputs = self.interpreter.outputs
|
model_outputs = self.interpreter.outputs
|
||||||
@@ -75,12 +74,6 @@ class OvDetector(DetectionApi):
|
|||||||
)
|
)
|
||||||
self.model_invalid = True
|
self.model_invalid = True
|
||||||
|
|
||||||
if model_inputs[0].get_shape() != ov.Shape([1, self.w, self.h, 3]):
|
|
||||||
logger.error(
|
|
||||||
f"SSD model input doesn't match. Found {model_inputs[0].get_shape()}."
|
|
||||||
)
|
|
||||||
self.model_invalid = True
|
|
||||||
|
|
||||||
output_shape = model_outputs[0].get_shape()
|
output_shape = model_outputs[0].get_shape()
|
||||||
if output_shape[0] != 1 or output_shape[1] != 1 or output_shape[3] != 7:
|
if output_shape[0] != 1 or output_shape[1] != 1 or output_shape[3] != 7:
|
||||||
logger.error(f"SSD model output doesn't match. Found {output_shape}.")
|
logger.error(f"SSD model output doesn't match. Found {output_shape}.")
|
||||||
@@ -100,13 +93,6 @@ class OvDetector(DetectionApi):
|
|||||||
f"YoloNAS models must be exported in flat format and only have 1 output. Found {len(model_outputs)}."
|
f"YoloNAS models must be exported in flat format and only have 1 output. Found {len(model_outputs)}."
|
||||||
)
|
)
|
||||||
self.model_invalid = True
|
self.model_invalid = True
|
||||||
|
|
||||||
if model_inputs[0].get_shape() != ov.Shape([1, 3, self.w, self.h]):
|
|
||||||
logger.error(
|
|
||||||
f"YoloNAS model input doesn't match. Found {model_inputs[0].get_shape()}, but expected {[1, 3, self.w, self.h]}."
|
|
||||||
)
|
|
||||||
self.model_invalid = True
|
|
||||||
|
|
||||||
output_shape = model_outputs[0].partial_shape
|
output_shape = model_outputs[0].partial_shape
|
||||||
if output_shape[-1] != 7:
|
if output_shape[-1] != 7:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class Rknn(DetectionApi):
|
|||||||
model_props["model_type"] = model_type
|
model_props["model_type"] = model_type
|
||||||
|
|
||||||
if model_matched:
|
if model_matched:
|
||||||
model_props["filename"] = model_path + f"-{soc}-v2.3.2-1.rknn"
|
model_props["filename"] = model_path + f"-{soc}-v2.3.2-2.rknn"
|
||||||
|
|
||||||
model_props["path"] = model_cache_dir + model_props["filename"]
|
model_props["path"] = model_cache_dir + model_props["filename"]
|
||||||
|
|
||||||
@@ -139,27 +139,12 @@ class Rknn(DetectionApi):
|
|||||||
if not os.path.isdir(model_cache_dir):
|
if not os.path.isdir(model_cache_dir):
|
||||||
os.mkdir(model_cache_dir)
|
os.mkdir(model_cache_dir)
|
||||||
|
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
urllib.request.urlretrieve(
|
urllib.request.urlretrieve(
|
||||||
f"https://github.com/MarcA711/rknn-models/releases/download/v2.3.2/{filename}",
|
f"{GITHUB_ENDPOINT}/MarcA711/rknn-models/releases/download/v2.3.2-2/{filename}",
|
||||||
model_cache_dir + filename,
|
model_cache_dir + filename,
|
||||||
)
|
)
|
||||||
|
|
||||||
def check_config(self, config):
|
|
||||||
if (config.model.width != 320) or (config.model.height != 320):
|
|
||||||
raise Exception(
|
|
||||||
"Make sure to set the model width and height to 320 in your config."
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.model.input_pixel_format != "bgr":
|
|
||||||
raise Exception(
|
|
||||||
'Make sure to set the model input_pixel_format to "bgr" in your config.'
|
|
||||||
)
|
|
||||||
|
|
||||||
if config.model.input_tensor != "nhwc":
|
|
||||||
raise Exception(
|
|
||||||
'Make sure to set the model input_tensor to "nhwc" in your config.'
|
|
||||||
)
|
|
||||||
|
|
||||||
def post_process_yolonas(self, output: list[np.ndarray]):
|
def post_process_yolonas(self, output: list[np.ndarray]):
|
||||||
"""
|
"""
|
||||||
@param output: output of inference
|
@param output: output of inference
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import ctypes
|
import ctypes
|
||||||
import logging
|
import logging
|
||||||
|
import platform
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@@ -219,6 +220,14 @@ class TensorRtDetector(DetectionApi):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, detector_config: TensorRTDetectorConfig):
|
def __init__(self, detector_config: TensorRTDetectorConfig):
|
||||||
|
if platform.machine() == "x86_64":
|
||||||
|
logger.error(
|
||||||
|
"TensorRT detector is no longer supported on amd64 system. Please use ONNX detector instead, see https://docs.frigate.video/configuration/object_detectors#onnx for more information."
|
||||||
|
)
|
||||||
|
raise ImportError(
|
||||||
|
"TensorRT detector is no longer supported on amd64 system. Please use ONNX detector instead, see https://docs.frigate.video/configuration/object_detectors#onnx for more information."
|
||||||
|
)
|
||||||
|
|
||||||
assert TRT_SUPPORT, (
|
assert TRT_SUPPORT, (
|
||||||
f"TensorRT libraries not found, {DETECTOR_KEY} detector not present"
|
f"TensorRT libraries not found, {DETECTOR_KEY} detector not present"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import multiprocessing as mp
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
@@ -73,13 +74,21 @@ class EmbeddingsContext:
|
|||||||
self.requestor = EmbeddingsRequestor()
|
self.requestor = EmbeddingsRequestor()
|
||||||
|
|
||||||
# load stats from disk
|
# load stats from disk
|
||||||
|
stats_file = os.path.join(CONFIG_DIR, ".search_stats.json")
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(CONFIG_DIR, ".search_stats.json"), "r") as f:
|
with open(stats_file, "r") as f:
|
||||||
data = json.loads(f.read())
|
data = json.loads(f.read())
|
||||||
self.thumb_stats.from_dict(data["thumb_stats"])
|
self.thumb_stats.from_dict(data["thumb_stats"])
|
||||||
self.desc_stats.from_dict(data["desc_stats"])
|
self.desc_stats.from_dict(data["desc_stats"])
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
except JSONDecodeError:
|
||||||
|
logger.warning("Failed to decode semantic search stats, clearing file")
|
||||||
|
try:
|
||||||
|
with open(stats_file, "w") as f:
|
||||||
|
f.write("")
|
||||||
|
except OSError as e:
|
||||||
|
logger.error(f"Failed to clear corrupted stats file: {e}")
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Write the stats to disk as JSON on exit."""
|
"""Write the stats to disk as JSON on exit."""
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
"""SQLite-vec embeddings database."""
|
"""SQLite-vec embeddings database."""
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import io
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from numpy import ndarray
|
from numpy import ndarray
|
||||||
|
from PIL import Image
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
from frigate.comms.inter_process import InterProcessRequestor
|
from frigate.comms.inter_process import InterProcessRequestor
|
||||||
@@ -199,14 +201,31 @@ class Embeddings:
|
|||||||
@param: upsert If embedding should be upserted into vec DB
|
@param: upsert If embedding should be upserted into vec DB
|
||||||
"""
|
"""
|
||||||
start = datetime.datetime.now().timestamp()
|
start = datetime.datetime.now().timestamp()
|
||||||
ids = list(event_thumbs.keys())
|
valid_ids = []
|
||||||
embeddings = self.vision_embedding(list(event_thumbs.values()))
|
valid_thumbs = []
|
||||||
|
for eid, thumb in event_thumbs.items():
|
||||||
|
try:
|
||||||
|
img = Image.open(io.BytesIO(thumb))
|
||||||
|
img.verify() # Will raise if corrupt
|
||||||
|
valid_ids.append(eid)
|
||||||
|
valid_thumbs.append(thumb)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
f"Embeddings reindexing: Skipping corrupt thumbnail for event {eid}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not valid_thumbs:
|
||||||
|
logger.warning(
|
||||||
|
"Embeddings reindexing: No valid thumbnails to embed in this batch."
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
embeddings = self.vision_embedding(valid_thumbs)
|
||||||
|
|
||||||
if upsert:
|
if upsert:
|
||||||
items = []
|
items = []
|
||||||
|
for i in range(len(valid_ids)):
|
||||||
for i in range(len(ids)):
|
items.append(valid_ids[i])
|
||||||
items.append(ids[i])
|
|
||||||
items.append(serialize(embeddings[i]))
|
items.append(serialize(embeddings[i]))
|
||||||
self.image_eps.update()
|
self.image_eps.update()
|
||||||
|
|
||||||
@@ -214,12 +233,12 @@ class Embeddings:
|
|||||||
"""
|
"""
|
||||||
INSERT OR REPLACE INTO vec_thumbnails(id, thumbnail_embedding)
|
INSERT OR REPLACE INTO vec_thumbnails(id, thumbnail_embedding)
|
||||||
VALUES {}
|
VALUES {}
|
||||||
""".format(", ".join(["(?, ?)"] * len(ids))),
|
""".format(", ".join(["(?, ?)"] * len(valid_ids))),
|
||||||
items,
|
items,
|
||||||
)
|
)
|
||||||
|
|
||||||
duration = datetime.datetime.now().timestamp() - start
|
duration = datetime.datetime.now().timestamp() - start
|
||||||
self.text_inference_speed.update(duration / len(ids))
|
self.text_inference_speed.update(duration / len(valid_ids))
|
||||||
|
|
||||||
return embeddings
|
return embeddings
|
||||||
|
|
||||||
@@ -315,27 +334,24 @@ class Embeddings:
|
|||||||
.paginate(current_page, batch_size)
|
.paginate(current_page, batch_size)
|
||||||
)
|
)
|
||||||
|
|
||||||
while len(events) > 0:
|
while events:
|
||||||
event: Event
|
event: Event
|
||||||
batch_thumbs = {}
|
batch_thumbs = {}
|
||||||
batch_descs = {}
|
batch_descs = {}
|
||||||
for event in events:
|
for event in events:
|
||||||
thumbnail = get_event_thumbnail_bytes(event)
|
totals["processed_objects"] += 1
|
||||||
|
|
||||||
if thumbnail is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
batch_thumbs[event.id] = thumbnail
|
|
||||||
totals["thumbnails"] += 1
|
|
||||||
|
|
||||||
if description := event.data.get("description", "").strip():
|
if description := event.data.get("description", "").strip():
|
||||||
batch_descs[event.id] = description
|
batch_descs[event.id] = description
|
||||||
totals["descriptions"] += 1
|
totals["descriptions"] += 1
|
||||||
|
|
||||||
totals["processed_objects"] += 1
|
if thumbnail := get_event_thumbnail_bytes(event):
|
||||||
|
batch_thumbs[event.id] = thumbnail
|
||||||
|
totals["thumbnails"] += 1
|
||||||
|
|
||||||
# run batch embedding
|
# run batch embedding
|
||||||
self.batch_embed_thumbnail(batch_thumbs)
|
if batch_thumbs:
|
||||||
|
self.batch_embed_thumbnail(batch_thumbs)
|
||||||
|
|
||||||
if batch_descs:
|
if batch_descs:
|
||||||
self.batch_embed_description(batch_descs)
|
self.batch_embed_description(batch_descs)
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ FACENET_INPUT_SIZE = 160
|
|||||||
|
|
||||||
class FaceNetEmbedding(BaseEmbedding):
|
class FaceNetEmbedding(BaseEmbedding):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
super().__init__(
|
super().__init__(
|
||||||
model_name="facedet",
|
model_name="facedet",
|
||||||
model_file="facenet.tflite",
|
model_file="facenet.tflite",
|
||||||
download_urls={
|
download_urls={
|
||||||
"facenet.tflite": "https://github.com/NickM-27/facenet-onnx/releases/download/v1.0/facenet.tflite",
|
"facenet.tflite": f"{GITHUB_ENDPOINT}/NickM-27/facenet-onnx/releases/download/v1.0/facenet.tflite",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.download_path = os.path.join(MODEL_CACHE_DIR, self.model_name)
|
self.download_path = os.path.join(MODEL_CACHE_DIR, self.model_name)
|
||||||
@@ -110,11 +111,12 @@ class FaceNetEmbedding(BaseEmbedding):
|
|||||||
|
|
||||||
class ArcfaceEmbedding(BaseEmbedding):
|
class ArcfaceEmbedding(BaseEmbedding):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
super().__init__(
|
super().__init__(
|
||||||
model_name="facedet",
|
model_name="facedet",
|
||||||
model_file="arcface.onnx",
|
model_file="arcface.onnx",
|
||||||
download_urls={
|
download_urls={
|
||||||
"arcface.onnx": "https://github.com/NickM-27/facenet-onnx/releases/download/v1.0/arcface.onnx",
|
"arcface.onnx": f"{GITHUB_ENDPOINT}/NickM-27/facenet-onnx/releases/download/v1.0/arcface.onnx",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.download_path = os.path.join(MODEL_CACHE_DIR, self.model_name)
|
self.download_path = os.path.join(MODEL_CACHE_DIR, self.model_name)
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ class PaddleOCRDetection(BaseEmbedding):
|
|||||||
model_file = (
|
model_file = (
|
||||||
"detection-large.onnx" if model_size == "large" else "detection-small.onnx"
|
"detection-large.onnx" if model_size == "large" else "detection-small.onnx"
|
||||||
)
|
)
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
super().__init__(
|
super().__init__(
|
||||||
model_name="paddleocr-onnx",
|
model_name="paddleocr-onnx",
|
||||||
model_file=model_file,
|
model_file=model_file,
|
||||||
download_urls={
|
download_urls={
|
||||||
model_file: f"https://github.com/hawkeye217/paddleocr-onnx/raw/refs/heads/master/models/{model_file}"
|
model_file: f"{GITHUB_ENDPOINT}/hawkeye217/paddleocr-onnx/raw/refs/heads/master/models/{model_file}"
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.requestor = requestor
|
self.requestor = requestor
|
||||||
@@ -94,11 +95,12 @@ class PaddleOCRClassification(BaseEmbedding):
|
|||||||
requestor: InterProcessRequestor,
|
requestor: InterProcessRequestor,
|
||||||
device: str = "AUTO",
|
device: str = "AUTO",
|
||||||
):
|
):
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
super().__init__(
|
super().__init__(
|
||||||
model_name="paddleocr-onnx",
|
model_name="paddleocr-onnx",
|
||||||
model_file="classification.onnx",
|
model_file="classification.onnx",
|
||||||
download_urls={
|
download_urls={
|
||||||
"classification.onnx": "https://github.com/hawkeye217/paddleocr-onnx/raw/refs/heads/master/models/classification.onnx"
|
"classification.onnx": f"{GITHUB_ENDPOINT}/hawkeye217/paddleocr-onnx/raw/refs/heads/master/models/classification.onnx"
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.requestor = requestor
|
self.requestor = requestor
|
||||||
@@ -154,11 +156,12 @@ class PaddleOCRRecognition(BaseEmbedding):
|
|||||||
requestor: InterProcessRequestor,
|
requestor: InterProcessRequestor,
|
||||||
device: str = "AUTO",
|
device: str = "AUTO",
|
||||||
):
|
):
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
super().__init__(
|
super().__init__(
|
||||||
model_name="paddleocr-onnx",
|
model_name="paddleocr-onnx",
|
||||||
model_file="recognition.onnx",
|
model_file="recognition.onnx",
|
||||||
download_urls={
|
download_urls={
|
||||||
"recognition.onnx": "https://github.com/hawkeye217/paddleocr-onnx/raw/refs/heads/master/models/recognition.onnx"
|
"recognition.onnx": f"{GITHUB_ENDPOINT}/hawkeye217/paddleocr-onnx/raw/refs/heads/master/models/recognition.onnx"
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.requestor = requestor
|
self.requestor = requestor
|
||||||
@@ -214,11 +217,12 @@ class LicensePlateDetector(BaseEmbedding):
|
|||||||
requestor: InterProcessRequestor,
|
requestor: InterProcessRequestor,
|
||||||
device: str = "AUTO",
|
device: str = "AUTO",
|
||||||
):
|
):
|
||||||
|
GITHUB_ENDPOINT = os.environ.get("GITHUB_ENDPOINT", "https://github.com")
|
||||||
super().__init__(
|
super().__init__(
|
||||||
model_name="yolov9_license_plate",
|
model_name="yolov9_license_plate",
|
||||||
model_file="yolov9-256-license-plates.onnx",
|
model_file="yolov9-256-license-plates.onnx",
|
||||||
download_urls={
|
download_urls={
|
||||||
"yolov9-256-license-plates.onnx": "https://github.com/hawkeye217/yolov9-license-plates/raw/refs/heads/master/models/yolov9-256-license-plates.onnx"
|
"yolov9-256-license-plates.onnx": f"{GITHUB_ENDPOINT}/hawkeye217/yolov9-license-plates/raw/refs/heads/master/models/yolov9-256-license-plates.onnx"
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -340,21 +340,22 @@ class EventCleanup(threading.Thread):
|
|||||||
.where(Event.has_clip == False, Event.has_snapshot == False)
|
.where(Event.has_clip == False, Event.has_snapshot == False)
|
||||||
.iterator()
|
.iterator()
|
||||||
)
|
)
|
||||||
events_to_delete = [e.id for e in events]
|
events_to_delete: list[Event] = [e for e in events]
|
||||||
|
|
||||||
for e in events:
|
for e in events_to_delete:
|
||||||
delete_event_thumbnail(e)
|
delete_event_thumbnail(e)
|
||||||
|
|
||||||
logger.debug(f"Found {len(events_to_delete)} events that can be expired")
|
logger.debug(f"Found {len(events_to_delete)} events that can be expired")
|
||||||
if len(events_to_delete) > 0:
|
if len(events_to_delete) > 0:
|
||||||
for i in range(0, len(events_to_delete), CHUNK_SIZE):
|
ids_to_delete = [e.id for e in events_to_delete]
|
||||||
chunk = events_to_delete[i : i + CHUNK_SIZE]
|
for i in range(0, len(ids_to_delete), CHUNK_SIZE):
|
||||||
|
chunk = ids_to_delete[i : i + CHUNK_SIZE]
|
||||||
logger.debug(f"Deleting {len(chunk)} events from the database")
|
logger.debug(f"Deleting {len(chunk)} events from the database")
|
||||||
Event.delete().where(Event.id << chunk).execute()
|
Event.delete().where(Event.id << chunk).execute()
|
||||||
|
|
||||||
if self.config.semantic_search.enabled:
|
if self.config.semantic_search.enabled:
|
||||||
self.db.delete_embeddings_description(event_ids=chunk)
|
self.db.delete_embeddings_description(event_ids=chunk)
|
||||||
self.db.delete_embeddings_thumbnail(event_ids=chunk)
|
self.db.delete_embeddings_thumbnail(event_ids=chunk)
|
||||||
logger.debug(f"Deleted {len(events_to_delete)} embeddings")
|
logger.debug(f"Deleted {len(ids_to_delete)} embeddings")
|
||||||
|
|
||||||
logger.info("Exiting event cleanup...")
|
logger.info("Exiting event cleanup...")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from typing import Any
|
|||||||
from frigate.const import (
|
from frigate.const import (
|
||||||
FFMPEG_HVC1_ARGS,
|
FFMPEG_HVC1_ARGS,
|
||||||
FFMPEG_HWACCEL_NVIDIA,
|
FFMPEG_HWACCEL_NVIDIA,
|
||||||
|
FFMPEG_HWACCEL_RKMPP,
|
||||||
FFMPEG_HWACCEL_VAAPI,
|
FFMPEG_HWACCEL_VAAPI,
|
||||||
FFMPEG_HWACCEL_VULKAN,
|
FFMPEG_HWACCEL_VULKAN,
|
||||||
LIBAVFORMAT_VERSION_MAJOR,
|
LIBAVFORMAT_VERSION_MAJOR,
|
||||||
@@ -70,8 +71,7 @@ PRESETS_HW_ACCEL_DECODE = {
|
|||||||
FFMPEG_HWACCEL_NVIDIA: "-hwaccel cuda -hwaccel_output_format cuda",
|
FFMPEG_HWACCEL_NVIDIA: "-hwaccel cuda -hwaccel_output_format cuda",
|
||||||
"preset-jetson-h264": "-c:v h264_nvmpi -resize {1}x{2}",
|
"preset-jetson-h264": "-c:v h264_nvmpi -resize {1}x{2}",
|
||||||
"preset-jetson-h265": "-c:v hevc_nvmpi -resize {1}x{2}",
|
"preset-jetson-h265": "-c:v hevc_nvmpi -resize {1}x{2}",
|
||||||
"preset-rk-h264": "-hwaccel rkmpp -hwaccel_output_format drm_prime",
|
f"{FFMPEG_HWACCEL_RKMPP}-no-dump_extra": "-hwaccel rkmpp -hwaccel_output_format drm_prime",
|
||||||
"preset-rk-h265": "-hwaccel rkmpp -hwaccel_output_format drm_prime",
|
|
||||||
# experimental presets
|
# experimental presets
|
||||||
FFMPEG_HWACCEL_VULKAN: "-hwaccel vulkan -init_hw_device vulkan=gpu:0 -filter_hw_device gpu -hwaccel_output_format vulkan",
|
FFMPEG_HWACCEL_VULKAN: "-hwaccel vulkan -init_hw_device vulkan=gpu:0 -filter_hw_device gpu -hwaccel_output_format vulkan",
|
||||||
}
|
}
|
||||||
@@ -85,6 +85,16 @@ PRESETS_HW_ACCEL_DECODE["preset-nvidia-mjpeg"] = PRESETS_HW_ACCEL_DECODE[
|
|||||||
FFMPEG_HWACCEL_NVIDIA
|
FFMPEG_HWACCEL_NVIDIA
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PRESETS_HW_ACCEL_DECODE[FFMPEG_HWACCEL_RKMPP] = (
|
||||||
|
f"{PRESETS_HW_ACCEL_DECODE[f'{FFMPEG_HWACCEL_RKMPP}-no-dump_extra']}{' -bsf:v dump_extra' if LIBAVFORMAT_VERSION_MAJOR >= 61 else ''}"
|
||||||
|
)
|
||||||
|
PRESETS_HW_ACCEL_DECODE["preset-rk-h264"] = PRESETS_HW_ACCEL_DECODE[
|
||||||
|
FFMPEG_HWACCEL_RKMPP
|
||||||
|
]
|
||||||
|
PRESETS_HW_ACCEL_DECODE["preset-rk-h265"] = PRESETS_HW_ACCEL_DECODE[
|
||||||
|
FFMPEG_HWACCEL_RKMPP
|
||||||
|
]
|
||||||
|
|
||||||
PRESETS_HW_ACCEL_SCALE = {
|
PRESETS_HW_ACCEL_SCALE = {
|
||||||
"preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}",
|
"preset-rpi-64-h264": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||||
"preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}",
|
"preset-rpi-64-h265": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||||
@@ -94,8 +104,7 @@ PRESETS_HW_ACCEL_SCALE = {
|
|||||||
FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2},hwdownload,format=nv12,eq=gamma=1.4:gamma_weight=0.5",
|
FFMPEG_HWACCEL_NVIDIA: "-r {0} -vf fps={0},scale_cuda=w={1}:h={2},hwdownload,format=nv12,eq=gamma=1.4:gamma_weight=0.5",
|
||||||
"preset-jetson-h264": "-r {0}", # scaled in decoder
|
"preset-jetson-h264": "-r {0}", # scaled in decoder
|
||||||
"preset-jetson-h265": "-r {0}", # scaled in decoder
|
"preset-jetson-h265": "-r {0}", # scaled in decoder
|
||||||
"preset-rk-h264": "-r {0} -vf scale_rkrga=w={1}:h={2}:format=yuv420p:force_original_aspect_ratio=0,hwmap=mode=read,format=yuv420p",
|
FFMPEG_HWACCEL_RKMPP: "-r {0} -vf scale_rkrga=w={1}:h={2}:format=yuv420p:force_original_aspect_ratio=0,hwmap=mode=read,format=yuv420p",
|
||||||
"preset-rk-h265": "-r {0} -vf scale_rkrga=w={1}:h={2}:format=yuv420p:force_original_aspect_ratio=0,hwmap=mode=read,format=yuv420p",
|
|
||||||
"default": "-r {0} -vf fps={0},scale={1}:{2}",
|
"default": "-r {0} -vf fps={0},scale={1}:{2}",
|
||||||
# experimental presets
|
# experimental presets
|
||||||
FFMPEG_HWACCEL_VULKAN: "-r {0} -vf fps={0},hwupload,scale_vulkan=w={1}:h={2},hwdownload",
|
FFMPEG_HWACCEL_VULKAN: "-r {0} -vf fps={0},hwupload,scale_vulkan=w={1}:h={2},hwdownload",
|
||||||
@@ -107,6 +116,12 @@ PRESETS_HW_ACCEL_SCALE["preset-nvidia-h265"] = PRESETS_HW_ACCEL_SCALE[
|
|||||||
FFMPEG_HWACCEL_NVIDIA
|
FFMPEG_HWACCEL_NVIDIA
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PRESETS_HW_ACCEL_SCALE[f"{FFMPEG_HWACCEL_RKMPP}-no-dump_extra"] = (
|
||||||
|
PRESETS_HW_ACCEL_SCALE[FFMPEG_HWACCEL_RKMPP]
|
||||||
|
)
|
||||||
|
PRESETS_HW_ACCEL_SCALE["preset-rk-h264"] = PRESETS_HW_ACCEL_SCALE[FFMPEG_HWACCEL_RKMPP]
|
||||||
|
PRESETS_HW_ACCEL_SCALE["preset-rk-h265"] = PRESETS_HW_ACCEL_SCALE[FFMPEG_HWACCEL_RKMPP]
|
||||||
|
|
||||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
|
||||||
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m {2}",
|
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m {2}",
|
||||||
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m {2}",
|
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m {2}",
|
||||||
@@ -116,7 +131,7 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE = {
|
|||||||
FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}",
|
FFMPEG_HWACCEL_NVIDIA: "{0} -hide_banner {1} -c:v h264_nvenc -g 50 -profile:v high -level:v auto -preset:v p2 -tune:v ll {2}",
|
||||||
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
|
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
|
||||||
"preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}",
|
"preset-jetson-h265": "{0} -hide_banner {1} -c:v h264_nvmpi -profile main {2}",
|
||||||
"preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
|
FFMPEG_HWACCEL_RKMPP: "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
|
||||||
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v main {2}",
|
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v main {2}",
|
||||||
"default": "{0} -hide_banner {1} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {2}",
|
"default": "{0} -hide_banner {1} -c:v libx264 -g 50 -profile:v high -level:v 4.1 -preset:v superfast -tune:v zerolatency {2}",
|
||||||
}
|
}
|
||||||
@@ -127,6 +142,13 @@ PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-nvidia-h265"] = (
|
|||||||
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA]
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_NVIDIA]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[f"{FFMPEG_HWACCEL_RKMPP}-no-dump_extra"] = (
|
||||||
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[FFMPEG_HWACCEL_RKMPP]
|
||||||
|
)
|
||||||
|
PRESETS_HW_ACCEL_ENCODE_BIRDSEYE["preset-rk-h264"] = PRESETS_HW_ACCEL_ENCODE_BIRDSEYE[
|
||||||
|
FFMPEG_HWACCEL_RKMPP
|
||||||
|
]
|
||||||
|
|
||||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
||||||
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m -pix_fmt yuv420p {2}",
|
"preset-rpi-64-h264": "{0} -hide_banner {1} -c:v h264_v4l2m2m -pix_fmt yuv420p {2}",
|
||||||
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m -pix_fmt yuv420p {2}",
|
"preset-rpi-64-h265": "{0} -hide_banner {1} -c:v hevc_v4l2m2m -pix_fmt yuv420p {2}",
|
||||||
@@ -137,7 +159,7 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE = {
|
|||||||
"preset-nvidia-h265": "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v hevc_nvenc {2}",
|
"preset-nvidia-h265": "{0} -hide_banner -hwaccel cuda -hwaccel_output_format cuda -extra_hw_frames 8 {1} -c:v hevc_nvenc {2}",
|
||||||
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
|
"preset-jetson-h264": "{0} -hide_banner {1} -c:v h264_nvmpi -profile high {2}",
|
||||||
"preset-jetson-h265": "{0} -hide_banner {1} -c:v hevc_nvmpi -profile main {2}",
|
"preset-jetson-h265": "{0} -hide_banner {1} -c:v hevc_nvmpi -profile main {2}",
|
||||||
"preset-rk-h264": "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
|
FFMPEG_HWACCEL_RKMPP: "{0} -hide_banner {1} -c:v h264_rkmpp -profile:v high {2}",
|
||||||
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v main {2}",
|
"preset-rk-h265": "{0} -hide_banner {1} -c:v hevc_rkmpp -profile:v main {2}",
|
||||||
"default": "{0} -hide_banner {1} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {2}",
|
"default": "{0} -hide_banner {1} -c:v libx264 -preset:v ultrafast -tune:v zerolatency {2}",
|
||||||
}
|
}
|
||||||
@@ -145,6 +167,13 @@ PRESETS_HW_ACCEL_ENCODE_TIMELAPSE["preset-nvidia-h264"] = (
|
|||||||
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[FFMPEG_HWACCEL_NVIDIA]
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[FFMPEG_HWACCEL_NVIDIA]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[f"{FFMPEG_HWACCEL_RKMPP}-no-dump_extra"] = (
|
||||||
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[FFMPEG_HWACCEL_RKMPP]
|
||||||
|
)
|
||||||
|
PRESETS_HW_ACCEL_ENCODE_TIMELAPSE["preset-rk-h264"] = PRESETS_HW_ACCEL_ENCODE_TIMELAPSE[
|
||||||
|
FFMPEG_HWACCEL_RKMPP
|
||||||
|
]
|
||||||
|
|
||||||
# encoding of previews is only done on CPU due to comparable encode times and better quality from libx264
|
# encoding of previews is only done on CPU due to comparable encode times and better quality from libx264
|
||||||
PRESETS_HW_ACCEL_ENCODE_PREVIEW = {
|
PRESETS_HW_ACCEL_ENCODE_PREVIEW = {
|
||||||
"default": "{0} -hide_banner {1} -c:v libx264 -profile:v baseline -preset:v ultrafast {2}",
|
"default": "{0} -hide_banner {1} -c:v libx264 -profile:v baseline -preset:v ultrafast {2}",
|
||||||
@@ -491,6 +520,6 @@ def parse_preset_output_record(arg: Any, force_record_hvc1: bool) -> list[str]:
|
|||||||
|
|
||||||
if force_record_hvc1:
|
if force_record_hvc1:
|
||||||
# Apple only supports HEVC if it is hvc1 (vs. hev1)
|
# Apple only supports HEVC if it is hvc1 (vs. hev1)
|
||||||
preset += FFMPEG_HVC1_ARGS
|
return preset + FFMPEG_HVC1_ARGS
|
||||||
|
|
||||||
return preset
|
return preset
|
||||||
|
|||||||
@@ -40,10 +40,15 @@ class GenAIClient:
|
|||||||
event: Event,
|
event: Event,
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""Generate a description for the frame."""
|
"""Generate a description for the frame."""
|
||||||
prompt = camera_config.genai.object_prompts.get(
|
try:
|
||||||
event.label,
|
prompt = camera_config.genai.object_prompts.get(
|
||||||
camera_config.genai.prompt,
|
event.label,
|
||||||
).format(**model_to_dict(event))
|
camera_config.genai.prompt,
|
||||||
|
).format(**model_to_dict(event))
|
||||||
|
except KeyError as e:
|
||||||
|
logger.error(f"Invalid key in GenAI prompt: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
logger.debug(f"Sending images to genai provider with prompt: {prompt}")
|
logger.debug(f"Sending images to genai provider with prompt: {prompt}")
|
||||||
return self._send(prompt, thumbnails)
|
return self._send(prompt, thumbnails)
|
||||||
|
|
||||||
|
|||||||
@@ -369,12 +369,13 @@ class PtzAutoTracker:
|
|||||||
logger.info(f"Camera calibration for {camera} in progress")
|
logger.info(f"Camera calibration for {camera} in progress")
|
||||||
|
|
||||||
# zoom levels test
|
# zoom levels test
|
||||||
|
self.zoom_time[camera] = 0
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.config.cameras[camera].onvif.autotracking.zooming
|
self.config.cameras[camera].onvif.autotracking.zooming
|
||||||
!= ZoomingModeEnum.disabled
|
!= ZoomingModeEnum.disabled
|
||||||
):
|
):
|
||||||
logger.info(f"Calibration for {camera} in progress: 0% complete")
|
logger.info(f"Calibration for {camera} in progress: 0% complete")
|
||||||
self.zoom_time[camera] = 0
|
|
||||||
|
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
# absolute move to 0 - fully zoomed out
|
# absolute move to 0 - fully zoomed out
|
||||||
@@ -1329,7 +1330,11 @@ class PtzAutoTracker:
|
|||||||
|
|
||||||
if camera_config.onvif.autotracking.enabled:
|
if camera_config.onvif.autotracking.enabled:
|
||||||
if not self.autotracker_init[camera]:
|
if not self.autotracker_init[camera]:
|
||||||
self._autotracker_setup(camera_config, camera)
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self._autotracker_setup(camera_config, camera), self.onvif.loop
|
||||||
|
)
|
||||||
|
# Wait for the coroutine to complete
|
||||||
|
future.result()
|
||||||
|
|
||||||
if self.calibrating[camera]:
|
if self.calibrating[camera]:
|
||||||
logger.debug(f"{camera}: Calibrating camera")
|
logger.debug(f"{camera}: Calibrating camera")
|
||||||
@@ -1476,7 +1481,8 @@ class PtzAutoTracker:
|
|||||||
self.tracked_object[camera] = None
|
self.tracked_object[camera] = None
|
||||||
self.tracked_object_history[camera].clear()
|
self.tracked_object_history[camera].clear()
|
||||||
|
|
||||||
self.ptz_metrics[camera].motor_stopped.wait()
|
while not self.ptz_metrics[camera].motor_stopped.is_set():
|
||||||
|
await self.onvif.get_camera_status(camera)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera}: Time is {self.ptz_metrics[camera].frame_time.value}, returning to preset: {autotracker_config.return_preset}"
|
f"{camera}: Time is {self.ptz_metrics[camera].frame_time.value}, returning to preset: {autotracker_config.return_preset}"
|
||||||
)
|
)
|
||||||
@@ -1486,7 +1492,7 @@ class PtzAutoTracker:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# update stored zoom level from preset
|
# update stored zoom level from preset
|
||||||
if not self.ptz_metrics[camera].motor_stopped.is_set():
|
while not self.ptz_metrics[camera].motor_stopped.is_set():
|
||||||
await self.onvif.get_camera_status(camera)
|
await self.onvif.get_camera_status(camera)
|
||||||
|
|
||||||
self.ptz_metrics[camera].tracking_active.clear()
|
self.ptz_metrics[camera].tracking_active.clear()
|
||||||
|
|||||||
+99
-81
@@ -48,6 +48,8 @@ class OnvifController:
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.ptz_metrics = ptz_metrics
|
self.ptz_metrics = ptz_metrics
|
||||||
|
|
||||||
|
self.status_locks: dict[str, asyncio.Lock] = {}
|
||||||
|
|
||||||
# Create a dedicated event loop and run it in a separate thread
|
# Create a dedicated event loop and run it in a separate thread
|
||||||
self.loop = asyncio.new_event_loop()
|
self.loop = asyncio.new_event_loop()
|
||||||
self.loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
self.loop_thread = threading.Thread(target=self._run_event_loop, daemon=True)
|
||||||
@@ -59,6 +61,7 @@ class OnvifController:
|
|||||||
continue
|
continue
|
||||||
if cam.onvif.host:
|
if cam.onvif.host:
|
||||||
self.camera_configs[cam_name] = cam
|
self.camera_configs[cam_name] = cam
|
||||||
|
self.status_locks[cam_name] = asyncio.Lock()
|
||||||
|
|
||||||
asyncio.run_coroutine_threadsafe(self._init_cameras(), self.loop)
|
asyncio.run_coroutine_threadsafe(self._init_cameras(), self.loop)
|
||||||
|
|
||||||
@@ -265,9 +268,15 @@ class OnvifController:
|
|||||||
"RelativeZoomTranslationSpace"
|
"RelativeZoomTranslationSpace"
|
||||||
][zoom_space_id]["URI"]
|
][zoom_space_id]["URI"]
|
||||||
else:
|
else:
|
||||||
if "Zoom" in move_request["Translation"]:
|
if (
|
||||||
|
move_request["Translation"] is not None
|
||||||
|
and "Zoom" in move_request["Translation"]
|
||||||
|
):
|
||||||
del move_request["Translation"]["Zoom"]
|
del move_request["Translation"]["Zoom"]
|
||||||
if "Zoom" in move_request["Speed"]:
|
if (
|
||||||
|
move_request["Speed"] is not None
|
||||||
|
and "Zoom" in move_request["Speed"]
|
||||||
|
):
|
||||||
del move_request["Speed"]["Zoom"]
|
del move_request["Speed"]["Zoom"]
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera_name}: Relative move request after deleting zoom: {move_request}"
|
f"{camera_name}: Relative move request after deleting zoom: {move_request}"
|
||||||
@@ -758,101 +767,110 @@ class OnvifController:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_camera_status(self, camera_name: str) -> None:
|
async def get_camera_status(self, camera_name: str) -> None:
|
||||||
if camera_name not in self.cams.keys():
|
async with self.status_locks[camera_name]:
|
||||||
logger.error(f"ONVIF is not configured for {camera_name}")
|
if camera_name not in self.cams.keys():
|
||||||
return
|
logger.error(f"ONVIF is not configured for {camera_name}")
|
||||||
|
|
||||||
if not self.cams[camera_name]["init"]:
|
|
||||||
if not await self._init_onvif(camera_name):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
status_request = self.cams[camera_name]["status_request"]
|
if not self.cams[camera_name]["init"]:
|
||||||
try:
|
if not await self._init_onvif(camera_name):
|
||||||
status = await self.cams[camera_name]["ptz"].GetStatus(status_request)
|
return
|
||||||
except Exception:
|
|
||||||
pass # We're unsupported, that'll be reported in the next check.
|
|
||||||
|
|
||||||
try:
|
status_request = self.cams[camera_name]["status_request"]
|
||||||
pan_tilt_status = getattr(status.MoveStatus, "PanTilt", None)
|
try:
|
||||||
zoom_status = getattr(status.MoveStatus, "Zoom", None)
|
status = await self.cams[camera_name]["ptz"].GetStatus(status_request)
|
||||||
|
except Exception:
|
||||||
|
pass # We're unsupported, that'll be reported in the next check.
|
||||||
|
|
||||||
# if it's not an attribute, see if MoveStatus even exists in the status result
|
try:
|
||||||
if pan_tilt_status is None:
|
pan_tilt_status = getattr(status.MoveStatus, "PanTilt", None)
|
||||||
pan_tilt_status = getattr(status, "MoveStatus", None)
|
zoom_status = getattr(status.MoveStatus, "Zoom", None)
|
||||||
|
|
||||||
# we're unsupported
|
# if it's not an attribute, see if MoveStatus even exists in the status result
|
||||||
if pan_tilt_status is None or pan_tilt_status not in [
|
if pan_tilt_status is None:
|
||||||
"IDLE",
|
pan_tilt_status = getattr(status, "MoveStatus", None)
|
||||||
"MOVING",
|
|
||||||
]:
|
# we're unsupported
|
||||||
raise Exception
|
if pan_tilt_status is None or pan_tilt_status not in [
|
||||||
except Exception:
|
"IDLE",
|
||||||
logger.warning(
|
"MOVING",
|
||||||
f"Camera {camera_name} does not support the ONVIF GetStatus method. Autotracking will not function correctly and must be disabled in your config."
|
]:
|
||||||
|
raise Exception
|
||||||
|
except Exception:
|
||||||
|
logger.warning(
|
||||||
|
f"Camera {camera_name} does not support the ONVIF GetStatus method. Autotracking will not function correctly and must be disabled in your config."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"{camera_name}: Pan/tilt status: {pan_tilt_status}, Zoom status: {zoom_status}"
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
if pan_tilt_status == "IDLE" and (zoom_status is None or zoom_status == "IDLE"):
|
if pan_tilt_status == "IDLE" and (
|
||||||
self.cams[camera_name]["active"] = False
|
zoom_status is None or zoom_status == "IDLE"
|
||||||
if not self.ptz_metrics[camera_name].motor_stopped.is_set():
|
):
|
||||||
self.ptz_metrics[camera_name].motor_stopped.set()
|
self.cams[camera_name]["active"] = False
|
||||||
|
if not self.ptz_metrics[camera_name].motor_stopped.is_set():
|
||||||
|
self.ptz_metrics[camera_name].motor_stopped.set()
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"{camera_name}: PTZ stop time: {self.ptz_metrics[camera_name].frame_time.value}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ptz_metrics[camera_name].stop_time.value = self.ptz_metrics[
|
||||||
|
camera_name
|
||||||
|
].frame_time.value
|
||||||
|
else:
|
||||||
|
self.cams[camera_name]["active"] = True
|
||||||
|
if self.ptz_metrics[camera_name].motor_stopped.is_set():
|
||||||
|
self.ptz_metrics[camera_name].motor_stopped.clear()
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name].frame_time.value}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ptz_metrics[camera_name].start_time.value = self.ptz_metrics[
|
||||||
|
camera_name
|
||||||
|
].frame_time.value
|
||||||
|
self.ptz_metrics[camera_name].stop_time.value = 0
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.config.cameras[camera_name].onvif.autotracking.zooming
|
||||||
|
!= ZoomingModeEnum.disabled
|
||||||
|
):
|
||||||
|
# store absolute zoom level as 0 to 1 interpolated from the values of the camera
|
||||||
|
self.ptz_metrics[camera_name].zoom_level.value = numpy.interp(
|
||||||
|
round(status.Position.Zoom.x, 2),
|
||||||
|
[
|
||||||
|
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Min"],
|
||||||
|
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Max"],
|
||||||
|
],
|
||||||
|
[0, 1],
|
||||||
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{camera_name}: PTZ stop time: {self.ptz_metrics[camera_name].frame_time.value}"
|
f"{camera_name}: Camera zoom level: {self.ptz_metrics[camera_name].zoom_level.value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# some hikvision cams won't update MoveStatus, so warn if it hasn't changed
|
||||||
|
if (
|
||||||
|
not self.ptz_metrics[camera_name].motor_stopped.is_set()
|
||||||
|
and not self.ptz_metrics[camera_name].reset.is_set()
|
||||||
|
and self.ptz_metrics[camera_name].start_time.value != 0
|
||||||
|
and self.ptz_metrics[camera_name].frame_time.value
|
||||||
|
> (self.ptz_metrics[camera_name].start_time.value + 10)
|
||||||
|
and self.ptz_metrics[camera_name].stop_time.value == 0
|
||||||
|
):
|
||||||
|
logger.debug(
|
||||||
|
f"Start time: {self.ptz_metrics[camera_name].start_time.value}, Stop time: {self.ptz_metrics[camera_name].stop_time.value}, Frame time: {self.ptz_metrics[camera_name].frame_time.value}"
|
||||||
|
)
|
||||||
|
# set the stop time so we don't come back into this again and spam the logs
|
||||||
self.ptz_metrics[camera_name].stop_time.value = self.ptz_metrics[
|
self.ptz_metrics[camera_name].stop_time.value = self.ptz_metrics[
|
||||||
camera_name
|
camera_name
|
||||||
].frame_time.value
|
].frame_time.value
|
||||||
else:
|
logger.warning(
|
||||||
self.cams[camera_name]["active"] = True
|
f"Camera {camera_name} is still in ONVIF 'MOVING' status."
|
||||||
if self.ptz_metrics[camera_name].motor_stopped.is_set():
|
|
||||||
self.ptz_metrics[camera_name].motor_stopped.clear()
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"{camera_name}: PTZ start time: {self.ptz_metrics[camera_name].frame_time.value}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ptz_metrics[camera_name].start_time.value = self.ptz_metrics[
|
|
||||||
camera_name
|
|
||||||
].frame_time.value
|
|
||||||
self.ptz_metrics[camera_name].stop_time.value = 0
|
|
||||||
|
|
||||||
if (
|
|
||||||
self.config.cameras[camera_name].onvif.autotracking.zooming
|
|
||||||
!= ZoomingModeEnum.disabled
|
|
||||||
):
|
|
||||||
# store absolute zoom level as 0 to 1 interpolated from the values of the camera
|
|
||||||
self.ptz_metrics[camera_name].zoom_level.value = numpy.interp(
|
|
||||||
round(status.Position.Zoom.x, 2),
|
|
||||||
[
|
|
||||||
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Min"],
|
|
||||||
self.cams[camera_name]["absolute_zoom_range"]["XRange"]["Max"],
|
|
||||||
],
|
|
||||||
[0, 1],
|
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
f"{camera_name}: Camera zoom level: {self.ptz_metrics[camera_name].zoom_level.value}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# some hikvision cams won't update MoveStatus, so warn if it hasn't changed
|
|
||||||
if (
|
|
||||||
not self.ptz_metrics[camera_name].motor_stopped.is_set()
|
|
||||||
and not self.ptz_metrics[camera_name].reset.is_set()
|
|
||||||
and self.ptz_metrics[camera_name].start_time.value != 0
|
|
||||||
and self.ptz_metrics[camera_name].frame_time.value
|
|
||||||
> (self.ptz_metrics[camera_name].start_time.value + 10)
|
|
||||||
and self.ptz_metrics[camera_name].stop_time.value == 0
|
|
||||||
):
|
|
||||||
logger.debug(
|
|
||||||
f"Start time: {self.ptz_metrics[camera_name].start_time.value}, Stop time: {self.ptz_metrics[camera_name].stop_time.value}, Frame time: {self.ptz_metrics[camera_name].frame_time.value}"
|
|
||||||
)
|
|
||||||
# set the stop time so we don't come back into this again and spam the logs
|
|
||||||
self.ptz_metrics[camera_name].stop_time.value = self.ptz_metrics[
|
|
||||||
camera_name
|
|
||||||
].frame_time.value
|
|
||||||
logger.warning(f"Camera {camera_name} is still in ONVIF 'MOVING' status.")
|
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
"""Gracefully shut down the ONVIF controller."""
|
"""Gracefully shut down the ONVIF controller."""
|
||||||
if not hasattr(self, "loop") or self.loop.is_closed():
|
if not hasattr(self, "loop") or self.loop.is_closed():
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class RecordingExporter(threading.Thread):
|
|||||||
minutes = int(diff / 60)
|
minutes = int(diff / 60)
|
||||||
seconds = int(diff % 60)
|
seconds = int(diff % 60)
|
||||||
ffmpeg_cmd = [
|
ffmpeg_cmd = [
|
||||||
self.config.ffmpeg.ffmpeg_path,
|
"/usr/lib/ffmpeg/7.0/bin/ffmpeg", # hardcode path for exports thumbnail due to missing libwebp support
|
||||||
"-hide_banner",
|
"-hide_banner",
|
||||||
"-loglevel",
|
"-loglevel",
|
||||||
"warning",
|
"warning",
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ def sync_recordings(limited: bool) -> None:
|
|||||||
|
|
||||||
if float(len(recordings_to_delete)) / max(1, recordings.count()) > 0.5:
|
if float(len(recordings_to_delete)) / max(1, recordings.count()) > 0.5:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"Deleting {(float(len(recordings_to_delete)) / recordings.count()):2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
f"Deleting {(len(recordings_to_delete) / max(1, recordings.count()) * 100):.2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ def sync_recordings(limited: bool) -> None:
|
|||||||
|
|
||||||
if float(len(files_to_delete)) / max(1, len(files_on_disk)) > 0.5:
|
if float(len(files_to_delete)) / max(1, len(files_on_disk)) > 0.5:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Deleting {(float(len(files_to_delete)) / len(files_on_disk)):2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
f"Deleting {(len(files_to_delete) / max(1, len(files_on_disk)) * 100):.2f}% of recordings DB entries, could be due to configuration error. Aborting..."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ async def set_gpu_stats(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# intel QSV GPU
|
# intel QSV GPU
|
||||||
intel_usage = get_intel_gpu_stats(config.telemetry.stats.sriov)
|
intel_usage = get_intel_gpu_stats(config.telemetry.stats.intel_gpu_device)
|
||||||
|
|
||||||
if intel_usage is not None:
|
if intel_usage is not None:
|
||||||
stats["intel-qsv"] = intel_usage or {"gpu": "", "mem": ""}
|
stats["intel-qsv"] = intel_usage or {"gpu": "", "mem": ""}
|
||||||
@@ -226,7 +226,9 @@ async def set_gpu_stats(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# intel VAAPI GPU
|
# intel VAAPI GPU
|
||||||
intel_usage = get_intel_gpu_stats(config.telemetry.stats.sriov)
|
intel_usage = get_intel_gpu_stats(
|
||||||
|
config.telemetry.stats.intel_gpu_device
|
||||||
|
)
|
||||||
|
|
||||||
if intel_usage is not None:
|
if intel_usage is not None:
|
||||||
stats["intel-vaapi"] = intel_usage or {"gpu": "", "mem": ""}
|
stats["intel-vaapi"] = intel_usage or {"gpu": "", "mem": ""}
|
||||||
|
|||||||
@@ -48,8 +48,9 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
################################### GET /review Endpoint ########################################################
|
################################### GET /review Endpoint ########################################################
|
||||||
####################################################################################################################
|
####################################################################################################################
|
||||||
|
|
||||||
# Does not return any data point since the end time (before parameter) is not passed and the review segment end_time is 2 seconds from now
|
def test_get_review_that_overlaps_default_period(self):
|
||||||
def test_get_review_no_filters_no_matches(self):
|
"""Test that a review item that starts during the default period
|
||||||
|
but ends after is included in the results."""
|
||||||
now = datetime.now().timestamp()
|
now = datetime.now().timestamp()
|
||||||
|
|
||||||
with TestClient(self.app) as client:
|
with TestClient(self.app) as client:
|
||||||
@@ -57,7 +58,7 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
response = client.get("/review")
|
response = client.get("/review")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert len(response_json) == 0
|
assert len(response_json) == 1
|
||||||
|
|
||||||
def test_get_review_no_filters(self):
|
def test_get_review_no_filters(self):
|
||||||
now = datetime.now().timestamp()
|
now = datetime.now().timestamp()
|
||||||
@@ -73,11 +74,13 @@ class TestHttpReview(BaseTestHttp):
|
|||||||
assert response_json[0]["has_been_reviewed"] == False
|
assert response_json[0]["has_been_reviewed"] == False
|
||||||
|
|
||||||
def test_get_review_with_time_filter_no_matches(self):
|
def test_get_review_with_time_filter_no_matches(self):
|
||||||
|
"""Test that review items outside the range are not returned."""
|
||||||
now = datetime.now().timestamp()
|
now = datetime.now().timestamp()
|
||||||
|
|
||||||
with TestClient(self.app) as client:
|
with TestClient(self.app) as client:
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
super().insert_mock_review_segment(id, now, now + 2)
|
super().insert_mock_review_segment(id, now - 2, now - 1)
|
||||||
|
super().insert_mock_review_segment(f"{id}2", now + 4, now + 5)
|
||||||
params = {
|
params = {
|
||||||
"after": now,
|
"after": now,
|
||||||
"before": now + 3,
|
"before": now + 3,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from typing import Any
|
|||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from peewee import DoesNotExist
|
from peewee import SQL, DoesNotExist
|
||||||
|
|
||||||
from frigate.camera.state import CameraState
|
from frigate.camera.state import CameraState
|
||||||
from frigate.comms.config_updater import ConfigSubscriber
|
from frigate.comms.config_updater import ConfigSubscriber
|
||||||
@@ -29,9 +29,13 @@ from frigate.config import (
|
|||||||
RecordConfig,
|
RecordConfig,
|
||||||
SnapshotsConfig,
|
SnapshotsConfig,
|
||||||
)
|
)
|
||||||
from frigate.const import FAST_QUEUE_TIMEOUT, UPDATE_CAMERA_ACTIVITY
|
from frigate.const import (
|
||||||
|
FAST_QUEUE_TIMEOUT,
|
||||||
|
UPDATE_CAMERA_ACTIVITY,
|
||||||
|
UPSERT_REVIEW_SEGMENT,
|
||||||
|
)
|
||||||
from frigate.events.types import EventStateEnum, EventTypeEnum
|
from frigate.events.types import EventStateEnum, EventTypeEnum
|
||||||
from frigate.models import Event, Timeline
|
from frigate.models import Event, ReviewSegment, Timeline
|
||||||
from frigate.track.tracked_object import TrackedObject
|
from frigate.track.tracked_object import TrackedObject
|
||||||
from frigate.util.image import SharedMemoryFrameManager
|
from frigate.util.image import SharedMemoryFrameManager
|
||||||
|
|
||||||
@@ -152,7 +156,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def snapshot(camera, obj: TrackedObject, frame_name: str):
|
def snapshot(camera: str, obj: TrackedObject) -> bool:
|
||||||
mqtt_config: CameraMqttConfig = self.config.cameras[camera].mqtt
|
mqtt_config: CameraMqttConfig = self.config.cameras[camera].mqtt
|
||||||
if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
|
if mqtt_config.enabled and self.should_mqtt_snapshot(camera, obj):
|
||||||
jpg_bytes = obj.get_img_bytes(
|
jpg_bytes = obj.get_img_bytes(
|
||||||
@@ -185,6 +189,10 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
retain=True,
|
retain=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def camera_activity(camera, activity):
|
def camera_activity(camera, activity):
|
||||||
last_activity = self.camera_activity.get(camera)
|
last_activity = self.camera_activity.get(camera)
|
||||||
|
|
||||||
@@ -357,6 +365,60 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
data=Timeline.data.update({"sub_label": (sub_label, score)})
|
data=Timeline.data.update({"sub_label": (sub_label, score)})
|
||||||
).where(Timeline.source_id == event_id).execute()
|
).where(Timeline.source_id == event_id).execute()
|
||||||
|
|
||||||
|
# only update ended review segments
|
||||||
|
# manually updating a sub_label from the UI is only possible for ended tracked objects
|
||||||
|
try:
|
||||||
|
review_segment = ReviewSegment.get(
|
||||||
|
(
|
||||||
|
SQL(
|
||||||
|
"json_extract(data, '$.detections') LIKE ?",
|
||||||
|
[f'%"{event_id}"%'],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
& (ReviewSegment.end_time.is_null(False))
|
||||||
|
)
|
||||||
|
|
||||||
|
segment_data = review_segment.data
|
||||||
|
detection_ids = segment_data.get("detections", [])
|
||||||
|
|
||||||
|
# Rebuild objects list and sync sub_labels
|
||||||
|
objects_list = []
|
||||||
|
sub_labels = set()
|
||||||
|
events = Event.select(Event.id, Event.label, Event.sub_label).where(
|
||||||
|
Event.id.in_(detection_ids)
|
||||||
|
)
|
||||||
|
for det_event in events:
|
||||||
|
if det_event.sub_label:
|
||||||
|
sub_labels.add(det_event.sub_label)
|
||||||
|
objects_list.append(
|
||||||
|
f"{det_event.label}-verified"
|
||||||
|
) # eg, "bird-verified"
|
||||||
|
else:
|
||||||
|
objects_list.append(det_event.label) # eg, "bird"
|
||||||
|
|
||||||
|
segment_data["sub_labels"] = list(sub_labels)
|
||||||
|
segment_data["objects"] = objects_list
|
||||||
|
|
||||||
|
updated_data = {
|
||||||
|
ReviewSegment.id.name: review_segment.id,
|
||||||
|
ReviewSegment.camera.name: review_segment.camera,
|
||||||
|
ReviewSegment.start_time.name: review_segment.start_time,
|
||||||
|
ReviewSegment.end_time.name: review_segment.end_time,
|
||||||
|
ReviewSegment.severity.name: review_segment.severity,
|
||||||
|
ReviewSegment.thumb_path.name: review_segment.thumb_path,
|
||||||
|
ReviewSegment.data.name: segment_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.requestor.send_data(UPSERT_REVIEW_SEGMENT, updated_data)
|
||||||
|
logger.debug(
|
||||||
|
f"Updated sub_label for event {event_id} in review segment {review_segment.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except ReviewSegment.DoesNotExist:
|
||||||
|
logger.debug(
|
||||||
|
f"No review segment found with event ID {event_id} when updating sub_label"
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_recognized_license_plate(
|
def set_recognized_license_plate(
|
||||||
|
|||||||
@@ -350,6 +350,9 @@ class TrackedObject:
|
|||||||
|
|
||||||
self.obj_data.update(obj_data)
|
self.obj_data.update(obj_data)
|
||||||
self.current_zones = current_zones
|
self.current_zones = current_zones
|
||||||
|
logger.debug(
|
||||||
|
f"{self.camera_config.name}: Updating {obj_data['id']}: thumb update? {thumb_update}, significant change? {significant_change}, path update? {path_update}, autotracker update? {autotracker_update} "
|
||||||
|
)
|
||||||
return (thumb_update, significant_change, path_update, autotracker_update)
|
return (thumb_update, significant_change, path_update, autotracker_update)
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
@@ -416,7 +419,7 @@ class TrackedObject:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
best_frame = cv2.cvtColor(
|
best_frame = cv2.cvtColor(
|
||||||
self.frame_cache[self.thumbnail_data["frame_time"]],
|
self.frame_cache[self.thumbnail_data["frame_time"]]["frame"],
|
||||||
cv2.COLOR_YUV2BGR_I420,
|
cv2.COLOR_YUV2BGR_I420,
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -445,7 +448,7 @@ class TrackedObject:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
best_frame = cv2.cvtColor(
|
best_frame = cv2.cvtColor(
|
||||||
self.frame_cache[self.thumbnail_data["frame_time"]],
|
self.frame_cache[self.thumbnail_data["frame_time"]]["frame"],
|
||||||
cv2.COLOR_YUV2BGR_I420,
|
cv2.COLOR_YUV2BGR_I420,
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
@@ -345,6 +345,13 @@ def get_ort_providers(
|
|||||||
"device_type": device,
|
"device_type": device,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
elif provider == "MIGraphXExecutionProvider":
|
||||||
|
# MIGraphX uses more CPU than ROCM, while also being the same speed
|
||||||
|
if device == "MIGraphX":
|
||||||
|
providers.append(provider)
|
||||||
|
options.append({})
|
||||||
|
else:
|
||||||
|
continue
|
||||||
elif provider == "CPUExecutionProvider":
|
elif provider == "CPUExecutionProvider":
|
||||||
providers.append(provider)
|
providers.append(provider)
|
||||||
options.append(
|
options.append(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import resource
|
||||||
import signal
|
import signal
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import traceback
|
import traceback
|
||||||
@@ -256,7 +257,7 @@ def get_amd_gpu_stats() -> Optional[dict[str, str]]:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
def get_intel_gpu_stats(sriov: bool) -> Optional[dict[str, str]]:
|
def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, str]]:
|
||||||
"""Get stats using intel_gpu_top."""
|
"""Get stats using intel_gpu_top."""
|
||||||
|
|
||||||
def get_stats_manually(output: str) -> dict[str, str]:
|
def get_stats_manually(output: str) -> dict[str, str]:
|
||||||
@@ -300,17 +301,20 @@ def get_intel_gpu_stats(sriov: bool) -> Optional[dict[str, str]]:
|
|||||||
"-o",
|
"-o",
|
||||||
"-",
|
"-",
|
||||||
"-s",
|
"-s",
|
||||||
"1",
|
"1000", # Intel changed this from seconds to milliseconds in 2024+ versions
|
||||||
]
|
]
|
||||||
|
|
||||||
if sriov:
|
if intel_gpu_device:
|
||||||
intel_gpu_top_command += ["-d", "sriov"]
|
intel_gpu_top_command += ["-d", intel_gpu_device]
|
||||||
|
|
||||||
p = sp.run(
|
try:
|
||||||
intel_gpu_top_command,
|
p = sp.run(
|
||||||
encoding="ascii",
|
intel_gpu_top_command,
|
||||||
capture_output=True,
|
encoding="ascii",
|
||||||
)
|
capture_output=True,
|
||||||
|
)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return None
|
||||||
|
|
||||||
# timeout has a non-zero returncode when timeout is reached
|
# timeout has a non-zero returncode when timeout is reached
|
||||||
if p.returncode != 124:
|
if p.returncode != 124:
|
||||||
@@ -748,3 +752,19 @@ def process_logs(
|
|||||||
log_lines.append(dedup_message)
|
log_lines.append(dedup_message)
|
||||||
|
|
||||||
return len(log_lines), log_lines[start:end]
|
return len(log_lines), log_lines[start:end]
|
||||||
|
|
||||||
|
|
||||||
|
def set_file_limit() -> None:
|
||||||
|
# Newer versions of containerd 2.X+ impose a very low soft file limit of 1024
|
||||||
|
# This applies to OSs like HA OS (see https://github.com/home-assistant/operating-system/issues/4110)
|
||||||
|
# Attempt to increase this limit
|
||||||
|
soft_limit = int(os.getenv("SOFT_FILE_LIMIT", "65536") or "65536")
|
||||||
|
|
||||||
|
current_soft, current_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||||||
|
logger.debug(f"Current file limits - Soft: {current_soft}, Hard: {current_hard}")
|
||||||
|
|
||||||
|
new_soft = min(soft_limit, current_hard)
|
||||||
|
resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft, current_hard))
|
||||||
|
logger.debug(
|
||||||
|
f"File limit set. New soft limit: {new_soft}, Hard limit remains: {current_hard}"
|
||||||
|
)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user