常见的教程都是对所选的四个点内部的区域进行透视变换,但是其他区域都被裁剪掉了。如何利用所选的局部区域对整个图像进行透视变换?
透视变换
透视变换的算法:OpenCV文档
dst
(
x
,
y
)
=
src
(
M
11
x
+
M
12
y
+
M
13
M
31
x
+
M
32
y
+
M
33
,
M
21
x
+
M
22
y
+
M
23
M
31
x
+
M
32
y
+
M
33
)
\texttt{dst} (x,y) = \texttt{src} \left ( \frac{M_{11} x + M_{12} y + M_{13}}{M_{31} x + M_{32} y + M_{33}} , \frac{M_{21} x + M_{22} y + M_{23}}{M_{31} x + M_{32} y + M_{33}} \right )
dst(x,y)=src(M31x+M32y+M33M11x+M12y+M13,M31x+M32y+M33M21x+M22y+M23)
推导过程:
通过getPerspectiveTransform()
函数得到一个线性变换的矩阵
M
M
M,随后的透视变换过程如下
[
x
′
y
′
z
′
]
=
[
M
11
M
12
M
13
M
21
M
22
M
23
M
31
M
32
1
]
[
x
y
1
]
\begin{bmatrix} x' \\ y' \\ z' \end{bmatrix} = \begin{bmatrix} M_{11} & M_{12} & M_{13} \\ M_{21} & M_{22} & M_{23} \\ M_{31} & M_{32} & 1 \\ \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}
⎣⎡x′y′z′⎦⎤=⎣⎡M11M21M31M12M22M32M13M231⎦⎤⎣⎡xy1⎦⎤
由于原始图像中没有
z
z
z轴上的维度,所以设置为1,但经过变换后的图像上的
z
′
z'
z′不为1,所以将其归一化,得到
[
x
′
′
y
′
′
1
]
=
1
z
′
[
x
′
y
′
z
′
]
\begin{bmatrix} x'' \\ y'' \\ 1 \end{bmatrix} = \frac{1}{z'} \begin{bmatrix} x' \\ y' \\ z' \end{bmatrix}
⎣⎡x′′y′′1⎦⎤=z′1⎣⎡x′y′z′⎦⎤
也就得到了OpenCV的公式
解决方案
解决方案参考:Stack Overflow的方案
被裁剪的原因是因为经过变换的坐标变换到了画布范围之外,因此需要确定新画布的大小,并将变换后的图像平移到画布内。
透视变换的矩阵记作M,格式如下:
[
M
11
M
12
M
13
M
21
M
22
M
23
M
31
M
32
1
]
\begin{bmatrix} M_{11} & M_{12} & M_{13} \\ M_{21} & M_{22} & M_{23} \\ M_{31} & M_{32} & 1 \\ \end{bmatrix}
⎣⎡M11M21M31M12M22M32M13M231⎦⎤
设原始图片的四角分别为
(0,0) ________ (0, w)
| |
|________|
(h,0) (h,w)
只要对四角进行变换,就可以确定新画布的边界。
首先坐标点写作矩阵的形式,三行分别代表
x
,
y
,
z
x,y,z
x,y,z轴坐标
P
=
[
0
w
w
0
0
0
h
h
1
1
1
1
]
P = \begin{bmatrix} 0 & w & w & 0 \\ 0 & 0 & h & h \\ 1 & 1 & 1 & 1 \end{bmatrix}
P=⎣⎡001w01wh10h1⎦⎤
类似地,
P
′
=
M
P
P'=MP
P′=MP,并将
P
′
P'
P′的各列除以各列的最后一行的那个元素进行归一化,得到
P
′
′
P''
P′′
P
′
′
=
[
p
11
p
12
p
13
p
14
p
21
p
22
p
23
p
24
1
1
1
1
]
P'' = \begin{bmatrix} p_{11} & p_{12} & p_{13} & p_{14} \\ p_{21} & p_{22} & p_{23} & p_{24} \\ 1 & 1 & 1 & 1 \end{bmatrix}
P′′=⎣⎡p11p211p12p221p13p231p14p241⎦⎤
获取
x
x
x上的(第一行)最大最小值
x
m
i
n
,
x
m
a
x
x_{min}, x_{max}
xmin,xmax
获取
y
y
y上的(第一行)最大最小值
y
m
i
n
,
y
m
a
x
y_{min}, y_{max}
ymin,ymax
Δ
x
=
x
m
a
x
−
x
m
i
n
,
Δ
y
=
y
m
a
x
−
y
m
i
n
\Delta x= x_{max}-x_{min},\Delta y= y_{max}-y_{min}
Δx=xmax−xmin,Δy=ymax−ymin即为新画布的宽和高。
(以上都是基于Stack Overflow上的回答所写,但正如其中的评论所述,第四步存在错误,但评论也表述不太清楚,在此详细分析一下)
为了进一步将变换后的图像平移到画布内,已知了透视变换是通过
M
M
M乘以坐标进行的,可以对
M
M
M进行更改来加入平移操作。
创建单位矩阵,并在第三列放入矩阵
P
′
′
P''
P′′对应行的最小值的负值,称为矩阵
T
T
T:
T
=
[
1
0
−
x
m
i
n
0
1
−
y
m
i
n
0
0
1
]
T= \begin{bmatrix} 1 & 0 & -x_{min}\\ 0 & 1 & -y_{min} \\ 0 & 0 & 1 \end{bmatrix}
T=⎣⎡100010−xmin−ymin1⎦⎤
则新的变换矩阵
M
′
=
T
M
M'=TM
M′=TM。可以简单验证一下,将
M
′
M'
M′与坐标相乘并展开,可以发现刚好得到
[
x
′
′
′
y
′
′
′
1
]
=
[
x
′
′
−
x
m
i
n
y
′
′
−
y
m
i
n
1
]
\begin{bmatrix} x''' \\ y''' \\ 1 \end{bmatrix}= \begin{bmatrix} x''-x_{min} \\ y''-y_{min} \\ 1 \end{bmatrix}
⎣⎡x′′′y′′′1⎦⎤=⎣⎡x′′−xminy′′−ymin1⎦⎤
因此最后的透视变换的参数为:
warpPerspective(src=src, M=M', dsize=(delta_x, delta_y))