basic plumbing for birdseye view

This commit is contained in:
Blake Blackshear 2021-05-22 21:02:26 -05:00
parent f4a0ec43a6
commit 7fc9026ca6
5 changed files with 123 additions and 0 deletions

View File

@ -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
View 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:

View File

@ -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
View 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

File diff suppressed because one or more lines are too long