将OpenCV的findHomography透视矩阵转换为iOS的CATransform3D

4
我希望能将OpenCV的findHomography函数返回的透视变换矩阵转换为iOS的CATransform3D(可以使用C++或Objective-C实现)。我希望它们在Core Graphics方面尽可能地精确重现“warp”效果。非常感谢提供示例代码!
来自iOS的CATransform3D.h:
/* Homogeneous three-dimensional transforms. */

struct CATransform3D
{
    CGFloat m11, m12, m13, m14;
    CGFloat m21, m22, m23, m24;
    CGFloat m31, m32, m33, m34;
    CGFloat m41, m42, m43, m44;
};

类似问题:

如何使用Core Graphics应用单应矩阵

如何将OpenCV仿射矩阵转换为CGAffineTransform

3个回答

5

免责声明

我从未尝试过这个,所以请谨慎对待。

CATRansform3D是一个4x4矩阵,它作用于一个三维齐次向量(4x1)上,产生另一个相同类型的向量。我假设当渲染时,由4x1向量描述的对象每个元素都被除以第四个元素,并且第三个元素仅用于确定哪些对象出现在哪些对象上方。假设这是正确的...

推理

findHomography返回的3x3矩阵作用于2维齐次向量。这个过程可以分为4个步骤:

  1. 将单应性的第一列乘以x
  2. 将单应性的第二列乘以y
  3. 将单应性的第三列乘以1
  4. 将得到的第一和第二个向量元素除以第三个元素

你需要在一个4x4向量中复制这个过程,我假设结果向量中的第三个元素对你的目的没有意义。

解决方案

像这样构建你的矩阵(H是你的单应性矩阵)

[H(0,0), H(0,1), 0, H(0,2),
 H(1,0), H(1,1), 0, H(1,2),
      0,      0, 1,      0
 H(2,0), H(2,1), 0, H(2,2)]

这显然满足了1、2和3。4是因为均匀元素始终是最后一个被满足的。这就是为什么“均匀行”必须向下移动一行的原因。第三行的1是为了让向量的z分量原封不动地通过。
所有以上内容都是按行主记法(像openCV)完成的,以尝试避免混淆。你可以查看Tommy的回答,看看如何转换为列主记法(基本上只需对其进行转置)。然而,请注意,目前Tommy和我在构建矩阵方面存在分歧。

我认为我们不再有分歧了,而且我明确认为你是正确的。我已经更新了我的答案,但说实话,如果我提供的代码是正确的,那么我认为你应该得到这个勾选。提供一些复制和粘贴的内容来说明行/列主要性感觉不太有帮助,与其如此,不如指出在计算机图形中,2D坐标通常是(x,y,1)的隐式表示,3D坐标是(x,y,z,1),其中隐含的部分是齐次部分,并始终向右推出。 - Tommy
@Tommy,在我看来,你发布的矩阵似乎与我建议的不同,注意到我的矩阵中第三列和第三行都是空的,除了1。以右下角的元素为例,我说它是H(2,2),而你说它是1。 - Hammer
非常感谢大家的回答,对于这个话题有多个视角的看法确实很有帮助。真的非常感激。结果证明,Hammer的解决方案起了作用。Tommy,你的方法也很接近,但我还是注意到了一些小的视觉差异。行与列的问题(以及需要转置)真的很令人困惑。再次感谢Hammer提供详细的说明。 - taber
@taber 谢谢您让我们知道!我同意当我不得不处理它时,转置让我发疯。 - Hammer
我认为主要的困惑在于,如果没有旋转或缩放,或者事物非常接近单位变换,那么你可以使用列主序或行主序,甚至不知道自己是错误的。 - taber
显示剩余3条评论

4
根据文档,CATransform3D中的m11等同于CGAffineTransform中的am12等同于b,以此类推。根据你下面的评论,我了解到OpenCV返回的矩阵是3x3的(回想起来,这是您预期的大小)。因此,您需要使用等同于单位矩阵的其他元素来填充其他元素。根据Hammer的答案,您希望在原位保留处理(通常隐含的)齐次坐标的部分,同时用单位矩阵填充其他所有部分。我的原始答案是错误的,我已将其编辑为正确的答案,因为我发布了代码,而Hammer没有。此帖子标记为社区wiki,以反映它不完全是我的答案。
CATransform3D MatToTransform(Mat cvTransform)
{
    CATransform3D transform;

    transform.m11 = cvTransform.at<float>(0, 0);
    transform.m12 = cvTransform.at<float>(1, 0);
    transform.m13 = 0.0f;
    transform.m14 = cvTransform.at<float>(2, 0);

    transform.m21 = cvTransform.at<float>(0, 1);
    transform.m22 = cvTransform.at<float>(1, 1);
    transform.m23 = 0.0f;
    transform.m24 = cvTransform.at<float>(2, 1);

    transform.m31 = 0.0f;
    transform.m32 = 0.0f;
    transform.m33 = 1.0f;
    transform.m34 = 0.0f;

    transform.m41 = cvTransform.at<float>(0, 2);
    transform.m42 = cvTransform.at<float>(1, 2);
    transform.m43 = 0.0f;
    transform.m44 = cvTransform.at<float>(2, 2);

    return transform;
}

如果你不想使用C++,可以使用cvGetReal1D


是的,但是findHomography返回的是一个3x3的矩阵而不是4x4的。 - Hammer
在这种情况下,使用单位元素填充其他条目应该是安全的。等一下,我会更新... - Tommy
我尝试过这个,但不起作用。结果的矩阵总是旋转 pi/2 的倍数和比例巨大(在 https://dev59.com/1W_Xa4cB1Zd3GeqP3qy0 中已回答比例和旋转之间的竞争)。 - Max Snijders
有人成功运行过这个函数吗?我已经找到了两张图像的单应性矩阵。我可以使用OpenCV的warpPerspective正确地转换一张图像,所以我知道我的单应性矩阵是正确的。然而,当我尝试使用上述代码创建CATransform3D并应用于CALayer时,它根本不正确。这篇帖子是互联网上最接近答案的东西,所以我希望重新提出这个话题,并看看是否有可靠的解决方案。 - VTPete
它对我有效,只需要在从Mat转换时使用double。 - Pavlo Razumovskyi
显示剩余2条评论

0
Tommy的答案对我有用,但我需要使用double而不是float。这也是代码的缩短版本:
CATransform3D MatToCATransform3D(cv::Mat H) {
    return {
        H.at<double>(0, 0), H.at<double>(1, 0), 0.0, H.at<double>(2, 0),
        H.at<double>(0, 1), H.at<double>(1, 1), 0.0, H.at<double>(2, 1),
        0.0, 0.0, 1.0, 0.0,
        H.at<double>(0, 2), H.at<double>(1, 2), 0.0f, H.at<double>(2, 2)
    };
}

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