3、单应性矩阵:定义与推导、平面场景下的单应性估计(DLT算法)、应用:图像拼接与透视校正

单应性矩阵,英文叫 Homography,是计算机视觉里一个非常基础但又极其强大的工具。说白了,它就是描述两个平面之间的一种投影映射关系。你想想看,我们拍两张照片,如果场景是平面(比如一面墙、一张纸、地面),或者相机只做旋转没有平移,那么这两张图之间的像素点就能用一个 3x3 的矩阵一一对应起来。这个矩阵,就是单应性矩阵。

我个人习惯把单应性矩阵看作是“平面与平面之间的桥梁”。在 SLAM 和 3D 重建里,它经常被用来做图像配准、相机位姿估计,甚至是构建全景图。今天我们就把它彻底讲透。

3.1 单应性矩阵的定义与数学推导

我们先从数学上定义它。假设空间中有个平面,上面有个点 P。它在相机1的像素坐标系下是 p1 = [u1, v1, 1]^T,在相机2的像素坐标系下是 p2 = [u2, v2, 1]^T。那么单应性矩阵 H 满足:

p2 = H * p1

这里的 H 是一个 3x3 的矩阵,有 8 个自由度(因为尺度等价性,通常我们设 h33 = 1)。

推导其实不复杂。我们考虑一个空间平面,其法向量为 n,到第一个相机光心的距离为 d。那么从相机1到相机2的变换关系可以写成:

p2 = K2 * (R - t * n^T / d) * K1^(-1) * p1

其中 K1、K2 是内参矩阵,R 和 t 是相机间的旋转和平移。那么单应性矩阵 H 就是:

H = K2 * (R - t * n^T / d) * K1^(-1)

嗯,这里要注意,这个推导假设了所有点都在同一个平面上。如果场景不是平面,单应性矩阵就不成立了,这时候就得用基础矩阵或者本质矩阵。

核心要点:单应性矩阵 H 是一个 3x3 的矩阵,8 自由度,描述了两个平面之间的投影映射。它要求场景是平面,或者相机只有旋转运动。

3.2 平面场景下的单应性估计:DLT 算法

知道了定义,怎么算出来呢?最经典的方法就是 DLT(Direct Linear Transform,直接线性变换)算法。我在项目中用过很多次,它简单、直接,而且效果不错。

DLT 的核心思想是:利用至少 4 对匹配点,构建线性方程组,然后求解 H。

具体步骤是这样的:

  1. 构建方程:对于每一对匹配点 (x1, y1) 和 (x2, y2),我们有 p2 = H * p1。展开后可以得到两个线性方程:
    x2 * (h31*x1 + h32*y1 + h33) = h11*x1 + h12*y1 + h13
    y2 * (h31*x1 + h32*y1 + h33) = h21*x1 + h22*y1 + h23
  2. 整理成矩阵形式:把未知数 h11 到 h33 排成一个向量,每个匹配点对可以贡献两个方程。4 对点就是 8 个方程,刚好解 8 个未知数。
  3. 求解:通常用 SVD(奇异值分解)来解这个齐次线性方程组。解出来的向量就是 H 矩阵的元素。
  4. 归一化:我建议在求解之前,先把点的坐标归一化到均值为 0、方差为 1 的范围。这样可以提高数值稳定性,避免因为坐标值太大导致精度损失。

我的经验:DLT 算法对噪声比较敏感。我曾经在一个项目中只用 4 对点,结果因为匹配误差大,H 矩阵算出来完全不对。后来我改用 RANSAC + DLT 的组合,先剔除外点,再用内点做 DLT,效果就好多了。

下面是一个简单的 Python 实现示例:

import numpy as np

def compute_homography_dlt(src_pts, dst_pts):
    """
    src_pts: 源图像中的点,形状 (N, 2)
    dst_pts: 目标图像中的点,形状 (N, 2)
    返回: 单应性矩阵 H,形状 (3, 3)
    """
    # 归一化
    src_mean = np.mean(src_pts, axis=0)
    dst_mean = np.mean(dst_pts, axis=0)
    src_std = np.std(src_pts)
    dst_std = np.std(dst_pts)

    T_src = np.array([[1/src_std, 0, -src_mean[0]/src_std],
                      [0, 1/src_std, -src_mean[1]/src_std],
                      [0, 0, 1]])
    T_dst = np.array([[1/dst_std, 0, -dst_mean[0]/dst_std],
                      [0, 1/dst_std, -dst_mean[1]/dst_std],
                      [0, 0, 1]])

    src_norm = (src_pts - src_mean) / src_std
    dst_norm = (dst_pts - dst_mean) / dst_std

    # 构建 A 矩阵
    N = src_pts.shape[0]
    A = []
    for i in range(N):
        x, y = src_norm[i]
        u, v = dst_norm[i]
        A.append([-x, -y, -1, 0, 0, 0, u*x, u*y, u])
        A.append([0, 0, 0, -x, -y, -1, v*x, v*y, v])
    A = np.array(A)

    # SVD 求解
    _, _, Vt = np.linalg.svd(A)
    H_norm = Vt[-1].reshape(3, 3)

    # 去归一化
    H = np.linalg.inv(T_dst) @ H_norm @ T_src
    return H / H[2, 2]

