目标
本节要学习多视角几何基础
学习什么是极点,极线,对极约束等
基本概念
在使用针孔相机时,可能会丢失大量重要的信心, 比如说图像的深度,或者说图像上的点和摄像机的距离, 因这是一个从 3D 到 2D 的转换。因此一个重要的问题就产生了, 使用这样的摄像机,能否计算除深度信息呢? 答案就是使用多个相机。我们的眼睛就是这样工作的, 使用两个摄像机(两个眼睛),这被称为立体视觉。 接下来看看 OpenCV 在这方面都提供了什么吧。 (《学习 OpenCV》一书有大量相关知识。)
在进入深度图像之前,要先掌握一些多视角几何的基本概念。 在本节中要处理对极几何。 下图为使用两台摄像机同时对一个一个场景进行拍摄的示意图。
如果只是用一台摄像机,不可能知道 3D 空间中的 X 点到图像平面的距离, 因为 OX 连线上的每个点投影到图像平面上的点都是相同的。 但是如果也考虑上右侧图像的话, 直线 OX 上的点将投影到右侧图像上的不同位置。
所以根据这两幅图像,就可以使用三角测量计算出 3D 空间中的点到摄像机的距离(深度)。 这就是整个思路。 直线 OX 上的不同点投射到右侧图像上形成的线 l′被称为与 x 点对应的极线
极线。也就是说,可以在右侧图像中沿着这条极线找到 x 点。 它可能在这条直线上某个位置(这意味着对两幅图像间匹配特征的二维搜索就转变成了沿着极线的一维搜索。 这不仅节省了大量的计算,还允许排除许多导致虚假匹配的点)。 这被称为对极约束。 与此相同,所有的点在其他图像中都有与之对应的极线。 平面 XOO' 被称为 对极平面。
O 和 O' 是摄像机的中心。 从上面的示意图可以看出,右侧摄像机的中心O' 投影到左侧图像平面的 e 点,这个点就被称为极点。 极点就是摄像机中心连线与图像平面的交点。 因此点 e' 是左侧摄像机的极点。 有些情况下,可能不会在图像中找到极点, 它们可能落在了图像之外(这说明这两个摄像机不能拍摄到彼此)。
所有的极线都要经过极点。所以为了找到极点的位置, 可以先找到多条极线,这些极线的交点就是极点。
本节的重点就是找到极线和极点。 为了找到它们,还需要两个元素, 本征矩阵(E )和 基础矩阵(F )。 本征矩阵包含了物理空间中两个摄像机相关的旋转和平移信息。 如下图所示(本图来源自:学习 OpenCV)
基础矩阵 F 除了包含 E 的信息外还包含了两个摄像机的内参数。 由于 F包含了这些内参数,因此它可以它在像素坐标系将两台摄像机关联起来。 (如果使用是校正之后的图像并通过除以焦距进行了归一化,F=E)。 简单来说,基础矩阵 F 将一副图像中的点映射到另一幅图像中的线(极线)上。 这是通过匹配两幅图像上的点来实现的。 要计算基础矩阵至少需要 8 个点(使用 8 点算法)。 点越多越好,可以使用 RANSAC 算法得到更加稳定的结果。
代码
为了得到基础矩阵,应该在两幅图像中找到尽量多的匹配点。 可以使用 SIFT 描述符,FLANN 匹配器和比值检测。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img1 = cv2.imread('myleft.jpg',0) #queryimage # left image
img2 = cv2.imread('myright.jpg',0) #trainimage # right image
sift = cv2.SIFT()
# find the keypoints and descriptors with SIFT
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)
# FLANN parameters
FLANN_INDEX_KDTREE = 0
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 Lowe'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]
#下一步我们要找到极线。我们会得到一个包含很多线的数组。所以我们要定义一个新的函数将这些线绘制到图像中。
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
#现在我们两幅图像中计算并绘制极线。
# 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()
下面是得到的结果:
从上图可以看出所有的极线都汇聚以图像外的一点,这个点就是极点。
为了得到更好的结果,应该使用分辨率比较高的图像和 non-planar
点。