基础概念
当我们使用针孔摄像机拍摄图像时,我们会丢失重要信息,图像的深度。或者图像每个点离摄像机有多远,因为这是一个3D到2D的转换。所以我们是否能找到深度信息就变得很重要。回答是使用多个摄像机。我们的眼睛也是类似的方法,我们使用两个摄像机(双眼),这被叫做立体影像。所以我们看看OpenCV提供了什么。
在更进一步之前,我们先明白一些多视几何的基础概念。在本节里,我们会用核面几何处理。看下面的图像显示了两个摄像机对同一个场景拍摄图像的基本设置。
如果我们只用左边的摄像机,我们没法找到点x的对应3D点。因为线OX上的每一个点在图像平面都投影到同一个点。但是如果加上右边的摄像机,现在OX线上的不同点在右边的平面投影到了不同的点(x')。所以用两个图像,我们可以三角测量正确的3D点。这就是所有想法。
在右边平面上不同点投影出来的直线(l')。我们叫做点x对应的极线。它表示,要找到右边图像里的点x,沿着这根直线找就行。它应该在这条线上的某出(这么想,要找到在另一张图上的对应点,你不需要搜索整张图片,只需要在这根直线上找就行。所以它提供了更好的性能和准确性)。这被叫做极限约束。类似的所有的点都会在另一张图上有对应的极线。平面XOO'背景叫做偏斜面。
O和O'是摄像机中心,从上面可以看到右边摄像机的O'的投影可以在左边图片看到,e点。这被叫做极点。极点是通过两个摄像机中心的直线与图片平面的焦点。类似的,e'是左边摄像机的极点。在某些情况下,你没法在图像内定位极点,他们可能在图像外(这表示摄像机没法看见彼此)。
所有的极线都通过极点。所以要找到极点的位置,我们可以找多条极线,然后找他们的交点。
我们要找到极线和极点,需要两个东西,基础矩阵(F)和本质矩阵(E)。本质矩阵包含平移和旋转的信息,描述了第二个摄像机相对于第一个在全局坐标系里的位置。看下图:
基础矩阵包含了和本质矩阵一样的信息,另外还有两个摄像机内联信息,这样我们可以把两个摄像机在像素坐标系内关联起来。(如果我们使用修正过的图像并按焦距把点分开正规化了,F=E)。简单的说,基础矩阵F把一个图像里的点映射到另一个图里的线(极线)。这是通过两个图像里的匹配点计算的。最少需要8个点来得到基础矩阵(使用8点算法),最好能有多个点来使用RANSAC来得到更健壮得到结果。
编码:
所以我们需要找到两张图像里尽可能多的匹配来找基础矩阵。为了这个,我们使用基于FLANN匹配子的SIFT描述子和比率测试
import cv2
import numpy as np
from matplotlib import pyplot as pltimg1 = cv2.imread('myleft.jpg',0) #queryimage # left image
img2 = cv2.imread('myright.jpg',0) #trainimage # right imagesift = 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 imagelines1 = 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()
下面是我们得到的结果
你可以看到在左边的图像里所有的极线汇聚在图像外的点。那个汇聚点就是极点。
要得到更好的结果,应该用有好的分辨率的图像,有很多非平面的点