Source code for aws.osml.photogrammetry.transforms
# Copyright 2023-2024 Amazon.com, Inc. or its affiliates.
from __future__ import annotations
import numpy as np
import numpy.typing as npt
[docs]
class ProjectiveTransform:
"""
This is a simple standalone projective transform class with an implementation that only depends on NumPy. There
are equivalent classes in Open CV and Scikit Imaging but this class can be used when we don't want to include those
dependencies.
"""
def __init__(self, matrix_parameters: npt.ArrayLike) -> None:
"""
Construct a projective transform from the given matrix parameters. Normally this constructor is not called
directly. See the ProjectiveTransform.estimate() method instead.
:param matrix_parameters: the matrix parameters for this transformation
:return: None
"""
self.matrix_parameters = matrix_parameters
[docs]
def forward(self, src_coords: npt.ArrayLike):
"""
Compute the forward src -> dst transformation for an array of source coordinates [[x, y], ...]
:param src_coords: the source coordinates
:return: the array of transformed coordinates [[x',y'], ...]
"""
a0, a1, a2, b0, b1, b2, c0, c1 = self.matrix_parameters
x = src_coords[:, 0]
y = src_coords[:, 1]
out = np.zeros(src_coords.shape)
out[:, 0] = (a0 + a1 * x + a2 * y) / (1 + c0 * x + c1 * y)
out[:, 1] = (b0 + b1 * x + b2 * y) / (1 + c0 * x + c1 * y)
return out
[docs]
def inverse(self, dst_coords: npt.ArrayLike):
"""
Compute the inverse, dst -> src, transformation for an array of destination coordinates [[x', y'], ...]
:param dst_coords: the destination coordinates
:return: the array of transformed coordinates [[x,y], ...]
"""
a0, a1, a2, b0, b1, b2, c0, c1 = self.matrix_parameters
x = dst_coords[:, 0]
y = dst_coords[:, 1]
out = np.zeros(dst_coords.shape)
out[:, 0] = (a2 * b0 - a0 * b2 + (b2 - b0 * c1) * x + (a0 * c1 - a2) * y) / (
a1 * b2 - a2 * b1 + (b1 * c1 - b2 * c0) * x + (a2 * c0 - a1 * c1) * y
)
out[:, 1] = (a0 * b1 - a1 * b0 + (b0 * c0 - b1) * x + (a1 - a0 * c0) * y) / (
a1 * b2 - a2 * b1 + (b1 * c1 - b2 * c0) * x + (a2 * c0 - a1 * c1) * y
)
return out
[docs]
@classmethod
def estimate(cls, src: npt.ArrayLike, dst: npt.ArrayLike) -> ProjectiveTransform:
"""
This method takes a list of source and destination points [x, y] and then applies the least squares fit
to estimate the projective transform matrix relating them. It then creates the ProjectiveTransform
object using that matrices. Each list needs to contain at least 4 points.
:param src: the source points
:param dst: the destination points
:return: the projective transform
"""
xs = src[:, 0]
ys = src[:, 1]
num_points = src.shape[0]
# The Coefficient Matrix
a = np.zeros((num_points * 2, 8))
a[:num_points, 0] = 1
a[:num_points, 1] = xs
a[:num_points, 2] = ys
a[num_points:, 3] = 1
a[num_points:, 4] = xs
a[num_points:, 5] = ys
a[:num_points, 6] = -dst[:, 0] * xs
a[:num_points, 7] = -dst[:, 0] * ys
a[num_points:, 6] = -dst[:, 1] * xs
a[num_points:, 7] = -dst[:, 1] * ys
# The dependent variable values
b = np.zeros((num_points * 2,))
b[:num_points] = dst[:, 0]
b[num_points:] = dst[:, 1]
# See: https://numpy.org/doc/stable/reference/generated/numpy.linalg.lstsq.html
matrix_parameters = np.linalg.lstsq(a, b, rcond=None)[0]
return cls(matrix_parameters)