常见的教程都是对所选的四个点内部的区域进行透视变换,但是其他区域都被裁剪掉了。如何利用所选的局部区域对整个图像进行透视变换?
透视变换的算法: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))