详解DLT直接线性变换算法及代码示例

0 引言

当涉及到相机标定或姿态估计等时,对极几何、DLTPNP是三个相关但不同的概念和方法:

  1. 对极几何(Epipolar Geometry):
    对极几何是研究两个摄像机之间的关系的几何学理论。它描述了两个视图之间的对应关系,以及在一幅图像中观察到的特征点与另一幅图像中可能的对应点之间的关系。对极几何的关键概念是极线和极点。极线是通过一个摄像机中的点与另一个摄像机的光心之间的直线,而极点是表示极线在另一个视图上的交点。对极几何提供了一种几何约束,用于估计相机之间的相对位置和姿态
    👉 2D-2D对极几何中的基本矩阵、本质矩阵和单应矩阵

  2. DLTDirect Linear Transform):
    DLT是一种基于对极几何原理的方法,用于估计相机的投影矩阵。DLT方法通过已知的三维点和其在图像中的对应点,建立一个线性方程组,并通过求解该方程组来获得相机的投影矩阵。DLT方法只能求解相机的投影矩阵,而无法直接获得相机的位置和姿态

  3. PNPPerspective-n-Point):
    PNP是用于估计相机的位置和姿态的算法。PNP算法通过已知的三维点和其在图像中的对应点,利用相机的内外参数,求解相机的位置和姿态。PNP算法可以使用不同的解法,其中包括DLT方法。DLT方法可以作为PNP算法的一种子问题,用于求解相机的投影矩阵。然后,通过进一步的非线性优化,例如使用迭代算法(如EPnPUPnP等),可以从投影矩阵中恢复相机的位置和姿态

综上所述,对极几何是描述两个摄像机之间关系的几何学理论,DLT是基于对极几何的方法,用于求解相机的投影矩阵,而PNP是用于估计相机位置和姿态的算法。DLT方法可以用于PNP算法的初始解,然后通过进一步的优化方法获得更准确的相机姿态估计结果。

本文主要详细解释DLT直接线性变换算法及代码示例。

1 基本原理

DLTDirect Linear Transform)算法常用于相机标定、三维重建和姿态估计等领域。然而,DLT方法仅提供了相机投影矩阵的初始解,并且没有考虑非线性畸变和误差。为了获得更准确的相机位置和姿态估计,通常需要进行进一步的非线性优化,例如使用PNP算法或其他迭代算法来改善估计结果。

投影矩阵:计算机视觉和计算机图形学中用于描述相机投影过程的一种矩阵表示,用于相机标定、三维重建、姿态估计等任务中。它将三维空间中的点映射到二维图像平面上。在相机投影中,三维点通过相机的内参数(如焦距、主点等)和外参数(相机的位置和姿态)进行投影转换,得到对应的二维图像坐标。投影矩阵通常表示为一个3x4的齐次矩阵,也称为相机矩阵。它由以下部分组成:

  1. 内参数矩阵Intrinsic Matrix):
    内参数矩阵包含相机的内部参数,如焦距、主点坐标等。通常表示为一个3x3的矩阵,也称为相机的内参数矩阵。内参数矩阵定义了相机的几何特性和成像属性。
  2. 外参数矩阵Extrinsic Matrix):
    外参数矩阵描述了相机的位置和姿态。它包含了相机的旋转矩阵和平移向量,用于将世界坐标系中的点转换到相机坐标系中。
    通过将内参数矩阵和外参数矩阵相乘,可以得到投影矩阵。投影矩阵将三维点表示为齐次坐标(四维向量),然后通过齐次除法将其变换为二维图像坐标。

DLT是一种用于估计相机投影矩阵的直接线性变换算法。它是通过已知的三维点和其在图像中的对应点,建立一个线性方程组,并通过求解该方程组来获得相机的投影矩阵。

DLT算法的基本思想是将三维点和其在图像中的对应点转换为齐次坐标,并建立一个线性方程组。每个方程对应一个已知的三维点和其在图像中的对应点,方程的形式如下:

[ x ′ ] = [ P ] [ X ] [x'] = [P] [X] [x]=[P][X]

其中, [ x ′ ] [x'] [x]是已知的二维图像点的齐次坐标, [ P ] [P] [P]是相机的投影矩阵,[X]是已知的三维点的齐次坐标。

通过展开上述方程,可以得到一个线性方程组的形式:

A x = 0 Ax = 0 Ax=0

其中, [ A ] [A] [A]是一个 2 n × 12 2n×12 2n×12的矩阵, [ x ] [x] [x]是相机投影矩阵的展开向量。

2 求解步骤

DLT算法的求解可以通过奇异值分解(SVD)或其他方法来进行。具体步骤如下:

  1. 将三维点(齐次坐标)和对应的二维图像点(齐次坐标)转换为线性方程组的形式,构建矩阵 [ A ] [A] [A]和向量 [ x ] [x] [x]
  2. 使用奇异值分解(SVD)或其他方法,求解上述线性方程组,得到一个近似解 [ x ] [x] [x]
  3. 将近似解 [ x ] [x] [x]重塑为3x4的相机投影矩阵 [ P ] [P] [P]

需要注意的是,由于方程组存在零空间解,因此DLT算法得到的解可能不是唯一的。通常情况下,可以对解进行归一化处理,例如将最后一行除以其模长,以确保投影矩阵的最后一行为[0, 0, 0, 1]

3 代码示例

3.1 借助numpy求解

在下述代码示例中:

首先定义了一个名为dlt的函数,该函数接受三维点集和对应的二维图像点集作为输入,调用np.linalg中的svd函数来求解线性方程,并返回估计的相机投影矩阵。

然后,提供了示例数据points_3dpoints_2d,其中points_3d是三维点的坐标,points_2d是对应的二维图像点的坐标。

最后,调用dlt函数来估计相机投影矩阵,并将结果打印输出。

import numpy as np

def dlt(points_3d, points_2d):
    # 将输入数据转换为齐次坐标形式
    points_3d_hom = np.hstack((points_3d, np.ones((points_3d.shape[0], 1))))
    points_2d_hom = np.hstack((points_2d, np.ones((points_2d.shape[0], 1))))
    
    # 构建线性方程组
    A = []
    for i in range(points_3d_hom.shape[0]):
        X, x = points_3d_hom[i], points_2d_hom[i]
        A.append([
            -X[0], -X[1], -X[2], -1, 0, 0, 0, 0, x[0]*X[0], x[0]*X[1], x[0]*X[2], x[0]
        ])
        A.append([
            0, 0, 0, 0, -X[0], -X[1], -X[2], -1, x[1]*X[0], x[1]*X[1], x[1]*X[2], x[1]
        ])
    
    A = np.array(A)
    
    # 使用奇异值分解(SVD)求解线性方程组
    _, _, V = np.linalg.svd(A)
    P = V[-1].reshape(3, 4)
    
    return P

# 示例数据
points_3d = np.array([
    [0, 0, 0],
    [1, 0, 0],
    [0, 1, 0],
    [1, 1, 0],
    [0, 0, 1],
    [1, 0, 1],
    [0, 1, 1],
    [1, 1, 1]
])

points_2d = np.array([
    [10, 20],
    [20, 20],
    [10, 30],
    [20, 30],
    [15, 15],
    [25, 15],
    [15, 25],
    [25, 25]
])

# 使用DLT算法估计相机投影矩阵
P = dlt(points_3d, points_2d)
print("Estimated Projection Matrix:")
print(P)

运行代码后输出结果为:

Estimated Projection Matrix:
[[ 3.64905183e-01  8.04911693e-16  1.82452591e-01  3.64905183e-01]
 [-2.78423118e-16  3.64905183e-01 -1.82452591e-01  7.29810365e-01]
 [ 1.73472348e-18  4.94396191e-17  1.20129601e-16  3.64905183e-02]]

3.2 借助scipy求解

Scipy是一个通用的科学计算库,其中包含了求解线性方程组的函数,所以下述代码和3.1的代码示例差不多,区别是用scipy.linalgsvd函数来求解线性方程组。

import numpy as np
from scipy.linalg import svd

def dlt(points_3d, points_2d):
    # 将输入数据转换为齐次坐标形式
    points_3d_hom = np.hstack((points_3d, np.ones((points_3d.shape[0], 1))))
    points_2d_hom = np.hstack((points_2d, np.ones((points_2d.shape[0], 1))))

    # 构建线性方程组
    A = []
    for i in range(points_3d_hom.shape[0]):
        X, x = points_3d_hom[i], points_2d_hom[i]
        A.append([
            -X[0], -X[1], -X[2], -1, 0, 0, 0, 0, x[0]*X[0], x[0]*X[1], x[0]*X[2], x[0]
        ])
        A.append([
            0, 0, 0, 0, -X[0], -X[1], -X[2], -1, x[1]*X[0], x[1]*X[1], x[1]*X[2], x[1]
        ])

    A = np.array(A)

    # 使用奇异值分解(SVD)求解线性方程组
    _, _, V = svd(A)
    P = V[-1, :].reshape(3, 4)

    return P

# 示例数据
points_3d = np.array([
    [0, 0, 0],
    [1, 0, 0],
    [0, 1, 0],
    [1, 1, 0],
    [0, 0, 1],
    [1, 0, 1],
    [0, 1, 1],
    [1, 1, 1]
])

points_2d = np.array([
    [10, 20],
    [20, 20],
    [10, 30],
    [20, 30],
    [15, 15],
    [25, 15],
    [15, 25],
    [25, 25]
])

# 使用自定义的DLT函数估计相机投影矩阵
P = dlt(points_3d, points_2d)

print("Estimated Projection Matrix:")
print(P)

运行代码后输出结果为:

Estimated Projection Matrix:
[[ 3.64905183e-01  3.83373888e-16  1.82452591e-01  3.64905183e-01]
 [ 1.27502175e-16  3.64905183e-01 -1.82452591e-01  7.29810365e-01]
 [ 2.08166817e-17  7.28583860e-17  9.62771529e-17  3.64905183e-02]]

当然,也可以直接调用OpenCV库中的相关函数来计算,这里就不展开了,相机标定中已包含。

4 优缺点

DLT算法作为一种直接线性变换算法,具有以下优点和缺点:

优点:

  1. 简单直观:DLT算法的基本原理和实现相对简单,易于理解和实现。
  2. 广泛适用:DLT算法适用于各种相机标定、三维重建和姿态估计等计算机视觉任务,具有广泛的应用范围。
  3. 可扩展性:DLT算法可以用于处理任意数量的三维点和其对应的二维图像点。

缺点:

  1. 线性假设:DLT算法假设相机投影过程是线性的,忽略了非线性畸变和误差。在实际应用中,相机系统通常存在畸变和误差,因此DLT算法的精度有限。
  2. 敏感性:由于方程组存在零空间解,DLT算法对输入数据的噪声和误差敏感。即使输入数据有微小的扰动,也可能导致较大的估计误差。
  3. 缺乏约束:DLT算法本身没有对相机投影矩阵的约束条件进行显式建模,因此可能得到不符合实际几何和成像约束的解。

为了克服DLT算法的缺点,通常需要进行进一步的非线性优化。例如,可以使用迭代算法(如EPnPUPnP等)进行相机位置和姿态的优化,或者结合其他约束条件(如平面约束、角点约束等)来提高估计精度。此外,还可以采用更精确的相机标定方法和优化技术来提高DLT算法的性能。


Reference




⭐️👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍🌔

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
直接线性变换(Direct Linear Transform, DLT)是一种图像处理中常用的变换方法,用于将一个平面上的像素坐标映射到另一个平面上。 在Python中,我们可以使用NumPy库来实现DLT。首先,我们需要收集至少4个已知点的对应坐标,这些点的坐标关系是已知的。然后,我们可以使用这些点来计算变换矩阵。 首先,导入所需的库: ```python import numpy as np ``` 然后,定义一个函数来实现DLT变换: ```python def dlt_transform(source_points, target_points): # 构建矩阵A A = [] for i in range(len(source_points)): x, y = source_points[i] u, v = target_points[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.asarray(A) # 计算SVD分解 _, _, V = np.linalg.svd(A) # 提取最小奇异值对应的矩阵 T = V[-1, :] H = np.reshape(T, (3, 3)) # 返回变换矩阵 return H ``` 接下来,我们可以定义待变换的源点和目标点: ```python source_points = [[x1, y1], [x2, y2], [x3, y3], ...] target_points = [[u1, v1], [u2, v2], [u3, v3], ...] ``` 调用`dlt_transform`函数来计算变换矩阵: ```python transform_matrix = dlt_transform(source_points, target_points) ``` 此时,`transform_matrix`即为根据已知的源点和目标点计算得到的DLT变换矩阵。 请注意,这只是DLT直接线性变换的基本实现方法。在实际应用中,可能需要对源点和目标点进行归一化处理,以提高变换的精度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZPILOTE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值