引言
图像的几何变换通常包括拉伸、缩放、扭曲和旋转等操作。
对于平面区域来说,分为两类几何转换:1
- ⭐️仿射变换(affine transform),基于
2x3矩阵
进行变换。指图像可以通过一系列的几何变换来实现平移、旋转等多种操作。该变换能够保持图像的平直性和平行性。平直性是指图像经过仿射变换后,直线仍然是直线;平行性是指图像在完成仿射变换后,平行线仍然是平行线。 - ⭐️透视变换(projective transform),基于
3x3矩阵
进行变换。透视变换将视锥体转换为长方体形状,视锥体的近端比远端小,具有扩大相机附近物体的效果。透视变换可以改变平行关系,将矩形映射为任意四边形。
透视变换通常被用于当作从特定角度观察三维平面的计算方法(非垂直观测),在三维视觉领域具有广泛的应用。
本文主要介绍opencv中的透视变换(projective transform)原理和代码。
透视变换(projective transform)
透视变换,又称为投影变换,是指将坐标为 ( X , Y , Z ) (X,Y,Z) (X,Y,Z)的物理点 Q Q Q映射到投影平面上坐标为 ( x , y ) (x,y) (x,y)的点 q q q的过程1。
基本投影几何
将定义摄像机参数的矩阵(包含 f x , f y , c x , c y f_x,f_y,c_x,c_y fx,fy,cx,cy)称为摄像机的内参数矩阵(camera intrinsics matrix)。
物理世界中的点 Q Q Q投影到摄像机上的点 q q q的过程,可以用下式表示:
q = M ⋅ Q , 其 中 q = [ x y w ] , M = [ f x 0 c x 0 f y c y 0 0 1 ] , Q = [ X Y Z ] q=M\cdot{Q},其中q= \begin{bmatrix} x \\ y \\ w \end{bmatrix} , M=\begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} ,Q=\begin{bmatrix} X \\ Y \\ Z \end{bmatrix} q=M⋅Q,其中q=⎣⎡xyw⎦⎤,M=⎣⎡fx000fy0cxcy1⎦⎤,Q=⎣⎡XYZ⎦⎤
单应性(Homography)
计算机视觉中,平面的单应性被定义为从一个平面到另一个平面的投影映射。(注:“单应性”在不同学科有不同含义,在数学上有更通用的意思。这里仅说明计算机视觉中的单应性。1)
二维平面上的点映射到摄像机成像画面上的映射,就是平面单应性的典型例子。
利用齐次坐标,可以对物理世界的点Q到成像画面上的点q的映射进行表示:
q
=
s
⋅
M
⋅
W
⋅
Q
q =s \cdot{M}\cdot{W} \cdot{Q}
q=s⋅M⋅W⋅Q
其中,参数s表示尺度比例,
M
=
[
f
x
0
c
x
0
f
y
c
y
0
0
1
]
M=\begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix}
M=⎣⎡fx000fy0cxcy1⎦⎤ ,W表示旋转R和平移t的影响之和
W
=
[
R
t
]
W = [R \space\space\space t]
W=[R t],旋转矩阵
R
=
[
r
1
⃗
r
2
⃗
r
3
⃗
]
R=\begin{bmatrix} \vec{r_1} & \vec{r_2} & \vec{r_3} \end{bmatrix}
R=[r1r2r3]
由于我们只关注这个物体平面,为了简化计算,令Z=0:
[
x
y
1
]
=
s
⋅
M
⋅
[
r
1
⃗
r
2
⃗
r
3
⃗
t
⃗
]
⋅
[
X
Y
0
1
]
=
s
⋅
M
⋅
[
r
1
⃗
r
2
⃗
t
⃗
]
⋅
[
X
Y
1
]
\begin{bmatrix} x \\ y \\ 1 \end{bmatrix} =s \cdot{M}\cdot{\begin{bmatrix} \vec{r_1} & \vec{r_2} & \vec{r_3} & \vec{t} \end{bmatrix}} \cdot{\begin{bmatrix} X \\ Y \\ 0 \\ 1 \end{bmatrix}} =s \cdot{M}\cdot{\begin{bmatrix} \vec{r_1} & \vec{r_2} & \vec{t} \end{bmatrix}} \cdot{\begin{bmatrix} X \\ Y \\ 1 \end{bmatrix}}
⎣⎡xy1⎦⎤=s⋅M⋅[r1r2r3t]⋅⎣⎢⎢⎡XY01⎦⎥⎥⎤=s⋅M⋅[r1r2t]⋅⎣⎡XY1⎦⎤
用H表示转换矩阵:
H
=
s
⋅
M
⋅
[
r
1
⃗
r
2
⃗
t
⃗
]
H = s \cdot{M}\cdot{\begin{bmatrix} \vec{r_1} & \vec{r_2} & \vec{t} \end{bmatrix}}
H=s⋅M⋅[r1r2t]
H由两部分组成:用于定位观察的物体平面的物理变换和使用摄像机内参数矩阵的投影。
对于同一摄像头拍摄的画面来说,无需计算内参矩阵和物理变换。仅通过下面这个简化的方程,就能利用单应性矩阵H将源平面上的点与目标像平面上的点联系起来:
P
d
s
t
=
H
P
s
r
c
,
P
s
r
c
=
H
−
1
P
d
s
t
P_{dst} = HP_{src},P_{src} = H^{-1}P_{dst}
Pdst=HPsrc,Psrc=H−1Pdst
P
d
s
t
=
[
x
d
s
t
y
d
s
t
1
]
,
P
s
r
c
=
[
x
s
r
c
y
s
r
c
1
]
P_{dst} = \begin{bmatrix} x_{dst} \\ y_{dst} \\ 1 \end{bmatrix}, P_{src} = \begin{bmatrix} x_{src} \\ y_{src} \\ 1 \end{bmatrix}
Pdst=⎣⎡xdstydst1⎦⎤,Psrc=⎣⎡xsrcysrc1⎦⎤
有了这个简化的公式,我们就能明白opencv中求解单应性矩阵的原理了。
opencv代码
仿射变换相关函数
- cv::transform:对一组点进行仿射变换
- cv::warpAffine:对整幅图像进行仿射变换
- cv::getAffineTransform:从一组点计算仿射变换矩阵
- cv::getRotationMatrix2D:计算旋转矩阵
投影变换相关的函数
- cv::perspectiveTransform:对一组点进行透射变换/投影变换
- cv::warpPerspective:对整幅图像进行透视变换/投影变换
- cv::getPerspectiveTransform:获取透视变换/投影变换矩阵
- cv::findHomography:计算单应性矩阵
其中,findHomography的method参数用于选择计算单应性矩阵的算法。
- cv::RANSAC:随机抽样方法( random sampling with consensus),随机选择所提供点的子集,并计算一个同源矩阵。RANSAC算法计算许多这样的随机抽样,并保留具有最大部分的抽样。该方法在实际应用中在拒绝噪声离群数据和寻找正确答案方面非常有效
- cv::LMEDS:最小二乘中位数(least median of squares method),顾名思义,LMeDS背后的想法是最小化中值误差,而不是用默认方法基本上最小化的均方误差。这种方法的缺点是,只有当插入器至少构成数据点的大多数时,它才能表现良好。相比之下,RANSAC可以正常工作,并在给出几乎任何信噪比时给出令人满意的答案。
- cv::RHO:RHO算法,在OpenCV3中使用,它基于一种被称为PROSAC的“加权”RANSAC修改,在许多异常值的情况下运行得更快。
鸟瞰图代码示例
《Learning OpenCV》中有一个经典的例子,是将前置车载摄像头拍摄的图像,利用透视变换转换成鸟瞰图的视角。
这里的做法是,选取前置摄像头拍摄的透视图中的一块区域的四个点,获取图像中这四个点对应在俯瞰视角中的坐标点,计算转换矩阵,然后进行重映射,就能将前视图重映射到鸟瞰图。
在没有相机标定参数的情况下,可以利用物理平面和像平面中的四个对应点计算单应性矩阵,从而实现从透视图变换到鸟瞰图的效果。
python代码如下:
import numpy as np
import cv2 as cv
# 读取图像
src = cv.imread(img_path)
# 获取四个对应点
dstPts = np.array([(0,0),(640,0),(0,520),(640,520)])
srcPts = np.array([(265,99),(696,99),(129,473),(832,473)])
# 获取单应性矩阵
H = cv.findHomography(srcPts,dstPts,method=cv.RANSAC)
#M = cv.getPerspectiveTransform(objPts,imgPts)
print(f"Homography: {H.shape} \n{H}")
if len(H)>1:
H = H[0]
# 利用转换矩阵进行重映射(Remap)
birdView = cv.warpPerspective(src,H,None,cv.INTER_LINEAR | cv.WARP_INVERSE_MAP | cv.WARP_FILL_OUTLIERS)
# 显示重映射后的鸟瞰图
while (1):
cv.imshow("perspective", birdView)
if cv.waitKey(100) == ord('q'): # 按下q退出
break
cv.destroyAllWindows()
如果需要对图像进行反变换,可以利用numpy对矩阵求逆np.linalg.inv(H)
,作为cv.warpPerspective的转换矩阵。
小结
本文整理了计算机视觉中透视变换 / 投影变换 / 单应性的基本概念,以及OpenCV中的函数和使用示例。
如果对你有帮助的话,欢迎一键三连支持下博主~