OpenCV provides:
getRotationMatrix2D
to get a 2x3 transformation matrix (rotation, scale, shift) defined bycenter
,angle
andscale
getAffineTransform
to get a 2x3 transformation matrix (rotation, scale, shift, sheer) defined by three pairs of points.
I'd like to get a transformation matrix with rotation, scale, and shift (i.e. no sheer) from two pairs of points.
Here is my current implementation, which works, but it's way too complex for my taste:
from typing import Tuple, Listimport cv2import numpy as npimport numpy.typingdef invert_vector(v: Tuple[float, float]) -> Tuple[float, float]: return -v[0], -v[1]def add_vectors(v1: Tuple[float, float], v2: Tuple[float, float]) -> Tuple[float, float]: return v2[0] + v1[0], v2[1] + v1[1]def subtract_vectors(v1: Tuple[float, float], v2: Tuple[float, float]) -> Tuple[float, float]: return add_vectors(v1, invert_vector(v2))def cross2d(point: Tuple[float, float]) -> Tuple[float, float]: return point[1], -point[0]def get_third_point(p1: Tuple[float, float], p2: Tuple[float, float]) -> Tuple[float, float]: diff = subtract_vectors(p1, p2) return add_vectors(p1, cross2d(diff))def stack_points(points: List[Tuple[float, float]]) -> np.typing.NDArray[np.float32]: return np.vstack([np.array(p, dtype=np.float32) for p in points])def get_transformation_between_two_point_pairs( src: Tuple[Tuple[float, float], Tuple[float, float]], dst: Tuple[Tuple[float, float], Tuple[float, float]]) -> np.typing.NDArray[np.float32]: # We don't actually need a full affine transformation (rotation/translation/scaling/sheering). # A rotation/translation/scaling matrix does suffice. # But cv2.getRotationMatrix2D does not take two point pairs, but instead center, angle, and scale. # To avoid needing to derive these from our two point pairs, # we instead invent a third point to have stable a triangle (isosceles, right-angled). # pylint: disable=no-member return cv2.getAffineTransform( # type: ignore stack_points([src[0], src[1], get_third_point(src[0], src[1])]), stack_points([dst[0], dst[1], get_third_point(dst[0], dst[1])]) ) # pylint: enable=no-memberprint(get_transformation_between_two_point_pairs(((10, 10), (17, 23)), ((30, 30), (70, 30))))
[[ 1.28440367 2.3853211 -6.69724771] [-2.3853211 1.28440367 41.00917431]]
Is there a simpler way to achieve the same result?