使用Python进行立体几何和立体校正的综合教程

这篇具有很好参考价值的文章主要介绍了使用Python进行立体几何和立体校正的综合教程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用Python进行立体几何和立体校正的综合教程

需要多个视图

在相机的针孔模型中,光线从物体上反射出来,照射到胶片上形成图像。因此,沿着同一条光线的所有点都将对应于图像中的一个点。

因此,给定图像中的一个点,不可能确定其在世界上的准确位置,也就是说,我们无法从单个图像中恢复深度。

我们也无法从图像中恢复结构。这方面的一个例子是影子艺术,艺术家用手势制作美丽的影子。我们不可能只通过看阴影就对手势做出任何评价。

在本文中,我们将学习如何使用两个视图来处理这种歧义。

极地约束

假设我给你两张从不同角度拍摄的图像。我在其中一张图片中显示一个点,并要求你在另一张图片上找到它。你会怎么做?这里有一个想法:你可以在图像中的点周围画一个小补丁,然后在另一张图像上滑动,看看它最匹配的地方。

然而,一旦你看了几何图形,你就会意识到你不需要扫描整个图像,因为点必然位于一条线上,如下图所示。

使用Python进行立体几何和立体校正的综合教程

直觉是,在现实世界中,点可以位于连接其投影和相机中心的线上的任何位置。所以我们可以推断,这一点在另一幅图像中的投影可以位于投影线上的任何位置。这条投影线称为外极线。所以现在我们的搜索空间缩小到这一行。这称为极线约束。

在本文中,我们将讨论如何用代数方法求解外极线。在此之前,让我们熟悉对极几何中的关键定义。

关键术语
  • 基线:连接两个相机中心的线。

  • 极线:投影点所在的线。极线成对出现,每一条都代表一幅图像。

  • 极面:包含基线和世界上一点的平面。极面在图像平面的外极线处与图像平面相交。

  • 极点:基线与图像平面相交的点。对应于不同点的所有外极线在极点相交。为什么?我们看到极面在外极线处与图像平面相交。现在,对应于不同点的每个极面都有共同的基线。由于极点是基线与图像平面相交的地方,这意味着所有外极线都将以极点为公共点。换句话说,所有的外极线都在极点处相交。此外,极点不必位于图像内,它可以位于扩展图像平面上。

相机矩阵

在本节中,我们将回顾我们将要使用的同质坐标和相机矩阵。如果你已经熟悉它们,或者你已经阅读了我以前关于相机校准的文章,你可以跳过这一节。

同质坐标

考虑点(u,v)。为了以其齐次形式表示它,我们简单地添加了另一个维度:(u,v,1)。这种表示的原因是因为平移和透视投影在齐次空间中成为线性操作;也就是说,它们可以通过矩阵乘法一次性计算出来。我们将在本文的后面部分详细讨论齐次变换矩阵。

齐次坐标的一个关键特性是它们是尺度不变的;意思是,(kx,ky,k)和(x,y,1)表示相同的点,其中k≠0和k∈ R、 要从齐次表示转换为欧几里得表示,我们只需除以最后一个坐标,如下所示:

使用Python进行立体几何和立体校正的综合教程

如果你想一想,从原点到点[x,y,1]的直线也有形式k[x,y,1]。所以我们可以说,图像空间中的一点表示为均匀空间中的光线。

线的齐次表示:考虑熟悉的方程ax+by+c=0。我们知道它表示通过点(x,y)的线的方程。现在,这个方程也可以表示为l⊺p=0,其中l为(a,b,c),l⊺ 是l的转置,p是(x,y,1)。p本质上是点(x,y)的齐次表示。

现在,l是标度不变的,因为方程l⊺如果与常数相乘,p=0不会改变。因此,我们可以说l是直线的齐次表示。

总之,给定齐次点p,方程l⊺p=0(或p⊺l=0)表示p位于l线上。记住这一点,我们将在讨论基本矩阵时重新讨论它。

外参矩阵

相机外参矩阵是将点的坐标从世界坐标系转换为相机坐标系的基矩阵的变化。它让我们从相机的角度看世界。它是旋转矩阵和平移矩阵的组合-旋转矩阵确定相机的方向,平移矩阵移动相机。方程式可以表示为:

使用Python进行立体几何和立体校正的综合教程

这里的符号如下:

使用Python进行立体几何和立体校正的综合教程
摄像机内参矩阵

一旦我们使用相机外参矩阵获得相机点的坐标,下一步就是将它们投影到相机的图像平面上,以形成图像。这是相机内参矩阵的工作。

相机内参矩阵在我的另一篇文章中进行了深入讨论,但总而言之,相机内参矩阵将相机坐标给定的点投影到相机的图像平面上。它基本上编码了相机胶片的属性。方程式如下:

使用Python进行立体几何和立体校正的综合教程

符号为:

使用Python进行立体几何和立体校正的综合教程
图像形成管道

因此,给定世界上的一个点和一个相机,我们以其齐次形式表示该点,并与外参矩阵相乘,以获得相机帧的坐标。然后我们与内参矩阵相乘,得6到其在相机像平面上的投影。最后,我们转换回欧几里得坐标,以获得点在图像中的像素位置。这是图像形成管道,如下所示:

使用Python进行立体几何和立体校正的综合教程

基本矩阵

好了,我们现在讨论立体几何的基础——基本矩阵。让我们推导它,看看它做了什么。

使用Python进行立体几何和立体校正的综合教程

考虑一个校准的系统,我们知道两个相机的相对位置和方向。让摄像机中心表示为Oc和Oc′。让X成为世界上的一个点。让我们将X在相机Oc的坐标表示为Xc,将在相机Oc′的坐标表示为Xc′。设Rc和Tc是基矩阵从Oc到Oc′的旋转和平移变化。这意味着给定X 在Oc的坐标,我们可以找到它们在Oc'的坐标为:

