pythonopencvcomputer-visionlinear-algebraaffinetransform

How to obtain a 2d transformation matrix from two pairs of points?


OpenCV provides:

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, List

import cv2
import numpy as np
import numpy.typing


def _third_triangle_point(p1: Tuple[float, float], p2: Tuple[float, float]) -> Tuple[float, float]:
    """Calculate the third point of an isosceles right-angled triangle."""
    p1_arr = np.array(p1, dtype=np.float32)
    p2_arr = np.array(p2, dtype=np.float32)
    diff = p2_arr - p1_arr
    perpendicular = np.array((diff[1], -diff[0]), dtype=np.float32)
    result = p1_arr + perpendicular
    return result[0], result[1]


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]:
    # cv2.getAffineTransform takes three point pairs.
    # It supports rotation, translation, scaling, and shearing.
    # We don't need the shearing,
    # so we invent a third point with a stable relation to the given two.
    return cv2.getAffineTransform(  # type: ignore
        _stack_points([src[0], src[1], _third_triangle_point(src[0], src[1])]),
        _stack_points([dst[0], dst[1], _third_triangle_point(dst[0], dst[1])])
    )


print(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?


Solution

  • The solution is simple, one can just use estimateAffinePartial2D:

    from typing import Tuple
    
    import cv2
    import numpy as np
    import numpy.typing
    
    
    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]:
        return cv2.estimateAffinePartial2D(np.array([src[0], src[1]]), np.array([dst[0], dst[1]]))[0]  # type: ignore
    
    
    print(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]]