如何在二维图像上应用纹理到不规则形状中?

4

enter image description here

我正在尝试使用UIColor图案图像在CALayer上应用纹理。 纹理正在应用,但透视变换不正确。 看起来我的绘图逻辑存在问题,即我需要使用纹理图像并将其映射到不规则形状上。 我进行了一些研究,并知道可以通过OpenGL或Metal在2D图像中将纹理图像映射到不规则形状来实现此目标。
寻求某种指导,如何正确透视变换平铺图案?
        let image = UIImage(named: "roofTiles_square")?.flattened
        
        if let realImage = image {
            let color = UIColor(patternImage: realImage)
            controller.quadView.quadLayer.fillColor = color.cgColor
        }


任何帮助将不胜感激。
谢谢。

请添加预期输出图像 - Awais Fayyaz
2个回答

3
我正在撰写一份详细的解决方案,但我想确保我正在解决正确的问题。计划是创建一个变换,正确地扭曲“屋瓦”图案(或任何类似的图案),以便在映射到右侧图像中的四边形时正确透视变形?即,四边形ABCE被映射到四边形A'B'C'E'?

enter image description here

第一步是计算一个单应矩阵,将四边形ABCD映射到A'B'C'D'。OpenCV提供了相关方法, 但我们自己来计算数学问题。我们要寻找一个3x3的矩阵H,将点A,B,C,D映射到点A',B',C',D',如下所示(实际上我们会反过来做):

enter image description here

使用三维齐次向量(x,y,w)可以让我们在三维空间中工作,并且除以w提供了必要的透视缩短效果(简而言之)。 事实证明,任何H的比例倍数都有效,这意味着它只有8个自由度(而不是完整的3 * 3 = 9)。这意味着我们希望HA'A的比例倍数,因此它们的叉积为零:

enter image description here

如果我们进行叉积运算,我们可以将最后一个方程重写为:

enter image description here

上面的最后一个方程实际上是前两个方程的线性组合(将第一个方程乘以x,将第二个方程乘以y,相加即可得到第三个方程)。由于第三个方程是线性相关的,我们将其舍去,只使用前两个方程。在否定第二个方程、交换它们并将它们转换为矩阵形式之后,我们得到

enter image description here

因此,一个点对应的A' - A会产生两个方程。如果我们有n个点对应,我们将得到2n个方程:

enter image description here

我们需要 n >= 4,才能至少得到8个方程以获得一个正确的解;也就是说,我们至少需要4个(非共线)点。 因此,我们有一个齐次方程组,我们使用奇异值分解来解决它:

enter image description here

显然,微不足道的解决方案 h = 0 是有效的,但并不是很有用。将 h 设置为 V 的最后一列会导致系统的最小二乘误差解,其中 h 的长度为单位长度。
让我们为您的特定示例计算 H。假设要转换的源图像为 WxH = 500x300,因此 A = (0,0),B = (W,0),C = (0,H) 和 D = (W,H)。目标图像为 484x217,我将屋顶的角落定位为 A' = (70.7, 41.3),B' = (278.8, 76.3),C' = (136.4, 121,2) 和 D' = (345.1, 153,2)。我将使用 Eigen 进行计算。因此,我将我的源点和目标点加载到矩阵中:
#include <Eigen/Dense>
...
constexpr double W = 500;
constexpr double H = 300;
constexpr size_t N = 4;

Eigen::Matrix<double,2,N> SRC;
SRC <<
    0, W, 0, W,
    0, 0, H, H;
Eigen::Matrix<double,2,N> DST;
DST <<
    70.7, 278.8, 136.4, 345.1,
    41.3,  76.3, 121.2, 153.2;

我按上述方式构造了一个8x9矩阵A。
Eigen::Matrix<double,2*N,9> A;
A.setZero();
for (size_t i = 0; i < N; i++) {
    const double x_ = DST(0,i), y_ = DST(1,i);
    const double x  = SRC(0,i), y  = SRC(1,i);
    A(2*i,0) = A(2*i+1,3) = x_;
    A(2*i,1) = A(2*i+1,4) = y_;
    A(2*i,2) = A(2*i+1,5) = 1;
    A(2*i,6) = -x*x_;
    A(2*i,7) = -x*y_;
    A(2*i,8) = -x;
    A(2*i+1,6) = -y*x_;
    A(2*i+1,7) = -y*y_;
    A(2*i+1,8) = -y;
}

