射影变换
射影变换(projective transformation)是在射影空间中由可逆线性映射诱导的变换。对实射影平面,取可逆 矩阵 ,它把齐次点 送到 。平面上的这种变换也称为单应变换(homography)。它保持点、线及相交关系,但一般不保持平行、长度、角度或中点。
一个具体例子是 只要 ,普通坐标中的关系就是 。例如 被送到 ,而 保持不动。同一幅图中不同位置的缩放比例不同,这正是它与一般仿射变换的区别之一。
可逆矩阵在齐次点上的作用
齐次坐标把非零向量的非零倍数视为同一点。若将代表 换为 ,则 ,所以输出点不变。可逆性保证 ,输出总是合法的齐次点;逆矩阵 则给出逆变换。这证明了该规则在整个射影平面上定义良好且为双射。[1]
矩阵 与 ()表示同一射影变换。九个矩阵元素模掉一个共同尺度,使一般平面单应变换具有八个自由度。可以把某个非零元素归一化为一,但不能预先断定右下角 非零。两次变换的复合按矩阵乘法表示:先 、后 ,结果是 。
写 ,有限点的像在 时为 分母为零表示像落在目标坐标片的无穷远直线上,并不表示射影变换本身在那里没有定义。若 ,可逆性迫使 ,归一化后便得到可逆仿射变换;一般情形的分母随位置变化。
直线按逆转置变换
设源直线的非零系数向量为 ,其点满足 。由于 ,可以代入 : 因此像直线的坐标可取 可逆性保证 。反过来,若一个目标点满足 ,其逆像必满足 ,故像恰好是整条直线,而不仅是它的一部分。这个双向推导证明了直线与点线关联的保持。[2]
不同点的像仍不同,否则用 会得到两个原点相同。于是两点的连线被送到像点的连线,两条不同线的交点被送到像线的交点。这个结论同时适用于有限交点与无穷远交点。直接把线系数乘以 一般是错的;点和线虽然都写成三元列向量,变换法则却不同。
在开篇算例中, 例如直线 的系数为 ,因此 ,像直线为 。把像点 和 代入,均满足该式。
正方形、消失点与消失线
取单位正方形顶点 、、、。按上述矩阵逐点计算, 四条边仍在直线上,像围成一个梯形。正方形内部也映到该梯形内部:由 得 ;再由 ,条件 恰好变为 、。
源平面中的竖直平行线 ,齐次系数为 ,像线为 所有这些线都经过 。原来竖直方向的无穷远点 被送到 ,这就是该方向的消失点。在普通坐标中,也可固定 并令 ,得到 、。没有一个源有限点被送到这个消失点,因为它的逆像是无穷远点。
所有源无穷远点组成源无穷远线 。其像为 即目标平面的直线 。这是源仿射平面的消失线;当源平面表示地面时,通常称为地平线。它不必是图像中的水平线,这取决于坐标与成像姿态;本例恰好水平。某些方向的消失点也可能仍在目标无穷远处,例如 ,所以水平平行线在本例中仍保持平行。[3]
另有一条容易混淆的线:哪些源有限点被送到目标无穷远处由 决定。它的系数是 ,即源直线 。例如 源中的 与目标中的消失线 属于不同平面、承担相反的像与逆像关系,不能混成同一条“分母为零的地平线”。
精确有理数实现
下面代码实现已知矩阵的点变换、线变换和逆变换。输入为整数或 Fraction,矩阵必须为可逆的 矩阵,点和线必须是非零三元组。它不修改输入;输出按“第一个非零分量归一化为一”选取代表,因此也适用于末坐标为零的点。affine 单独进行有限坐标的除法,像在无穷远处时明确拒绝这一步。
from fractions import Fraction
def rational(value):
if isinstance(value, bool) or not isinstance(value, (int, Fraction)):
raise TypeError("use int or Fraction, not floating-point input")
return Fraction(value)
def vector3(values):
v = tuple(rational(x) for x in values)
if len(v) != 3 or not any(v):
raise ValueError("a nonzero homogeneous triple is required")
return v
def canonical(values):
v = vector3(values)
scale = next(x for x in v if x)
return tuple(x / scale for x in v)
def affine(values):
x, y, w = vector3(values)
if w == 0:
raise ValueError("point is at infinity in the W=1 chart")
return x / w, y / w
def cross(u, v):
return (u[1]*v[2] - u[2]*v[1],
u[2]*v[0] - u[0]*v[2],
u[0]*v[1] - u[1]*v[0])
def matvec(matrix, vector):
return tuple(sum((a*b for a, b in zip(row, vector)), Fraction(0))
for row in matrix)
class Homography:
def __init__(self, matrix):
rows = tuple(tuple(rational(x) for x in row) for row in matrix)
if len(rows) != 3 or any(len(row) != 3 for row in rows):
raise ValueError("matrix must be 3 by 3")
self.H = rows
self.C = (cross(rows[1], rows[2]), cross(rows[2], rows[0]),
cross(rows[0], rows[1]))
self.det = sum((rows[0][j] * self.C[0][j] for j in range(3)),
Fraction(0))
if self.det == 0:
raise ValueError("singular matrix is not a projectivity")
def point(self, values):
return canonical(matvec(self.H, vector3(values)))
def line(self, values):
# C = det(H) * H^(-T); the common nonzero scale cancels.
return canonical(matvec(self.C, vector3(values)))
def inverse(self):
# C^T = det(H) * H^(-1), equally valid projectively.
return Homography(tuple(zip(*self.C)))
H = Homography(((1, 0, 0), (0, 1, 0), (0, 1, 1)))
q = H.point((1, 1, 1))
print(tuple(map(str, affine(q)))) # ('1/2', '1/2')
print(tuple(map(str, H.line((1, 0, -1))))) # ('1', '1', '-1')
print(H.inverse().point(q) == canonical((1, 1, 1))) # True
J = Homography(((1, 0, 0), (0, 0, 1), (0, 1, 0)))
print(tuple(map(str, affine(J.point((2, 3, 1)))))) # ('2/3', '1/3')
代码中的 是代数余子式矩阵,三行依次由另两行的叉积构成。记 的行为 、 的行为 ,则 ,因为三重积的循环排列不变;当 时, 正是构成 的一个叉积因子,故 。逐项相乘便得到 ,于是 、。共同的非零因子不改变齐次结果。矩阵非奇异保证两种乘法都不会把非零输入变为零;归一化只去掉一个公共尺度。这给出了点变换、线变换及逆变换的正确性理由,也说明代码无需固定 。
对固定的三维齐次表示,建立矩阵、变换一个点或一条线以及构造逆变换,各需要常数次有理数运算和常数个有理数存储。处理 个对象需要 次这样的运算;若保留所有结果,输出占用 个三元组。精确有理数的分子、分母可能很长,实际位运算成本还随其长度增长,不能把任意大整数运算视为固定时间。实现拒绝普通浮点数,包括 NaN 和无穷;用精确整数或分数可避免数值溢出和“接近零”判定的歧义,但不能消除输入观测误差。
边界、变式与矩阵估计的条件
代码最后的矩阵 有 ,但 ,所以它是合法射影变换。它把 送到 ,因此 的像为 。把它强行除以右下角来“归一化”会错误地丢掉这个变换。若将主例点改为 , 的像为 ;point 可以返回它,但 affine 会拒绝除以零。
可逆性也有实际作用。奇异矩阵 把 送到零向量,其余点的像又都落在同一条无穷远直线上,不能定义整个射影平面的双射。代码因此在构造时就拒绝奇异矩阵。
射影变换虽然保持共线,一般不保持中点。主例中 、 的中点为 ,其像是 ;像端点的普通中点却是 。两者不同,所以不能在透视变换后直接沿用原来的仿射比例。
若 未知,点对应 给出约束 ,对每一对非零齐次点只有两条独立线性约束。直接线性变换法(DLT)把这些约束堆叠为 ,其中 排列矩阵九个元素。在无噪声情况下,唯一到尺度的候选要求 ,且重排后的候选矩阵仍须可逆;至少需要四对点,四对点的常用非退化配置要求源、目标两边都无三点共线。仅有“四对”这个数量并不能保证可解。带噪声时常求最小奇异值对应的方向,但代数残差不等于几何重投影误差。本篇的精确代码只应用已知变换,没有实现或证明完整 DLT 估计流程。[4]
单应变换用于平面图像校正与拼接时,还需检查场景模型。理想透视相机拍摄同一平面、投影中心不在该平面上,或两幅图像具有相同投影中心,是产生单应关系的典型条件;相机平移且场景具有一般深度变化时,不能把全部点强行套进同一个平面单应模型。[5]
参考资料
- ↑ Tomas Pajdla. Elements of Geometry for Computer Vision,2017年2月27日版,§8.4,第73—75页,式(8.78)—(8.89)及满秩矩阵的比例表示。
- ↑ Pajdla,同上,§9.3,第96—97页,式(9.27)—(9.31);§9.3.1—9.3.3,第97—98页讨论连线与交点在单应下的行为。
- ↑ Pajdla,同上,§9.4—9.5,第98—99页,消失点、消失线及地面对应的 horizon;本篇矩阵和坐标为独立构造的算例。
- ↑ Carnegie Mellon University, 16-385 Computer Vision. Image homographies, Spring 2020, Lecture 9,PDF第39—42页:应用矩阵与八个自由度;第43—50、53—54页:点对应、齐次线性系统与SVD。另见 Pajdla,同上,§8.6.1,第76—78页,式(8.111)—(8.124)及秩八的非退化条件。
- ↑ Pajdla,同上,§8.4,第73页,列出相同投影中心、平面及平面两幅像三种情况与满秩条件。