6

In the context of machine vision, I need to evaluate the rotation of a polygon recognized on an image based on a reference polygon. This is better explained with a picture:

Polygon rotation

The reference polygon is available as an array of coordinates, ref_coords_array. So is the actual polygon to evaluate, eval_coords_array. In the example, the arrays contain 5 coordinates. From ref_coords_array and eval_coords_array, the angle must be evaluated in a way that is independent of homothecy and slight changes, like in the two last examples on the image.

I have searched for similar problems or solutions but I did not find anything. Maybe because I am not looking for the right terms.

Ideally, the solution would be based on Python and/or OpenCV. But an abstract description would be helpful too.

ocroquette
  • 221
  • 4

1 Answers1

6

valentas pointed me in the right direction and the following function estimateAffinePartial2D() from OpenCV solves almost the problem. Almost, because it assumes that the points in the source and destination coordinates are in the optimal order. See case 4 below for an example. To address this, I use a brute force approach and try all possible permutations and keep the one that leads to the smallest reprojection error.

import itertools

import cv2 import numpy as np import math

Simple triangle:

src_points = np.array([[50, 50], [100, 50], [75, 100]], dtype=np.float32)

Case 1: X translation:

dst_points = np.array([[1050, 50], [1100, 50], [1075, 100]], dtype=np.float32)

Case 2: Y translation:

dst_points = np.array([[50, 1050], [100, 1050], [75, 1100]], dtype=np.float32)

Case 3: 180° rotation:

dst_points = np.array([[100, 100], [50, 100], [75, 50]], dtype=np.float32)

Case 4: 180° rotation with different point indices:

dst_points = np.array([[50, 100], [75, 50], [100, 100]], dtype=np.float32)

best_error = None best_angle = None best_permutation = None for permutation in itertools.permutations(dst_points): np_permutation = np.array(permutation) # Estimate affine transformation matrix (rotation + translation) transformation_matrix, inliers = cv2.estimateAffinePartial2D(src_points, np_permutation) if transformation_matrix is not None: # Extract the rotation matrix part from the transformation matrix cos_theta = transformation_matrix[0, 0] sin_theta = transformation_matrix[1, 0] rotation_angle_radians = math.atan2(sin_theta, cos_theta) rotation_angle_degrees = np.degrees(rotation_angle_radians) # Calculate reprojection error (Euclidean distance between transformed points and destination points) # Only using inliers to calculate the error inlier_points = src_points[inliers] # Apply the transformation to the inlier points transformed_points = cv2.transform(inlier_points, transformation_matrix)[0]

    # Now calculate the reprojection error (Euclidean distance) for inliers
    reprojection_errors = np.linalg.norm(transformed_points - np_permutation[inliers], axis=1)
    reprojection_error = np.mean(reprojection_errors)

    print(f"reprojection_error: {reprojection_error}")
    if best_error is None or best_error > reprojection_error:
        best_permutation = permutation
        best_error = reprojection_error
        best_angle = rotation_angle_degrees
else:
    print("Transformation estimation failed.")

print() print(f"Best permutation: {permutation}") print(f"Best error: {best_error}") print(f"Best angle: {best_angle}")

I wonder now if there is a better solution than the brute-force approach.

ocroquette
  • 221
  • 4