mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02:00
basic plumbing for birdseye view
This commit is contained in:
parent
f4a0ec43a6
commit
7fc9026ca6
@ -14,6 +14,7 @@ from peewee_migrate import Router
|
|||||||
from playhouse.sqlite_ext import SqliteExtDatabase
|
from playhouse.sqlite_ext import SqliteExtDatabase
|
||||||
from playhouse.sqliteq import SqliteQueueDatabase
|
from playhouse.sqliteq import SqliteQueueDatabase
|
||||||
|
|
||||||
|
from frigate.birdseye import BirdsEyeFrameOutputter
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
||||||
from frigate.edgetpu import EdgeTPUProcess
|
from frigate.edgetpu import EdgeTPUProcess
|
||||||
@ -250,6 +251,10 @@ class FrigateApp:
|
|||||||
capture_process.start()
|
capture_process.start()
|
||||||
logger.info(f"Capture process started for {name}: {capture_process.pid}")
|
logger.info(f"Capture process started for {name}: {capture_process.pid}")
|
||||||
|
|
||||||
|
def start_birdseye_outputter(self):
|
||||||
|
self.birdseye_outputter = BirdsEyeFrameOutputter(self.stop_event)
|
||||||
|
self.birdseye_outputter.start()
|
||||||
|
|
||||||
def start_event_processor(self):
|
def start_event_processor(self):
|
||||||
self.event_processor = EventProcessor(
|
self.event_processor = EventProcessor(
|
||||||
self.config,
|
self.config,
|
||||||
@ -306,6 +311,7 @@ class FrigateApp:
|
|||||||
self.start_detected_frames_processor()
|
self.start_detected_frames_processor()
|
||||||
self.start_camera_processors()
|
self.start_camera_processors()
|
||||||
self.start_camera_capture_processes()
|
self.start_camera_capture_processes()
|
||||||
|
self.start_birdseye_outputter()
|
||||||
self.init_stats()
|
self.init_stats()
|
||||||
self.init_web_server()
|
self.init_web_server()
|
||||||
self.start_event_processor()
|
self.start_event_processor()
|
||||||
|
84
frigate/birdseye.py
Normal file
84
frigate/birdseye.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import multiprocessing as mp
|
||||||
|
import numpy as np
|
||||||
|
import subprocess as sp
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
from frigate.util import SharedMemoryFrameManager
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# methods for maintaining the birdseyeframe in the object processing thread
|
||||||
|
# avoids work when no clients are listening
|
||||||
|
class BirdsEyeFrameManager:
|
||||||
|
def __init__(self):
|
||||||
|
# self.config = config
|
||||||
|
self.frame_manager = SharedMemoryFrameManager()
|
||||||
|
self._frame_shape = (1080, 1920)
|
||||||
|
self.frame_shape_yuv = (self._frame_shape[0] * 3 // 2, self._frame_shape[1])
|
||||||
|
self.frame_shm = mp.shared_memory.SharedMemory(
|
||||||
|
name=f"birdseye-frame",
|
||||||
|
create=True,
|
||||||
|
size=self.frame_shape_yuv[0] * self.frame_shape_yuv[1],
|
||||||
|
)
|
||||||
|
self.frame = np.ndarray(
|
||||||
|
self.frame_shape_yuv, dtype=np.uint8, buffer=self.frame_shm.buf
|
||||||
|
)
|
||||||
|
|
||||||
|
# initialize the frame as black and with the frigate logo
|
||||||
|
self.blank_frame = np.zeros((1080 * 3 // 2, 1920), np.uint8)
|
||||||
|
self.blank_frame[:] = 128
|
||||||
|
self.blank_frame[0:1080, 0:1920] = 16
|
||||||
|
|
||||||
|
self.frame[:] = self.blank_frame
|
||||||
|
|
||||||
|
def update_frame(self, camera, object_count, motion_count, frame_time, frame):
|
||||||
|
# determine how many cameras are tracking objects (or recently were)
|
||||||
|
# decide on a layout for the birdseye view (try to avoid too much churn)
|
||||||
|
# calculate position of each camera
|
||||||
|
# calculate resolution of each position in the layout
|
||||||
|
# if layout is changing, wipe the frame black again
|
||||||
|
# For each camera currently tracking objects (alphabetical):
|
||||||
|
# - resize the current frame and copy into the birdseye view
|
||||||
|
# signal to birdseye process that the frame is ready to send
|
||||||
|
|
||||||
|
self.frame[:] = frame
|
||||||
|
|
||||||
|
|
||||||
|
# separate process for managing the external ffmpeg process and sending frame
|
||||||
|
# bytes to ffmpeg
|
||||||
|
class BirdsEyeFrameOutputter(threading.Thread):
|
||||||
|
def __init__(self, stop_event):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.stop_event = stop_event
|
||||||
|
self.frame_shm = mp.shared_memory.SharedMemory(
|
||||||
|
name=f"birdseye-frame",
|
||||||
|
create=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_ffmpeg(self):
|
||||||
|
ffmpeg_cmd = "ffmpeg -f rawvideo -pix_fmt yuv420p -video_size 1920x1080 -i pipe: -f mpegts -codec:v mpeg1video -b:v 1000k -bf 0 http://localhost:8081/birdseye".split(
|
||||||
|
" "
|
||||||
|
)
|
||||||
|
self.process = sp.Popen(
|
||||||
|
ffmpeg_cmd,
|
||||||
|
stdout=sp.DEVNULL,
|
||||||
|
# TODO: logging
|
||||||
|
stderr=sp.DEVNULL,
|
||||||
|
stdin=sp.PIPE,
|
||||||
|
start_new_session=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.start_ffmpeg()
|
||||||
|
|
||||||
|
while not self.stop_event.wait(1):
|
||||||
|
if self.process.poll() != None:
|
||||||
|
logger.info(f"ffmpeg process is not running. restarting ...")
|
||||||
|
self.start_ffmpeg()
|
||||||
|
|
||||||
|
self.process.stdin.write(self.frame_shm.buf.tobytes())
|
||||||
|
|
||||||
|
|
||||||
|
# separate process for passing jsmpeg packets over websockets
|
||||||
|
# signals to the frame manager when a client is listening
|
||||||
|
# class JSMpegSocketServer:
|
@ -17,6 +17,7 @@ import cv2
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from frigate.birdseye import BirdsEyeFrameManager
|
||||||
from frigate.config import FrigateConfig, CameraConfig
|
from frigate.config import FrigateConfig, CameraConfig
|
||||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
||||||
from frigate.edgetpu import load_labels
|
from frigate.edgetpu import load_labels
|
||||||
@ -563,6 +564,7 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
self.camera_states: Dict[str, CameraState] = {}
|
self.camera_states: Dict[str, CameraState] = {}
|
||||||
self.frame_manager = SharedMemoryFrameManager()
|
self.frame_manager = SharedMemoryFrameManager()
|
||||||
|
self.birdseye_frame_manager = BirdsEyeFrameManager()
|
||||||
|
|
||||||
def start(camera, obj: TrackedObject, current_frame_time):
|
def start(camera, obj: TrackedObject, current_frame_time):
|
||||||
self.event_queue.put(("start", camera, obj.to_dict()))
|
self.event_queue.put(("start", camera, obj.to_dict()))
|
||||||
@ -717,6 +719,14 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
frame_time, current_tracked_objects, motion_boxes, regions
|
frame_time, current_tracked_objects, motion_boxes, regions
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.birdseye_frame_manager.update_frame(
|
||||||
|
camera,
|
||||||
|
len(current_tracked_objects),
|
||||||
|
len(motion_boxes),
|
||||||
|
camera_state.current_frame_time,
|
||||||
|
camera_state._current_frame,
|
||||||
|
)
|
||||||
|
|
||||||
# update zone counts for each label
|
# update zone counts for each label
|
||||||
# for each zone in the current camera
|
# for each zone in the current camera
|
||||||
for zone in self.config.cameras[camera].zones.keys():
|
for zone in self.config.cameras[camera].zones.keys():
|
||||||
|
22
web/public/jsmpeg.html
Normal file
22
web/public/jsmpeg.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>JSMpeg Stream Client</title>
|
||||||
|
<style type="text/css">
|
||||||
|
html, body {
|
||||||
|
background-color: #111;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="video-canvas"></canvas>
|
||||||
|
<script type="text/javascript" src="jsmpeg.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var canvas = document.getElementById('video-canvas');
|
||||||
|
var url = 'ws://'+document.location.hostname+':5000/live/birdseye';
|
||||||
|
var player = new JSMpeg.Player(url, {canvas: canvas});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
web/public/jsmpeg.min.js
vendored
Normal file
1
web/public/jsmpeg.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user