在 MATLAB 中从 4 个点计算二维齐次透视变换矩阵
Calculate a 2D homogeneous perspective transformation matrix from 4 points in MATLAB
我得到了形成一个矩形的 4 个二维点的坐标,以及它们在应用透视变换后的坐标。
透视变换在齐次坐标系中计算并由 3x3 矩阵定义 M
。如果不知道矩阵,如何从给定的点计算它?
一分的计算方式为:
| M11 M12 M13 | | P1.x | | w*P1'.x |
| M21 M22 M23 | * | P1.y | = | w*P1'.y |
| M31 M32 M33 | | 1 | | w*1 |
为了同时计算所有点,我将它们一起写在一个矩阵中 A
类似地,对于矩阵中的变换点 B
:
| P1.x P2.x P3.x P4.x |
A = | P1.y P2.y P3.y P4.y |
| 1 1 1 1 |
所以方程是 M*A=B
并且这可以在 MATLAB 中通过 M = B/A
或 M = (A'\B')'
.
求解 M
但这并不容易。我知道变换后的点坐标,但不知道具体的B
,因为有因子w
,齐次变换后不一定是1。因为在齐次坐标中,向量的每个倍数都是同一个点,我不知道我会得到哪个倍数。
为了考虑这些未知因素,我将等式写为 M*A=B*W
其中 W
是一个对角矩阵,对角线上 B
中的每个点都有因子 w1...w4。所以 A
和 B
现在完全已知,我必须求解 M
和 W
.
的方程式
如果我可以将方程重新排列成 x*A=B
或 A*x=B
的形式,其中 x
类似于 M*W
我可以解决它并且知道 [= =31=] 可能已经足够了。然而,尽管尝试了所有可能的重新安排,但我还是没能做到。直到我意识到封装 (M*W)
是不可能的,因为一个是 3x3 矩阵,另一个是 4x4 矩阵。我卡在这里了。
另外M*A=B*W
没有M
的单一解,因为M
的每一个倍数都是相同的变换。将其写成一个线性方程组,可以简单地修正 M
中的一个条目以获得单一解。此外,可能存在根本无法解决 M
的输入,但我们暂时不用担心。
我实际上想要实现的是某种矢量图形编辑程序,用户可以在其中拖动形状边界框的角来对其进行变换,同时在内部计算变换矩阵。
实际上我在 JavaScript 中需要这个,但如果我什至不能在 MATLAB 中解决这个问题,我就会完全陷入困境。
本来应该是个简单的问题。那么如何将 M*A=B*W
变成可求解的形式呢?它只是矩阵乘法,所以我们可以把它写成一个线性方程组。你知道像:M11*A11 + M12*A21 + M13*A31 = B11*W11 + B12*W21 + B13*W31 + B14*W41
。每个线性方程组都可以写成 Ax=b
的形式,或者为了避免与我的问题中已经使用的变量混淆:N*x=y
。就这些了。
根据我的问题举个例子:我生成了一些已知 M
和 W
的输入数据:
M = [
1 2 3;
4 5 6;
7 8 1
];
A = [
0 0 1 1;
0 1 0 1;
1 1 1 1
];
W = [
4 0 0 0;
0 3 0 0;
0 0 2 0;
0 0 0 1
];
B = M*A*(W^-1);
那我忘了M
和W
。这意味着我现在有 13 个变量要解决。我将 M*A=B*W
重写为线性方程组,并从那里重写为 N*x=y
的形式。在 N
中,每一列都有一个变量的因子:
N = [
A(1,1) A(2,1) A(3,1) 0 0 0 0 0 0 -B(1,1) 0 0 0;
0 0 0 A(1,1) A(2,1) A(3,1) 0 0 0 -B(2,1) 0 0 0;
0 0 0 0 0 0 A(1,1) A(2,1) A(3,1) -B(3,1) 0 0 0;
A(1,2) A(2,2) A(3,2) 0 0 0 0 0 0 0 -B(1,2) 0 0;
0 0 0 A(1,2) A(2,2) A(3,2) 0 0 0 0 -B(2,2) 0 0;
0 0 0 0 0 0 A(1,2) A(2,2) A(3,2) 0 -B(3,2) 0 0;
A(1,3) A(2,3) A(3,3) 0 0 0 0 0 0 0 0 -B(1,3) 0;
0 0 0 A(1,3) A(2,3) A(3,3) 0 0 0 0 0 -B(2,3) 0;
0 0 0 0 0 0 A(1,3) A(2,3) A(3,3) 0 0 -B(3,3) 0;
A(1,4) A(2,4) A(3,4) 0 0 0 0 0 0 0 0 0 -B(1,4);
0 0 0 A(1,4) A(2,4) A(3,4) 0 0 0 0 0 0 -B(2,4);
0 0 0 0 0 0 A(1,4) A(2,4) A(3,4) 0 0 0 -B(3,4);
0 0 0 0 0 0 0 0 1 0 0 0 0
];
而y
是:
y = [ 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 1 ];
注意N
最后一行描述的方程,根据y
其解为1。这就是我在问题中提到的内容,您必须修复 M
的其中一个条目才能获得单一解决方案。 (我们可以这样做,因为 M
的每个倍数都是相同的变换。)根据这个等式,我说 M33 应该是 1。
我们为 x
解决了这个问题:
x = N\y
并得到:
x = [ 1.00000; 2.00000; 3.00000; 4.00000; 5.00000; 6.00000; 7.00000; 8.00000; 1.00000; 4.00000; 3.00000; 2.00000; 1.00000 ]
哪些是[ M11, M12, M13, M21, M22, M23, M31, M32, M33, w1, w2, w3, w4 ]
的解决方案
W
计算完M
后就不需要了。对于一个通用点(x, y)
,在求解x'
和y'
.
的同时计算对应的w
| M11 M12 M13 | | x | | w * x' |
| M21 M22 M23 | * | y | = | w * y' |
| M31 M32 M33 | | 1 | | w * 1 |
在 JavaScript 中解决这个问题时,我可以使用 Numeric JavaScript library which has the needed function solve 来解决 Ax=b。
OpenCV 有一个叫做 getPerspectiveTransform. The source code for this function is available on github 的简洁函数,描述如下:
/* Calculates coefficients of perspective transformation
* which maps (xi,yi) to (ui,vi), (i=1,2,3,4):
*
* c00*xi + c01*yi + c02
* ui = ---------------------
* c20*xi + c21*yi + c22
*
* c10*xi + c11*yi + c12
* vi = ---------------------
* c20*xi + c21*yi + c22
*
* Coefficients are calculated by solving linear system:
* / x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\
* | x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1|
* | x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2|
* | x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|,
* | 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0|
* | 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1|
* | 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2|
* \ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/
*
* where:
* cij - matrix coefficients, c22 = 1
*/
这个方程组较小,因为它避免求解 W
和 M33
(OpenCV 称为 c22
)。那么它是怎样工作的?可以通过以下步骤重新创建线性系统:
从一个等式开始:
| c00 c01 c02 | | xi | | w*ui |
| c10 c11 c12 | * | yi | = | w*vi |
| c20 c21 c22 | | 1 | | w*1 |
将其转化为方程组,求解 ui
和 vi
,并消去 w
。你得到投影变换的公式:
c00*xi + c01*yi + c02
ui = ---------------------
c20*xi + c21*yi + c22
c10*xi + c11*yi + c12
vi = ---------------------
c20*xi + c21*yi + c22
两边都乘以分母:
(c20*xi + c21*yi + c22) * ui = c00*xi + c01*yi + c02
(c20*xi + c21*yi + c22) * vi = c10*xi + c11*yi + c12
分发 ui
和 vi
:
c20*xi*ui + c21*yi*ui + c22*ui = c00*xi + c01*yi + c02
c20*xi*vi + c21*yi*vi + c22*vi = c10*xi + c11*yi + c12
假设 c22 = 1
:
c20*xi*ui + c21*yi*ui + ui = c00*xi + c01*yi + c02
c20*xi*vi + c21*yi*vi + vi = c10*xi + c11*yi + c12
收集左侧所有cij
:
c00*xi + c01*yi + c02 - c20*xi*ui - c21*yi*ui = ui
c10*xi + c11*yi + c12 - c20*xi*vi - c21*yi*vi = vi
最后将四对点转换为矩阵形式:
/ x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\
| x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1|
| x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2|
| x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|
| 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0|
| 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1|
| 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2|
\ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/
现在是Ax=b
的形式,可以用x = A\b
得到解。请记住 c22 = 1
.
我得到了形成一个矩形的 4 个二维点的坐标,以及它们在应用透视变换后的坐标。
透视变换在齐次坐标系中计算并由 3x3 矩阵定义 M
。如果不知道矩阵,如何从给定的点计算它?
一分的计算方式为:
| M11 M12 M13 | | P1.x | | w*P1'.x |
| M21 M22 M23 | * | P1.y | = | w*P1'.y |
| M31 M32 M33 | | 1 | | w*1 |
为了同时计算所有点,我将它们一起写在一个矩阵中 A
类似地,对于矩阵中的变换点 B
:
| P1.x P2.x P3.x P4.x |
A = | P1.y P2.y P3.y P4.y |
| 1 1 1 1 |
所以方程是 M*A=B
并且这可以在 MATLAB 中通过 M = B/A
或 M = (A'\B')'
.
M
但这并不容易。我知道变换后的点坐标,但不知道具体的B
,因为有因子w
,齐次变换后不一定是1。因为在齐次坐标中,向量的每个倍数都是同一个点,我不知道我会得到哪个倍数。
为了考虑这些未知因素,我将等式写为 M*A=B*W
其中 W
是一个对角矩阵,对角线上 B
中的每个点都有因子 w1...w4。所以 A
和 B
现在完全已知,我必须求解 M
和 W
.
如果我可以将方程重新排列成 x*A=B
或 A*x=B
的形式,其中 x
类似于 M*W
我可以解决它并且知道 [= =31=] 可能已经足够了。然而,尽管尝试了所有可能的重新安排,但我还是没能做到。直到我意识到封装 (M*W)
是不可能的,因为一个是 3x3 矩阵,另一个是 4x4 矩阵。我卡在这里了。
另外M*A=B*W
没有M
的单一解,因为M
的每一个倍数都是相同的变换。将其写成一个线性方程组,可以简单地修正 M
中的一个条目以获得单一解。此外,可能存在根本无法解决 M
的输入,但我们暂时不用担心。
我实际上想要实现的是某种矢量图形编辑程序,用户可以在其中拖动形状边界框的角来对其进行变换,同时在内部计算变换矩阵。
实际上我在 JavaScript 中需要这个,但如果我什至不能在 MATLAB 中解决这个问题,我就会完全陷入困境。
本来应该是个简单的问题。那么如何将 M*A=B*W
变成可求解的形式呢?它只是矩阵乘法,所以我们可以把它写成一个线性方程组。你知道像:M11*A11 + M12*A21 + M13*A31 = B11*W11 + B12*W21 + B13*W31 + B14*W41
。每个线性方程组都可以写成 Ax=b
的形式,或者为了避免与我的问题中已经使用的变量混淆:N*x=y
。就这些了。
根据我的问题举个例子:我生成了一些已知 M
和 W
的输入数据:
M = [
1 2 3;
4 5 6;
7 8 1
];
A = [
0 0 1 1;
0 1 0 1;
1 1 1 1
];
W = [
4 0 0 0;
0 3 0 0;
0 0 2 0;
0 0 0 1
];
B = M*A*(W^-1);
那我忘了M
和W
。这意味着我现在有 13 个变量要解决。我将 M*A=B*W
重写为线性方程组,并从那里重写为 N*x=y
的形式。在 N
中,每一列都有一个变量的因子:
N = [
A(1,1) A(2,1) A(3,1) 0 0 0 0 0 0 -B(1,1) 0 0 0;
0 0 0 A(1,1) A(2,1) A(3,1) 0 0 0 -B(2,1) 0 0 0;
0 0 0 0 0 0 A(1,1) A(2,1) A(3,1) -B(3,1) 0 0 0;
A(1,2) A(2,2) A(3,2) 0 0 0 0 0 0 0 -B(1,2) 0 0;
0 0 0 A(1,2) A(2,2) A(3,2) 0 0 0 0 -B(2,2) 0 0;
0 0 0 0 0 0 A(1,2) A(2,2) A(3,2) 0 -B(3,2) 0 0;
A(1,3) A(2,3) A(3,3) 0 0 0 0 0 0 0 0 -B(1,3) 0;
0 0 0 A(1,3) A(2,3) A(3,3) 0 0 0 0 0 -B(2,3) 0;
0 0 0 0 0 0 A(1,3) A(2,3) A(3,3) 0 0 -B(3,3) 0;
A(1,4) A(2,4) A(3,4) 0 0 0 0 0 0 0 0 0 -B(1,4);
0 0 0 A(1,4) A(2,4) A(3,4) 0 0 0 0 0 0 -B(2,4);
0 0 0 0 0 0 A(1,4) A(2,4) A(3,4) 0 0 0 -B(3,4);
0 0 0 0 0 0 0 0 1 0 0 0 0
];
而y
是:
y = [ 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 1 ];
注意N
最后一行描述的方程,根据y
其解为1。这就是我在问题中提到的内容,您必须修复 M
的其中一个条目才能获得单一解决方案。 (我们可以这样做,因为 M
的每个倍数都是相同的变换。)根据这个等式,我说 M33 应该是 1。
我们为 x
解决了这个问题:
x = N\y
并得到:
x = [ 1.00000; 2.00000; 3.00000; 4.00000; 5.00000; 6.00000; 7.00000; 8.00000; 1.00000; 4.00000; 3.00000; 2.00000; 1.00000 ]
哪些是[ M11, M12, M13, M21, M22, M23, M31, M32, M33, w1, w2, w3, w4 ]
W
计算完M
后就不需要了。对于一个通用点(x, y)
,在求解x'
和y'
.
w
| M11 M12 M13 | | x | | w * x' |
| M21 M22 M23 | * | y | = | w * y' |
| M31 M32 M33 | | 1 | | w * 1 |
在 JavaScript 中解决这个问题时,我可以使用 Numeric JavaScript library which has the needed function solve 来解决 Ax=b。
OpenCV 有一个叫做 getPerspectiveTransform. The source code for this function is available on github 的简洁函数,描述如下:
/* Calculates coefficients of perspective transformation
* which maps (xi,yi) to (ui,vi), (i=1,2,3,4):
*
* c00*xi + c01*yi + c02
* ui = ---------------------
* c20*xi + c21*yi + c22
*
* c10*xi + c11*yi + c12
* vi = ---------------------
* c20*xi + c21*yi + c22
*
* Coefficients are calculated by solving linear system:
* / x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\
* | x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1|
* | x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2|
* | x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|,
* | 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0|
* | 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1|
* | 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2|
* \ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/
*
* where:
* cij - matrix coefficients, c22 = 1
*/
这个方程组较小,因为它避免求解 W
和 M33
(OpenCV 称为 c22
)。那么它是怎样工作的?可以通过以下步骤重新创建线性系统:
从一个等式开始:
| c00 c01 c02 | | xi | | w*ui |
| c10 c11 c12 | * | yi | = | w*vi |
| c20 c21 c22 | | 1 | | w*1 |
将其转化为方程组,求解 ui
和 vi
,并消去 w
。你得到投影变换的公式:
c00*xi + c01*yi + c02
ui = ---------------------
c20*xi + c21*yi + c22
c10*xi + c11*yi + c12
vi = ---------------------
c20*xi + c21*yi + c22
两边都乘以分母:
(c20*xi + c21*yi + c22) * ui = c00*xi + c01*yi + c02
(c20*xi + c21*yi + c22) * vi = c10*xi + c11*yi + c12
分发 ui
和 vi
:
c20*xi*ui + c21*yi*ui + c22*ui = c00*xi + c01*yi + c02
c20*xi*vi + c21*yi*vi + c22*vi = c10*xi + c11*yi + c12
假设 c22 = 1
:
c20*xi*ui + c21*yi*ui + ui = c00*xi + c01*yi + c02
c20*xi*vi + c21*yi*vi + vi = c10*xi + c11*yi + c12
收集左侧所有cij
:
c00*xi + c01*yi + c02 - c20*xi*ui - c21*yi*ui = ui
c10*xi + c11*yi + c12 - c20*xi*vi - c21*yi*vi = vi
最后将四对点转换为矩阵形式:
/ x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\
| x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1|
| x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2|
| x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|
| 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0|
| 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1|
| 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2|
\ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/
现在是Ax=b
的形式,可以用x = A\b
得到解。请记住 c22 = 1
.