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.sqliteq import SqliteQueueDatabase
from frigate.birdseye import BirdsEyeFrameOutputter
from frigate.config import FrigateConfig
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
from frigate.edgetpu import EdgeTPUProcess
@ -250,6 +251,10 @@ class FrigateApp:
capture_process.start()
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):
self.event_processor = EventProcessor(
self.config,
@ -306,6 +311,7 @@ class FrigateApp:
self.start_detected_frames_processor()
self.start_camera_processors()
self.start_camera_capture_processes()
self.start_birdseye_outputter()
self.init_stats()
self.init_web_server()
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 numpy as np
from frigate.birdseye import BirdsEyeFrameManager
from frigate.config import FrigateConfig, CameraConfig
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
from frigate.edgetpu import load_labels
@ -563,6 +564,7 @@ class TrackedObjectProcessor(threading.Thread):
self.stop_event = stop_event
self.camera_states: Dict[str, CameraState] = {}
self.frame_manager = SharedMemoryFrameManager()
self.birdseye_frame_manager = BirdsEyeFrameManager()
def start(camera, obj: TrackedObject, current_frame_time):
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
)
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
# for each zone in the current camera
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