Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make faster AbsolutePath drawer, and allow FixedCamera to work with Homographies #305

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
14 changes: 7 additions & 7 deletions demos/camera_motion/src/demo.py
Expand Up @@ -142,7 +142,7 @@ def run():
"--fixed-camera-scale",
type=float,
default=0,
help="Scale of the fixed camera, set to 0 to disable. Note that this only works for translation",
help="Scale of the fixed camera, set to 0 to disable.",
)
parser.add_argument(
"--draw-absolute-grid",
Expand All @@ -160,13 +160,13 @@ def run():
"--draw-paths",
dest="draw_paths",
action="store_true",
help="Pass this flag to draw the paths of the objects (SLOW)",
help="Pass this flag to draw the paths of the objects",
)
parser.add_argument(
"--path-history",
"--path-drawer-scale",
type=int,
default=20,
help="Length of the paths",
default=None,
help="Canvas (background) scale relative to frame size for the AbsolutePath drawer",
)
parser.add_argument(
"--id-size",
Expand Down Expand Up @@ -215,7 +215,7 @@ def run():
fixed_camera = FixedCamera(scale=args.fixed_camera_scale)

if args.draw_paths:
path_drawer = AbsolutePaths(max_history=args.path_history, thickness=2)
path_drawer = AbsolutePaths(scale=args.path_drawer_scale)

video = Video(input_path=input_path)
show_or_write = (
Expand Down Expand Up @@ -286,7 +286,7 @@ def run():

if args.draw_paths:
frame = path_drawer.draw(
frame, tracked_objects, coord_transform=coord_transformations
frame, tracked_objects, coord_transformations=coord_transformations
)

if use_fixed_camera:
Expand Down
116 changes: 69 additions & 47 deletions norfair/drawing/fixed_camera.py
@@ -1,7 +1,14 @@
from typing import Union

try:
import cv2
except ImportError:
from norfair.utils import DummyOpenCVImport

cv2 = DummyOpenCVImport()
import numpy as np

from norfair.camera_motion import TranslationTransformation
from norfair.utils import warn_once
from norfair.camera_motion import HomographyTransformation, TranslationTransformation


class FixedCamera:
Expand Down Expand Up @@ -51,13 +58,17 @@ class FixedCamera:
>>> video.write(bigger_frame)
"""

def __init__(self, scale: float = 2, attenuation: float = 0.05):
def __init__(self, scale: float = None, attenuation: float = 0.05):
self.scale = scale
self._background = None
self._attenuation_factor = 1 - attenuation

def adjust_frame(
self, frame: np.ndarray, coord_transformation: TranslationTransformation
self,
frame: np.ndarray,
coord_transformations: Union[
HomographyTransformation, TranslationTransformation
],
) -> np.ndarray:
"""
Render scaled up frame.
Expand All @@ -66,7 +77,7 @@ def adjust_frame(
----------
frame : np.ndarray
The OpenCV frame.
coord_transformation : TranslationTransformation
coord_transformations : Union[TranslationTransformation, HomographyTransformation]
The coordinate transformation as returned by the [`MotionEstimator`][norfair.camera_motion.MotionEstimator]

Returns
Expand All @@ -77,6 +88,12 @@ def adjust_frame(

# initialize background if necessary
if self._background is None:
if self.scale is None:
if coord_transformations is None:
self.scale = 1
else:
self.scale = 3

original_size = (
frame.shape[1],
frame.shape[0],
Expand All @@ -89,53 +106,58 @@ def adjust_frame(
[scaled_size[1], scaled_size[0], frame.shape[-1]],
frame.dtype,
)
# top_left is the anchor coordinate from where we start drawing the fame on top of the background
self.top_left = (
np.array(self._background.shape[:2]) // 2
- np.array(frame.shape[:2]) // 2
)
self.top_left = self.top_left[::-1]
else:
self._background = (self._background * self._attenuation_factor).astype(
frame.dtype
)

# top_left is the anchor coordinate from where we start drawing the fame on top of the background
# aim to draw it in the center of the background but transformations will move this point
top_left = (
np.array(self._background.shape[:2]) // 2 - np.array(frame.shape[:2]) // 2
)
top_left = (
coord_transformation.rel_to_abs(top_left[::-1]).round().astype(int)[::-1]
)
# box of the background that will be updated and the limits of it
background_y0, background_y1 = (top_left[0], top_left[0] + frame.shape[0])
background_x0, background_x1 = (top_left[1], top_left[1] + frame.shape[1])
background_size_y, background_size_x = self._background.shape[:2]

# define box of the frame that will be used
# if the scale is not enough to support the movement, warn the user but keep drawing
# cropping the frame so that the operation doesn't fail
frame_y0, frame_y1, frame_x0, frame_x1 = (0, frame.shape[0], 0, frame.shape[1])
if (
background_y0 < 0
or background_x0 < 0
or background_y1 > background_size_y
or background_x1 > background_size_x
):
warn_once(
"moving_camera_scale is not enough to cover the range of camera movement, frame will be cropped"
# warp the frame with the following composition:
# top_left_translation o rel_to_abs
if isinstance(coord_transformations, HomographyTransformation):
top_left_translation = np.array(
[[1, 0, self.top_left[0]], [0, 1, self.top_left[1]], [0, 0, 1]]
)
full_transformation = (
top_left_translation @ coord_transformations.inverse_homography_matrix
)
background_with_current_frame = cv2.warpPerspective(
frame,
full_transformation,
tuple(self._background.shape[:2][::-1]),
cv2.INTER_LINEAR,
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0),
)
elif isinstance(coord_transformations, TranslationTransformation):

full_transformation = np.array(
[
[1, 0, self.top_left[0] - coord_transformations.movement_vector[0]],
[0, 1, self.top_left[1] - coord_transformations.movement_vector[1]],
]
)
# crop left or top of the frame if necessary
frame_y0 = max(-background_y0, 0)
frame_x0 = max(-background_x0, 0)
# crop right or bottom of the frame if necessary
frame_y1 = max(
min(background_size_y - background_y0, background_y1 - background_y0), 0
background_with_current_frame = cv2.warpAffine(
frame,
full_transformation,
tuple(self._background.shape[:2][::-1]),
cv2.INTER_LINEAR,
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0),
)
frame_x1 = max(
min(background_size_x - background_x0, background_x1 - background_x0), 0
try:
self._background = cv2.addWeighted(
self._background,
0.5,
background_with_current_frame,
0.5,
0.0,
)
# handle cases where the limits of the background become negative which numpy will interpret incorrectly
background_y0 = max(background_y0, 0)
background_x0 = max(background_x0, 0)
background_y1 = max(background_y1, 0)
background_x1 = max(background_x1, 0)
self._background[
background_y0:background_y1, background_x0:background_x1, :
] = frame[frame_y0:frame_y1, frame_x0:frame_x1, :]
return self._background
return self._background
except UnboundLocalError:
return frame