使用Python进行立体几何和立体校正的综合教程
叉积矩阵

让我们绕开一小段,讨论向量叉积。两个向量a,b的叉积将是垂直于它们的向量,由a×b = [-a3b2 + a2b3, a3b1-a1b3, -a2b1 + a1b2]表示,其中a=[a1,a2,a3]和b=[b1,b2,b3]。

我们可以将其以矩阵形式表示为:

使用Python进行立体几何和立体校正的综合教程

这种矩阵形式的叉积表示为[a×]b,其中[a×]是3×3矩阵,b是3×1向量。

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

现在,向量与自身的叉积为零。a×a=[a×]a=0。所以我们可以说[a×]是秩为2的斜对称矩阵。

回到我们的方程式:

使用Python进行立体几何和立体校正的综合教程

同时乘以向量Tc,我们得到:

使用Python进行立体几何和立体校正的综合教程

Tc×Tc=0。接下来,我们取两边的点积:

使用Python进行立体几何和立体校正的综合教程

现在,向量𝑇 ×Xc′垂直于Xc′。因此,Xc′.(𝑇 ×Xc′)=0。

使用Python进行立体几何和立体校正的综合教程

现在,Tc和RcXc都是三维向量。因此,我们可以将它们的叉积表示为矩阵形式:

使用Python进行立体几何和立体校正的综合教程

最后,我们可以将方程表示为:

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

矩阵E称为基本矩阵,它将两个不同相机帧的点的坐标关联起来。

寻找外极线

现在,我们如何使用基本矩阵找到外极线?让我们更深入地看看这个方程式。

使用Python进行立体几何和立体校正的综合教程

这里Xc是点X相对于相机帧Oc 的坐标。这意味着我们可以将连接X和Oc的线上的任何点表示为𝛼Xc其中𝛼 是一个常数。现在,如果我们将Xc替换为𝛼方程中的Xc,它仍然满足。

使用Python进行立体几何和立体校正的综合教程

类似地,我们可以用𝛽Xc′其中𝛽 是常数,方程保持不变。

因此,我们可以说,基本矩阵方程由任意两点满足,这两点位于连接该点及其各自相机中心的投影射线上,其中点的坐标由其相机帧表示。

让x𝑐和xc′是X点在摄像机Oc和O𝑐′ 的像平面上的投影. 它们必须满足基本矩阵方程,因为它们位于投影射线上。所以我们可以写:

使用Python进行立体几何和立体校正的综合教程

现在xc是3×1向量,E是3×3矩阵,所以它们的乘积将是3×1的向量。让我们用l表示:

使用Python进行立体几何和立体校正的综合教程

这个方程式你应该很熟悉。如前一节所述,该方程表示齐次点xc′位于直线l上。我们可以说,l是对应于点xc的外极线,而xc′则位于该直线上。这是极线约束的数学形式。

使用Python进行立体几何和立体校正的综合教程

类似地,如上式所示,l′是对应于点xc′的外极线,xc位于该线上。

因此,给定一个点在一个视图中的投影,我们将其与基本矩阵相乘,以获得另一个视图中点的投影所在的外极线。

这在实践中实现起来有点棘手。

Python示例

这是本节的代码示例:

依赖

%matplotlib widget

import matplotlib.pyplot as plt
from utils import *
from stereo_utils import *

定义相机配置

首先,我们设置一个环境,其中有一个世界点和两个相机以一个角度面对该点

# define parameters for the image plane
f = 2
img_size = (5, 5)

# Define camera 1 configuration

# rotate the camera first at an angle of 90 along the Y axis, then rotate it
# at an angle of 30 along the negative Z-axis
angles = [np.pi/2, -np.pi/6]
order = 'yz'

# translate the camera by an offset
offset1 = np.array([0, -10, 0])

# create rotation transformation matrix
R1 = create_rotation_transformation_matrix(angles, order)
R1_ = np.identity(4)
R1_[:3, :3] = R1

# create translation transformation matrix
T1_ = create_translation_matrix(offset1)

# Define camera 2 configuration and repeat the same steps

angles = [np.pi/2, np.pi/6]
order = 'yz'
offset2 = np.array([0, 10, 0])

R2 = create_rotation_transformation_matrix(angles, order)
R2_ = np.identity(4)
R2_[:3, :3] = R2
T2_ = create_translation_matrix(offset2)

绘制环境

打印整个设置,包括相机、世界点、相机的图像平面和交点。

# define a world point
point = np.array([[-6, 5, 2]])
# create and transform camera 1
xx1, yy1, Z1 = create_image_grid(f, img_size)
pt1_h = convert_grid_to_homogeneous(xx1, yy1, Z1, img_size)
pt1_h_transformed = T1_ @ R1_ @ pt1_h
xxt1, yyt1, Zt1 = convert_homogeneous_to_grid(pt1_h_transformed, img_size)

# create and transform camera 2
xx2, yy2, Z2 = create_image_grid(f, img_size)
pt2_h = convert_grid_to_homogeneous(xx2, yy2, Z2, img_size)
pt2_h_transformed = T2_ @ R2_ @ pt2_h
xxt2, yyt2, Zt2 = convert_homogeneous_to_grid(pt2_h_transformed, img_size)
# define axis and figure
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111,projection='3d')

# set limits
ax.set(xlim=(-10, 5), ylim=(-15, 15), zlim=(-3, 10))

# plot both the camera centers
ax = pr.plot_basis(ax, R1, offset1, label="camera 1")
ax = pr.plot_basis(ax, R2, offset2, label="camera 2")

# plot both the image planes
ax.plot_surface(xxt1, yyt1, Zt1, alpha=0.75)
ax.plot_surface(xxt2, yyt2, Zt2, alpha=0.75)

