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:
Nicolas Mowen 2023-06-16 06:35:36 -06:00 committed by GitHub
parent ca7853c087
commit 0592cedcde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

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