Bläddra i källkod

add layout calculations

Blake Blackshear 3 år sedan
förälder
incheckning
4eed27e178
1 ändrade filer med 69 tillägg och 16 borttagningar
  1. 69 16
      frigate/output.py

+ 69 - 16
frigate/output.py

@@ -1,12 +1,14 @@
+import datetime
+import math
 import multiprocessing as mp
 import queue
 import signal
 import subprocess as sp
 import threading
-import numpy as np
 from multiprocessing import shared_memory
 from wsgiref.simple_server import make_server
 
+import numpy as np
 from setproctitle import setproctitle
 from ws4py.server.wsgirefserver import (
     WebSocketWSGIHandler,
@@ -68,29 +70,80 @@ class BroadcastThread(threading.Thread):
 
 class BirdsEyeFrameManager:
     def __init__(self, height, width):
-        frame_shape = (height, width)
-        yuv_shape = (height * 3 // 2, width)
-        self.frame = np.ndarray(yuv_shape, dtype=np.uint8)
+        self.frame_shape = (height, width)
+        self.yuv_shape = (height * 3 // 2, width)
+        self.frame = np.ndarray(self.yuv_shape, dtype=np.uint8)
 
         # 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[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
 
-    def update(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
+        self.last_active_frames = {}
+        self.camera_layout = []
+
+    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
-        # if layout is changing, wipe the frame black again
-        # For each camera currently tracking objects (alphabetical):
+        width = self.frame_shape[1] / self.columns
+        height = self.frame_shape[0] / self.rows
+
+        # For each camera in the layout:
         #   - resize the current frame and copy into the birdseye view
-        # signal to birdseye process that the frame is ready to send
 
         self.frame[:] = frame
 
+        return True
+
 
 def output_frames(config, video_output_queue):
     threading.current_thread().name = f"output"
@@ -170,14 +223,14 @@ def output_frames(config, video_output_queue):
             ws.environ["PATH_INFO"].endswith("birdseye")
             for ws in websocket_server.manager
         ):
-            birdseye_manager.update(
+            if birdseye_manager.update(
                 camera,
                 len(current_tracked_objects),
                 len(motion_boxes),
                 frame_time,
                 frame,
-            )
-            converters["birdseye"].write(birdseye_manager.frame.tobytes())
+            ):
+                converters["birdseye"].write(birdseye_manager.frame.tobytes())
 
         if camera in previous_frames:
             frame_manager.delete(previous_frames[camera])