# plot baseline
ax.plot(*make_line(offset1, offset2), color="red", alpha=0.5, label="baseline")

# plot the world point
ax.scatter(*point[0], color="black")
ax.plot(*make_line(point, offset1), color="purple", alpha=0.25)
ax.plot(*make_line(point, offset2), color="purple", alpha=0.25)

# intersection points (manually computed with trial and error)
c1_intn_world = offset1 + (point[0] - offset1) * 0.16
ax.scatter(*c1_intn_world, color="green")
c2_intn_world = offset2 + (point[0] - offset2) * 0.26
ax.scatter(*c2_intn_world, color="green")

ax.set_title("stereo geometry")
ax.set_xlabel("X-axis")
ax.set_ylabel("Y-axis")
ax.set_zlabel("Z-axis")

plt.legend()
使用Python进行立体几何和立体校正的综合教程

计算摄像机上点的投影

# create a simple camera intrinsic matrix with focal length f 
# and use it for both the cameras
K = compute_intrinsic_parameter_matrix(f, 0, 1, 0, 0)

# create the projection matrix and compute the projection of the world point for both the cameras

# compute projection for camera 1
E1 = np.linalg.inv(T1_ @ R1_)
E1_ = E1[:-1, :]
M1 = K @ E1_

proj_point1 = compute_world2img_projection(point.reshape(3, -1), M1, is_homogeneous=False)

# compute projection for camera 2
E2 = np.linalg.inv(T2_ @ R2_)
E2_ = E2[:-1, :]
M2 = K @ E2_

proj_point2 = compute_world2img_projection(point.reshape(3, -1), M2, is_homogeneous=False)

绘制两个相机的点投影

h, w = img_size
nrows = 1
ncols = 2

fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(6, 4))

