在这章节的开始,首先我们得知道什么是映射,映射就是图像之间的变换,加上使用一些计算变换的方法。可以实现图像扭曲变形和图像配准,适用于全景拼接。普遍变换方法有 单应性变换、仿射变换、阿尔法通道等等。图像的映射类型有: 平移、旋转、仿射、透视映射、尺度变换,不同的类型对应不一样的方法。经过这些处理就可以达到自己想要实现的映射效果。


1.单应性变换
单应性变换是将一个平面内的点映射到另一个平面内的二维投影变换。在这里,平面是指图像或者三维中的平面表面。单应性变换具有很强的实用性,比如图像配准、图像纠正和纹理扭曲,以及创建全景图像。我们将频繁地使用单应性变换。本质上,单应性变换 H,按照下面的方程映射二维中的点(齐次坐标意义下):
对于图像平面内的点,齐次坐标 是个非 常有用的表示方式。点的齐次坐标是依赖 于 其 尺度定义的,所以,x=[x,y,w]=[αx,αy,αw]=[x/w,y/w,1] 都表示同一个二维点。因此,单应性矩阵 H 也仅依赖尺度定义,所以,单应性矩阵具有 8 个独立的自由度。我们通常使用 w=1 来归一化点,这样,点具有唯一的图像坐标 x 和 y。这个额外的坐标使得我们可以简单地使用一个矩阵来表示变换。
2. 直接线性变换
单应性矩阵可以由两幅图像(或者平面)中对应点对计算出来。前面已经提到过,一个完全射影变换具有 8 个自由度。根据对应点约束,每个对应点对可以写出两个方程,分别对应于 x 和 y 坐标。因此,计算单应性矩阵 H 需要4个对应点对。
**DLT(Direct Linear Transformation,直接线性变换)**是给定4个或者更多对应点对矩阵,来计算单应性矩阵 H 的算法。将单应性矩阵 H 作用在对应点对上,重新写出该方程,我们可以得到下面的方程:
或者 Ah=0,其中 A 是一个具有对应点对二倍数量行数的矩阵。将这些对应点对方程的系数堆叠到一个矩阵中,我们可以使用 SVD(Singular Value Decomposition,奇异值分解)算法找到 H 的最小二乘解。
def H_from_points(fp, tp):
"""使用线性DLT方法,计算单应性矩阵H,使fp映射到tp。点自动进行归一化"""
if fp.shape != tp.shape:
raise RuntimeError('number of points do not match')
# 对点进行归一化(对数值计算很重要)
# --- 映射起始点 ---
m = mean(fp[:2], axis=1)
maxstd = max(std(fp[:2], axis=1)) + 1e-9
C1 = diag([1/maxstd, 1/maxstd, 1])
C1[0][2] = -m[0]/maxstd
C1[1][2] = -m[1]/maxstd
fp = dot(C1,fp)
# --- 映射对应点 ---
m = mean(tp[:2], axis=1)
maxstd = max(std(tp[:2], axis=1)) + 1e-9
C2 = diag([1 / maxstd, 1 / maxstd, 1])
C2[0][2] = -m[0] / maxstd
C2[1][2] = -m[1] / maxstd
tp = dot(C2, tp)
# 创建用于线性方法的矩阵,对于每个对应对,在矩阵中会出现两行数值
nbr_correspondences = fp.shape[1]
A = zeros((2 * nbr_correspondences, 9))
for i in range(nbr_correspondences):
A[2*i] = [-fp[0][i], -fp[1][i],-1,0,0,0,
tp[0][i]*fp[0][i],tp[0][i]*fp[1][i],tp[0][i]]
A[2*i+1] = [0,0,0,-fp[0][i],-fp[1][i],-1,
tp[1][i]*fp[0][i],tp[1][i]*fp[1][i],tp[1][i]]
U,S,V = linalg.svd(A)
H = V[8].reshape((3,3))
#反归一化
H = dot(linalg.inv(C2),dot(H,C1))
#归一化,然后返回
return H / H[2,2]
对这些点进行归一化操作,使其均值为 0,方差为 1。因为算法的稳定性取决于坐标的表示情况和部分数值计算的问题,所以归一化操作非常重要。使用对应点对来构造矩阵 A。最小二乘解即为矩阵 SVD 分解后所得矩阵 V 的最后一行。该行经过变形后得到矩阵 H。然后对这个矩阵进行处理和归一化,返回输出。