我有一个四边形,其坐标已知。我需要使用 Skia 进行透视变换并将图像扭曲到这些坐标。我参考了 Skia 页面中的链接,如 https://developer.xamarin.com/guides/xamarin-forms/advanced/skiasharp/transforms/non-affine/ 但无法使用坐标。如何使用坐标而不是矩阵?
这是一个函数,它获取目标图像上四个角的坐标和源图像的大小。然后它生成一个可以传递给skia的矩阵:
public static SKMatrix CreateMatrixFromPoints(SKPoint topLeft, SKPoint topRight, SKPoint botRight, SKPoint botLeft, float width, float height)
{
(float x1, float y1) = (topLeft.X, topLeft.Y);
(float x2, float y2) = (topRight.X, topRight.Y);
(float x3, float y3) = (botRight.X, botRight.Y);
(float x4, float y4) = (botLeft.X, botLeft.Y);
(float w, float h) = (width, height);
float scaleX = (y1 * x2 * x4 - x1 * y2 * x4 + x1 * y3 * x4 - x2 * y3 * x4 - y1 * x2 * x3 + x1 * y2 * x3 - x1 * y4 * x3 + x2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
float skewX = (-x1 * x2 * y3 - y1 * x2 * x4 + x2 * y3 * x4 + x1 * x2 * y4 + x1 * y2 * x3 + y1 * x4 * x3 - y2 * x4 * x3 - x1 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
float transX = x1;
float skewY = (-y1 * x2 * y3 + x1 * y2 * y3 + y1 * y3 * x4 - y2 * y3 * x4 + y1 * x2 * y4 - x1 * y2 * y4 - y1 * y4 * x3 + y2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
float scaleY = (-y1 * x2 * y3 - y1 * y2 * x4 + y1 * y3 * x4 + x1 * y2 * y4 - x1 * y3 * y4 + x2 * y3 * y4 + y1 * y2 * x3 - y2 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
float transY = y1;
float persp0 = (x1 * y3 - x2 * y3 + y1 * x4 - y2 * x4 - x1 * y4 + x2 * y4 - y1 * x3 + y2 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
float persp1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
float persp2 = 1;
return new SKMatrix(scaleX, skewX, transX, skewY, scaleY, transY, persp0, persp1, persp2);
}
从您链接的页面,skia 计算转换后的坐标,如下所示:
x' = ScaleX·x + SkewX·y + TransX
y' = SkewY·x + ScaleY·y + TransY
z` = Persp0·x + Persp1·y + Persp2
xFinal = x' / z'
yFinal = y' / z'
其中
(x, y)
是像素的原始位置,(xFinal, yFinal)
是其新位置,Scale/Skew/Trans/Persp
的变体是所提供矩阵中的值。
首先,我们稍微重写一下公式:
xFinal = (ScaleX·x + SkewX·y + TransX) / (Persp0·x + Persp1·y + Persp2)
yFinal = (SkewY·x + ScaleY·y + TransY) / (Persp0·x + Persp1·y + Persp2)
给定四边形
(x1,y1)
、(x2,y2)
、(x3,y3)
、(x4,y4)
的角点位置以及图像(w,h)
的大小,我们需要找到ScaleX
、SkewX
的值, TransX
、SkewY
、ScaleY
、TransY
、Persp0
、Persp1
和 Persp2
,应用上述公式后:
(0,0)
变为 (x1,y1)
(图像左上角 -> 四边形左上角)(w,0)
变为 (x2,y2)
(图像右上角 -> 四边形右上角)(w,h)
变为 (x3,y3)
(图像右下角 -> 四边形右下角)(0,h)
变为 (x4,y4)
(图像左下角 -> 四边形左下角)现在我们可以用
(x,y)
代替(0,0)
; (w,0)
; (w,h)
; (0,h)
和 (xFinal,yFinal)
表示 (x1,y1)
; (x2,y2)
; (x3,y3)
; (x4,y4)
。
x1 = (ScaleX·0 + SkewX·0 + TransX) / (Persp0·0 + Persp1·0 + Persp2)
y1 = (SkewY·0 + ScaleY·0 + TransY) / (Persp0·0 + Persp1·0 + Persp2)
x2 = (ScaleX·w + SkewX·0 + TransX) / (Persp0·w + Persp1·0 + Persp2)
y2 = (SkewY·w + ScaleY·0 + TransY) / (Persp0·w + Persp1·0 + Persp2)
x3 = (ScaleX·w + SkewX·h + TransX) / (Persp0·w + Persp1·h + Persp2)
y3 = (SkewY·w + ScaleY·h + TransY) / (Persp0·w + Persp1·h + Persp2)
x4 = (ScaleX·0 + SkewX·h + TransX) / (Persp0·0 + Persp1·h + Persp2)
y4 = (SkewY·0 + ScaleY·h + TransY) / (Persp0·0 + Persp1·h + Persp2)
通过简化和重新排列,我们最终得到一个线性方程组。
x1·Persp2 - TransX = 0
y1·Persp2 - TransY = 0
Persp0·w·x2 + Persp2·x2 - ScaleX·w - TransX = 0
Persp0·w·y2 + Persp2·y2 - SkewY·w - TransY = 0
Persp0·w·x3 + Persp1·h·x3 + Persp2·x3 - ScaleX·w - SkewX·h - TransX = 0
Persp0·w·y3 + Persp1·h·y3 + Persp2·y3 - SkewY·w - ScaleY·h - TransY = 0
Persp1·h·x4 + Persp2·x4 - SkewX·h - TransX = 0
Persp1·h·y4 + Persp2·y4 - ScaleY·h - TransY = 0
剩下的就是解决
ScaleX
、SkewX
、TransX
、SkewY
、ScaleY
、TransY
、Persp0
、Persp1
和 Persp2
》。换句话说,我们想要
找到一种方法来用我们知道的所有其他变量来表达这些变量。我使用this计算器来完成这项工作。它的输出是从一开始的公式。
Python版本:
def create_matrix_from_points(top_left, top_right, bottom_right, bottom_left, width, height):
x1, y1 = top_left.x, top_left.y
x2, y2 = top_right.x, top_right.y
x3, y3 = bottom_right.x, bottom_right.y
x4, y4 = bottom_left.x, bottom_left.y
scale_x = (y1 * x2 * x4 - x1 * y2 * x4 + x1 * y3 * x4 - x2 * y3 * x4 - y1 * x2 * x3 + x1 * y2 * x3 - x1 * y4 * x3 + x2 * y4 * x3) / (x2 * y3 * width + y2 * x4 * width - y3 * x4 * width - x2 * y4 * width - y2 * width * x3 + y4 * width * x3)
skew_x = (-x1 * x2 * y3 - y1 * x2 * x4 + x2 * y3 * x4 + x1 * x2 * y4 + x1 * y2 * x3 + y1 * x4 * x3 - y2 * x4 * x3 - x1 * y4 * x3) / (x2 * y3 * height + y2 * x4 * height - y3 * x4 * height - x2 * y4 * height - y2 * height * x3 + y4 * height * x3)
translation_x = x1
skew_y = (-y1 * x2 * y3 + x1 * y2 * y3 + y1 * y3 * x4 - y2 * y3 * x4 + y1 * x2 * y4 - x1 * y2 * y4 - y1 * y4 * x3 + y2 * y4 * x3) / (x2 * y3 * width + y2 * x4 * width - y3 * x4 * width - x2 * y4 * width - y2 * width * x3 + y4 * width * x3)
scale_y = (-y1 * x2 * y3 - y1 * y2 * x4 + y1 * y3 * x4 + x1 * y2 * y4 - x1 * y3 * y4 + x2 * y3 * y4 + y1 * y2 * x3 - y2 * y4 * x3) / (x2 * y3 * height + y2 * x4 * height - y3 * x4 * height - x2 * y4 * height - y2 * height * x3 + y4 * height * x3)
translation_y = y1
perspective0 = (x1 * y3 - x2 * y3 + y1 * x4 - y2 * x4 - x1 * y4 + x2 * y4 - y1 * x3 + y2 * x3) / (x2 * y3 * width + y2 * x4 * width - y3 * x4 * width - x2 * y4 * width - y2 * width * x3 + y4 * width * x3)
perspective1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * height + y2 * x4 * height - y3 * x4 * height - x2 * y4 * height - y2 * height * x3 + y4 * height * x3)
perspective2 = 1
return skia.Matrix(scale_x, skew_x, translation_x, skew_y, scale_y, translation_y, perspective0, perspective1, perspective2)