# plot projection for camera 1
ax1 = axes[0]
ax1.set(xlim = (-(h // 2), w // 2), ylim = (-(h // 2), w // 2))
ax1.set_title("Camera 1")
ax1.scatter(*proj_point1.reshape(-1))

# plot projection for camera 2
ax2 = axes[1]
ax2.set(xlim = (-(h // 2), w // 2), ylim = (-(h // 2), w // 2))
ax2.set_title("Camera 2")
ax2.scatter(*proj_point2.reshape(-1))

plt.tight_layout()
使用Python进行立体几何和立体校正的综合教程

基本矩阵

# convert the world point to homogeneous coords
point_hg = to_hg_coords(point.T)

计算两台摄像机的世界点坐标

point_c1 = E1_ @ point_hg # coordinates of the point wrt camera 1
print("coordinates of the point wrt camera 1:", "\n", point_c1, "\n")
point_c2 = E2_ @ point_hg # coordinates of the point wrt camera 2
print("coordinates of the point wrt camera 2:", "\n", point_c2)
coordinates of the point wrt camera 1: 
 [[ 2.        ]
 [ 9.99038106]
 [12.69615242]] 

coordinates of the point wrt camera 2: 
 [[ 2.        ]
 [-1.33012702]
 [ 7.69615242]]

得到从摄像机1到摄像机2的基矩阵的变化

# compute change of basis matrix from camera 1 to camera 2
Ec = (E2 @ np.linalg.inv(E1))[:-1, :]

# extract rotation and translation matrix from the change of basis matrix
Rc = Ec[:, :-1]
Tc = Ec[:, -1]
# validate the rotation and transalation change of basis matrices
is_vectors_close(point_c2.reshape(-1), Rc @ point_c1.reshape(-1) + Tc)
# compute essential matrix
Tm = get_cross_product_matrix(Tc)
essential_matrix = Tm @ Rc
# validating the essential matrix equation
np.round(point_c2.T @ essential_matrix @ point_c1)[0][0]
-0.0
# check p2.T @ E @ p1 = 0 
is_vectors_close(point_c2.T @ essential_matrix @ point_c1, np.array([[0]]))
# convert the intersection points' coordinates from world system to camera system

# convert the intersection points to homogeneous coordinates
c1_intn_world_hg = to_hg_coords(np.expand_dims(c1_intn_world, axis=1))
c2_intn_world_hg = to_hg_coords(np.expand_dims(c2_intn_world, axis=1))

# compute the coordinates of the intersection points wrt the camera
c1_intn_hg = E1 @ c1_intn_world_hg
c2_intn_hg = E2 @ c2_intn_world_hg

# convert back to euclidean coordinates
c1_intn = c1_intn_hg[:-1, :]
c2_intn = c2_intn_hg[:-1, :]
# check p2_intn.T @ E @ p1_intn = 0 
is_vectors_close(c2_intn.T @ essential_matrix @ c1_intn, np.array([[0]]))

绘制极线

计算并绘制齐次空间中的外极线

nrows = 1
ncols = 2
h, w = img_size

fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(6, 4))

# Epipolar line in camera 1 given the point wrt camera 2
ax1 = axes[0]
ax1.set_title("camera 1")
ax1.set(xlim = (-(h // 2), w // 2), ylim = (-(h // 2), w // 2))

# compute the epipolar line in camera 1
coeffs = (point_c2.T @ essential_matrix).reshape(-1)
x, y = plot_line(coeffs, (-1, 1))

# convert c2_intn from homogeneous coordinate to pixel coordinate
u, v = to_eucld_coords(c1_intn).reshape(-1)

ax1.plot(x, y, label="epipolar line")
ax1.scatter(u, v, color="orange", label="point")

# Epipolar line in camera 2 given the point wrt camera 1
ax2 = axes[1]
ax2.set_title("camera 2")
ax2.set(xlim = (-(h // 2), w // 2), ylim = (-(h // 2), w // 2))

coeffs = (essential_matrix @ point_c1).reshape(-1)
x, y = plot_line(coeffs, (-1, 1))

u, v = to_eucld_coords(c2_intn).reshape(-1)

ax2.plot(x, y, label="epipolar line")
ax2.scatter(u, v, color="orange", label="point")

plt.tight_layout()
使用Python进行立体几何和立体校正的综合教程

基本矩阵

计算基本矩阵

fundamental_matrix =  np.linalg.inv(K).T @ essential_matrix @ np.linalg.inv(K)
nrows = 1
ncols = 2

fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(6, 4))

# plot projection 1
ax1 = axes[0]
ax1.set(xlim = (-(h // 2), w // 2), ylim = (-(h // 2), w // 2))
ax1.set_title("Camera 1 Image")

proj_point2_hg = to_hg_coords(proj_point2)
coeffs = (proj_point2_hg.T @ fundamental_matrix).reshape(-1)
x, y = plot_line(coeffs, (-2, 2))

ax1.plot(x, y, color="orange")
ax1.scatter(*proj_point1.reshape(-1))

# plot projection 2
ax2 = axes[1]
ax2.set(xlim = (-(h // 2), w // 2), ylim = (-(h // 2), w // 2))
ax2.set_title("Camera 2 Image")

proj_point1_hg = to_hg_coords(proj_point1)
coeffs = (fundamental_matrix @ proj_point1_hg).reshape(-1)
x, y = plot_line(coeffs, (-2, 2))

ax2.plot(x, y, color="orange")
ax2.scatter(*proj_point2.reshape(-1))

plt.tight_layout()
使用Python进行立体几何和立体校正的综合教程
设置环境
使用Python进行立体几何和立体校正的综合教程

首先,我们为两个相机定义外部参数,使它们以一定角度相距一定距离。外部参数包括相机的位置和方向。

接下来,我们定义决定图像形成的内在参数。这里我们已经定义了图像平面的大小和焦距,这基本上是图像平面离相机中心的距离的度量。

我们还定义了世界上的一个点,以便它被两个相机捕获。在本例中,我们将找到该点投影的外极线。

两个视图中捕获的图像如下所示:

使用Python进行立体几何和立体校正的综合教程
计算基本矩阵

为了找到基本矩阵,我们需要找到两个相机之间的相对几何结构,以便给定点在相机1的坐标,我们应该能够找到它在相机2的坐标。

现在,我们知道相机外参矩阵是基矩阵从世界坐标系到相机坐标系的变化。使用该信息,可以如下计算从相机1到相机2的基矩阵的变化:

从相机1到相机2的基矩阵变化=(从世界到相机2基矩阵的变化)×

⟹ 从相机1到相机2的基矩阵变化=(从世界到相机2基矩阵的变化)×

⟹ 从相机1到相机2的基矩阵的变化=(相机2的外参矩阵)×(相机1的外参矩阵的逆)

一旦我们获得了基矩阵的这种变化,我们就可以提取相机的相对方向和偏移。这个3×4矩阵的前3列将给出方向Rc,最后一列将给出偏移量Tc。

使用Python进行立体几何和立体校正的综合教程

然后我们计算Tc的叉积矩阵,并将其与Rc相乘,以获得基本矩阵。

使用Python进行立体几何和立体校正的综合教程

作为健全性检查,我们可以验证基本矩阵方程。

使用Python进行立体几何和立体校正的综合教程
绘制极线

要找到外极线,我们首先需要找到相机图像平面上该点的投影。在这个例子中,我已经通过试错手动绘制了这些点,但我们将在后面的章节中看到更好的方法来找到它们。

一旦我们找到这些点,我们将它们与它们各自的外参矩阵相乘,得到相机帧的坐标。然后我们将它们插入基本矩阵方程,并找到相应的外极线。

使用Python进行立体几何和立体校正的综合教程

例如,如果我们知道相机1中的投影点,我们将其与基本矩阵相乘,得到相机2中的外极线。然后我们在2D图像空间中绘制这条线。

为了验证相机2中的投影点位于这条线上,我们将其转换为欧几里得坐标,并将其绘制在同一空间中。我们对另一台相机重复同样的过程。

图像空间中的投影点及其对应的外极线如下所示:

使用Python进行立体几何和立体校正的综合教程

平行

使用Python进行立体几何和立体校正的综合教程

如果图像平面彼此平行会发生什么?在上述系统中,我们有两个相机,它们的光轴沿着Y轴彼此平行。让我们假设相机中心在X轴上,相距b。

由于相机是平行的,因此它们之间没有相对旋转。因此,Rc将是一个单位矩阵,Tc将等于[-b,0,0]。因此,我们可以将基本矩阵计算为:

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

让x𝑐 xc′是X点在摄像机Oc和O𝑐′的像平面上的投影. 如果我们假设两个相机的焦距都是f,我们可以写xc=[x,y,f]和xc′=[x′,y′,f]。我们可以将它们插入基本矩阵方程中,如下所示:

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

我们可以看到外极线具有相同的Y坐标,这意味着它们沿着X轴平行。因此,我们可以说,如果两个相机彼此平行,那么它们的外极线也将在图像空间中平行。

如下所示:

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

此部分的代码可在此处找到:

https://github.com/wingedrasengan927/Stereo-Geometry/blob/master/Parallel%20Image%20Planes.ipynb

如果相机沿着Y轴平行,图像中的外极线将是水平的;如果它们沿着X轴平行,则外极线将是垂直的。

基本矩阵

在现实生活中,我们很少有关于世界上点的位置的信息。然而,我们可以在图像中找到它们的位置。因此,我们需要修改基本矩阵方程,以考虑点的图像位置。

现在,给定世界上与相机帧相关的一个点,内参矩阵负责将其投影到相机的图像平面上。

使用Python进行立体几何和立体校正的综合教程

我们可以将κ发送到另一侧,并将等式改写为:

使用Python进行立体几何和立体校正的综合教程

因此,给定图像中的一个点,我们将其表示为齐次形式,并与内参矩阵的逆相乘,得到该点在世界上的齐次表示。

现在,我们无法从图像中确定点在世界上的准确位置,因为齐次坐标是比例不变的,并且点可以位于射线上的任何位置。

使用Python进行立体几何和立体校正的综合教程

考虑两个相机帧l和r,以及在两个图像平面上投影的点Pc。这里的基本矩阵方程为:

使用Python进行立体几何和立体校正的综合教程

在这个等式中,我们可以这样替换齐次图像坐标:

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

这被称为基本矩阵方程。

基本矩阵将同一点的坐标关联在两个不同的视图中,而基本矩阵将它们关联在两张不同的图像中。

在前面的例子中,我们可以计算基本矩阵并绘制外极线,如下所示:

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

如果你观察到,外极线看起来和以前完全一样,但是,这一次它们是使用基本矩阵方程直接从图像坐标中计算出来的。

从点对应关系计算基本矩阵

在现实世界中,我们很少使用校准过的系统。然而,由于基本矩阵直接关联图像点,我们仍然可以在不了解世界和相机的情况下找到它。下面是方法。

设(u,v)和(u′,v′)表示两个不同图像中的同一点。我们可以用齐次形式表示它们,并插入基本矩阵方程:

使用Python进行立体几何和立体校正的综合教程

这里f1、f2、…表示基本矩阵的未知参数。上面的等式表示一个同质系统,我在这里的另一篇文章中已经深入讨论了它们。然而,我将在这里再次讨论直觉。

第一步是将等式改写为:

使用Python进行立体几何和立体校正的综合教程

改写这种方法的一个原因是,我们可以在同一个等式中堆叠许多点对应关系,如下所示:

使用Python进行立体几何和立体校正的综合教程

该方程可用矩阵表示法表示为Af= 0,其中A是点对应矩阵和向量f ⃗  是平坦的基本矩阵。现在,f ⃗ 可以通过将方程的两边除以其大小*|f* ⃗ *|*而得到单位向量.

可以通过计算*|Ax* ⃗ |的最小值找到受约束|x ⃗ |= 1 时Ax= 0的方程的解。.

八点算法

其思想是找到图像之间的点对应关系并计算矩阵𝐴. 然后,可以通过计算𝐴⊺𝐴 并将其重塑为3 x 3矩阵。

计算f至少需要多少点?现在,f有9个未知数,所以你会说我们需要9个方程或9个点来求解它。但如果你观察到,f是尺度不变的,这意味着我们可以将f乘以任何常数,并且方程Af= 0仍然满足。因此,我们可以将 f 与其值之一相除,如下所示:

使用Python进行立体几何和立体校正的综合教程

现在看到有8个未知数需要解决,我们至少只需要8个点。

还有一件事我们需要解释。你看,3×3基本矩阵F的秩=2。我们这里不讨论证明,但这与叉积矩阵的秩2有关。

为此,我们对矩阵F进行奇异值分解,使其最后一个奇异值为零,然后重新组合。

八点算法的代码如下所示:

def compute_fundamental_matrix(points1, points2):
    '''
    Compute the fundamental matrix given the point correspondences
    
    Parameters
    ------------
    points1, points2 - array with shape [n, 3]
        corresponding points in images represented as 
        homogeneous coordinates
    '''
    # validate points
    assert points1.shape[0] == points2.shape[0], "no. of points don't match"
    
    u1 = points1[:, 0]
    v1 = points1[:, 1]
    u2 = points2[:, 0]
    v2 = points2[:, 1]
    one = np.ones_like(u1)
    
    # construct the matrix 
    # A = [u2.u1, u2.v1, u2, v2.u1, v2.v1, v2, u1, v1, 1] for all the points
    # stack columns
    A = np.c_[u1 * u2, v1 * u2, u2, u1 * v2, v1 * v2, v2, u1, v1, one]
    
    # peform svd on A and find the minimum value of |Af|
    U, S, V = np.linalg.svd(A, full_matrices=True)
    f = V[-1, :]
    F = f.reshape(3, 3) # reshape f as a matrix
    
    # constrain F
    # make rank 2 by zeroing out last singular value
    U, S, V = np.linalg.svd(F, full_matrices=True)
    S[-1] = 0 # zero out the last singular value
    F = U @ np.diag(S) @ V
    return F
标准化八点算法

现代图像具有约4000-6000像素的高分辨率。这会导致点对应关系中的大量差异,这可能会破坏算法。因此,为了解释这一点,我们在将这些点插入八点算法之前对它们进行了归一化。

这个想法是,对于每个图像,我们计算点对应的质心(平均值),并从每个图像中减去。接下来,我们对它们进行缩放,使其与质心的距离(方差)为√2,如下所示:

使用Python进行立体几何和立体校正的综合教程

接下来,我们创建一个执行上述转换的矩阵,并使用它来转换点,如下面的代码所示:

def compute_fundamental_matrix_normalized(points1, points2):
    '''
    Normalize points by calculating the centroid, subtracting 
    it from the points and scaling the points such that the distance 
    from the origin is sqrt(2)
    
    Parameters
    ------------
    points1, points2 - array with shape [n, 3]
        corresponding points in images represented as 
        homogeneous coordinates
    '''
    # validate points
    assert points1.shape[0] == points2.shape[0], "no. of points don't match"
    
    # compute centroid of points
    c1 = np.mean(points1, axis=0)
    c2 = np.mean(points2, axis=0)
    
    # compute the scaling factor
    s1 = np.sqrt(2 / np.mean(np.sum((points1 - c1) ** 2, axis=1)))
    s2 = np.sqrt(2 / np.mean(np.sum((points2 - c2) ** 2, axis=1)))
    
    # compute the normalization matrix for both the points
    T1 = np.array([
        [s1, 0, -s1 * c1[0]],
        [0, s1, -s1 * c1[1]],
        [0, 0 ,1]
    ])
    T2 = np.array([
        [s2, 0, -s2 * c2[0]],
        [0, s2, -s2 * c2[1]],
        [0, 0, 1]
    ])
    
    # normalize the points
    points1_n = T1 @ points1.T
    points2_n = T2 @ points2.T
    
    # compute the normalized fundamental matrix
    F_n = compute_fundamental_matrix(points1_n.T, points2_n.T)
    
    # de-normalize the fundamental
    return T2.T @ F_n @ T1

寻找极点

我们知道极点是图像中所有外极线相交的点。数学上可以表示为:

使用Python进行立体几何和立体校正的综合教程

其中l1,l2,…是外极线,e是极点。这可以用矩阵形式表示为:

使用Python进行立体几何和立体校正的综合教程

现在这看起来像一个齐次方程组。因此,要找到极点,我们可以使用上一节中讨论的线性最小二乘估计找到Le ⃗ = 0的解。

但是等等,可以从基本矩阵计算外极线。因此,我们可以将等式改写为:

使用Python进行立体几何和立体校正的综合教程

现在,为了找到这个方程的解,我们简单地计算F的线性最小二乘估计,这是它的最后一个奇异值。

计算极点的代码如下所示:

def compute_epipole(F):
    '''
    Compute epipole using the fundamental matrix.
    pass F.T as argument to compute the other epipole
    '''
    U, S, V = np.linalg.svd(F)
    e = V[-1, :]
    e = e / e[2]
    return e

为了找到另一幅图像的极点,我们找到了F转置的线性最小二乘估计。

Python示例

好吧,让我们看看基本矩阵的作用。

%matplotlib widget

import matplotlib.pyplot as plt
from skimage import io
from skimage.transform import resize
from skimage.transform import warp, ProjectiveTransform
from stereo_utils import *
from skimage.color import rgb2gray, rgba2rgb

打印图像和匹配点

# load images
im1 = io.imread("data/bench/right.png")
im1 = rgb2gray(rgba2rgb(im1))
im2 = io.imread("data/bench/left.png")
im2 = rgb2gray(rgba2rgb(im2))

# load matching points
points1 = np.load("data/bench/right_points.npy")
points2 = np.load("data/bench/left_points.npy")

assert (points1.shape == points2.shape)

绘制匹配点

show_matching_result(im1, im2, points1, points2)
使用Python进行立体几何和立体校正的综合教程

基本矩阵

# compute the normalized fundamental matrix 
F = compute_fundamental_matrix_normalized(points1, points2)
# validate the fundamental matrix equation
p1 = points1.T[:, 0]
p2 = points2.T[:, 0]

np.round(p2.T @ F @ p1)
0.0

绘制极线

plot_epipolar_lines(im1, im2, points1, points2, show_epipole=False)
使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

绘制极点

e1 = compute_epipole(F)
e2 = compute_epipole(F.T)
# validate epioles
np.round(e2.T @ F @ e1)
0.0
plot_epipolar_lines(im1, im2, points1, points2, show_epipole=True)
使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

立体校正

H1, H2 = compute_matching_homographies(e2, F, im2, points1, points2)
# Transform points based on the homography matrix
new_points1 = H1 @ points1.T
new_points2 = H2 @ points2.T
new_points1 /= new_points1[2,:]
new_points2 /= new_points2[2,:]
new_points1 = new_points1.T
new_points2 = new_points2.T

# warp images based on the homography matrix
im1_warped = warp(im1, ProjectiveTransform(matrix=np.linalg.inv(H1)))
im2_warped = warp(im2, ProjectiveTransform(matrix=np.linalg.inv(H2)))

绘制新的外极线和匹配点

h, w = im1.shape

nrows = 2
ncols = 1
fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(6, 8))

# plot image 1
ax1 = axes[0]
ax1.set_title("Image 1 warped")
ax1.imshow(im1_warped, cmap="gray")

# plot image 2
ax2 = axes[1]
ax2.set_title("Image 2 warped")
ax2.imshow(im2_warped, cmap="gray")

# plot the epipolar lines and points
n = new_points1.shape[0]
for i in range(n):
    p1 = new_points1[i]
    p2 = new_points2[i]

    ax1.hlines(p2[1], 0, w, color="orange")
    ax1.scatter(*p1[:2], color="blue")

    ax2.hlines(p1[1], 0, w, color="orange")
    ax2.scatter(*p2[:2], color="blue")
使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

经验观察

plot_epipolar_lines(im1_warped, im2_warped, new_points1, new_points2, show_epipole=False)
使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

我们可以看到外极线不准确

笔记本可以在这里找到:

https://github.com/wingedrasengan927/Stereo-Geometry/blob/master/Fundamental%20Matrix%20and%20Stereo%20Rectification.ipynb

首先,我们需要同一物体的两张图像。于是我打开了我的编辑器,放置了两个相机,看着同一个物体,并截屏。

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

接下来,我们需要两个图像之间的点对应或匹配。在这里,我手动标记了它们,但在现实世界中,我们可以使用SIFT等算法自动计算它们。

使用Python进行立体几何和立体校正的综合教程

好了,现在我们可以使用归一化八点算法从点计算基本矩阵。

使用Python进行立体几何和立体校正的综合教程

我们还可以使用计算的矩阵来验证基本矩阵方程。

使用Python进行立体几何和立体校正的综合教程

让我们绘制两幅图像的外极线。

使用Python进行立体几何和立体校正的综合教程

我们可以看到对应于一幅图像的外极线穿过另一幅图像中的点。

接下来,让我们找到并绘制外极线。

使用Python进行立体几何和立体校正的综合教程

基本矩阵方程也适用于极点,因为它们也是图像平面中的点。

使用Python进行立体几何和立体校正的综合教程使用Python进行立体几何和立体校正的综合教程

我们可以在这里看到,极点位于图像之外,所有外极线都在它们处相交。

立体校正

由于外极线是平行的,所以使用具有平行图像平面的图像很容易。然而,通过战略性地扭曲它们,可以使它们平行。这个过程被称为立体校正。让我们看看怎么做。

使用Python进行立体几何和立体校正的综合教程

对于平行图像,外极沿水平轴位于无穷远处。所以第一步是创建变换矩阵,将点移动到无穷远。

我们需要三个变换矩阵:一个将一个点旋转到水平轴,一个将点移动到无穷远,另一个将原点平移到中心。让我们看看每一个。

将点旋转到水平轴

给定一个与X轴成角度θ的点,我们创建一个旋转矩阵,将其旋转-θ,并将其带回X轴,如下所示:

使用Python进行立体几何和立体校正的综合教程

注意:如果点位于X轴的另一侧,符号将反转。

由于我们处理的是齐次坐标,我们需要考虑额外的维度。

齐次坐标的一般变换矩阵,也称为单应矩阵,如下所示:

使用Python进行立体几何和立体校正的综合教程

因此,将齐次坐标旋转回X轴所需的矩阵为:

使用Python进行立体几何和立体校正的综合教程
将点移到无穷远

接下来我们需要将点移到无穷远。无穷远处的点表示为(∞, 0)或(-∞, 0). 这可以用齐次坐标表示为(x,0,0),如下所示:

使用Python进行立体几何和立体校正的综合教程

因此,给定一个位于X轴上的点,以其齐次形式表示为(X,0,1),我们需要一个将其转换为(X、0,0)的矩阵。

以下矩阵可完成此工作:

使用Python进行立体几何和立体校正的综合教程
将原点移动到中心

默认情况下,Python假定图像的原点位于左上角,因此我们需要创建一个平移矩阵来将原点移动到中心。

使用Python进行立体几何和立体校正的综合教程

应用变换后,我们可以将其移回原来的位置。

扭曲图像

因此,组合上述矩阵的总变换矩阵由下式给出:

使用Python进行立体几何和立体校正的综合教程

想法是我们用上面的矩阵变换点,然后为了“撤消”效果,我们用矩阵的逆矩阵扭曲整个图像。

使用这种技术,我们可以扭曲一幅图像。现在,如何扭曲另一个图像?理查德·哈特利(Richard Hartley)在他的论文中认为,为了获得最佳结果,两个立体图像都需要对齐,这意味着变换后的点对应之间的距离应该最小。

因此,可以通过最小化变换后的点对应关系之间的平方距离之和来找到匹配单应矩阵H1:

使用Python进行立体几何和立体校正的综合教程

这里我们不讨论计算H1的证明,但我已经在最后的参考资料部分将其链接起来,以供感兴趣的读者参考。

计算H1和H2的代码片段如下所示:

def compute_matching_homographies(e2, F, im2, points1, points2):
    '''
    Compute the matching homography matrices
    '''
    h, w = im2.shape
    # create the homography matrix H2 that moves the epipole to infinity
    
    # create the translation matrix to shift to the image center
    T = np.array([[1, 0, -w/2], [0, 1, -h/2], [0, 0, 1]])
    e2_p = T @ e2
    e2_p = e2_p / e2_p[2]
    e2x = e2_p[0]
    e2y = e2_p[1]
    # create the rotation matrix to rotate the epipole back to X axis
    if e2x >= 0:
        a = 1
    else:
        a = -1
    R1 = a * e2x / np.sqrt(e2x ** 2 + e2y ** 2)
    R2 = a * e2y / np.sqrt(e2x ** 2 + e2y ** 2)
    R = np.array([[R1, R2, 0], [-R2, R1, 0], [0, 0, 1]])
    e2_p = R @ e2_p
    x = e2_p[0]
    # create matrix to move the epipole to infinity
    G = np.array([[1, 0, 0], [0, 1, 0], [-1/x, 0, 1]])
    # create the overall transformation matrix
    H2 = np.linalg.inv(T) @ G @ R @ T

    # create the corresponding homography matrix for the other image
    e_x = np.array([[0, -e2[2], e2[1]], [e2[2], 0, -e2[0]], [-e2[1], e2[0], 0]])
    M = e_x @ F + e2.reshape(3,1) @ np.array([[1, 1, 1]])
    points1_t = H2 @ M @ points1.T
    points2_t = H2 @ points2.T
    points1_t /= points1_t[2, :]
    points2_t /= points2_t[2, :]
    b = points2_t[0, :]
    a = np.linalg.lstsq(points1_t.T, b, rcond=None)[0]
    H_A = np.array([a, [0, 1, 0], [0, 0, 1]])
    H1 = H_A @ H2 @ M
    return H1, H2

好吧,让我们纠正我们示例中的图像。

一旦我们计算了单应矩阵,就可以使用它们的逆来扭曲图像,如下所示:

使用Python进行立体几何和立体校正的综合教程我们现在已经用平行的图像平面校正了图像,所以外极线只是通过点的水平线。

使用Python进行立体几何和立体校正的综合教程
实证观察

如果我使用归一化八点算法计算扭曲图像的基本矩阵和外极线,结果不准确,如下所示:

使用Python进行立体几何和立体校正的综合教程

我们不知道确切的原因,但我们认为算法正在崩溃。

校正后的图像现在可以用于各种下游任务,如视差估计、模板匹配等。

使用Python进行立体几何和立体校正的综合教程

结论

好了,我们已经到了终点。在本文中,我们研究了处理从两个视图捕获的图像的技术。我们还看到了如何使用立体校正从两张图像中估计某些模糊度,如深度。如果我们有多个视图,我们还可以使用称为“运动结构”的技术来估计场景的结构。

参考引用

  1. cs231a Notes(https://web.stanford.edu/class/cs231a/course_notes/03-epipolar-geometry_2022.pdf)

  2. https://github.com/chizhang529/cs231a

  3. Theory and Practice of Stereo Rectification by Richard Hartley(https://users.cecs.anu.edu.au/~hartley/Papers/joint-epipolar/journal/joint3.pdf)

☆ END ☆

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「uncle_pn」,欢迎添加小编微信「 woshicver」,每日朋友圈更新一篇高质量博文。

扫描二维码添加小编↓文章来源地址https://www.toymoban.com/news/detail-488256.html

到了这里,关于使用Python进行立体几何和立体校正的综合教程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • (数字图像处理MATLAB+Python)第八章图像复原-第五、六节:盲去卷积复原和几何失真校正

    盲去卷积复原 :当我们考虑图像复原中的盲去卷积复原时,我们可以使用以下数学符号和方程来描述该问题 原始图像 :我们用I表示原始图像,其中I是一个二维离散函数。 I ( x , y ) I(x, y) I ( x , y ) 表示在坐标 ( x , y ) (x, y) ( x , y ) 处的图像强度值 模糊核 :我们用 H H H 表示未

    2024年02月04日
    浏览(35)
  • 使用python-opencv对双目摄像头进行立体视觉图像矫正,去畸变

            1、一张棋盘图         可以直接从opencv官方github下载,这是一个拥有10*7个格子的棋盘,共有 9*6个角点,每个格子24mm ,本文所使用的就是这一个棋盘。你需要将它打印在A4纸上用于后续使用。(也可以根据官方教程自行设置棋盘大小OpenCV: Create calibration pattern)

    2024年02月10日
    浏览(37)
  • 图像几何校正

    几何校正中混淆的概念 名词 描述 几何校正 几何畸变会给基于遥感图像的定量分析、变化检测、图像融合 、地图测量或更新等处理带来误差(主要指二维平面坐标),所以需要针对图像的几何畸变进行校正,也就是几何校正。 图像配准 图像配准与几何校正的原理是完全相同

    2024年02月10日
    浏览(24)
  • opencv进行双目标定以及极线校正 python代码

    参考博客 OpenCV相机标定全过程 [OpenCV实战]38 基于OpenCV的相机标定 opencv立体标定函数 stereoCalibrate() 将打印的结果保存到标定文件中即可 参考博客 机器视觉学习笔记(8)——基于OpenCV的Bouguet立体校正 小白视角之Bouguet双目立体校正原理 校正前 左图 右图 校正后

    2024年02月11日
    浏览(28)
  • Python综合练习:期末大作业使用openpyxl进行模拟学生宿舍管理系统设计与开发

    1.1 问题背景 随着办公智能化的发展,为方便对大学生宿舍的动态管理,宿舍管理系统储存了每个宿舍学生的基本个人信息,同时需要针对一些特殊情况,如转专业、退学等,对宿舍的信息实现动态调整,支持显示、增加、删除、修改、查询成员信息,从而实现宿舍管理员对

    2024年02月07日
    浏览(48)
  • 使用几何和线性代数从单个图像进行 3D 重建

    使用几何和线性代数从单个图像进行 3D 重建 萨蒂亚         3D重构是一个挑战性题目,而且这个新颖的题目正处于启发和膨胀阶段;因此,各种各样的尝试层出不穷,本篇说明尝试的一种,至于其它更多的尝试,我们在陆续的跟踪中。 图1         以上这3张图片有什

    2024年02月13日
    浏览(28)
  • 在 Python 中使用 OpenCV 通过透视校正转换图像

    在计算机视觉和图像处理领域,透视变换是一个强大的工具。它允许我们改变图像的视角以获得新的视点,通常用于校正扭曲或模拟不同的相机角度。本文将探讨一个 Python 脚本,该脚本使用计算机视觉领域流行的 OpenCV 库对图像执行透视变换。我们将详细介绍该脚本的工作原

    2024年01月25日
    浏览(34)
  • opencv对相机进行畸变校正,及校正前后的坐标对应

    目前有个项目,需要用到热成像相机。但是这个热成像相机它的畸变比较厉害,因此需要用标定板进行标定,从而消除镜头畸变。 同时需要实现用户用鼠标点击校正后的画面后,显示用户点击位置的像素所代表的温度。 另外热成像sdk中还有个功能:选定一个rect,可以返回这

    2024年02月14日
    浏览(33)
  • ASIC设计学习笔记——使用Design Compiler进行综合

    综合是ASIC的前端设计中极为重要的步骤,所谓的综合过程,是指将行为级描述的电路、RTL级的电路转换到门级网表的过程。本文介绍使用Synopsys公司的Design Compiler作为工具完成综合的过程。 在ASIC开发中,当使用verilog等硬件描述语言完成对所需要的功能的代码编写和仿真后,

    2024年02月07日
    浏览(24)
  • [足式机器人]Part3机构运动微分几何学分析与综合Ch03-1 空间约束曲线与约束曲面微分几何学——【读书笔记】

    本文仅供学习使用 本文参考: 《机构运动微分几何学分析与综合》-王德伦、汪伟 《微分几何》吴大任 连杆机构中的连杆与连架杆构成运动副,该运动副元素的 特征点 或 特征线 在 机架坐标系 中的 运动轨迹曲线或曲面 称为 约束曲线 或 约束曲面 ,是联系刚体运动与机构

    2024年02月11日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包