两组点之间的转换

20
我有一个对象,假设它在模型图像上,我想计算对象在模型图像和目标图像上的转换(位移、缩放、旋转)。我希望做出这样的假设:对象可以视为2D,因此只需要计算2D变换。
首先,我想以手动辅助的方式完成它。用户在模型图像上选择基准点,然后在目标图像上选择目标点。用户应该定义点的数量(但至少不少于某些最小2-3个点)。当点给出不同的信息时,应该平均变换,例如从中可以计算匹配的质量。
因此,问题更多地涉及计算两组点的转换,但由于我想在图像上执行此操作,因此我已添加了图像处理标签。
尤其欢迎提供一些代码或伪代码片段的参考和建议。
使用两个点非常容易,只需要取线的旋转、缩放和位移即可,但如何用更多的点进行平均并计算一些质量因素呢? 当前解决方案是:
void transformFnc(std::vector<PointF> basePoints, std::vector<PointF> targetPoints,
                  PointF& offset, double rotation, double scale)
{
    std::vector<Line> basePointsLines;
    std::vector<Line> targetPointsLines;
    assert(basePoints.size() == targetPoints.size());
    int pointsNumber = basePoints.size();

    for(int i = 0; i < pointsNumber; i++)
    {
         for(int j = i + 1; j < pointsNumber; j++)
         {
             basePointsLines.push_back(Line(basePoints[i], basePoints[j]));
             targetPointsLines.push_back(Line(targetPoints[i], targetPoints[j]));
         }
    }
    std::vector<double> scalesVector;
    std::vector<double> rotationsVector;
    double baseCenterX = 0, baseCenterY = 0, targetCenterX = 0, targetCenterY = 0;
    for(std::vector<Line>::iterator it = basePointsLines.begin(), i = targetPointsLines.begin();
        it != basePointsLines.end(), i != targetPointsLines.end(); it++, i++)
    {
        scalesVector.push_back((*i).length()/(*it).length());
        baseCenterX += (*it).pointAt(0.5).x(); 
        baseCenterY += (*it).pointAt(0.5).y();
        targetCenterX += (*i).pointAt(0.5).x();
        targetCenterY += (*i).pointAt(0.5).y();
        double rotation;
        rotation = (*i).angleTo((*it));
        rotationsVector.push_back(rotation);
    }
    baseCenterX = baseCenterX / pointsNumber;
    baseCenterY = baseCenterY / pointsNumber;
    targetCenterX = targetCenterX / pointsNumber;
    targetCenterY = targetCenterY / pointsNumber;

    offset = PointF(targetCenterX - baseCenterX, targetCenterY - baseCenterY);
    scale = sum(scalesVector) / scalesVector.size();
    rotation = sum(rotationsVector) / rotationsVector.size();
}

这段代码中我能找到的唯一优化是从尺度和旋转中剔除那些与其余值差异太大的值。

我正在寻找解决方案的代码或伪代码。也可以是一些代码的参考资料。

从答案中得知:

  • 可以使用RANSAC算法
  • 需要在最小二乘意义下寻找某些仿射变换算法

您是否也想要其他的变换,例如拉伸和剪切? - Generic Human
不,假设它是一个实体对象。没有透视,拉伸或剪切。 - krzych
1
可能是将一个三角形转化为另一个三角形的重复问题。 - finnw
1
你需要接受这里的一个答案。它们似乎已经充分回答了你的问题。 - Void Star
当赏金结束时,我会接受其中一个。不幸的是,它们中没有一个值得赏金。 - krzych
5个回答

29

首先,将问题概括为一个带有3x3仿射变换矩阵的简单仿射变换:

[M11 M12 M13]
[M21 M22 M23]
[M31 M32 M33]

既然我们已经知道第三行始终是[0 0 1],我们可以简单地忽略它。

现在我们可以将问题描述为以下矩阵方程

[xp0]     [x0 y0 1  0  0  0 ]
[yp0]     [0  0  0  x0 y0 1 ]     [M11]
[xp1]     [x1 y1 1  0  0  0 ]     [M12]
[yp1]  =  [0  0  0  x1 y1 1 ]  *  [M13]
[xp2]     [x2 y2 1  0  0  0 ]     [M21]
[yp2]     [0  0  0  x2 y2 1 ]     [M22]
[xp3]     [x3 y3 1  0  0  0 ]     [M23]
[yp3]     [0  0  0  x3 y3 1 ]

其中xp和yp是投影坐标,x和y是原始坐标。

我们称之为

proj = M * trans

我们可以通过最小二乘法计算变换的最佳拟合。

trans = pinv(M) * proj

其中pinv是伪逆矩阵。

这给我们提供了一个仿射变换,它最好地拟合了以最小二乘方式给出的点。

现在显然这也会给出剪切、坐标翻转以及非均匀缩放,而这些都不是您想要的,因此我们需要以某种方式限制仿射变换以避免剪切。这事实上很容易做到,我们可以使用单个向量来描述旋转(向量的方向)和缩放(向量的大小),另一个向量将简单地垂直于它。这将自由度减少了两个。

M21 = -M12
M22 = M11

所以要减少

[xp0]     [x0  y0 1 0]
[yp0]     [y0 -x0 0 1]
[xp1]     [x1  y1 1 0]     [M11]
[yp1]  =  [y1 -x1 0 1]  *  [M12]
[xp2]     [x2  y2 1 0]     [M13]
[yp2]     [y2 -x2 0 1]     [M23]
[xp3]     [x3  y3 1 0]
[yp3]     [y3 -x3 0 1]

在解决了上述矩阵方程之后,从 M12 和 M11 计算出 M21 和 M22。


在这种方法中,所有点都被平等对待,尽管它对噪声不够稳健。此外,我如何获取要匹配的点的方法描述对于这个问题非常重要。 - krzych
4
在你的问题中,你只问及匹配已经手动完成的情况。该方法使用超定系统的最小二乘拟合,因此对噪声具有强鲁棒性,匹配的点越多,噪声就会更加平均。如果所有点的噪声分布相同,则该解决方案足够好。如果你已经知道不同点的噪声比率,可以通过按照已知噪声比率对projM进行缩放来获得更好的结果。 - wich
好的,我明白了。不幸的是,这种方法中的噪声分布不会相同。您能否比较您提出的方法与我的当前解决方案之间使用点之间的线的误差?在我的方法中,我可以通过消除给出不同旋转和比例的点来消除噪声。 - krzych
我认为你把实例和分布搞混了,当然点A的误差肯定会与点B不同,但问题在于,在大量问题实例中检查误差的分布情况,相对于点B,点A的误差变化是否更大?或者说它们的变化程度大致相同?你可以参考http://en.wikipedia.org/wiki/Normal_distribution - wich
1
请注意,最小二乘法已经减弱了异常值的影响。如果你有20个点表明旋转角度是alpha,而只有两个点表明是beta,那么算法的结果将在alpha和beta之间,但更接近于alpha而不是beta。 - wich
也许我没有表述清楚。但是所选点的数量是由用户定义的,最小数量为3。用户想要选择超过10个点的情况很少见。虽然需要比最小二乘法更好地消除异常值。 - krzych

6
我建议尝试使用“最近点迭代”算法(Iterative closest point algorithm)。 这里 是该算法的维基百科页面。另外,这个链接 提供了一个带缩放功能的实现(SICP)。此外,您可以查看另一个有用的链接

你提供的第二个链接可以抵抗规模。我也需要计算规模。你知道如何从计算变换代码中找到规模吗? - krzych
这是解决此问题的规范,被广泛认可的方法。因为其效果非常出色,所以它被用于基于特征的全景拼接软件中。 - Kaganar
这种方法依赖于特征的质心。如果特征分布不均匀,误差会变大。 - krzych

2
为了简化问题,假设您的输入 x1,...,xn 和输出 y1,...,yn 都是复数。
  1. 您可以通过计算 avg(y) - avg(x) 来计算平均位移,并且一旦完成此操作,您可以从 xy 中减去平均值,使它们都以0为中心。

  2. 现在您想要找到一个旋转和缩放。您可以将两者表示为单个复数 z,使得 x*z 尽可能接近于 y。但是,x*zR 坐标系下的线性函数,因此您可以使用经典的线性代数来解决 z,使得 x*z 在最小二乘意义下尽可能接近于 y

将所有内容综合起来,这将给出在最小二乘意义下的最佳变换。

1

您的转换是一种仿射变换,可以用3*3矩阵来表示。因此,您的问题基本上是计算从一个点集到另一个点集的最小均方误差仿射变换。

这个问题在常见的计算几何文献中很容易解决。一本好的经典书籍是这本:http://www.robots.ox.ac.uk/~vgg/hzbook/hzbook1.html(没有广告,它只是大多数人的参考书)。您将找到有关2D和3D几何的所有信息。在像“仿射变换LMSE”这样的词上快速搜索也会给您提供信息和代码。

此外,您还可以使用其他类型的算法,如RANSAC等强健算法。根据您的应用程序,深入探讨这个方向可能会更有趣。


据我所知,仿射变换与我所描述的不同。它包括拉伸、剪切等操作。 - krzych
正确,这是一种概括。但是从位置获得方程的方法是相同的,您只需多一个约束条件(无剪切)。 - Bentoy13

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接