我接着计算奇异值分解(SVD),从V的最后一列中提取出解,并将结果存储在一个3x3矩阵中:

Eigen::JacobiSVD<Eigen::Matrix<double,2*N,9>> svd(A, Eigen::ComputeFullV);
Eigen::Matrix<double,9,1> h = svd.matrixV().col(8);
Eigen::Matrix3d Homography;
Homography <<
    h(0), h(1), h(2),
    h(3), h(4), h(5),
    h(6), h(7), h(8);

产生所需的3x3矩阵H:
  -0.016329     0.013427      0.599927
   0.004571    -0.0271779     0.799277
   1.78122e-06 -2.83812e-06  -0.00613631

我们可以使用OpenCV查看一个扭曲图像的样例。我加载我的源纹理和单应矩阵H,并使用OpenCV的warpPerspective函数
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>

int main() {
    cv::Mat sourceImage = imread("texture.png", cv::IMREAD_COLOR);
    cv::Matx33d H(-0.016329, 0.013427, 0.599927,
                  0.004571, -0.0271779, 0.799277,
                  1.78122e-06, -2.83812e-06, -0.00613631);
    cv::Mat destImage;
    cv::warpPerspective(sourceImage, destImage, H, cv::Size(487,217),
                        cv::INTER_LINEAR | cv::WARP_INVERSE_MAP);
    cv::imwrite("warped.png", destImage);
    return 0;
}

结果看起来是合理的:

enter image description here enter image description here


是的,完全正确。四边形ABCD(左侧)应映射到四边形A'B'C'E'(右侧)。然后我需要添加一件事情,即基于所选部分的基本长度AB或CE的瓷砖大小。我认为我可以通过调整图案图像的大小来处理瓷砖大小逻辑。 - Farhan Amjad
非常感谢。如果所选部分是六边形或n角形怎么办?我在问题主体中添加了一张截图。 - Farhan Amjad
1
一个单应性矩阵可以将任何平面点集映射到另一个平面点集(六边形和其他平面多边形都可以),但是这一切都在图像空间中完成。如果您正在使用OpenGL并且有一个3D模型,则会为每个顶点分配纹理坐标,并填充每个多边形的面,以便纹理在透视变换下正确扭曲。当您只有图像而没有底层3D模型时,单应性矩阵在计算机视觉中更常用。 - wcochran
非常感谢您,教授,提供如此详细的答案。 - Farhan Amjad
实际上,我是在OpenCV步骤中要求对代码进行编辑。抱歉没有表达清楚。实际上,我尝试过将黑色像素的RGB值更改为白色,但无法使其透明。 - Farhan Amjad
请您帮忙审核一下。 https://stackoverflow.com/questions/67415631/how-to-compute-homography-for-triangle-and-hexagon - Farhan Amjad

0

用与屋顶主体相同的平行四边形方式进行,如果您不在意它在房子内部的外观... enter image description here

他们说一张图片胜过千言万语。我修改的上面的图片显示了您应该贴图到的绿色平行四边形。它的右侧部分可能会被 Z-排序到主屋顶下方。在某些房屋的阁楼中也会发生同样的事情,建筑商不必支付材料费,而是按小时计费。他们只需切割屋顶并插入一个矩形预制件即可...


抱歉!请您能详细说明吗? - Farhan Amjad
1
有一种通过计算单应性矩阵来获取正确透视畸变的方法。如果您需要,我可以写出一个解决方案,但可能超出了您的期望。 - wcochran
@wcochran非常感谢您,教授。我应用了纯色,看起来就像您在图像中提到的那样,但是我正在从图像模式中创建颜色。在这种情况下,输出结果如上面的附件所示。看起来您的答案会起作用。请分享。再次感谢。 - Farhan Amjad
你也可以在Photoshop或Gimp中伪造一个单应性,方法是将纹理放在平行四边形上,然后将其恢复到原始形状,然后进行屏幕截图以制作新的纹理。如果有必要清除阁楼空间,例如如果您要在其中放置HVAC系统模型,我才会费心去做这些事情... - YinOrYan
@wcochran,仍在等待您的回答。谢谢。 - Farhan Amjad
@YinOrYan 谢谢。在我的情况下,阁楼空间不是重点。 - Farhan Amjad

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