避坑指南:我曾经遇到过一个问题,匹配点共线了,结果 SVD 解出来的 H 矩阵全是 NaN。所以一定要确保你的 4 对点不共线,而且分布要均匀。另外,如果点对数量很多,建议用 RANSAC 先做一遍粗匹配,再用所有内点做一次 DLT 精化。

3.3 应用一:图像拼接

图像拼接,说白了就是把多张有重叠区域的照片拼成一张全景图。单应性矩阵在这里扮演了核心角色。

流程大概是这样的:

  1. 特征提取与匹配:用 SIFT、ORB 等算法提取特征点,然后做匹配。
  2. 单应性估计:用 DLT + RANSAC 估计相邻图像之间的单应性矩阵。
  3. 图像变换:把其中一张图像通过 H 矩阵投影到另一张图像的坐标系下。
  4. 融合:在重叠区域做加权平均或者多频段融合,消除接缝。

我记得有一次做无人机航拍拼接,地面是起伏的,用单应性矩阵效果很差。后来我意识到,单应性矩阵只适用于平面场景。对于非平面场景,得用更复杂的模型,比如圆柱面投影或者球面投影。

关键点:图像拼接的成功与否,很大程度上取决于单应性矩阵的精度。如果 H 矩阵算得不准,拼接出来的图像会有重影或者错位。所以,特征匹配的质量和 RANSAC 的参数设置非常关键。

3.4 应用二:透视校正

透视校正,也叫去畸变或者矫正。比如你拍了一张文档的照片,因为角度问题,文档看起来是梯形的。用单应性矩阵可以把它矫正成矩形。

做法很简单:

  1. 在原始图像上找到文档的四个角点。
  2. 设定目标图像中这四个角点的位置(比如一个标准矩形)。
  3. 用这 4 对点计算单应性矩阵 H。
  4. 用 H 对整张图像做变换。

我建议在做透视校正时,注意目标矩形的宽高比。如果比例不对,矫正后的图像会变形。比如文档扫描,最好保持原始文档的宽高比。

小技巧:如果你不确定目标矩形的尺寸,可以先计算原始四个角点围成的四边形的面积,然后设定一个合理的宽高比。我一般会取原始四边形最长边和最短边的比例作为参考。

3.5 本章知识结构图

下面这张图总结了单应性矩阵的核心知识体系,从定义到推导,再到估计方法和实际应用。你可以把它当作一个快速复习的路线图。

单应性矩阵知识结构图 单应性矩阵 H 定义与推导 DLT 算法估计 实际应用 3x3 矩阵 8 自由度 平面映射 至少 4 对点 SVD 求解 归一化预处理 图像拼接 透视校正 全景图生成 核心:单应性矩阵是平面到平面的投影映射,DLT 算法是估计它的标准方法

3.6 总结与对比

最后,我用一个表格来对比单应性矩阵在不同场景下的表现,方便你快速查阅。

场景 是否适用单应性矩阵 原因 替代方案
平面场景(墙面、地面、文档) 是 所有点都在同一平面上 —
相机纯旋转 是 没有平移,深度不影响映射 —
非平面场景(有深度变化) 否 不同深度的点映射关系不同 基础矩阵 / 本质矩阵
图像拼接(平面场景) 是 适合无人机航拍、文档扫描 —
图像拼接(非平面场景) 否 会产生重影和错位 圆柱面 / 球面投影

嗯,单应性矩阵的内容就讲到这里。它虽然简单,但非常实用。你在做图像拼接、透视校正、甚至 AR 应用时,都会频繁用到它。记住,关键是要理解它的适用条件——平面场景或者纯旋转。用对了,它就是一把利器;用错了,结果会很糟糕。