感念論壇分享,在這邊也學習很多!小弟也來分享一個最近弄好的一個專案,是利用一個名叫Frigate NVR的開源NVR錄影系統,主要優勢在於它的AI物件偵測能力,可以搭配上同樣開源的模型物件,來判斷畫面裡面的東西,不管是車、動物、還是物件都可以。
志在分享!資料都是網路上找來的,如果有講得不準確的地方還請告訴小弟,非常感謝!
在用這套系統之前,小弟是用簡單的門窗感應器搭配上紅軍大大的NR呼叫相機截圖通知流程,來達到開關門會拍照記錄通知,並且在開門達到一段時間後會自動傳送即時連結。
Line Notify Token + 攝影機截圖 Line訊息傳送 NR應用方法
分享 透過 Line 傳送攝影機即時影像連結
沒什麼特別需求的話,其實這樣就相當好用了!但是小弟強迫症發作,看到那每天開關門那樣的無用訊息轟炸,到最後會變成根本沒在看那訊息。另外在HA裡面利用相機截圖或是錄影都會有延遲輸出,都會需要額外微調才有辦法真的拍照重點,不然都只會有開門關門的照片卻沒拍到人…
但利用這套NVR所內建的通知API所產出的縮圖或視錄影幾乎可以達到即時,就算有延遲也差不多1~2秒(視硬體規格而定),而且還可以真的是偵測到人的畫面才會產生縮圖,更甚至還能放大畫面加偵測框以及時間(就像上面第一張圖一樣)
先講硬體,小弟使用的攝影機就是在群組裡大家推崇的TP-LINK Tapo C210,其他相機設定可以參考Frigate說明文件,主要是要能支援RTSP,直接輸出H.264視訊和AAC音源最好,主機部分是有能支援硬體解碼處理器,一般工控機不要等級太弱的都行,如果有AI加速器(Coral)更好,我自己是直接使用群暉的DS720搭配Coral USB Accelerator,依實際使用上來看NAS的處理器資源占用平均都只有10~20%。
https://docs.frigate.video/frigate/hardware
安裝環境部分虛擬機、Docker都可以使用(官方推薦是Docker),HA內也有附加元件可以直接安裝,如果像小弟一樣會使用到AI加速器的話則不太建議用虛擬機(效能會被打折)。那為了將系統區分以及方便維護,小弟是選擇用docker compose安裝方式,使用的群暉DSM則為最新的7.2版本
安裝步驟
小弟是先在資料加內新增config以及storagey資料夾後面備用,然後開啟Container Manager新增一個專案將下面的yaml程式碼按照自己需求貼上後按下一步、下一步(對內服務使用,網頁入口設定不用設定),建立容器。
version: "3.9"
services:
frigate:
container_name: frigate
privileged: true #權限最大化建議開啟增加
restart: unless-stopped
image: ghcr.io/blakeblackshear/frigate:stable
# environment:
# - LIBVA_DRIVER_NAME=i965 #硬體解碼J4125處理器建議增加
shm_size: "64mb" #兩個720p鏡頭差不多64mb,如果更多的話就再額外增加
# devices:
# - /dev/bus/usb:/dev/bus/usb #有裝AI加速器要增加
# - /dev/dri/renderD128 #INTEL硬體解碼驅動
volumes:
- /etc/localtime:/etc/localtime:ro
- /volume1/docker/frigate/config:/config #設定檔位置
- /volume1/docker/frigate/storage:/media/frigate #截圖錄影存檔位置
- type: tmpfs
target: /tmp/cache
tmpfs:
size: 1000000000
ports:
- "5000:5000" #必要,需要給HA串接使用
# - "8554:8554" #非必要,如果有同步用go2rtc解碼才需要
# - "8555:8555/tcp" #非必要,如果有同步用go2rtc解碼才需要
# - "8555:8555/udp" #非必要,如果有同步用go2rtc解碼才需要
完成後會自動建立一個名為frigate容器,docker安裝動作就這樣結束了!
如果你的compose跟我的一樣的話,輸入NAS區域網路網址:5000,就可以進入compose設定畫面。再來就是進入config先來簡單配置一下相機設定,細部設定我們後面再來討論,先完成HA和frigate連結!設定檔使用的格式都是yaml,如果會用HA設定基本上不會太難。
mqtt:
enabled: False
cameras:
name_of_your_camera: #攝影機名稱
ffmpeg:
inputs:
- path: rtsp://10.0.10.10:554/rtsp #攝影機RTSP連結
roles: #需要的模式,有detect 檢測、record錄影
- detect
detect:
enabled: False #還沒配置好檢測模式和區域先不要開,會額外消耗主機效能
width: 1280 #攝影機的寬度,視畫面情況而定,解析度不是越高越好
height: 720 #攝影機的高度,視畫面情況而定,解析度不是越高越好
正常來說,設定好攝影機名稱和RTSP連結後,存檔重開就可以看到攝影機畫面了!
Home assistant設定步驟
再來跳到HA設定裡面,由於小弟沒有要控制攝影機方位,所以沒有安裝HACS裡的Tapo: Cameras Control,但是HA裡還是需要camera實體,所以小弟的替代方案就用frigate推出的add-on來替代
安裝完重新HA後,新增附加元件。
如果你是跟我一樣用docker安裝的話,那網址就是剛剛上面的NAS區域網路網址:5000
完成加入後,攝影機實體跳出來就完成了。
如果有需要直接在HA控制的話可以另外在Supervisor add-on裡面安裝Frigate Proxy,
frigate設定步驟
在官方文件裡面有完整說明全部的設定,小弟也是英文苦手,全部的都是靠著google翻譯慢慢研究測試,如果有更好的偵測設定歡迎分享給小弟
以下附上我的配置,偵測環境主要為客廳玄關門,如果是透天使用可能需要多配置
mqtt:
enabled: true #打開才會用MQTT去做通知
host: #MQTT網址
port: 1883
user: #MQTT帳號
password: #MQTT網址密碼
detectors: #有使用AI加速器要加上這段,不然就砍掉,預設是處理器解碼
coral:
type: edgetpu
device: usb
ffmpeg:
hwaccel_args: preset-vaapi #使用INTEL硬體解碼
cameras:
camera: #攝影機名稱
ffmpeg:
inputs:
- path: #攝影機RTSP連結
roles:
- record #錄影
- detect #偵測
record: #錄影設定
enabled: true
retain: #全時錄影保留3天
days: 3
mode: all
events:
pre_capture: 1 #偵測錄影前提前1秒
post_capture: 3 #偵測錄影後剪輯往後3秒
retain: #偵測錄影保留10天
default: 10
detect: #偵測設定
enabled: true
fps: 15
width: 1280
height: 720
stationary:
interval: 30 #靜態偵測持續偵測影格
threshold: 150 #靜態偵測影格
snapshots: #快照
enabled: true
retain:
default: 10
mqtt: #MQTT通知
enabled: true
timestamp: false
bounding_box: true
crop: false
height: 480
quality: 100
objects: #偵測物件
track:
- person
filters: #不須偵測範圍遮罩
person:
mask:
- 453,0,466,276,617,354,610,0,1280,0,1280,720,0,720,0,0
按照我的配置,只要有"人"出現在玄關位置,就會自動拍照並且錄影
可以先觀察幾天,確認偵測的設定都有準確偵測的話就可以進行通知的環節了
Node red設定步驟
通知的部分官方有直接提供縮圖、快照以及錄影的api可以直接使用,讓你的NVR不須對外門戶大開(HA本身還是要對外)
其中的event-id部分就是由每次偵測後frigate會自動產生,小弟一樣是利用NR來流程配置,程式碼就貼在下面讓大家匯入參考囉!
[{"id":"f69513513bf98c8c","type":"group","z":"a45d65a9191f182c","name":"","style":{"label":true},"nodes":["67c9dc67ed4d2c6f","e2f49ed002d17626","f1f98d37d2b244fa","5516e61faa9f2d8e","2ecb3636ef2ae317"],"x":174,"y":679,"w":792,"h":82},{"id":"67c9dc67ed4d2c6f","type":"mqtt in","z":"a45d65a9191f182c","g":"f69513513bf98c8c","name":"","topic":"frigate/events","qos":"2","datatype":"auto-detect","broker":"","nl":false,"rap":true,"rh":0,"inputs":0,"x":270,"y":720,"wires":[["f1f98d37d2b244fa"]]},{"id":"e2f49ed002d17626","type":"change","z":"a45d65a9191f182c","g":"f69513513bf98c8c","name":"","rules":[{"t":"set","p":"tts","pt":"msg","to":"偵測動態","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":720,"wires":[["5516e61faa9f2d8e"]]},{"id":"f1f98d37d2b244fa","type":"rbe","z":"a45d65a9191f182c","g":"f69513513bf98c8c","name":"","func":"rbe","gap":"","start":"","inout":"out","septopics":true,"property":"payload.after.id","topi":"topic","x":410,"y":720,"wires":[["e2f49ed002d17626"]]},{"id":"5516e61faa9f2d8e","type":"function","z":"a45d65a9191f182c","g":"f69513513bf98c8c","name":"偵測錄影截圖","func":"var TimeNow = new Date();\nvar yyyy = TimeNow.getFullYear()\nvar MM = (TimeNow.getMonth() + 1 < 10 ? '0' : '') + (TimeNow.getMonth() + 1);\nvar dd = (TimeNow.getDate() < 10 ? '0' : '') + TimeNow.getDate();\nvar h = (TimeNow.getHours() < 10 ? '0' : '') + TimeNow.getHours();\nvar m = (TimeNow.getMinutes() < 10 ? '0' : '') + TimeNow.getMinutes();\nvar s = (TimeNow.getSeconds() < 10 ? '0' : '') + TimeNow.getSeconds();\nvar filedate = '_' + yyyy + MM + dd + '_' + h + m + '_' + s;\nvar date = yyyy + '/' + MM + '/' + dd + ' ' + h + ':' + m + ':' + s;\n\nlet evenid = msg.payload.after.id;\nlet clip = \"https://HA網址/api/frigate/notifications/\" + evenid + \"/clip.mp4\"\n\nmsg.payload_link = { \n \"data\": { \"url\": \"https://HA網址/api/frigate/notifications/\" + evenid + \"/snapshot.jpg\" },\n \"message\": date + \"\\n\" + msg.tts + \"\\n\" + clip\n};\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":720,"wires":[["2ecb3636ef2ae317"]]},{"id":"2ecb3636ef2ae317","type":"api-call-service","z":"a45d65a9191f182c","g":"f69513513bf98c8c","name":"公開LINE","server":"f3d3d353.161b2","version":5,"debugenabled":false,"domain":"notify","service":"line_notify","areaId":[],"deviceId":[],"entityId":[],"data":"msg.payload_link","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":880,"y":720,"wires":[[]]},{"id":"f3d3d353.161b2","type":"server","name":"Home Assistant","version":5,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]
小弟只用其中雞毛蒜皮的功能,這套系統還有很多應用,就再麻煩大大們互相分享啦!
2/5更新,不會用NR也可以用HA的自動化流程
alias: frigate event
description: ""
trigger:
- platform: mqtt
topic: frigate/events
id: frigate-event
value_template: "{{ value_json['after']['camera'] }}"
variables:
after_zones: "{{ trigger.payload_json['after']['entered_zones'] }}"
before_zones: "{{ trigger.payload_json['before']['entered_zones'] }}"
camera: "{{ trigger.payload_json['after']['camera'] }}"
id: "{{ trigger.payload_json['after']['id'] }}"
label: "{{ trigger.payload_json['after']['label'] }}"
score: "{{ trigger.payload_json['after']['score'] }}"
time_clip_start: "{{ trigger.payload_json['after']['start_time'] - 10.0 }}"
condition:
- condition: or
conditions:
- condition: template
value_template: "{{ trigger.payload_json['type'] == 'new' }}"
- condition: template
value_template: "{{ before_zones | length == 0 }}"
- condition: template
value_template: "{{ trigger.payload_json[\"after\"][\"entered_zones\"]|length > 0 }}"
- condition: template
value_template: "{{ [\"entrance\"] | select(\"in\", after_zones) | list | length > 0 }}" #要改這段,你的zone
action:
- choose:
- conditions:
- condition: trigger
id: frigate-event
sequence:
- service: notify.line_notif_private
data_template:
message: >-
{{ label }} detected in the {{ after_zones[0] | replace("_", "
") | title }}
data:
url: >-
https://your.domain.com/api/frigate/notifications/{{ id
}}/snapshot.jpg #要改這段
- repeat:
until:
- condition: template
value_template: "{{ wait.trigger.payload_json[\"type\"] == \"end\" }}"
sequence:
- wait_for_trigger:
- platform: mqtt
topic: frigate/events
payload: "{{ trigger.payload_json[\"after\"][\"id\"] }}"
value_template: "{{ value_json[\"after\"][\"id\"] }}"
continue_on_timeout: false
timeout: "00:02:00"
- condition: template
value_template: "{{ wait.trigger.payload_json['type'] == 'end' }}"
default: []
mode: parallel
max: 10