add layout calculations

This commit is contained in:
Blake Blackshear 2021-06-04 07:09:16 -05:00
parent 8e2ba4a8ea
commit 4eed27e178

View File

@ -1,12 +1,14 @@
import datetime
import math
import multiprocessing as mp import multiprocessing as mp
import queue import queue
import signal import signal
import subprocess as sp import subprocess as sp
import threading import threading
import numpy as np
from multiprocessing import shared_memory from multiprocessing import shared_memory
from wsgiref.simple_server import make_server from wsgiref.simple_server import make_server
import numpy as np
from setproctitle import setproctitle from setproctitle import setproctitle
from ws4py.server.wsgirefserver import ( from ws4py.server.wsgirefserver import (
WebSocketWSGIHandler, WebSocketWSGIHandler,
@ -68,29 +70,80 @@ class BroadcastThread(threading.Thread):
class BirdsEyeFrameManager: class BirdsEyeFrameManager:
def __init__(self, height, width): def __init__(self, height, width):
frame_shape = (height, width) self.frame_shape = (height, width)
yuv_shape = (height * 3 // 2, width) self.yuv_shape = (height * 3 // 2, width)
self.frame = np.ndarray(yuv_shape, dtype=np.uint8) self.frame = np.ndarray(self.yuv_shape, dtype=np.uint8)
# initialize the frame as black and with the frigate logo # initialize the frame as black and with the frigate logo
self.blank_frame = np.zeros(yuv_shape, np.uint8) self.blank_frame = np.zeros(self.yuv_shape, np.uint8)
self.blank_frame[:] = 128 self.blank_frame[:] = 128
self.blank_frame[0 : frame_shape[0], 0 : frame_shape[1]] = 16 self.blank_frame[0 : self.frame_shape[0], 0 : self.frame_shape[1]] = 16
self.frame[:] = self.blank_frame self.frame[:] = self.blank_frame
def update(self, camera, object_count, motion_count, frame_time, frame): self.last_active_frames = {}
# determine how many cameras are tracking objects (or recently were) self.camera_layout = []
# decide on a layout for the birdseye view (try to avoid too much churn)
# calculate position of each camera def clear_frame(self):
self.frame[:] = self.blank_frame
def update(self, camera, object_count, motion_count, frame_time, frame) -> bool:
# maintain time of most recent active frame for each camera
if object_count > 0:
self.last_active_frames[camera] = frame_time
# TODO: avoid the remaining work if exceeding 5 fps and return False
# determine how many cameras are tracking objects within the last 30 seconds
now = datetime.datetime.now().timestamp()
active_cameras = [
cam
for cam, frame_time in self.last_active_frames.items()
if now - frame_time < 30
]
if len(active_cameras) == 0 and len(self.camera_layout) == 0:
return False
# if the sqrt of the layout and the active cameras don't round to the same value,
# we need to resize the layout
if round(math.sqrt(len(active_cameras))) != round(
math.sqrt(len(self.camera_layout))
):
# decide on a layout for the birdseye view (try to avoid too much churn)
self.columns = math.ceil(math.sqrt(len(active_cameras)))
self.rows = round(math.sqrt(len(active_cameras)))
self.camera_layout = [None] * (self.columns * self.rows)
self.clear_frame()
# remove inactive cameras from the layout
self.camera_layout = [
cam if cam in active_cameras else None for cam in self.camera_layout
]
# place the active cameras in the layout
while len(active_cameras) > 0:
cam = active_cameras.pop()
if cam in self.camera_layout:
continue
# place camera in the first available spot in the layout
for i in range(0, len(self.camera_layout) - 1):
if self.camera_layout[i] is None:
self.camera_layout[i] = cam
break
# calculate resolution of each position in the layout # calculate resolution of each position in the layout
# if layout is changing, wipe the frame black again width = self.frame_shape[1] / self.columns
# For each camera currently tracking objects (alphabetical): height = self.frame_shape[0] / self.rows
# For each camera in the layout:
# - resize the current frame and copy into the birdseye view # - resize the current frame and copy into the birdseye view
# signal to birdseye process that the frame is ready to send
self.frame[:] = frame self.frame[:] = frame
return True
def output_frames(config, video_output_queue): def output_frames(config, video_output_queue):
threading.current_thread().name = f"output" threading.current_thread().name = f"output"
@ -170,14 +223,14 @@ def output_frames(config, video_output_queue):
ws.environ["PATH_INFO"].endswith("birdseye") ws.environ["PATH_INFO"].endswith("birdseye")
for ws in websocket_server.manager for ws in websocket_server.manager
): ):
birdseye_manager.update( if birdseye_manager.update(
camera, camera,
len(current_tracked_objects), len(current_tracked_objects),
len(motion_boxes), len(motion_boxes),
frame_time, frame_time,
frame, frame,
) ):
converters["birdseye"].write(birdseye_manager.frame.tobytes()) converters["birdseye"].write(birdseye_manager.frame.tobytes())
if camera in previous_frames: if camera in previous_frames:
frame_manager.delete(previous_frames[camera]) frame_manager.delete(previous_frames[camera])