mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Rewrite birdseye auto layout (#6818)
* Prefer horizontal layout to vertical * Rewrite birdseye to use aspect ratios instead of resolutions as layout configurator * Improve layout with slightly larger than 16:9 cameras * Remove manual 2 camera layout * Lint * Remove log
This commit is contained in:
parent
ca7853c087
commit
0592cedcde
@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
@ -269,161 +270,118 @@ class BirdsEyeFrameManager:
|
|||||||
def update_frame(self):
|
def update_frame(self):
|
||||||
"""Update to a new frame for birdseye."""
|
"""Update to a new frame for birdseye."""
|
||||||
|
|
||||||
def calculate_two_cam_layout(canvas, cameras_to_add: list[str]) -> tuple[any]:
|
|
||||||
"""Calculate the optimal layout for 2 cameras."""
|
|
||||||
first_camera = cameras_to_add[0]
|
|
||||||
first_camera_dims = self.cameras[first_camera]["dimensions"].copy()
|
|
||||||
second_camera = cameras_to_add[1]
|
|
||||||
second_camera_dims = self.cameras[second_camera]["dimensions"].copy()
|
|
||||||
|
|
||||||
# check for optimal layout
|
|
||||||
if first_camera_dims[0] + second_camera_dims[0] < canvas_width:
|
|
||||||
# place cameras horizontally
|
|
||||||
first_scaled_width = int(
|
|
||||||
canvas_height * first_camera_dims[0] / first_camera_dims[1]
|
|
||||||
)
|
|
||||||
second_scaled_width = int(
|
|
||||||
canvas_height * second_camera_dims[0] / second_camera_dims[1]
|
|
||||||
)
|
|
||||||
first_height = canvas_height
|
|
||||||
second_height = canvas_height
|
|
||||||
|
|
||||||
if first_scaled_width + second_scaled_width > canvas_width:
|
|
||||||
if first_scaled_width > second_scaled_width:
|
|
||||||
first_scaled_width = canvas_width - second_scaled_width
|
|
||||||
first_height = int(
|
|
||||||
first_scaled_width
|
|
||||||
* first_camera_dims[1]
|
|
||||||
/ first_camera_dims[0]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
second_scaled_width = canvas_width - first_scaled_width
|
|
||||||
second_height = int(
|
|
||||||
second_scaled_width
|
|
||||||
* second_camera_dims[1]
|
|
||||||
/ second_camera_dims[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
(
|
|
||||||
first_camera,
|
|
||||||
(0, 0, first_scaled_width, first_height),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
second_camera,
|
|
||||||
(
|
|
||||||
first_scaled_width + 1,
|
|
||||||
0,
|
|
||||||
second_scaled_width,
|
|
||||||
second_height,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# place cameras vertically
|
|
||||||
top_scaled_width = int(
|
|
||||||
(canvas_height / 2) * first_camera_dims[0] / first_camera_dims[1]
|
|
||||||
)
|
|
||||||
bottom_scaled_width = int(
|
|
||||||
(canvas_height / 2) * second_camera_dims[0] / second_camera_dims[1]
|
|
||||||
)
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
(
|
|
||||||
first_camera,
|
|
||||||
(0, 0, top_scaled_width, int(canvas_height / 2)),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
[
|
|
||||||
(
|
|
||||||
second_camera,
|
|
||||||
(
|
|
||||||
0,
|
|
||||||
int(canvas_height / 2),
|
|
||||||
bottom_scaled_width,
|
|
||||||
int(canvas_height / 2),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
]
|
|
||||||
|
|
||||||
def calculate_layout(
|
def calculate_layout(
|
||||||
canvas, cameras_to_add: list[str], coefficient
|
canvas, cameras_to_add: list[str], coefficient
|
||||||
) -> tuple[any]:
|
) -> tuple[any]:
|
||||||
"""Calculate the optimal layout for 3+ cameras."""
|
"""Calculate the optimal layout for 2+ cameras."""
|
||||||
camera_layout: list[list[any]] = []
|
camera_layout: list[list[any]] = []
|
||||||
camera_layout.append([])
|
camera_layout.append([])
|
||||||
canvas_aspect = canvas[0] / canvas[1]
|
canvas_gcd = math.gcd(canvas[0], canvas[1])
|
||||||
|
canvas_aspect_x = (canvas[0] / canvas_gcd) * coefficient
|
||||||
|
canvas_aspect_y = (canvas[0] / canvas_gcd) * coefficient
|
||||||
starting_x = 0
|
starting_x = 0
|
||||||
x = starting_x
|
x = starting_x
|
||||||
y = 0
|
y = 0
|
||||||
y_i = 0
|
y_i = 0
|
||||||
max_height = 0
|
max_y = 0
|
||||||
for camera in cameras_to_add:
|
for camera in cameras_to_add:
|
||||||
camera_dims = self.cameras[camera]["dimensions"].copy()
|
camera_dims = self.cameras[camera]["dimensions"].copy()
|
||||||
camera_aspect = camera_dims[0] / camera_dims[1]
|
camera_gcd = math.gcd(camera_dims[0], camera_dims[1])
|
||||||
|
camera_aspect_x = camera_dims[0] / camera_gcd
|
||||||
|
camera_aspect_y = camera_dims[1] / camera_gcd
|
||||||
|
|
||||||
|
if round(camera_aspect_x / camera_aspect_y, 1) == 1.8:
|
||||||
|
# account for slightly off 16:9 cameras
|
||||||
|
camera_aspect_x = 16
|
||||||
|
camera_aspect_y = 9
|
||||||
|
elif round(camera_aspect_x / camera_aspect_y, 1) == 1.3:
|
||||||
|
# make 4:3 cameras the same relative size as 16:9
|
||||||
|
camera_aspect_x = 12
|
||||||
|
camera_aspect_y = 9
|
||||||
|
|
||||||
if camera_dims[1] > camera_dims[0]:
|
if camera_dims[1] > camera_dims[0]:
|
||||||
portrait = True
|
portrait = True
|
||||||
elif camera_aspect < canvas_aspect:
|
|
||||||
# if the camera aspect ratio is less than canvas aspect ratio, it needs to be scaled down to fit
|
|
||||||
camera_dims[0] *= camera_aspect / canvas_aspect
|
|
||||||
camera_dims[1] *= camera_aspect / canvas_aspect
|
|
||||||
portrait = False
|
|
||||||
else:
|
else:
|
||||||
portrait = False
|
portrait = False
|
||||||
|
|
||||||
if (x + camera_dims[0] * coefficient) <= canvas[0]:
|
if (x + camera_aspect_x) <= canvas_aspect_x:
|
||||||
# insert if camera can fit on current row
|
# insert if camera can fit on current row
|
||||||
scaled_width = int(camera_dims[0] * coefficient)
|
|
||||||
camera_layout[y_i].append(
|
camera_layout[y_i].append(
|
||||||
(
|
(
|
||||||
camera,
|
camera,
|
||||||
(
|
(
|
||||||
x,
|
camera_aspect_x,
|
||||||
y,
|
camera_aspect_y,
|
||||||
scaled_width,
|
|
||||||
int(camera_dims[1] * coefficient),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
x += scaled_width
|
|
||||||
|
|
||||||
if portrait:
|
if portrait:
|
||||||
starting_x = scaled_width
|
starting_x = camera_aspect_x
|
||||||
else:
|
else:
|
||||||
max_height = max(
|
max_y = max(
|
||||||
max_height,
|
max_y,
|
||||||
int(camera_dims[1] * coefficient),
|
camera_aspect_y,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
x += camera_aspect_x
|
||||||
else:
|
else:
|
||||||
# move on to the next row and insert
|
# move on to the next row and insert
|
||||||
y += max_height
|
y += max_y
|
||||||
y_i += 1
|
y_i += 1
|
||||||
camera_layout.append([])
|
camera_layout.append([])
|
||||||
x = starting_x
|
x = starting_x
|
||||||
|
|
||||||
if camera_dims[0] * coefficient > canvas_width:
|
if x + camera_aspect_x > canvas_aspect_x:
|
||||||
safe_coefficient = 1
|
return None
|
||||||
else:
|
|
||||||
safe_coefficient = coefficient
|
|
||||||
|
|
||||||
camera_layout[y_i].append(
|
camera_layout[y_i].append(
|
||||||
(
|
(
|
||||||
camera,
|
camera,
|
||||||
(
|
(camera_aspect_x, camera_aspect_y),
|
||||||
x,
|
|
||||||
y,
|
|
||||||
int(camera_dims[0] * safe_coefficient),
|
|
||||||
int(camera_dims[1] * safe_coefficient),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
x += int(camera_dims[0] * safe_coefficient)
|
x += camera_aspect_x
|
||||||
|
|
||||||
return (camera_layout, y + max_height)
|
if y + max_y > canvas_aspect_y:
|
||||||
|
return None
|
||||||
|
|
||||||
|
row_height = int(canvas_height / coefficient)
|
||||||
|
|
||||||
|
final_camera_layout = []
|
||||||
|
starting_x = 0
|
||||||
|
y = 0
|
||||||
|
|
||||||
|
for row in camera_layout:
|
||||||
|
final_row = []
|
||||||
|
x = starting_x
|
||||||
|
for cameras in row:
|
||||||
|
camera_dims = self.cameras[cameras[0]]["dimensions"].copy()
|
||||||
|
|
||||||
|
if camera_dims[1] > camera_dims[0]:
|
||||||
|
scaled_height = int(row_height * coefficient)
|
||||||
|
scaled_width = int(
|
||||||
|
scaled_height * camera_dims[0] / camera_dims[1]
|
||||||
|
)
|
||||||
|
starting_x = scaled_width
|
||||||
|
else:
|
||||||
|
scaled_height = row_height
|
||||||
|
scaled_width = int(
|
||||||
|
scaled_height * camera_dims[0] / camera_dims[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
x + scaled_width > canvas_width
|
||||||
|
or y + scaled_height > canvas_height
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
|
final_row.append((cameras[0], (x, y, scaled_width, scaled_height)))
|
||||||
|
x += scaled_width
|
||||||
|
y += row_height
|
||||||
|
final_camera_layout.append(final_row)
|
||||||
|
|
||||||
|
return final_camera_layout
|
||||||
|
|
||||||
# determine how many cameras are tracking objects within the last 30 seconds
|
# determine how many cameras are tracking objects within the last 30 seconds
|
||||||
active_cameras = set(
|
active_cameras = set(
|
||||||
@ -493,30 +451,28 @@ class BirdsEyeFrameManager:
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
elif len(active_cameras) == 2:
|
|
||||||
self.camera_layout = calculate_two_cam_layout(
|
|
||||||
(canvas_width, canvas_height), active_cameras_to_add
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# calculate optimal layout
|
# calculate optimal layout
|
||||||
coefficient = 1.0
|
coefficient = 2
|
||||||
calculating = True
|
calculating = True
|
||||||
|
|
||||||
# decrease scaling coefficient until height of all cameras can fit into the birdseye canvas
|
# decrease scaling coefficient until height of all cameras can fit into the birdseye canvas
|
||||||
while calculating:
|
while calculating:
|
||||||
layout_candidate, total_height = calculate_layout(
|
layout_candidate = calculate_layout(
|
||||||
(canvas_width, canvas_height),
|
(canvas_width, canvas_height),
|
||||||
active_cameras_to_add,
|
active_cameras_to_add,
|
||||||
coefficient,
|
coefficient,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (canvas_height * 0.75) < total_height <= canvas_height:
|
if not layout_candidate:
|
||||||
calculating = False
|
if coefficient < 10:
|
||||||
elif total_height < canvas_height * 0.75:
|
coefficient += 1
|
||||||
coefficient += 0.1
|
continue
|
||||||
calculating = False
|
else:
|
||||||
else:
|
logger.error("Error finding appropriate birdseye layout")
|
||||||
coefficient -= 0.1
|
return
|
||||||
|
|
||||||
|
calculating = False
|
||||||
|
|
||||||
self.camera_layout = layout_candidate
|
self.camera_layout = layout_candidate
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user