目标
在本节中, 将学习
- 多视图几何的基础知识
- 了解什么是极点(epipole),极线,极线约束等
基础概念
当使用针孔相机拍摄图像时,会失去一些重要信息,即图像深度, 或者图像中的每个点距相
机多远,因为它是3D到2D转换。 因此,是否能够使用这些摄像机找到深度信息是一个重要的问题。 答案是使用不止一台摄像机。 在使用两台摄像机(两只眼睛)的情况下,与我们的眼睛工作方式相似,这称为立体视觉。 因此,OpenCV在这个领域中提供了一些相关的内容。
在深入图像之前,首先了解多视图几何中的一些基本概念。在本节中,将讨论对极几何。下图显示了使用两台摄像机拍摄同一场景的图像的基本设置。
如果仅使用左摄像机,则无法找到与图像中的点相对应的3D点,因为线上的每个点都投影到图像平面上的同一点。但也要考虑正确的形象。现在,直线OXOX上的不同点投射到右侧平面上的不同点(x′x’)。因此,使用这两个图像,可以对正确的3D点进行三角剖分。这就是整个想法。
不同点的投影在右平面OXOX上形成一条线(line l′l’),将其称为对应于该点的Epiline。这意味着,
要在正确的图像上找到该点,沿着该轮廓线搜索即可。它应该在这条线上的某处(以这种方式考
虑,可以在其他图像中找到匹配点,而无需搜索整个图像,只需沿着Epiline搜索即可。因此,
可以提供更好的性能和准确性),这称为对极约束(Epipolar Constraint)。类似地,所有点在另一幅图像中将具有其对应的Epiline,该平面称为对极面(Epipolar Plane)
OO和O′O’是相机中心。从上面给出的设置中,可以看到右侧图像在左侧图像上看到右侧相机O′O’的投影,这被称为极点。极点是穿过相机中心和图像平面的线的交点。左摄像机的极点也同理。在某些情况下,将无法在图像中找到极点,它们可能位于图像外部(这意味着一个摄像机看不
到另一个摄像机)。
所有的极线都通过其极点。因此,要找到中心线的位置,可以找到许多中心线并找到它们的交点。
本节中,将重点放在寻找对极线和极线。但是在此之前,需要了解另外两个概念基础矩阵(Fundamental Matrix F)和本征矩阵(Essential Matrix E),基础矩阵包含有关平移和旋转的信息,这些信息在全局坐标中描述了第二个摄像头相对于第一个摄像头的位置。参见下图:
现实中可能更喜欢在像素坐标中进行测量 ,基础矩阵除包含有关两个摄像头的内在信息之外,还包含与本征矩阵相同的信息,因此可以将两个摄像头的像素坐标关联起来。(如果使用的是校正后的图像,并用焦距除以标准化该点,F=E
)。简而言之,基础矩阵F
将一个图像中的点映射到另一图像中的线(上)。这是从两个图像的匹配点计算得出的,至少需要8个这样的点才能找到基本矩阵(使用8点算法时)。
代码
首先需要在两个图像之间找到尽可能多的匹配项以找到基础矩阵。为此,将SIFT描述符与基于FLANN的匹配器和比率测试结合使用。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img1 = cv2.imread('left.jpg', 0)
img2 = cv2.imread('right.jpg', 0)
sift = cv2.xfeatures2d.SIFT_create()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# FLANN parameters
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
good = []
pts1 = []
pts2 = []
# ratio test as per low;s paper
for i, (m, n) in enumerate(matches):
if m.distance < 0.8 * n.distance:
good.append(m)
pts2.append(kp2[m.trainIdx].pt)
pts1.append(kp1[m.queryIdx].pt)
现在,获得了两张图片的最佳匹配列表,基于此找到基础矩阵
pts1 = np.int32(pts1)
pts2 = np.int32(pts2)
F, mask = cv2.findFundamentalMat(pts1, pts2, cv2.FM_LMEDS)
# we select only inlier points
pts1 = pts1[mask.ravel() == 1]
pts2 = pts2[mask.ravel() == 1]
接下来,找到对应的Epilines。在第二张图像上绘制与第一张图像中的点相对应的Epilines。因此,定义了一个新函数来在图像上绘制这些线条。
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 = cv2.cvtColor(img1, cv2.COLOR_GRAY2BGR)
img2 = cv2.cvtColor(img2, cv2.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 = cv2.line(img1, (x0,y0), (x1,y1), color, 1)
img1 = cv2.circle(img1, tuple(pt1), 5, color, -1)
img2 = cv2.circle(img2, tuple(pt2), 5, color, -1)
return img1, img2
现在,我们在两个图像中都找到了Epiline并将其绘制。
# Find epilines corresponding to points in right image (second image) and
# drawing its lines on left image
lines1 = cv2.computeCorrespondEpilines(pts2.reshape(-1, 1, 2), 2, F)
lines1 = lines1.reshape(-1, 3)
img5, img6 = drawlines(img1, img2, lines1, pts1, pts2)
# Find epilines corresponding to points in left image (first image) and
# drawing its lines on right image
lines2 = cv2.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()
以下是得到的结果:
可以在左侧图像中看到所有极点都收敛在右侧图像的外部,那个汇合点就是极点。 为了获得更好的结果,应使用具有良好分辨率和许多非平面点的图像。
基础矩阵估计对匹配质量、异常值等敏感。当所有选定的匹配位于同一平面上时,它会变得更糟
附加资源
- docs.opencv.org/4.1.2/da/de…
- en.wikipedia.org/wiki/Fundam…
- en.wikipedia.org/wiki/Essent…
- blog.csdn.net/h763247747/…
- zhuanlan.zhihu.com/p/79845576