getPerspectiveTransform 矩阵变换使用超过 4 个点对的复杂形状

getPerspectiveTransform Matrix transforms with complex shapes using more than 4 points pairs

我有两个形状或坐标系,我希望能够将点从一个系统转换到另一个系统。

我发现,如果形状是四边形并且我有 4 对对应点,那么我可以计算一个变换矩阵,然后使用该矩阵计算 Shape B 中的任何点到它在 Shape A.

这是进行此计算的工作 python 代码:

import numpy as np
import cv2

shape_a_points = np.array([
    [0.6, 0],
    [1, 0.75],
    [0.8, 1],
    [0.5, 0.6]
], dtype="float32")

shape_b_points = np.array([
    [0, 0],
    [1, 0],
    [1, 1],
    [0, 1],
], dtype="float32")

test_points = [0.5, 0.5]


matrix = cv2.getPerspectiveTransform(shape_b_points, shape_a_points)

print(matrix)

result = cv2.perspectiveTransform(np.array([[test_points]], dtype="float32"), matrix)
print(result)

如果你 运行 这段代码,你会看到 Shape B 上的 (0.5, 0.5) 测试点(正中间),结果为 (0.73, 0.67) Shape A,视觉上看起来是正确的。

但是形状再复杂一点怎么办。比如4+N个顶点,以及4+N对对应点?或者更复杂,如果形状中有曲线怎么办?

例如:

如果两个形状通过透视变换相关联,那么任何四个点都将导致相同的变换,至少只要它们不共线即可。理论上你可以选择任意四个这样的点,其余的应该就可以了。

在实践中,数字因素可能会发挥作用。如果您选择彼此非常接近的点,那么它们位置上的小误差会导致距离这些点更远的更大误差。您可能会进行一些涉及错误间隔的复杂分析,但根据经验,我会尝试针对转换的输入端和输出端的任意两点之间的较大距离。

An answer from me on Math Exchange 解释了为点对给出的透视变换定义中的一些计算。这可能有助于理解数字 4 的来源。

如果您有 4 对以上的点,并且使用其中任意四个点定义转换都不能正确转换其余点,那么您可能属于其他两个用例之一。

要么您确实在寻找透视变换,但输入数据不佳。您可能有来自特征检测的位置,并且可能不精确。有些特征甚至可以间接匹配。因此,在这种情况下,您将寻找最佳转换来描述具有小错误的数据。你的问题听起来不像是你的用例,所以我不会详细说明。

我们有一个不是透视变换的变换。特别是任何将直线变成弯曲曲线或反之亦然的东西都不再是透视变换。您可能正在寻找其他 class 变换,或者寻找分段投影变换之类的东西。在不了解您的用例的情况下,很难为此提出好的 class 转换建议。

感谢@christoph-rackwitz 为我指明了正确的方向。

我发现使用 OpenCV ThinPlateSplineShapeTransformer 进行转换的结果非常好。

下面是我的示例脚本。请注意,我有 7 对点。 “匹配项”只是一个 7 的列表(告诉脚本点 #1 来自 Shape A 与点 #1 从 Shape B 匹配......等等)

import numpy as np
import cv2

number_of_points = 7

shape_a_points = np.array([
    [0.6, 0],
    [1, 0.75],
    [0.8, 1],
    [0.5, 0.6],
    [0.75, 0],
    [1, 0],
    [1, 0.25]
], dtype="float32").reshape((-1, number_of_points, 2))

shape_b_points = np.array([
    [0, 0],
    [1, 0],
    [1, 1],
    [0, 1],
    [0.25, 0],
    [0.5, 0],
    [0.75, 0]
], dtype="float32").reshape((-1, number_of_points, 2))

test_points = [0.5, 0.5]

matches = [cv2.DMatch(i, i, 0) for i in range(number_of_points)]

tps = cv2.createThinPlateSplineShapeTransformer()
tps.estimateTransformation(shape_b_points, shape_a_points, matches)
M = tps.applyTransformation(np.array([[test_points]], dtype="float32"))
print(M[1])

我不知道你为什么需要重塑数组; “你就去做”,否则它不会起作用。

我也把它写成一个简单的class如果有人要用的话:

import cv2
import numpy as np

class transform:
    def __init__(self, points_a, points_b):
        assert len(points_a) == len(points_b), "Number of points in set A and set B should be same count"

        matches = [cv2.DMatch(i, i, 0) for i in range(len(points_a))]

        self.tps = cv2.createThinPlateSplineShapeTransformer()
        self.tps.estimateTransformation(np.array(points_b, dtype="float32").reshape((-1, len(points_a), 2)),
                                        np.array(points_a, dtype="float32").reshape((-1, len(points_a), 2)), matches)

    def transformPoint(self, point):
        result = self.tps.applyTransformation(np.array([[point]], dtype="float32"))
        return result[1][0][0]