文章目录
- Epipolar Geometry 对极几何
- 目标
- 基本概念
- 代码
- 练习
Epipolar Geometry 对极几何
目标
在本节中,
- 我们将学习多视图几何的基础知识
- 我们将了解什么是对极点、对极线、对极约束等。
基本概念
当我们使用针孔相机拍摄图像时,我们会丢失一个重要信息,即图像的深度。或者图像中每个点与相机的距离有多远,因为它是 3D 到 2D 的转换。因此,我们能否使用这些相机找到深度信息是一个重要的问题。答案是使用多个相机。我们的眼睛以类似的方式工作,我们使用两个相机(两只眼睛),这称为立体视觉。那么让我们看看 OpenCV 在这一领域提供了什么。
(Gary Bradsky 的《学习 OpenCV》包含大量该领域的信息。)
在介绍深度图像之前,我们先了解一下多视图几何中的一些基本概念。在本节中,我们将讨论对极几何。参见下图,其中显示了两个摄像头拍摄同一场景图像的基本设置。
如果我们只使用左相机,我们将无法找到图像中与点 x x x 对应的 3D 点,因为线 O X OX OX 上的每个点都投影到图像平面上的同一点。但请考虑右图。现在线 O X OX OX 上的不同点投影到右平面上的不同点 ( x ′ x' x′)。因此,通过这两幅图像,我们可以三角测量出正确的 3D 点。这就是整个想法。
O X OX OX 上不同点的投影在右平面上形成一条线(线 l ′ l' l′)。我们将其称为与点 x x x 对应的极线。这意味着,要找到右图上的点 x x x,请沿着这条极线进行搜索。它应该在这条线上的某个地方(这样想,要在其他图像中找到匹配点,您无需搜索整个图像,只需沿着极线搜索即可。因此它提供了更好的性能和准确性)。这称为极线约束。同样,所有点在另一幅图像中都会有其对应的极线。平面 X O O ′ XOO' XOO′ 称为极线平面。
O O O 和 O ′ O' O′ 是相机中心。从上面给出的设置中,您可以看到右相机 O ′ O' O′ 的投影在左图像上的点 e e e 处可见。它被称为极点。极点是通过相机中心的线与图像平面的交点。类似地, e ′ e' e′ 是左相机的极点。在某些情况下,您将无法在图像中找到极点,它们可能在图像之外(这意味着一个相机看不到另一个相机)。
所有极线都经过其极点。因此,要找到极点的位置,我们可以找到许多极线并找到它们的交点。
因此,在本节中,我们专注于寻找极线和极点。但要找到它们,我们还需要两个要素,基本矩阵 (F) 和 基本矩阵 (E)。基本矩阵包含有关平移和旋转的信息,它们描述了第二个相机相对于第一个相机在全局坐标中的位置。参见下图(图片来源:Gary Bradsky 的 Learning OpenCV):
但是我们更喜欢在像素坐标中进行测量,对吗?除了有关两个相机的内在信息之外,基本矩阵还包含与基本矩阵相同的信息,以便我们可以在像素坐标中关联两个相机。(如果我们使用校正后的图像并通过除以焦距来规范化该点,则 F = E F=E F=E)。简而言之,基本矩阵 F 将一个图像中的点映射到另一个图像中的一条线(极线)。这是根据两个图像中的匹配点计算得出的。至少需要 8 个这样的点才能找到基本矩阵(使用 8 点算法)。更多点是首选,并使用 RANSAC 获得更稳健的结果。
代码
因此,首先我们需要在两个图像之间找到尽可能多的匹配项来找到基本矩阵。为此,我们使用 SIFT 描述符和基于 FLANN 的匹配器和比率测试。
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img1 = cv.imread('myleft.jpg',0) #queryimage # left image
img2 = cv.imread('myright.jpg',0) #trainimage # right image
sift = cv.SIFT_create()
# 使用 SIFT 找到关键点和描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# FLANN 参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks=50)
flann = cv.FlannBasedMatcher(index_params,search_params)
matches = flann.knnMatch(des1,des2,k=2)
pts1 = []
pts2 = []
# 按照 Lowe 的论文进行比率测试
for i,(m,n) in enumerate(matches):
if m.distance < 0.8*n.distance:
pts2.append(kp2[m.trainIdx].pt)
pts1.append(kp1[m.queryIdx].pt)
现在我们有了两幅图像中最佳匹配的列表。让我们找到基本矩阵。
pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv.findFundamentalMat(pts1,pts2,cv.FM_LMEDS)
# 我们只选择内点
pts1 = pts1[mask.ravel()==1]
pts2 = pts2[mask.ravel()==1]
接下来我们找到极线。对应于第一幅图像中的点的极线绘制在第二幅图像上。因此,在这里提到正确的图像很重要。我们得到了一个线数组。所以我们定义了一个新函数来在图像上绘制这些线。
def drawlines(img1,img2,lines,pts1,pts2):
''' img1 - image on which we draw the epilines for the points in img2
lines - corresponding epilines '''
r,c = img1.shape
img1 = cv.cvtColor(img1,cv.COLOR_GRAY2BGR)
img2 = cv.cvtColor(img2,cv.COLOR_GRAY2BGR)
for r,pt1,pt2 in zip(lines,pts1,pts2):
color = tuple(np.random.randint(0,255,3).tolist())
x0,y0 = map(int, [0, -r[2]/r[1] ])
x1,y1 = map(int, [c, -(r[2]+r[0]*c)/r[1] ])
img1 = cv.line(img1, (x0,y0), (x1,y1), color,1)
img1 = cv.circle(img1,tuple(pt1),5,color,-1)
img2 = cv.circle(img2,tuple(pt2),5,color,-1)
return img1,img2
现在我们在两幅图像中找到极线并绘制它们。
# 找到对应于右图(第二幅图像)中点的极线和
# 在左图上绘制它的线
lines1 = cv.computeCorrespondEpilines(pts2.reshape(-1,1,2), 2,F)
lines1 = lines1.reshape(-1,3)
img5,img6 = drawlines(img1,img2,lines1,pts1,pts2)
# 找到对应于左图(第一幅图像)中点的极线和
# 在右图上绘制它的线
lines2 = cv.computeCorrespondEpilines(pts1.reshape(-1,1,2), 1,F)
lines2 = lines2.reshape(-1,3)
img3,img4 = drawlines(img2,img1,lines2,pts2,pts1)
plt.subplot(121),plt.imshow(img5)
plt.subplot(122),plt.imshow(img3)
plt.show()
以下是我们得到的结果:
您可以在左图中看到所有极线都汇聚在右侧图像外部的一点。那个交汇点就是极点。
为了获得更好的结果,应使用分辨率高且有许多非平面点的图像。
练习
-
一个重要主题是相机的向前移动。 然后,极点将出现在两者的相同位置,极线从固定点出现。 参见此讨论。
-
基本矩阵估计对匹配质量、异常值等很敏感。 当所有选定的匹配都位于同一平面上时,情况会变得更糟。 查看此讨论。