Bonjour à tous,
ca fait quelques semaines que je m’amuse avec un nouvel outil (pour moi, mais sans doute que certains connaissent déjà !), et je me suis donc amusé à intégrer ceci à mon jeedom.
Frigate, c’est quoi : un NVR - Network Video Recording - qui va donc gérer l’ensemble de vos flux vidéo en provenance de vos cameras. une fois bien paramétré, il va faire office de passerelle vidéo entre vos caméras et votre Jeedom, il permet :
- de récupérer des flux video en rstp, mjpeg, webRTC
- de récupérer des snapshots
- de récupérer toutes les infos de detection … plus aucune intelligence dans la camera, il se charge simplement de transmettre les images
- de piloter ces camera via OnVIF (version 13 en beta au 09/12/2023)
En standard, Frigate integre un moteur d’IA tensorflow qui tourne dans sa version Light sous un RPI4 (c’est mon cas, avec 4 caméras. C’est un peu juste mas ca passe). Je suis en attente de reception d’une clé google coral pour améliorer tout ca.
Au programme, on definit coté Frigate, des masques, des zones, des objets à reperer, et il se charge du reste :
- Detection des objets en temps reel (avec un pourcentage de correspondance)
- enregistrement d’une video, debutant X min avant et se terminant x minutes après la detection de l’objet dans la zone
- enregistrement de snapshots
- transmission d’alertes à Jeedom en MQTT (de type nouvelle alerte, mise à jour de l’alerte, fin de l’alerte)
- En jaune, la zone de detection de « personnes »
- En orange, la zone de detection de « vehicules »
- En rouge, une zon eou un mouvement a été detecté (puis analysé par tensorflowLite, pour definir si il reconnait quelquechose
Bref, c’est un bonheur. Une fois la camera connectée, on ne retourne plus la voir. tout se passe dans Frigate
Je ne détaillerais pas l’installation, c’ets très bien expliqué, tout se fait en docker : Installation | Frigate
Coté paramétrage : rien de compliquer pour commencer. Le point le plus important est de commencer par paramétrer ces caméras en mode restream coté Frigate, il faut juste bien paramétré les bons streams dans l’entrée go2rtc (cf mon fichier de conf).
Chez moi, j’ai paramétré 2 streams pour chaque caméra :
- Un stream principale (resolution maximal)
- Un stream secondaire (sub) pour la detection
La detection se fait sur le stream secondaire, mais l’enregistrement sur le stream principal.
Ma config go2rtc pour ceux que ca interesse
Config go2rtc dans la config frigate
go2rtc:
streams:
rtsp_allee:
- rtsp://jeedom:xxxx@192.168.8.243:88/videoMain
- ffmpeg:rtsp_allee#audio=opus
rtsp_allee_sub:
- rtsp://jeedom:xxxx@192.168.8.243:88/videoSub
- ffmpeg:rtsp_allee_sub#audio=opus
rtsp_appentis:
- rtsp://jeedom:xxxx@192.168.8.242:88/videoMain
- ffmpeg:rtsp_appentis#audio=opus
rtsp_appentis_sub:
- rtsp://jeedom:xxxx@192.168.8.242:88/videoSub
- ffmpeg:rtsp_appentis_sub#audio=opus
rtsp_piscine:
- rtsp://jeedom:xxxx@192.168.8.240:88/videoMain
- ffmpeg:rtsp_piscine#audio=opus
rtsp_piscine_sub:
- rtsp://jeedom:xxxx@192.168.8.240:88/videoSub
- ffmpeg:rtsp_piscine_sub#audio=opus
rtsp_salon:
- rtsp://jeedom:xxxx@192.168.8.241:554/videoMain
- ffmpeg:rtsp_salon#audio=opus
rtsp_salon_sub:
- rtsp://jeedom:xxxx@192.168.8.241:554/videoSub
- ffmpeg:rtsp_salon_sub#audio=opus
ce sont ensuite les flux go2rtc qui sont utilisés dans frigate ou dans jeedom
Je ne vais pas detailler mon paramétrage de frigate, c’ets propre à chaque config, mais je vous le mets à titre d’info :
Config frigate
mqtt:
enabled: true
user: frigate
password: hNWv9duV5baCN5hjTTdYW2
host: 192.168.8.200
port: 1883
ffmpeg:
hwaccel_args: preset-rpi-64-h264 #Enable Hardware Acceleration
global_args: -hide_banner -loglevel error -threads 3
output_args:
# Optional: output args for detect streams (default: shown below)
detect: -threads 3 -f rawvideo -pix_fmt yuv420p
# Optional: output args for record streams (default: shown below)
record: preset-record-generic
retry_interval: 10
logger:
default: info
logs:
frigate.mqtt: info
birdseye:
enabled: true
mode: continuous
width: 1280
height: 720
quality: 4
model:
width: 320
height: 320
labelmap:
0: person
1: vehicle
2: vehicle
3: vehicle
5: vehicle
7: vehicle
16: animal
17: animal
18: animal
19: animal
20: animal
record:
enabled: true
retain:
days: 30
mode: active_objects
events:
pre_capture: 5
post_capture: 5
retain:
default: 30
mode: active_objects
# mode: motion
snapshots:
enabled: true
clean_copy: true
timestamp: true
bounding_box: true
crop: false
retain:
default: 15
detect:
fps: 5
width: 1280
height: 720
go2rtc:
streams:
rtsp_allee:
- rtsp://jeedom:xxxx@192.168.8.243:88/videoMain
- ffmpeg:rtsp_allee#audio=opus
rtsp_allee_sub:
- rtsp://jeedom:xxxx@192.168.8.243:88/videoSub
- ffmpeg:rtsp_allee_sub#audio=opus
rtsp_appentis:
- rtsp://jeedom:xxxx@192.168.8.242:88/videoMain
- ffmpeg:rtsp_appentis#audio=opus
rtsp_appentis_sub:
- rtsp://jeedom:xxxx@192.168.8.242:88/videoSub
- ffmpeg:rtsp_appentis_sub#audio=opus
rtsp_piscine:
- rtsp://jeedom:xxxx@192.168.8.240:88/videoMain
- ffmpeg:rtsp_piscine#audio=opus
rtsp_piscine_sub:
- rtsp://jeedom:xxxx@192.168.8.240:88/videoSub
- ffmpeg:rtsp_piscine_sub#audio=opus
rtsp_salon:
- rtsp://jeedom:xxxx@192.168.8.241:554/videoMain
- ffmpeg:rtsp_salon#audio=opus
rtsp_salon_sub:
- rtsp://jeedom:xxxx@192.168.8.241:554/videoSub
- ffmpeg:rtsp_salon_sub#audio=opus
cameras:
Appentis:
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/rtsp_appentis
input_args: preset-rtsp-restream
roles:
- record
- path: rtsp://127.0.0.1:8554/rtsp_appentis_sub
input_args: preset-rtsp-restream
roles:
- detect
detect:
enabled: true
live:
stream_name: rtsp_appentis
birdseye:
enabled: true
motion:
mask:
- 664,186,1280,221,1280,0,1280,0,0,0,0,111,0,437
- 1280,720,1280,215,1122,194,986,720
objects:
track:
- person
- vehicle
- animal
zones:
terrasse:
coordinates: 986,720,1035,591,707,509,583,720
objects:
- vehicle
avant:
coordinates: 0,720,992,720,1076,421,673,386,837,223,589,243,0,437
objects:
- person
- animal
Allee:
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/rtsp_allee
input_args: preset-rtsp-restream
roles:
- record
- path: rtsp://127.0.0.1:8554/rtsp_allee_sub
input_args: preset-rtsp-restream
roles:
- detect
detect:
enabled: true
live:
stream_name: rtsp_allee
birdseye:
enabled: true
objects:
track:
- person
- vehicle
- animal
motion:
mask:
- 324,0,330,49,687,49,708,0
zones:
allee:
coordinates: 1280,720,615,720,0,720,120,453,354,102,523,123,468,229,510,342,631,433
objects:
- vehicle
tout_aee:
coordinates: 0,0,758,0,1280,140,1280,720,0,720
objects:
- person
- animal
Salon:
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/rtsp_salon
input_args: preset-rtsp-restream
roles:
- record
- path: rtsp://127.0.0.1:8554/rtsp_salon_sub
input_args: preset-rtsp-restream
roles:
- detect
detect:
enabled: true
fps: 5
width: 640
height: 360
live:
stream_name: rtsp_salon
birdseye:
enabled: true
onvif:
host: 192.168.8.241
port: 888
user: jeedom
password: nadege08
objects:
track:
- person
Piscine:
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/rtsp_piscine
input_args: preset-rtsp-restream
roles:
- record
- path: rtsp://127.0.0.1:8554/rtsp_piscine_sub
input_args: preset-rtsp-restream
roles:
- detect
detect:
enabled: true
live:
stream_name: rtsp_piscine
birdseye:
enabled: true
motion:
mask:
- 590,87,1280,121,1280,0,276,0
- 764,527,996,240,664,192,280,366
objects:
track:
- person
- animal
Une fois votre frigate opérationnel et le flux MQTT configuré vers votre jeedom, le plus dire est fait
1- on crée des équipements pour gérer la détection avec plugin-jmqtt
Voilà ce que ca donne :
Un équipement par camera permettant d’activer ou desactiver la detection, et les status de detection pour la caméra
Un équipement Event qui remonte le dernier evenement (toute camera confondue
Un équipement pour gerer le serveur (très succinct chez moi)
Je vous les modèles jmqtt des equipememnt (je laisse @bad juger de l’interet de l’integrer dans les templates jmqtt
Frigate_Serveur.json.txt (7,5 Ko)
Frigate_Events_V2.json.txt (11,6 Ko)
Frigate_Camera.json.txt (19,0 Ko)
Voilà, vous avez toutes vos remontées d’evenement et la gestion de vos detections dans Jeedom
2 - pour avoir les images/videos dans son jeedom
Rien de plus simple, on paramètre une camera via le plugin-camera
avec comme adresse ip l’adresse de votre serveur frigate
comme port, le port du serveur frigate (5000 par défaut)
et les différentes adresses de flux
**3 - et pour finir, les notifications sur le salertes avec envoi d’un snapshot horodaté, ainsi que la zone de détection et le niveau de fiabilité de la detection
Un simple scénario (en bloc code) sur declencheur de la modification de la commande info events (équipement events). Ce bloc code envoie une notif à plugin-jeedomconnect
Vous constaterez en regardant la photo que la zone de detection est entourée et il est indiqué le type de detection (personne) et le tx de « justesse »
Dans le bloc code, je ne notifie que sur un évènement de type end qui correspond à la fin de la génération de l’evenement MQTT
Bloc code de notification sur detection
// Définition de la version du script
$version = '18/06/2024 23:00';
$scenario->setLog('┌──────────── Logs bloc code - version du ' . $version);
// Variables
$urlFrigate = 'http://127.0.0.1:55000'; // URL de base pour les API Frigate
$eqLogicEventId = eqLogic::byString('#[Frigate][Events]#')->getId(); // ID de l'équipement logique pour les événements
$destinationPath = '/var/www/html/data/events'; // Chemin où les fichiers événements seront stockés
$cmdNotifJC = cmd::byString('#[Norbert][JC_Norbert][Notif_Caméra]#'); // Commande de notification
// Fonction pour créer le répertoire si nécessaire
function createDir($destinationPath, $scenario) {
// Vérifie si le répertoire n'existe pas
if (!is_dir($destinationPath)) {
// Crée le répertoire avec les permissions appropriées
if (mkdir($destinationPath, 0777, true)) {
$scenario->setLog('Répertoire créé avec succès : ' . $destinationPath);
} else {
$scenario->setLog('Erreur lors de la création du répertoire : ' . $destinationPath);
}
}
}
// Fonction pour récupérer et sauvegarder un fichier depuis une URL
function recupFile($sourceUrl, $eventId, $destinationPath, $scenario) {
// Récupère le contenu du fichier depuis l'URL
$fileContent = file_get_contents($sourceUrl);
$localFileName = $eventId . '.png'; // Nom du fichier local basé sur l'ID de l'événement
$destinationFile = $destinationPath . '/' . $localFileName; // Chemin complet pour le fichier sauvegardé
if ($fileContent !== false) {
// Crée une image PNG à partir du contenu récupéré
imagepng(imagecreatefromstring($fileContent), $destinationFile);
$scenario->setLog('| Fichier récupéré et redimensionné : ' . $destinationFile);
return $destinationFile;
} else {
$scenario->setLog('| Impossible de récupérer le fichier depuis l\'URL ' . $sourceUrl);
return false;
}
}
// Fonction pour modifier les permissions des fichiers dans un répertoire
function chmodAll666($dir) {
// Scanne le répertoire pour récupérer tous les fichiers
$files = scandir($dir);
foreach ($files as $file) {
if ($file != '.' && $file != '..') {
$path = $dir . DIRECTORY_SEPARATOR . $file; // Chemin complet du fichier
chmod($path, 0666); // Change les permissions du fichier
}
}
}
// Programme principal
// Récupération du type d'événement et vérification des conditions
$type = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Type')->execCmd(); // Type d'événement
$has_clip = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Has_clip')->execCmd(); // Vérifie si l'événement a un clip
$has_snapshot = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Has_snapshot')->execCmd(); // Vérifie si l'événement a un snapshot
if ($type == 'end' && $has_clip && $has_snapshot) {
// Récupération des informations de l'événement
$eventId = cmd::byEqLogicIdCmdName($eqLogicEventId, 'ID')->execCmd(); // ID de l'événement
$camera = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Camera')->execCmd(); // Nom de la caméra
$timestamp = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Timestamp')->execCmd(); // Timestamp de l'événement
$label = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Label')->execCmd(); // Label de l'événement
$score = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Score')->execCmd(); // Score de l'événement
$zone = cmd::byEqLogicIdCmdName($eqLogicEventId, 'Zone actuelle')->execCmd(); // Zone de l'événement
// Log des informations de l'événement
$scenario->setLog('| Événement : ' . $eventId);
$scenario->setLog('| Lieu : ' . $camera);
$scenario->setLog('| Zone : ' . $zone);
$scenario->setLog('| Snapshot : ' . $has_snapshot . ' | Clip : ' . $has_clip);
$scenario->setLog('| Date : ' . date('d/m H:i:s', $timestamp));
$scenario->setLog('| Label : ' . $label . ' (' . $score . '%) - Type : ' . $type);
// URL source pour récupérer le snapshot de l'événement
$sourceUrl = $urlFrigate . '/api/events/' . $eventId . '/snapshot.jpg?bbox=1×tamp=1';
// Création du répertoire si nécessaire
createDir($destinationPath, $scenario);
// Récupération et sauvegarde du fichier
$destinationFile = recupFile($sourceUrl, $eventId, $destinationPath, $scenario);
if ($destinationFile !== false) {
// Modification des droits sur les fichiers
chmodAll666($destinationPath);
// Envoi de la notification avec le fichier
$cmdNotifJC->execCmd([
'title' => 'title=' . $camera . ' Detection mvt | files=' . $destinationFile,
'message' => 'Heure : ' . date('d/m H:i:s', $timestamp) . '<br>Type : ' . $label . ' (' . $score . '%) - zone : ' . $zone
], 0);
$scenario->setLog('| Envoi de la notification avec le fichier ' . $destinationFile);
} else {
// Envoi de la notification sans le fichier
$cmdNotifJC->execCmd([
'title' => 'title=' . $camera . ' Detection mvt',
'message' => 'Heure : ' . date('j/m H:i:s', $timestamp) . '<br>Type : ' . $label . ' (' . $score . '%) - zone : ' . $zone
], 0);
$scenario->setLog('| Envoi de la notification sans fichier');
}
}
// Fin des logs du script
$scenario->setLog('└───────────────────────────');
Et voilà une belle intégration pour un bel outil et votre Jeedom équipé d’une reconnaissance d’objet sur vos cameras.
Autre avantage, tout ajout de nouvelle caméra se fait en 10min coté Jeedom ensuite
A noter que j’utilise la version 13 en beta6 (au 09/12/2023) qui integre donc la gesiton des snapshots et du PTZ via onvif :
repo : Releases · blakeblackshear/frigate · GitHub
doc : https://deploy-preview-6262--frigate-docs.netlify.app/
Norbert