目标
- 学习多视角几何基础
- 学习什么是极点、极线、对极约束等
基本概念
在我们使用针孔相机时,我们会丢失大量重要的信息。比如说图像的深度,或者说图像上的点和摄像机的距离,因为这是一个从3D 到2D 的转换。因此一个重要的问题就产生了,使用这样的摄像机我们能否计算出深度信息呢?答案就是使用多个相机。我们的眼睛就是这样工作的,使用两个摄像机(两个眼睛),这被称为立体视觉。下面让我们来看看OpenCV 在这方面给我们都提供了什么吧。值得一提的是《学习OpenCV》一书中有大量的这方面相关知识。
在进入深度图像之前,我们要先掌握一些多视角几何的基本概念。在本节中我们要处理对极几何。下图为使用两台摄像机同时对一个一个场景进行拍摄的示意图。
如果只是用一台摄像机我们不可能知道 3D 空间中的X 点到图像平面的距离,因为OX 线上的每个点投影到图像平面上的点都是相同的。但是如果我们也考虑上右侧图像的话,直线OX 上的点将投影到右侧图像上的不同位置。所以根据这两幅图像,我们就可以使用三角测量计算出3D 空间中的点到摄像机的距离(深度)。这就是整个思路。
直线OX 上的不同点投射到右侧图像上形成的线l′ 被称为与x 点对应的极线。也就是说我们可以在右侧图像中沿着这条极线找到x 点。它可能在这条直线上某个位置(这意味着对两幅图像的匹配特征的二维搜索就变成了沿着极线的一维搜索。这不仅节省了大量的计算,还允许我们排除很多导致虚假匹配的点)。这被称为对极约束。与此相同,所有的点在其他图像中都有与之对应的极线。平面 XOO' 被称为对极平面。
O 和O' 是摄像机的中心。从上面的示意图可以看出,右侧摄像机的中心O' 投影到左侧图像平面的e 点,这个点就被称为极点。极点就是摄像机中心连线与图像平面的交点。因此点e' 是左侧摄像机的极点。有些情况下,我们可能不会在图像中找到极点,它们可能落在了图像之外,这说明两个摄像机不能拍摄到彼此。
所有的极线都要经过极点。所以为了找到极点的位置,我们可以先找到多条极线,这些极线的交点就是极点。
本节我们的重点就是找到极线和极点。为了找到它们,我们还需要两个元素本征矩阵E和基础矩阵F。本征矩阵包含了物理空间中两个摄像机相关的旋转和平移信息。如下图所示:
基础矩阵 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点。