如何在二维图像中的不规则形状上应用纹理?
How to apply texture on irregular shapes in 2d image?
我正在尝试将来自 UIColor 图案图像的纹理应用到 CALayer 上。正在应用纹理,但未正确转换透视图。看起来我的绘图逻辑有问题,即我需要使用纹理图像并将其映射到不规则形状上。我做了一些研究,了解到这件事可以通过 OpenGL 或金属通过将纹理图像映射到二维图像中的不规则形状来实现。
寻找某种指导,我如何才能正确透视变换瓷砖图案?
let image = UIImage(named: "roofTiles_square")?.flattened
if let realImage = image {
let color = UIColor(patternImage: realImage)
controller.quadView.quadLayer.fillColor = color.cgColor
}
如有任何帮助,我们将不胜感激。
谢谢
按照与平行四边形屋顶主要部分相同的方式进行操作,也就是说,如果您不关心它在屋内的外观...
好吧,他们说一张图片抵得上 1000 个字。上面我修改过的图片显示了绿色的平行四边形,您应该将其贴图贴图。大概它的正确部分将在主屋顶下进行 Z 排序。同样的事情发生在一些房子的阁楼上,建筑商不必支付材料费用,而是按小时支付。他们只会切入屋顶并插入直线预制件...
我正在写一个详细的解决方案,但我想确保我正在解决正确的问题。计划是创建一个适当扭曲版本的“屋顶瓦片”图案(或任何此类图案)的转换,以便在映射到右侧图像中的四边形时正确地透视扭曲?即四边形 ABCE 映射到四边形 A'B'C'E'?
第一步是计算 homography that maps the quad ABCD to the quad A'B'C'D'. OpenCV provides methods for this,但让我们自己算一下。我们正在搜索一个将点 A、B、C、D 映射到点 A'、B'、C'、D' 的 3x3 矩阵 H,如下所示(我们实际上会反过来做):
使用 3D 同质向量 (x,y,w) 允许我们在 3-D 中工作,除以 w 提供必要的透视缩短(长话短说)。
事实证明,H 的任何比例倍数都有效,这意味着它只有 8 个自由度(而不是完整的 3*3 = 9)。这意味着我们希望 HA' 是 A 的比例倍数,因此它们的叉积为零:
如果我们执行叉积,我们可以将最后一个等式重写为
上面最后一个方程实际上是前两个方程的线性组合(第一个方程乘以x,第二个方程乘以y,相加得到第三个方程)。由于线性相关的第三个方程我们将其丢弃并仅使用前两个。将第二个方程取反,交换它们,然后将它们转换为矩阵形式后,我们得到
因此一点对应 A' - A 产生两个方程。
如果我们有 n 个点对应,我们得到 2n 个方程:
我们需要 n >= 4 才能得到至少 8 个方程才能得到正确的解;即,我们至少需要 4 (non-collinear) 个点。
因此我们有一个齐次方程组
我们使用 singular value decomposition:
解决
显然简单的解决方案 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
function
#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;
}
结果看起来合理:
我正在尝试将来自 UIColor 图案图像的纹理应用到 CALayer 上。正在应用纹理,但未正确转换透视图。看起来我的绘图逻辑有问题,即我需要使用纹理图像并将其映射到不规则形状上。我做了一些研究,了解到这件事可以通过 OpenGL 或金属通过将纹理图像映射到二维图像中的不规则形状来实现。
寻找某种指导,我如何才能正确透视变换瓷砖图案?
let image = UIImage(named: "roofTiles_square")?.flattened
if let realImage = image {
let color = UIColor(patternImage: realImage)
controller.quadView.quadLayer.fillColor = color.cgColor
}
如有任何帮助,我们将不胜感激。
谢谢
按照与平行四边形屋顶主要部分相同的方式进行操作,也就是说,如果您不关心它在屋内的外观...
好吧,他们说一张图片抵得上 1000 个字。上面我修改过的图片显示了绿色的平行四边形,您应该将其贴图贴图。大概它的正确部分将在主屋顶下进行 Z 排序。同样的事情发生在一些房子的阁楼上,建筑商不必支付材料费用,而是按小时支付。他们只会切入屋顶并插入直线预制件...
我正在写一个详细的解决方案,但我想确保我正在解决正确的问题。计划是创建一个适当扭曲版本的“屋顶瓦片”图案(或任何此类图案)的转换,以便在映射到右侧图像中的四边形时正确地透视扭曲?即四边形 ABCE 映射到四边形 A'B'C'E'?
第一步是计算 homography that maps the quad ABCD to the quad A'B'C'D'. OpenCV provides methods for this,但让我们自己算一下。我们正在搜索一个将点 A、B、C、D 映射到点 A'、B'、C'、D' 的 3x3 矩阵 H,如下所示(我们实际上会反过来做):
使用 3D 同质向量 (x,y,w) 允许我们在 3-D 中工作,除以 w 提供必要的透视缩短(长话短说)。 事实证明,H 的任何比例倍数都有效,这意味着它只有 8 个自由度(而不是完整的 3*3 = 9)。这意味着我们希望 HA' 是 A 的比例倍数,因此它们的叉积为零:
如果我们执行叉积,我们可以将最后一个等式重写为
上面最后一个方程实际上是前两个方程的线性组合(第一个方程乘以x,第二个方程乘以y,相加得到第三个方程)。由于线性相关的第三个方程我们将其丢弃并仅使用前两个。将第二个方程取反,交换它们,然后将它们转换为矩阵形式后,我们得到
因此一点对应 A' - A 产生两个方程。 如果我们有 n 个点对应,我们得到 2n 个方程:
我们需要 n >= 4 才能得到至少 8 个方程才能得到正确的解;即,我们至少需要 4 (non-collinear) 个点。 因此我们有一个齐次方程组 我们使用 singular value decomposition:
解决显然简单的解决方案 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
function
#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;
}
结果看起来合理: